level16
- 소스코드의 16 행에서 fgets 함수를 통해 44(40+4) 바이트 만큼 버퍼를 복사한다.
- 소스코드의 14 행에서 call 함수 포인터 변수에 printit 함수의 주소를 넣기 때문에 buf 배열 이상의 데이터를 덮어 씌울수 있다.
- 소스코드의 17 행에서 call 함수 포인터를 호출하기 전 fgets 함수를 통해 call 함수 포인터 변수가 가리키는 printit 함수 주소가 아니라 shell 함수의 주소로 변조함으로써 level17(3097) 권한의 쉘을 획득하였다.
1. #include <stdio.h>
2.
3. void shell() {
4. setreuid(3097,3097);
5. system("/bin/sh");
6. }
7.
8. void printit() {
9. printf("Hello there!\n");
10. }
11.
12. main()
13. { int crap;
14. void (*call)()=printit;
15. char buf[20];
16. fgets(buf,48,stdin);
17. call();
18. }
19.
20.
- setuid 가 설정되어 있으면 gdb 가 동작하지 않기 떄문에 tmp 디렉토리에 cp 명령을 이용하여 복사한다.
- attackme 소스코드 16행을 보면 fgets 함수를 통해 stdin(Standard Input) 에서 입력받은 버퍼를 buf 배열에 최대 48바이트만큼 복사한다.
- FTZ LEVEL14 와 같이 소스코드 상에서는 buf 배열 크기가 20 이지만 실제로 20 이상의 버퍼가 할당되어 있을것이다.(자세한 내용 참조 : http://mdkstudy.tistory.com/41)
- 또한, 소스코드 14행에서 call 함수 포인터 변수에 printit 함수의 주소를 할당받는다.
- call 함수 포인터 변수를 변조시킴으로써 printit 함수가 아닌 shell 함수를 호출시킬수 있다.
- 이제 소스코드를 통해 전체적인 흐름은 파악했으니 gdb 를 이용해 실행시켜보면서 동적 분석을 진행한다.
- gdb 에 실행파일을 로드한 뒤 main 함수의 ret 와 call 함수 포인터 변수가 가리키는 주소를 호출하는 루틴에 브레이크포인트를 건다.
- 또한, main+36 라인에서 eax 레지스터에 ebp-16 을 mov 하고 main+39 라인에서 eax 레지스터를 호출하는것을 보아하니 ebp-16 위치의 값을 변조하면 원하는 함수를 호출시킬 수 있다.
- Dummy Data 44("A"*40 + "B"*4) 바이트를 넣고 실행한다.
- bp(BreakPoint) 가 걸리고 ebp-16 를 확인해보면 필자가 넣은 Dummy Data "A" 40바이트 뒤에 "B" 4바이트가 입력된것을 확인할 수 있다.
- 또한 ebp-16 위치의 값을 eax 레지스터에 복사되어진것도 확인할 수 있다.
- 이제 printit 함수의 주소가 아닌 shell 함수의 주소를 구하기위해 disas shell 명령을 통해 shell 함수의 시작주소를 구한다.
- shell 함수의 시작주소를 구하였으니 Dummy data 40 바이트 뒤에 위에서 구한 shell 함수의 시작주소를 Big Endian 형식으로 넣어주고 실행한다.
- 예상했던바와 같이 ebp-16 과 eax 레지스터를 확인해보면 shell 함수의 시작주소로 변조된 것을 확인할 수 있다.
python -c 'print "A"*40 + "\xd0\x84\x04\x08"' > payload
(cat payload;cat) | ./attackme
my-pass