함수는 종료될 때 Return address(RET)를 pop하여 다음 명령어로 분기됩니다. 하지만 RET Overwrite를 이용해 되돌아갈 RET를 다른 주소로 변경한다면 해당 주소로 이동해 명령을 수행합니다.
[그림 1] RET Overwrite
'BBBB'가 입력될 자리에 쉘코드의 주소가 들어간다면 함수가 리턴된 뒤 쉘코드로 인해 쉘이 실행될 수 있습니다.
BOF는 버퍼의 크기를 고려하지 않거나 입력 값의 길이를 검증하지 않는 경우에 발생할 수 있습니다.
BOF(Buffer OverFlow)
메모리를 다루는 데에 오류가 발생하여 잘못된 동작을 하는 프로그램 취약점입니다.(위키피디아)
(BOF를 이해하려면 함수가 호출되고 리턴될 때 발생하는 함수 프롤로그 및 에필로그에 대해 알아야합니다.)
입력된 데이터를 저장하는 버퍼에 지정된 버퍼크기 이상의 데이터를 입력할 경우 버퍼를 넘어선 다른 영역에 데이터를 덮어 씌우게 됩니다(RET Overwrite). 이때 다른 영역이라함은 함수가 호출될 때와 프롤로그를 수행할 때 저장된 Return address와 Base Pointer입니다.
실습
[그림 2] bugfile1.c
BOF를 실습할 수 있는 코드를 작성해줍니다. 해당 코드에서 strcpy는 argv[1]의 길이가 얼마인지 검사하지 않고 크기가 256인 buf에 복사합니다. 그리고 buf를 출력합니다.
[그림 3] bugfile1 실행-1
256이하의 길이만큼 입력하였을 때는 정상적으로 동작하는 것을 확인할 수 있습니다.
[그림 4] bugfile1 실행-2
그림 4와 같이 256이 넘는 264 크기만큼을 입력 후 출력했을 때 Segmentation fault가 발생합니다.
[그림 5] gdb -q bugfile1[그림 6] strcpy 후 bp 설정
gdb 통해 bugfile을 디스어셈블한 것을 그림 5를 통해 확인할 수 있습니다. strcpy 후 buf에 어떤 값이 들어가는지 확인하기 위해 bp를 걸어줍니다(그림6).
[그림 7] buf의 주소
그림7을 통해 buf의 주소가 0xbffff0df인 것을 확인할 수 있습니다.
[그림 8] 스크립트 작성 - 1
그림 7을 구한 buf의 주소에 쉘 코드를 집어 넣으나 쉘이 실행되지 않습니다.
그 이유는 gdb를 통해 바이너리를 실행한 경우와 그냥 실행했을 때와의 차이 때문입니다.
1. 파일명 길이
2. $_ 환경변수 존재 여부
3. 절대경로/상대경로 여부
4. $OLDPWD 환경변수 존재 여부
이러한 차이로 인해 환경변수의 길이가 달라지게 되는데 이때 지역 변수의 주소도 변경되게 됩니다.
그 차이는 16(0x10)배수 만큼 차이나게 됩니다.
[그림 9] buf 주소 출력문 추가
실제 buf의 주소를 알기 위해 buf 주소 출력문을 추가해줍니다.
[그림 10] buf 주소 재출력
[그림 11] 스크립트 작성 - 2
buf의 주소를 0xbffff218로 바꾸어 주었음에도 쉘이 실행되지 않습니다.
스크립트 작성 후 buf의 주소가 0xbffff118로 변경되었습니다.
[그림 12] 쉘 동작
다시 주소를 buf의 주소를 0xbffff118로 변경하여 스크립트를 실행시키니 쉘이 동작하는 것을 확인할 수 있었습니다.
int에도 long long int 데이터형이 존재한다는 사실을 이때까지 모르고 있었습니다. 또한 해당 데이터 타입이 표현할 수 있는 수의 범위가 "-9,223,372,036,854,775,808 ~ +9,223,372,036,854,775,807"인 것을 알게 된 문제였습니다.