Antares (Launched 2001)
- Username:
antares
-
Click to reveal password:
thatlanguage
- Points: 10 for code, 5 for write-up
Relevant lectures: 3 & 4 - Memory Safety Vulnerabilities
The exchange from Deneb was shocking. You realize the Jupiter orbiter may not be what you once thought it was. You are left with no choice but to dig deeper. Antares is a Gobian targeting satellite that is used to provide midcourse calibrations to the royal guard's anti-spacecraft missiles. Your job is to hack into Antares, obtain the targeting data, and with it, what Gobians knew about the orbiter.
In this question, we’re going to walk you through using a format string vulnerability to redirect execution to malicious shellcode.
Step 0: High-Level Overview
Our high-level goal is to redirect execution to our malicious shellcode. We have an arg file, which is loaded into the argv
parameter of main
, and an env file, which is piped into standard input.
For this question, we place the shellcode in arg. Your first step is to find the address of this shellcode: do so, and then write that address down - we’ll need it later.
Remember, the shellcode itself should start with 0xcd58326a
.
Step 1: Analyze the Code
At what line is the vulnerable printf
call? Set a breakpoint at the vulnerable function call, and draw a stack diagram up to that point. Below is a template you may use to write out a text-based stack diagram - if you request help during office hours, this is the first thing that we’ll want to see!
...
0x00000000 [ ][ ][ ][ ] ___________
0x00000000 [ ][ ][ ][ ] RIP of Main
0x00000000 [ ][ ][ ][ ] ___________
...
Step 2: Quick Format String Review
A quick reminder about how format string vulnerabilities work: when you have a line of code that looks like print(buf)
, where we control buf
, you can pass format string specifiers into the user-provided input. When the CPU sees a format string identifier being used, it expects arguments located in incrementally increasing positions above the zeroth argument to printf
(&buf
, denoted arg0
here), seen here on the stack as arg1
, arg2
, etc.
...
[ ][ ][ ][ ] <-- arg2
[ ][ ][ ][ ] <-- arg1
[ ][ ][ ][ ] <-- arg0 (&buf)
[ ][ ][ ][ ] <-- RIP of printf
[ ][ ][ ][ ] <-- SFP of printf
...
Imagine that printf
has a pointer that initially points at arg1
. Every time it sees a format string identifier, it moves that pointer up by four, thus “consuming” the argument located at the original location of the pointer. For example, if we set buf
to '%d%d'
, then printf
would look at arg1
for the first '%d'
, and arg2
for the second '%d'
. Here are a few important format string specifiers you should be aware of:
Specifier | Description |
---|---|
%c |
Treats the corresponding arg as a VALUE. Print it as a character. |
%<k>u |
Treats the corresponding arg as a VALUE. Prints the corresponding arg as an unsigned integer and adds whitespace in front to display a total of k characters. For example, printf("%7u\n", 123) prints “ 123”, i.e. 4 spaces before 123 and 7 total characters. |
%s |
Treats the corresponding arg as a POINTER. Dereference the pointer and print the resulting value as a string. |
%n |
Treats the corresponding arg as a POINTER. Write the number of bytes that have been currently printed (as a four-byte number) to the memory address in the corresponding arg . |
%hn |
Treats the corresponding arg as a POINTER. Write the number of bytes that have been currently printed (as a two-byte number) to the memory address in the corresponding arg . |
We often use specifiers that read values (e.g. %c
, which reads a char) to “skip” arguments on the stack. Why? Sometimes, we want to work our way up the stack until we reach a place that we have write-access to (e.g. a buffer), so that we can use user-crafted inputs in our format string exploits. As such, we may find ourselves using something like '%c' * ____
, which will walk up the stack and skip past arg1
, arg2
, etc.
Step 3: Analyzing our Write Vector
Ok, so what do we know at this point?
- We know that (a) we want to redirect execution to shellcode by setting the RIP of
calibrate
to a shellcode address. This is our end goal. - We can use our write vector (the
%hn
inprintf
) to write numbers to certain locations at the stack.
That’s great…but how do we use such a limited write vector ('%n'
or '%hn'
) to write an entire memory address? We could try to convert the memory address to an integer (e.g. 0xDEADBEEF
=> 3735928559
) and print that many bytes, and then use %n
to write that number to the stack. But printing that many bytes would crash the program! Instead, we can break up our write into two halves, and use the '%hn'
specifier instead to write one half at a time.
For example, if we’re trying to write 0xFFFF1234
to 0xFFFF5550
, we can:
- Write
0x1234
to memory address0xFFFF5550
, and then… - Write
0xFFFF
to memory address0xFFFF5552
After these writes, the stack will look like the following:
0xFFFF5550 [??][??][??][??] (original)
0xFFFF5550 [34][12][??][??] (after first '%hn' write)
0xFFFF5550 [34][12][FF][FF] (after second '%hn' write)
Step 4: Attack
See the comments in the blocks to walk through the attack. Good luck!
Success State
If you see a new shell pop up ~ #
, you can run cat README
. If cat README
prints out the next question’s username and password, you’ve solved the question, congratulations!
Note: in this question, you may need to scroll to see the ~ $
. Furthermore, the ~ #
may not be aligned to the left hand side of your terminal. Think about why this might be the case, based on the exploit that you performed.
Deliverables
- Two scripts,
egg
andarg
- A write-up.