v(*'='*)v かに

UTCTF Quals 2019 Writeup

I participated in the UTCTF Quals 2019 as a member of a team insecure. We reached 18th place with 8250points. Many interesting challenges were provided in the competition. Thanks to all the admins! I'm writing up some challenges which I solved.

[Cryptography 200pts][basics]crypto


Can you make sense of this file?

by balex

We were given a file: binary.txt which had many binary-formed integers.

01010101 01101000 00101101 01101111 01101000 00101100 00100000 01101100 01101111 01101111 01101011 01110011 00100000 01101100 01101001 01101011 01100101 00100000 01110111 01100101 00100000 01101000 01100001 01110110 01100101 00100000 01100001 01101110 01101111 01110100 01101000 01100101 01110010 00100000 01100010 01101100 01101111 01100011 01101011 00100000 01101111 01100110 00100000 01110100 01100101 01111000 01110100 00101100 00100000 01110111 01101001 01110100 01101000 00100000 01110011 01101111 01101101 01100101 00100000 01110011 01101111 01110010 01110100 00100000 01101111 01100110 00100000 01110011 01110000 01100101 01100011 01101001 01100001 01101100 00100000 01100101 01101110 01100011 01101111 01100100 01101001 01101110 01100111 00101110 00100000 01000011 01100001 01101110 00100000 01111001 01101111 01110101 00100000 01100110 01101001 01100111 01110101 01110010 01100101 00100000 01101111 01110101 01110100 00100000 01110111 01101000 01100001 01110100 00100000 01110100 01101000 01101001 01110011 00100000 01100101 01101110 01100011 01101111 01100100 01101001 01101110 01100111 00100000 01101001 01110011 00111111 00100000 00101000 01101000 01101001 01101110 01110100 ...

I decoded them to a sequence of characters and I got a base64 encoded string, which was the next part of this challenge.

Uh-oh, looks like we have another block of text, with some sort of special encoding. Can you figure out what this encoding is? (hint: if you look carefully, you'll notice that there only characters present are A-Z, a-z, 0-9, and sometimes / and +. See if you can find an encoding that looks like this one.)

I decoded it and I found the last part of the output was encrypted with the substitution cipher.

New challenge! Can you figure out what's going on here? It looks like the letters are shifted by some constant. (hint: you might want to start looking up Roman people).
kvbsqrd, iye'bo kvwycd drobo! Xyg pyb dro psxkv (kxn wkilo dro rkbnocd...) zkbd: k celcdsdedsyx mszrob. Sx dro pyvvygsxq dohd, S'fo dkuox wi wocckqo kxn bozvkmon ofobi kvzrklodsm mrkbkmdob gsdr k mybboczyxnoxmo dy k nsppoboxd mrkbkmdob - uxygx kc k celcdsdedsyx mszrob. Mkx iye psxn dro psxkv pvkq? rsxd: Go uxyg drkd dro pvkq sc qysxq dy lo yp dro pybwkd edpvkq{...} - grsmr wokxc drkd sp iye coo drkd zkddobx, iye uxyg grkd dro mybboczyxnoxmoc pyb e, d, p, v k, kxn q kbo. Iye mkx zbylklvi gybu yed dro bowksxsxq mrkbkmdobc li bozvkmsxq drow kxn sxpobbsxq mywwyx gybnc sx dro Oxqvscr vkxqekqo. Kxydrob qbokd wodryn sc dy eco pboaeoxmi kxkvicsc: go uxyg drkd 'o' crygc ez wycd ypdox sx dro kvzrklod, cy drkd'c zbylklvi dro wycd mywwyx mrkbkmdob sx dro dohd, pyvvygon li 'd', kxn cy yx. Yxmo iye uxyg k pog mrkbkmdobc, iye mkx sxpob dro bocd yp dro gybnc lkcon yx mywwyx gybnc drkd cryg ez sx dro Oxqvscr vkxqekqo.
rghnxsdfysdtghu! qgf isak cthtuike dik zknthhkx rxqldgnxsliq risyykhnk. ikxk tu s cysn cgx syy qgfx isxe kccgxdu: fdcysn{3hrxqld10h_15_r00y}. qgf vtyy cthe disd s ygd gc rxqldgnxsliq tu pfud zftyethn gcc ditu ugxd gc zsutr bhgvykenk, she td xksyyq tu hgd ug zse scdkx syy. iglk qgf khpgqke dik risyykhnk!

The online solver could solve it. The flag was utflag{3ncrypt10n_15_c00l}.

[Forensics 100pts][basics]forensics


My friend said they hid a flag in this picture, but it's broken!

by balex

We were given a file which had an extension .jpeg, however, it was the ascii text utflag{d0nt_tru5t_f1l3_3xt3ns10n5}.

[Web 650pts]HabbyDabby's Secret Stash


HabbyDabby's hidden some stuff away on his web server that he created and wrote from scratch on his Mac. See if you can find out what he's hidden and where he's hidden it!


by copperstick6

This challenge's page had an LFI vulnerability. The query http://a.goodsecurity.fail/?file=/etc/passwd showed the contents of /etc/passwd. As the challenge description said that HabbyDabby used a Mac machine to develop the page, I tried reading the file .DS_Store, which is usucally created by the macOS automatically. It had the information about the directory structure. I used this library to view them. I explored some directories and found the flag at http://a.goodsecurity.fail/e/d/e/flag.txt. By the way, the LFI vulnerability wasn't necessary to solve this challenge. What did that mean?

[Reverse Engineering 400pts]Domain Generation Algorithm


This executable calculates a new domain depending on the system time. See if you can predict what the new domain will be on Tue, 20 Apr 2021 13:25:03 GMT.

Note: the flag is utflag{domain_name.tld}

by jitterbug_gang

The given file dga was an ELF. It was created with pyinstaller. Pyinstaller stores compiled bytes in the ELF file. So we can extract it using archive_viewer.py.

By the way, the byte-compilation of python(CPython) has 3 steps. The first step is the conversion from source code to bytecode. We can use the builtin compile function for this. The next step is to marshal the bytecode, this step corresponds to marshal.dumps function. The last step is to create the .pyc format data. The structure of .pyc is magic + timestamp + padding + marshaled-bytecode. The bytes which we can extract from the ELF are the marshaled-bytecode.

I wrote the following script for formatting the extracted bytecode to .pyc format and making pycdc decompile it to a source code.

import marshal
import struct
import time
import imp
import sys
import os

with open(sys.argv[1], 'rb') as f:
    code = f.read()

t = struct.pack('i', int(time.time()))
padding = b'A\x00\x00\x00'
# bytecode = marshal.dumps(code)
bytecode = code

with open(sys.argv[1]+".pyc", 'wb') as f:

I could restore the original source code successfully. My last work was to modify the code so that it prints the generated domain and uses a specified time.

# Source Generated with Decompyle++
# File: dga.pyc.pyc (Python 3.6)

from nistbeacon import NistBeacon
import time
from datetime import datetime
import hashlib

def gen_domain():
    # now = int(time.time())
    now = datetime.strptime('2021-04-20 13:25:03 +0000', '%Y-%m-%d %H:%M:%S %z').timestamp()
    now -= 100000000
    record = NistBeacon.get_previous(now)
    val = record.output_value
    h = hashlib.md5()
    res = h.digest()
    domain = ''
    for ch in res:
        tmp = (ch & 15) + (ch >> 4) + ord('a')
        if tmp <= ord('z'):
            domain += chr(tmp)
    domain += '.org'
    return domain

if __name__ == '__main__':

The flag was: yervwuusmmiis.org.

[Reverse Engineering 750pts]simple python script


simple python script I wrote while not paying attention in graphics

by asper

We were given a python script whose name was wtf.py. It was obfuscated as shown below. Wow there are cats!

flag = input("> ")
for i in range(0, len(flag), int((544+5j).imag)):
    inputs = []
    (lambda ねこ, _, __, ___, ____, _____, ネコ, q, k: getattr(ねこ, "extend")([(lambda _, __, ___: _(_, __, ___))(lambda _, __, ___:bytes([___ % __]) + _(_, __, ___ // __) if ___ else(lambda: _).__code__.co_lnotab,(_ << k),(((q << ネコ) - _____) << ((((_____ << __) - _) << ____) + _____)) + (((q << ネコ) - ((___ << __) + _)) << ((((_____ << __) - _) << ____) - q))...
    getattr(temp, "update")(getattr(flag[i:i + 5], "encode")("utf-8"))
    if getattr(__import__("difflib"), "SequenceMatcher")(None, getattr(getattr(temp, "hexdigest")(), "lower")(), getattr(inputs[i // 5], "decode")("utf-8").lower()).ratio() != 1.0:


I think the print debug is effective in this sort of challenges. I categorized the nested lambda expression into 2 parts: the function-call part and the arguments-prepare part. Then I understood the work of this code. I simplified the obfuscated code into the following script.

s = input()
hashes = ['26d33687bdb491480087ce1096c80329aaacbec7', '1c3bcf656687cd31a3b486f6d5936c4b76a5d749', '11a3e059c6f9c223ce20ef98290f0109f10b2ac6', '6301cb033554bf0e424f7862efcc9d644df8678d', '95d79f53b52da1408cc79d83f445224a58355b13']
for i in range(0, len(s), 5):
  if sha1(s[i:i+5]).hexdigest() != hashes[i//5]:

I found that the input string was checked in sha1 every 5-byte block. To find the correct input I cracked these hashes. Because the original text were just 5 characters long, bruteforce worked. The flag was puppyp1zzaanimetoruskitty.

[Reverse Engineering 1200pts]Crackme


Attention all UTCTF players, asper is in great danger, and he needs YOUR help to reverse engineer this binary and figure out the password. To do this, he needs IDA Pro and a couple of breakpoints. To help him, all he needs is your credit card number, the three numbers on the back, and the expiration month and date. But you gotta be quick so that asper can secure the flag, and achieve the epic victory R O Y A L.

Note: the flag is the password with utflag{} wrapped around it.

by jitterbug_gang

Also, this binary was compiled a little differently, and you may need to install some extra dependencies to run it. (Or you can try solving this with just static analysis.) Try installing libc++abi-dev and libcxxtools-dev to run this challenge.

If that doesn't work for you, try to preload this libc file with LD_PRELOAD.

At first I replaced __libc_csu_init with nop because ptrace prevented me from debugging dynamically. I used radare2 for this work.

By analysing the binary dynamically & statically, I found that the binary was working like the following C code. In fact, this binary used an exception for jumping to somewhere in order to make it complicated. Also the loop I commented stuff was constructed on the memory at runtime.

char test[] = {...};
int main() {
    char buf[0x40];
    fgets(buf, 0x40, stdin);
    buf[0x34] ^= 0x43;
    buf[0x2f] ^= 0x44;
    for (int i = 0; i < strlen(buf); i++) {
        buf[i] ^= 0x27;
    for (int i = 0; i < strlen(buf); i++) { // stuff
        buf[i] ^= (i + 0x33);
    if (memcmp(buf, test, 0x40) == 0) {
    } else {

The correct input could be calculated by the following script.

data = [0x5, 0x4C, 0x7A, 0x70, 0x66, 0x2C, 0x41, 0x2C, 0x72, 0x7D, 0x2A, 0x6B, 0x75, 0x6, 0x12, 0x54, 0x54, 0xD, 0x3D, 0x15, 0x8, 0xE, 0x1A, 0x32, 0x1B, 0x5A, 0x6, 0x5, 0x37, 0x1B, 0x13, 0x14, 0x10, 0x2C, 0x6, 0x41, 0x2F, 0xB, 0x16, 0x4E, 0x23, 0x1A, 0x8, 0xB, 0x4B, 0x34, 0x32, 0x5E, 0x74, 0x25, 0x1D, 0x22, 0x33, 0x3F, 0x3E, 0x7E, 0x3E, 0x38, 0x3E, 0x20, 0x2B, 0x3C, 0x60]

for i in range(0, len(data)):
    data[i] = data[i] ^ (i + 0x33)

for i in range(0, len(data)):
    data[i] = data[i] ^ 0x27

data[0x34] = data[0x34] ^ 0x43
data[0x2f] = data[0x2f] ^ 0x44

print(repr("".join(map(chr, data))))

The result was \x11_hav3_1nf0rmat10n_that_w1ll_lead_t0_th3_arr3st_0f_c0pp3rstick6. The first character seemed wrong, but easy to guess the correct value was 1. The correct input was 1_hav3_1nf0rmat10n_that_w1ll_lead_t0_th3_arr3st_0f_c0pp3rstick6 and the flag was utctf{1_hav3_1nf0rmat10n_that_w1ll_lead_t0_th3_arr3st_0f_c0pp3rstick6}.