Ret2CSU

Nick Mckenney
5 min readMay 8, 2024

--

Credit: https://i.blackhat.com/briefings/asia/2018/asia-18-Marco-return-to-csu-a-new-method-to-bypass-the-64-bit-Linux-ASLR-wp.pdf

This is a challenge from ROPEmporium. This involves Ret2CSU. Typically when I first debug a program I run it in Ghidra

Its clear I am running into issues with this. There is no disassembly or decompiled code. This is a first with me using Ghidra to run into this issue. Time to move to dynamic analysis for the time being

I usually like to start with main before moving to the very beginning being functions like “start”

Its clear from the first 2 instructions we are saving the stack before calling pwnme. The pwn@plt is the procedure linkage table. This allows GOT to get resolved with the pointer to pwnme. This is how dynamic linking works. It allows for shorter program sizes. Whats exciting about this is that this is then cached. So the next call to pwnme will actually be a lot faster since it doesnt have to look it up. PLT is the heart of dynamic linking

The beginning of pwnme again establishes a stack frame. This is basically a thread of a process, though not quite, more of an abstraction.

In almost every call the prolouge will be the below

The sub rsp,0x20 instruction reserves space on the stack. This is typically for variables.

Going through the pwnme function I find this

memset initializes a block of memory
0x7ffebaec9ca0 this address is the pointer to the memory. From the edx instruction its pretty obvious this is where it is setting up for the call

The rdx register indicates the size. Time to make note of why the program nulls out 0x20 bytes at this address. Its important to mention that memset only works on memory that is already allocated. This is known as compile time memory allocation

Moving on a few instructions I find my first input into the program

To me this is a clear buffer overflow vuln. Read is taking in 0x200 bytes for the address that memset reserved only 0x20 bytes.

Above I inspected the stack frame after placing in an input shorter than 0x20 bytes. Its clear from this image I dont have control over the program yet. A simple cyclic fixes this issue

With control of the stack pointer I can now point to where I want to go next. Normally people would direct to shellcode but this program has the NX bit enabled. So no executing shellcode on the stack. ROP gadgets are where I would go to next.

Investigating the program functions I find a clear use of registers I want to use for my gadgets. I want RDI, RSI, and RDX

Unfortunately I am missing an RDX gadget. The article linked above goes into great detail to bypass this

This above image from the article explains it perfectly. I will use another register to move my value in, then pop to another gadget where itll mov the value I want into rdx

From my search in the libc csu init function I find a way to control the rdx register. I need to fill r15 with 0xd00df00d….

My current issue im spotting is at offset 73. Seems its calling a reference address

I can use the symbol &_DYNAMIC which is used as a dynamic linker. Generally this locates loaded binaries

What I want is just a ret instruction. The above disassembly seems more complicated then the below

time to just use the address 0x600e35 as a reference to 0x4006b4

Looks like I was able to move r15 to rdx from the above image! This will allow me to pass the comparison in ret2win function!

My biggest hicup once reaching this point was my RDI register changed due to the instruction changing it from a 64 bit to a 32 bit. This was a simple fix since there was a pop rdi, ret gadget

Going back to my ret2win function I can see my values are loaded properly into the registers

Sweet, Theres our FLAG!

#!/usr/bin/python3
from pwn import *
import sys
context.log_level = 'DEBUG'
context(os='linux', arch='amd64')
exe = './ret2csu'
e = context.binary = ELF(exe, checksec=False)
libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")
ret2csuso = ELF("./libret2csu.so")

pop_rdi_ret = 0x4006a3
rdxCOMP=0xd00df00dd00df00d
pop_rsi_pop_r15_ret = 0x4006a1
rsiCOMP=0xcafebabecafebabe
rdiCOMP=0xdeadbeefdeadbeef
ret2csu=0x40069a
ret2csuGadget2=0x400680
ret = 0x4004e6

p = e.debug(gdbscript='''
source /home/nick/global/halfdisp.py
break *pwnme+152
c
'''
)
ret2win= e.symbols.ret2win
offset=40
payload = flat([ b"\x90"*40,
p64(ret2csu),
p64(0x0),
p64(0x1),
p64(0x600e38),
#The callq *(%r12,%rbx,8) will calculate the destination address as (%r12 + (%rbx * 8) ).#
#https://i.blackhat.com/briefings/asia/2018/asia-18-Marco-return-to-csu-a-new-method-to-bypass-the-64-bit-Linux-ASLR-wp.pdf#
#x/10gx &_DYNAMIC
p64(rdiCOMP),
p64(rsiCOMP),
p64(rdxCOMP),
p64(ret2csuGadget2),
b"\x90"*48,
p64(rdiCOMP),
p64(pop_rdi_ret),
p64(rdiCOMP),
p64(ret2win)])
p.sendline(payload)
p.interactive()

--

--

No responses yet