PicoCTF 2021 (Pwn only)

Ditto
7 min readApr 8, 2021

My results: 11/16 completed!

Managed to solve Cache Me Outside locally but not on remote. :(

I will only be writing for these challenges as I feel some of the other challenges are rather similar.

  1. Binary Gauntlet 2
  2. Here’s a LibC
  3. Unsubscriptions are Free
  4. Horsepower
  5. Turboflan

All my solutions are here:

1) Binary Gauntlet 2

Vulnerability:
1) Format String Vulnerability
2) Buffer overflow
Exploit Concept:
1) Use the format string vulnerability to leak stack address
2) From the leak, calculate the shellcode address
3) Buffer overflow and jump to shellcode
Challenges faced:
1) When overwriting RIP during the buffer overflow, it is done by strcpy so there is some address restrictions. (Null byte restriction etc)
2) Offset to the shellcode address on the server is off by +16 compared to local

2) Here’s a LibC

Vulnerability:
1) Buffer overflow in scanf (do_stuff + 38)
Exploit Concept:
1) Use ROP to leak PUTS_GOT address and restart the binary
2) Calculate LibC base with the leak
3) Use ROP to execve and get shell

Elaboration on Exploit Concept (1):

Here is a very standard way of leaking PUTS_GOT addresspayload = b’A’ * offset
payload += p64(POPRDI_gadget+1) #Ret gadget to do stack alignment
payload += p64(POPRDI_gadget) #Pop what you want to leak
payload += p64(OFFSET_PUTS_GOT) #This address is placed into rdi
payload += p64(OFFSET_PUTS_PLT) #Call puts_PLT which will leak GOT
payload += p64(POPRDI_gadget+1)
payload += p64(OFFSET_MAIN_PLT) #Restarting the binary

Elaboration on Exploit Concept (2) :

Calculating LibC base is easy with PwnTools. Assuming we leaked PUTS_GOT address, PwnTools can calculate the standard PUTS_GOT address if we provide it with the correct libc.

libc = ELF("./libc.so.6") #Server's libC
libc_base = leak_dec - libc.sym["puts"] #PwnTools will calculate PUT

Elaboration on Exploit Concept (3):

Specifying libc.address, we can easily search BINSH, SYSTEM and EXIT which will be used for execve.

Specifying libc.address, we can easily search BINSH, SYSTEM and exit which will be used for execvelibc.address = libc_base
BINSH = next(libc.search(b"/bin/sh")) #Verify with find /bin/sh
SYSTEM = libc.sym["system"]
EXIT = libc.sym["exit"]

3) Unsubscriptions are Free

This was my first heap challenge. It is pretty good, I must say.. There are a lot of details in this code that one should pay attention to.

Detail 1: When program is at ProcessInput(), the program asks for username straight instead of jumping to m. This is unlike the other choices.

Detail 2: LeaveMessage() is also done in ProcessInput() and not in doProcess()

Vulnerability:
Does not check for a valid user in main, specifically the if(user) has been commented out. This allows for a Use-After-Free
Exploitation Concept:
1) Create some messages (just for fun)
2) Call delete account (this deletes the user object and whatToDo)
3) Leave a message of size 0x8 (This enters the memory space of user->whatToDo)
4) Now if your message is the leaked function pointer of get flag, it will trigger the function!

4) Horsepower

This is a good start to v8 pwn. Before you read this, it will be good to try out the earlier challenge on Kit Engine. Also, some good references to read (in order):

  1. https://faraz.faith/2019-12-13-starctf-oob-v8-indepth/
  2. https://pwning.tech/2020/09/09/v8-pwn-downunderctf/

For reference 1, you won’t be able to reproduce the same results as there is pointer compression (pointers are now only 4 bytes, so you won’t see the full addresses when doing DebugPrint) in the v8 engine. But it is very detailed in explaining all the basics and the primitives required.

Reference 2, it is more recent, and I will be using these techniques to complete the challenge.

It is important to understand the basics of maps and element pointer (touched on in the second reference) before you read on.

Vulnerability:
Reading the patch, we have a method (setHorsepower) that allows us to modify our initialized array length to any value. With this, we have OOB read/write.
Exploitation Concept:
1) Position objects/arrays nicely in memory.
2) Trigger setHorsepower to modify our float array length.
3) Read OOB to get float array map and element pointer.
4) Read OOB to get object array map and element pointer.
5) Get AddrOf and FakeObj primitives

