picoCTF 2025 writeup (Reversing)

picoCTF 2025 の Rev 問題の Writeup.
難易度 medium のみ

Quantum Scrambler

# load cipher
with open("./cipher.txt", "r") as f:
    cipher = eval(f.read())
    flag = "pi"

    # decode
    for i in range(1, len(cipher)-2):
        flag += chr(int(cipher[i][0], 16)) + chr(int(cipher[i][2], 16))

    flag += chr(int(cipher[-2][0], 16)) + chr(int(cipher[-1][0], 16))

    print(flag)
        

Chronohack

import time
from pwn import *
import random

def get_random(seed: int):
    alphabet = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
    random.seed(seed)  # seeding with current time 
    s = ""
    for i in range(20):
        s += random.choice(alphabet)
    return s

host = $HOST
port = $PORT

for itr in range(500):
    connected_time = int(time.time() * 1000)
    sh = remote(host, port)

    delay_time = itr * 50
    print("delay", delay_time)

    prompt = sh.recvuntil("(or exit):")

    print(prompt.decode())

    for i in range(50):
        payload = get_random(connected_time + i + delay_time)
        sh.sendline(payload)

    prompt = sh.recvuntil("Bye!")
    print(prompt.decode())

    sh.close()

print("no hit!")
sh.interactive()
        
import time
from pwn import *
import random

def get_random(seed: int):
    alphabet = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
    random.seed(seed)  # seeding with current time 
    s = ""
    for i in range(20):
        s += random.choice(alphabet)
    return s

host = $HOST
port = $PORT

connected_time = int(time.time() * 1000)
sh = remote(host, port)

delay_time = 1650

prompt = sh.recvuntil("(or exit):")

print(prompt.decode())

for i in range(50):
    payload = get_random(connected_time + i + delay_time)
    sh.sendline(payload)

sh.interactive()
        

Tap into Hash

for i in range(0, len(plaintext), block_size):
        block = plaintext[i:i + block_size]
        cipher_block = xor_bytes(block, key_hash)
        ciphertext += cipher_block
        
import hashlib

def xor_bytes(a, b):
    return bytes(x ^ y for x, y in zip(a, b))

key = $KEY
encrypted_chain = $CHAIN
block_size = 16
key_hash = hashlib.sha256(key).digest()

decoded_text = b""

for i in range(0, len(encrypted_chain), block_size):
    encrypted_block = encrypted_chain[i:i + block_size]
    decoded_block = xor_bytes(encrypted_block, key_hash)
    decoded_text += decoded_block

print(decoded_text)
        

perplexed

check_list = []
check_list.append(-0x1f)
check_list.append(-0x59)
check_list.append(0x1e)
check_list.append(-8)
check_list.append('u')
check_list.append('#')
check_list.append('{')
check_list.append('a')
check_list.append(-0x47)
check_list.append(-99)
check_list.append(-4)
check_list.append('Z')
check_list.append('[')
check_list.append(-0x21)
check_list.append('i')
check_list.append(0xd2)
check_list.append(-2)
check_list.append(0x1b)
check_list.append(-0x13)
check_list.append(-0xc)
check_list.append(-0x13)
check_list.append('g')
check_list.append(-0xc)

decoded_pass = ""

for i in range(len(check_list)):
    if type(check_list[i]) == str:
        check_list[i] = ord(check_list[i])
    elif check_list[i] < 0:
        check_list[i] = 0xff ^ (-check_list[i] - 1)

print(list(map(hex, check_list)))

var_1c = 0
var_20 = 0
var_2c = 0
pass_hex = 0

for i in range(0x17):
    for j in range(8):
        if var_20 == 0:
            var_20 = 1
        
        var_30 = 1 << (7 - j & 0x1f)
        var_34 = 1 << (7 - var_20 & 0x1f)
        print(var_30, var_20)

        if 0 < check_list[i] & var_30:
            pass_hex |= var_34

        var_20 += 1

        if var_20 == 8:
            var_20 = 0
            var_1c += 1
            decoded_pass += chr(pass_hex)
            print("passhex:", pass_hex, "phrase:", chr(pass_hex))
            pass_hex = 0

        if var_1c == 0x1b:
            print("end")
            break

print(decoded_pass)
        

Binary Instrumentation 1

frida-trace -f bininst1.exe -i Sleep
        
/*
 * Auto-generated by Frida. Please modify to match the signature of Sleep.
 * This stub is currently auto-generated from manpages when available.
 *
 * For full API reference, see: https://frida.re/docs/javascript-api/
 */

defineHandler({
  onEnter(log, args, state) {
    log('Sleep()');
    args[0] = ptr(0);
    console.log("sleep modified!");
  },

  onLeave(log, retval, state) {
  }
});
        

Binary Instrumentation 2

frida-trace -f bininst2.exe -i CreateFileA -i WriteFile
        
/*
 * Auto-generated by Frida. Please modify to match the signature of CreateFileA.
 * This stub is currently auto-generated from manpages when available.
 *
 * For full API reference, see: https://frida.re/docs/javascript-api/
 */

defineHandler({
  onEnter(log, args, state) {
    log('CreateFileA()');

    console.log("filename:", args[0].readAnsiString());
  },

  onLeave(log, retval, state) {
  }
});
        
/*
 * Auto-generated by Frida. Please modify to match the signature of CreateFileA.
 * This stub is currently auto-generated from manpages when available.
 *
 * For full API reference, see: https://frida.re/docs/javascript-api/
 */

defineHandler({
  onEnter(log, args, state) {
    log('CreateFileA()');

    console.log("filename:", args[0].readAnsiString());

    var newFileName = Memory.allocUtf8String('flag.txt');
    args[0] = newFileName;

    console.log("filename:", args[0].readAnsiString());
  },

  onLeave(log, retval, state) {
  }
});
        
/*
 * Auto-generated by Frida. Please modify to match the signature of WriteFile.
 * This stub is currently auto-generated from manpages when available.
 *
 * For full API reference, see: https://frida.re/docs/javascript-api/
 */

defineHandler({
  onEnter(log, args, state) {
    log('WriteFile()');
    console.log("WriteFile was called!");
    console.log("buf:", args[1].readAnsiString());
    
  },

  onLeave(log, retval, state) {
  }
});