Skip to main content Link Search Menu Expand Document (external link)

Antares (Launched 2001)

  • Username: antares
  • Click to reveal password: thatlanguage
  • Points: 15
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 used to provide midcourse
calibrations to 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 first argument to printf (buf), seen here on the stack as args[0], args[1], etc.

[  ][  ][  ][  ] <-- args[1]
[  ][  ][  ][  ] <-- args[0]
[  ][  ][  ][  ] <-- &buf
[  ][  ][  ][  ] <-- RIP of printf
[  ][  ][  ][  ] <-- SFP of printf

Imagine that printf has a pointer to &buf. 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 args[0] for the first "%d", and args[1] for the second "%d". Here are a few important format string specifiers you should be aware of:

Specifier Description
%c Treats args[i] as a VALUE. Print it as a character.
%__u Treats args[i] as a VALUE. Print a variable-length number of bytes starting from args[i] (set ___ to the desired length).
%s Treats args[i] as a POINTER. Dereference the pointer and print the resulting value as a string.
%n Treats args[i] as a POINTER. Write the number of bytes that have been currently printed (as a four-byte number) to the memory address args[i].
%hn Treats args[i] as a POINTER. Write the number of bytes that have been currently printed (as a two-byte number) to the memory address args[i].

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 args[i], args[i+1], etc.

Step 3: Analyzing our Write Vector

Ok, so what do we know at this point?

  1. 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.
  2. We can use our write vector (the %hn in printf) 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:

  1. Write 0xFFFF to memory address 0xFFFF5550, and then…
  2. Write 0x1234 to memory address 0xFFFF5552

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!


  • Two scripts, egg and arg
  • A writeup