- Manipulate writeable address and with rop we can read any file.
- Where to write? (e.g., The address should be writable and will not change during runtime to be a filename)
- What to write? (e.g., What filename do we desire? How to concatenate the strings?)
- How to make a ROP chain? (e.g., What gadget to find? In this challenge,
rdionly consumes pointer, so how to pass the pointer to
My first thought:
- Make a symlink that points to the
nonexistent -> flag.txt,so we can get the flag easily (it will work just by calling the usefulFunction). But it turns out that it will only work locally, and if we want to solve the pwn challenge remotely, we stand no chance of changing the server’s environment.
Hopefully, you’ve realised that ROP is just a form of arbitrary code execution and if we get creative, we can leverage it to do things like write to or read from memory. The question we need to answer is: what mechanism are we going to use to solve this problem? Is there any built-in functionality to do the writing or do we need to use gadgets? In this challenge, we won’t be using built-in functionality since that’s too similar to the previous challenges, instead we’ll be looking for gadgets that let us write a value to memory such as mov [reg], reg.
Perhaps the most important thing to consider in this challenge is where we’re going to write our “flag.txt” string. Use rabin2 or readelf to check out the different sections of this binary and their permissions. Learn a little about ELF sections and their purpose. Consider how much space each section might give you to work with and whether corrupting the information stored at these locations will cause you problems later if you need some kind of stability from this program.
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 its 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.
jeff14994@jeff14994-VirtualBox:~/Desktop/asu-hacking/03_03$ unzip -l write4.zip
We got three files from the
jeff14994@jeff14994-VirtualBox:~/Desktop/asu-hacking/03_03$ ldd write4
write4 is the primary binary.
libwrite4.so is the library
write4.(we can find it out with
flag.txt is our target filename.
First take a look at main function:
.text:0000000000400607 ; int __cdecl main(int argc, const char **argv, const char **envp)
.plt:0000000000400500 ; __int64 __fastcall pwnme(__int64, __int64, __int64)
We can see the main function is calling
_pwnme, which is an external function in
.text:0000000000400617 usefulFunction proc near
The binary provides a
usefulFunction which moves
edi and calls
_print_file. We know from calling convention that
edi will be the first argument for
_print_file that we can exploit. If we can change
"./flag.txt", we will have a chance to capture the flag!
Although it doesn’t show in the list of functions in IDA Pro, we can find it under the
usefulFuntion in assembly mode. We will use this gadget to pass the pointer of the file name.
Purpose: To find the padding before the
There are two ways to find the buffer size:
1. Use GDB
// 1. Create payload
Use cyclic to count the offset from
rsp (don’t need any arithmetic) and we can get the padding is
2. Read pwnme() assembly
Look through the code from IDA Pro (which is more straightforward.)
- From line 32, we know that it puts
rax(which is the buffer size) to
raxcomes from line 30, which is
0x20(from line 4). Thus we know the buffer size is
- Before the return address, there lies a
rbp,so we have to plus another 8 bytes to
0x20. Thus, we got
In both ways, we can conclude that to reach the return address we have to insert
40 bytes padding.
Once we control the
rip, we can make this binary execute anything we want!
from pwn import *
I’ll provide some illustrations of finding the address from line 4 to line 9 in the exploit code.
jeff14994@jeff14994-VirtualBox:~/Desktop/asu-hacking/03_03$ readelf --section-headers -W ./write4 | grep data
- The reason that we choose
.rodatais because it’s
WA(writeable and accessible). Thus that’s how we get
- 0x601030 is calculated by
0x601028 + 0x8, since the
./flag.txt(10 bytes) is too long, and the gadget we get can only move qword (8 bytes) at a time, so we have to move the leftover after next 8 bytes.
3. Use the usefulGadgets which is mentioned above to get 0x400628 and 0x400690
jeff14994@jeff14994-VirtualBox:~/Desktop/asu-hacking/03_03$ ropper -f ./write4 | grep r14
- We use
pop r14; pop r15; ret;to store the pointer and the filename respectively. Besides,
mov qword ptr [r14], r15; ret;it first dereferences r14 and stores the value from r15 to it.
jeff14994@jeff14994-VirtualBox:~/Desktop/asu-hacking/03_03$ ropper -f ./write4 | grep rdi
- It’s the gadget to store the pointer to the filename
.text:0000000000400617 usefulFunction proc near
- Although usefulFunction starts at
0x400617, it changes the
0x40061B, which destroys our plan. Thus we decide to jump from
0x400620and make it only call
jeff14994@jeff14994-VirtualBox:~/Desktop/asu-hacking/03_03$ python exploit.py