CTF toolkit: lib (net, crypto_utils), templates per category, scaffold, cheatsheet, ps_and_qs example
This commit is contained in:
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,26 @@
|
||||
"""
|
||||
CRYPTO skeleton — copy & fill. (p4-team style: read source, do the math)
|
||||
|
||||
For RSA challenges, start by importing lib.crypto_utils and try the recipes:
|
||||
recover_n_from_keys, common_modulus_attack, wiener, hastad_broadcast
|
||||
"""
|
||||
import lib.crypto_utils as cu
|
||||
from Crypto.Util.number import long_to_bytes, bytes_to_long, getPrime, inverse
|
||||
|
||||
|
||||
def main():
|
||||
# 1) Read the challenge output / source. Example: RSA with weird params.
|
||||
e = 0x10001
|
||||
# ... load n, c, etc from the provided data ...
|
||||
|
||||
# 2) Try the right recipe. Example (from 'lost_modulus'):
|
||||
# p, q = cu.recover_n_from_keys(e, d, ipmq, iqmp)
|
||||
# n = p * q
|
||||
# m = pow(c, d, n)
|
||||
# print(long_to_bytes(m))
|
||||
|
||||
raise NotImplementedError("fill in the crypto math")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,35 @@
|
||||
"""
|
||||
FORENSICS / MISC skeleton — copy & fill. (p4-team style: oracle / byte-by-byte,
|
||||
pcap parse, stego.)
|
||||
|
||||
Example (from 'heXdump'): the service uses `xxd -r -ps` which does NOT truncate,
|
||||
so you overwrite 1 byte at a time and brute the flag char-by-char against a known
|
||||
oracle output.
|
||||
"""
|
||||
from lib.net import nc, receive_until, receive_until_match, send, sendline # noqa
|
||||
import string
|
||||
|
||||
CHARSET = string.ascii_letters + string.digits + "{}_-!@#$%^&*()+=/."
|
||||
|
||||
|
||||
def byte_by_byte_oracle(base_conn_setup, oracle_fn, known_prefix="flag{"):
|
||||
"""Generic oracle recover: find next char where oracle output == baseline."""
|
||||
known = known_prefix
|
||||
while "}" not in known:
|
||||
baseline = oracle_fn(known) # output for current known prefix
|
||||
for c in CHARSET:
|
||||
test = oracle_fn(known + c)
|
||||
if test == baseline:
|
||||
known += c
|
||||
print(known)
|
||||
break
|
||||
else:
|
||||
known += "?"
|
||||
print("stuck at", known)
|
||||
break
|
||||
return known
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# Wire oracle_fn to your specific protocol; see heXdump writeup.
|
||||
pass
|
||||
@@ -0,0 +1,57 @@
|
||||
"""
|
||||
MISC skeleton — copy & fill. (p4-team style: oracle, encoding, stego, brute)
|
||||
|
||||
p4 'misc' covers a lot: byte-by-byte oracle (heXdump), encoding tricks,
|
||||
PRNG reversing, image stego, constraint solving. See forensics_skeleton.py
|
||||
for the generic oracle helper.
|
||||
"""
|
||||
import base64
|
||||
import string
|
||||
|
||||
|
||||
# ---- encoding chain helper (common in misc) ----
|
||||
def try_decodings(blob):
|
||||
"""Brute a chain of common decodings to spot a flag."""
|
||||
results = []
|
||||
data = blob
|
||||
for _ in range(3):
|
||||
for name, fn in [
|
||||
("b64", lambda d: base64.b64decode(d)),
|
||||
("b32", lambda d: base64.b32decode(d)),
|
||||
("b16", lambda d: base64.b16decode(d)),
|
||||
("hex", lambda d: bytes.fromhex(d.decode())),
|
||||
]:
|
||||
try:
|
||||
out = fn(data)
|
||||
if b"flag" in out.lower() or b"CTF" in out:
|
||||
results.append((name, out))
|
||||
data = out
|
||||
except Exception:
|
||||
pass
|
||||
return results
|
||||
|
||||
|
||||
# ---- generic byte-by-byte oracle ----
|
||||
CHARSET = string.ascii_letters + string.digits + "{}_-!@#$%^&*()+=/."
|
||||
|
||||
|
||||
def recover_oracle(known_start, oracle_fn, stop="}"):
|
||||
"""oracle_fn(prefix) returns a stable baseline string for a given known prefix."""
|
||||
known = known_start
|
||||
while stop not in known:
|
||||
baseline = oracle_fn(known)
|
||||
found = None
|
||||
for c in CHARSET:
|
||||
if oracle_fn(known + c) == baseline:
|
||||
found = c
|
||||
break
|
||||
if not found:
|
||||
known += "?"
|
||||
break
|
||||
known += found
|
||||
print(known)
|
||||
return known
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise NotImplementedError("pick a misc technique and fill it in")
|
||||
@@ -0,0 +1,55 @@
|
||||
"""
|
||||
PWN skeleton — copy & fill. (p4-team style)
|
||||
|
||||
Workflow:
|
||||
1. Leak (format string / GOT / libc) -> step 1
|
||||
2. Build ROP / overwrite -> step 2
|
||||
3. Get shell / read flag -> step 3
|
||||
"""
|
||||
from lib.net import nc, receive_until, receive_until_match, send, sendline # noqa
|
||||
from pwn import * # noqa (ELF, ROP, p64, u64, context)
|
||||
|
||||
context.log_level = 'info'
|
||||
context.arch = 'amd64' # or 'i386'
|
||||
|
||||
HOST, PORT = "challenge.host", 1337
|
||||
# binary = ELF('./challenge')
|
||||
# libc = ELF('./libc.so.6')
|
||||
|
||||
|
||||
def step1_leak(s):
|
||||
"""Leak libc/stack/PIE base. Adapt to the vuln (format string shown here)."""
|
||||
s.recv_until(b"name > ")
|
||||
# classic format-string leak
|
||||
sendline(s, b"%9$p|%11$p")
|
||||
line = s.recv_until(b"\n")
|
||||
leak = int(line.split(b"|")[0], 16)
|
||||
log.info("leak = %#x", leak)
|
||||
return leak
|
||||
|
||||
|
||||
def step2_exploit(s, leak):
|
||||
"""Construct payload. Fill with your gadgets/ROP."""
|
||||
payload = b"A" * 40 # padding to saved RIP
|
||||
payload += p64(leak) # example: overwrite with leaked addr
|
||||
# payload += rop.chain(...)
|
||||
s.recv_until(b"message > ")
|
||||
sendline(s, payload)
|
||||
|
||||
|
||||
def step3(s):
|
||||
s.recv_until(b"$ ") # shell prompt, or just:
|
||||
sendline(s, b"cat flag*; cat /flag*")
|
||||
print(s.recvall(timeout=3).decode(errors='replace'))
|
||||
|
||||
|
||||
def main():
|
||||
s = nc(HOST, PORT)
|
||||
leak = step1_leak(s)
|
||||
step2_exploit(s, leak)
|
||||
step3(s)
|
||||
s.close()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,65 @@
|
||||
"""
|
||||
RE (reverse engineering) skeleton — copy & fill. (p4-team style)
|
||||
|
||||
Common p4 patterns:
|
||||
* Extract bytes from a .rodata / data dump (IDA "db 30h" lines) -> often just
|
||||
RSA key material. See 'reversing_is_amazing': parse the bytes, RSA.importKey,
|
||||
then decrypt the given ciphertext.
|
||||
* Symbolic execution / path constraint solving with angr.
|
||||
* Binary parsing / patching with lief; emulation with unicorn.
|
||||
"""
|
||||
import re
|
||||
|
||||
# ---- pattern A: RSA key from an IDA/Ghidra byte dump ----
|
||||
def bytes_from_rodata(dump_text):
|
||||
"""Parse lines like: .rodata:0000 db 30h, 82h, 2, 5Ch ; comment"""
|
||||
out = []
|
||||
for line in dump_text.splitlines():
|
||||
m = re.search(r"\bdb\b\s+(.*)", line)
|
||||
if not m:
|
||||
continue
|
||||
body = m.group(1).split(";")[0]
|
||||
for tok in re.findall(r"([0-9a-fA-F]+)h?|(\d+)", body):
|
||||
val = tok[0] or tok[1]
|
||||
try:
|
||||
out.append(int(val, 16) if (tok[0] and val.endswith("h")) or
|
||||
(tok[0] and not val.isdigit()) else int(val))
|
||||
except ValueError:
|
||||
pass
|
||||
return bytes(out)
|
||||
|
||||
|
||||
def solve_rsa_from_dump(dump_text, ciphertext_int):
|
||||
from Crypto.PublicKey import RSA
|
||||
from Crypto.Util.number import long_to_bytes
|
||||
data = bytes_from_rodata(dump_text)
|
||||
key = RSA.importKey(bytearray(data))
|
||||
return long_to_bytes(pow(ciphertext_int, key.e, key.n))
|
||||
|
||||
|
||||
# ---- pattern B: angr symbolic execution (uncomment & adapt) ----
|
||||
"""
|
||||
import angr, claripy
|
||||
def solve_with_angr(binary, find_addr, avoid_addr, input_len=20):
|
||||
proj = angr.Project(binary, auto_load_libs=False)
|
||||
sim = proj.factory.entry_state()
|
||||
flag = sim.solver.BVS('flag', input_len*8)
|
||||
for i in range(input_len):
|
||||
sim.memory.store(sim.regs.rsp + i, flag.get_byte(i))
|
||||
sim = proj.factory.simgr(sim)
|
||||
sim.explore(find=find_addr, avoid=avoid_addr)
|
||||
if sim.found:
|
||||
return sim.found[0].solver.eval(flag, cast_to=bytes)
|
||||
"""
|
||||
|
||||
# ---- pattern C: lief parse / patch ----
|
||||
"""
|
||||
import lief
|
||||
def parse(binary):
|
||||
f = lief.parse(binary)
|
||||
for sym in f.symbols: print(sym.name, hex(sym.value))
|
||||
"""
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise NotImplementedError("pick a pattern above and fill it in")
|
||||
@@ -0,0 +1,29 @@
|
||||
"""
|
||||
WEB skeleton — copy & fill. (p4-team style: read the source, find the
|
||||
sanitization-order bug, then script the request.)
|
||||
|
||||
Key lesson from 'piapiapia': filter() ran AFTER serialize() -> length
|
||||
manipulation / object injection. Always diff the order of sanitize vs use.
|
||||
"""
|
||||
import requests
|
||||
|
||||
BASE = "http://challenge.host:port"
|
||||
S = requests.Session()
|
||||
|
||||
|
||||
def get_token():
|
||||
r = S.get(BASE + "/login")
|
||||
# parse CSRF / cookies as needed
|
||||
return None
|
||||
|
||||
|
||||
def exploit():
|
||||
# 1) Find the input that bypasses validation (array, encoding, filter order).
|
||||
# 2) Craft payload.
|
||||
# 3) Send & parse response for flag.
|
||||
r = S.post(BASE + "/endpoint", data={"nickname[]": "PAYLOAD"})
|
||||
print(r.text)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
exploit()
|
||||
Reference in New Issue
Block a user