I will further explain on 5 because here is the part where things get confusing! This might be useful:

AddrOf primitive:

Both the references above explain very well what happens when you overwrite the object array with a float array map. This allows you to have addrof primitive. A concise version of the explanation is as such:

In a normal obj_array with obj_array map, obj_array will hold the address of obj at index 0. When you call obj_array[0], compiler looks at the map of the obj_array and knows how to get to the obj. However, when you overwrite the obj_array map with a float_array map, the compiler will just output index 0 as a float and since index 0 is holding the address to obj, we will leak the address of the obj!

So bottom line is if we read index 0 of obj_array with a float array map, we get a leak. To construct our addrof primitives, we will change the element pointer of our float array to the element pointer of the obj array. When we read index 0 of float array, we will leak the address of the object!

FakeObj primitive:

I have a tough time visualizing the fakeobj primitive because I do not know how to test if it works until later on in the exploit.(LOL) I have came up with some ways to help me in making my fakeobj primitive. First, it is to think that the fakeobj primitive is just the reverse of the addrof primitive (I should end up returning something with an obj_array map). And secondly, it should return me an array if i do a fakeobj on a working address (rw_helper).

  1. Set index 0 to be the address I want to place my fake object. (our initial map and element pointer should be that of the float array, so it will be indexed properly)
  2. Overwrite map to object array.
  3. Return index 0.
  4. Compiler will think that I have an object at the address now.

I faced a little challenge in making this primitive because I did not understand how to test this. Also, the above 2 references does not work wholesale for this exploit. For this exploit, it is important to correct our element pointer and array map after the primitive is done.

Once these 2 primitives are done, test your arb_read. If it is working, the rest can just be referenced from the second link I shared earlier.

And booms, flag!!!

5) TurboFlan

Disclaimer: My write up for this is really bare. Waiting for some one to publish something with the visualizer!

References to read:

For this exploit, our vulnerability is very similar to the one above. The above reference by Hpasser by exploits the vulnerability with this code.

var obj = {};
obj.c = {a: 1.1};

function leaker(o){
return o.c.a;
}
for (var i = 0; i < 0x4000; i++) {
leaker(obj);
}

var buf_to_leak = new ArrayBuffer();
obj.c = {b: buf_to_leak}
console.log(leaker(obj)) //output: 2.0289592652999e-310 //Address of buf_to_leak is leaked!

I simplied the above exploit and added in a new feature. This is my JIT function instead:

f = function(a,val) { 
a.z = val;
return a.x;
}
var corruptObj = {x : 1.5, y:1.5, v:1.5, w:1.5, a:1.5, b:3, c:3, d:3, e:3, f:3, i:3, j:3, k:3, k:3, z: 3 };
for (let i = 0; i < 10000; i++){
f(corruptObj,1);
}

So now, if I do this:

obj = {a:aux_obj_arr};  //need to allocate 2 objects
obj = {a:aux_obj_arr};
//position aux_float_arr below it to corrupt it
//aux_float_arr will be corrupted, giving it a very large length, allowing OOB read/write
aux_float_arr = [1.1, 2.2, 3.3];
//overwrite the length of aux_float_arr with 0x55
var leak = f(obj,0x55);

Explaining the vulnerability:

Compiler thinks it is getting a property array that have a number of properties. However, it is only getting one property. It still continues to set property z to be a value. Hence, we have an OOB write to one memory location. If we position an array after it, and overwrite the length of this array with the JIT function, we have an OOB read/write after the array!

In fact, once you managed to overwrite the length, the rest is similar to the Horsepower challenge (haha)

I faced more weird issues in this exploit:

  1. Memory corruption of the corrupted array changes as I initialize more instructions. For example, after I written my addrof primitive and continue writing my exploit below it, the above memory corruption will fail CONTINUOUSLY. Some times even adding comments will break it… However, I found that if I just initialize some junk instructions like (var a = 1; var b = 1; …) It miraculously fixed the exploit…

Now we get the flag!!!

Major thanks to PicoCTF for getting me started in heap exploitation and d8! It was a really great start.

--

--