Skip to content

Instantly share code, notes, and snippets.

@hyunsikjeong
Created May 29, 2023 01:54
Show Gist options
  • Save hyunsikjeong/006f95072b63622119154ca989e4a840 to your computer and use it in GitHub Desktop.
Save hyunsikjeong/006f95072b63622119154ca989e4a840 to your computer and use it in GitHub Desktop.
Solver for ziggypop from DEFCON CTF 2023 Quals
from typing import Any, Union
from pwn import *
exe = ELF("./main")
context.binary = exe
# curve25519 is from https://gist.github.com/nickovs/cc3c22d15f239a2640c185035c06f8a3
from curve25519 import *
from curve25519 import _fix_secret, _unpack_number, _point_add, _point_double, _pack_number, _raw_curve25519, P
from pwn import *
import hmac
import random
import os
base_script = """
set detach-on-fork off
set follow-fork-mode child
set $BASE = 0x7ffff7feb000
"""
# 0. Get server public
con = remote('ziggypop-wmuute4z4x4r2.shellweplayaga.me', 10001)
con.sendlineafter(b'Ticket please: ', b'ticket{ticket}')
server_public = con.recv(32)
print("Server public:", server_public.hex())
# 0-2. Brute-force our public value to get the shared key with the byte C0AA
base = (_unpack_number(server_public), 1)
point = _point_double(base)
prev = base
for our_secret in range(3, 2 ** 24):
if our_secret % 5000 == 0:
print("Searcing secret...", our_secret)
new_point = _point_add(point, base, prev)
x, z = new_point
inv_z = pow(z, P - 2, P)
res = (x * inv_z) % P
res = _pack_number(res)
if res[3] == 0xaa and res[4] == 0xc0:
break
point, prev = new_point, point
our_public = _pack_number(_raw_curve25519(9, our_secret))
shared_key = _pack_number(_raw_curve25519(_unpack_number(server_public), our_secret))
print("Our public:", our_public.hex())
con.send(our_public)
# 1. First run to get the encrypted result of "\x02" * 256
base_script += "b *$BASE + 0x3113\nb *$BASE + 0x353E\nc\n"
v = int.from_bytes(shared_key, 'little')
for i in range(5):
t = (v >> (51 * i)) & ((1 << 51) - 1)
base_script += f"set *(unsigned long long *)($rsp + 0x{0x700 + i * 8:x}) = 0x{t:x}\n"
gs = base_script + "c\nx/20gx $rsp + 0x1900\n"
print(gs)
l_gdb = gdb.debug([exe.path], gdbscript=gs, env={}, aslr=False)
local = remote('localhost', 8675)
local.recv(32)
local.send(our_public)
local.send(b"a" * 0x18)
local.send(b"\x02" * 0x110)
ct = b''
for i in range(4):
inp = input(f"{i}th line > ")
v1, v2 = inp.split(':')[1].split('0x')[1:]
ct += int(v1.strip(), 16).to_bytes(8, 'little')
ct += int(v2.strip(), 16).to_bytes(8, 'little')
assert len(ct) == 64
input("Fin?")
l_gdb.close()
local.close()
# 2. Second run to get the checksum of "\x02" * 256
gs = base_script + "b *$BASE + 0x3600\nc\nc\nx/20gx $rsp + 0x30\n"
l_gdb = gdb.debug([exe.path], gdbscript=gs, env={}, aslr=False)
local = remote('localhost', 8675)
local.recv(32)
local.send(our_public)
local.send(b"a" * 0x18)
# You can send ct again, cuz it's stream cipher
local.send(b"\x02" * 0x10 + ct[32:64] + b"\x02" * 0xe0)
checksum = b''
inp = input("checksum > ")
v1, v2 = inp.split(':')[1].split('0x')[1:]
checksum += int(v1.strip(), 16).to_bytes(8, 'little')
checksum += int(v2.strip(), 16).to_bytes(8, 'little')
input("Fin?")
l_gdb.close()
local.close()
# 3. Third run to decrypt the encrypted flag from the remote
con.send(b'a' * 0x18)
con.send(checksum + ct[32:64] + b"\x02" * 0xe0)
nonce = con.recv(0x18)
enc_flag = con.recv(0x110)
con.close()
gs = base_script + "b *$BASE + 0x36CA\nc\nc\nx/s $rsp + 0x1800\n"
l_gdb = gdb.debug([exe.path], gdbscript=gs, env={}, aslr=False)
local = remote('localhost', 8675)
local.recv(32)
local.send(our_public)
local.send(nonce)
local.send(enc_flag)
input("Fin?")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment