Skip to content
hackingcybersecurity

CyberTalents Write-up: Ethiopia National Cybersecurity CTF 2020

How we solved 9 of the 10 challenges in Ethiopia's first national CTF.

Yesterday, I participated in the National Cybersecurity CTF hosted in Ethiopia by CyberTalents. Considering it was my first CTF, as well as my having only one other team member and missing the training sessions given by CyberTalents, I was very satisfied with getting 4th place out of 37 teams.

The format was 10 challenges from easy to hard over 7 hours, testing General Information, Digital Forensics, Web Security, Cryptography, and Malware Reverse Engineering. Here are write-ups for the 8 we solved during the competition plus one we got immediately after. Number 10 was way out of my league.

Cracker — General Information

A simple question asking for the name of a popular Linux tool used as a “packet sniffer, WEP and WPA/WPA2-PSK cracker”.

The answer is obviously aircrack-ng.

Unprotected — Digital Forensics

We were given Unprotected.pcap. Opening it in Wireshark we can see it’s a combination of TCP and HTTP packets.

Wireshark showing the packet capture

A simple filter data.data contains "flag" leaves just one TCP packet, which contains the flag flag{cl3ar_t3xt_15_alway35_5asy}.

Filtered TCP packet

Encrypted RSA — Cryptography

We were given an RSA-encrypted file secret plus the p, q, and e used to encrypt it. Enough to decrypt. A fast search on crypto.stackexchange.com gave us this script:

def egcd(a, b):
    x, y, u, v = 0, 1, 1, 0
    while a != 0:
        q, r = b // a, b % a
        m, n = x - u*q, y - v*q
        b, a, x, y, u, v = a, r, u, v, m, n
        gcd = b
    return gcd, x, y

def main():
    p = 11882546252751469607361356421348933496327112595288260315935663917400681403905188808476289112967043136936045873689827577396206505769293138372274271493958287
    q = 10374751834382966611285517450958269115435289482194774831009591093240922739864785750413607023913149510232252798244495377789107452564252835088008933746132847
    e = 65537

    cipher_text = int.from_bytes(open("secret","rb").read(), byteorder="big")

    n = p * q
    phi = (p - 1) * (q - 1)
    gcd, a, b = egcd(e, phi)
    d = a

    pt = pow(cipher_text, d, n)
    print("plain text: " + str(int.to_bytes(pt, 128, byteorder="big")))

main()

The output: a series of null bytes followed by Nice Job, flag is FLAG{Gr3at_J0b_F0r_Th3_D3crypti0n}.

Decryption output

Gu55y — Web Security

We were given a URL to exploit. Its functionality was to take your inputs and store them in your cookies as a serialized PHP list, so they would re-display on refresh.

The Gu55y page

Crossing out SQL injection as a vector, we inspected the page source and found an HTML comment reading <!-- I love vim~ -->. Knowing vim was involved, we searched for known vim file extensions like .php~ and .php.un~. We managed to download .index.php.swp, which gave us this code:

# try to read fl4g.php
class l33t {
    public function __toString() {
        return highlight_file($this->source, true);
    }
}

This gave us two things: the location of the flag and the vector to get it. We constructed a l33t object with its source set to fl4g.php, serialized it in a list, URL-encoded it, and stuck it in our list cookie.

Serialized: a:1:{i:0;O:4:"l33t":1:{s:6:"source";s:8:"fl4g.php";}}

URL-encoded: a%3A1%3A%7Bi%3A0%3BO%3A4%3A%22l33t%22%3A1%3A%7Bs%3A6%3A%22source%22%3Bs%3A8%3A%22fl4g.php%22%3B%7D%7D

Setting the cookie
The flag returned

Refreshing the page gives us the flag flag{5w337_PHP_0bj3c7_!nj3c7!0n}.

Habibamod — Digital Forensics

We were given another packet capture, Habibamod.pcap. In Wireshark we can see it’s an HTTP session where a file is being uploaded.

HTTP upload session

The file contents were a JSON object with data and encoder properties. Decoding the encoder field as base64 (the == at the end was the giveaway) gave us a Python function my_encoder. After analyzing it, we wrote a reverse:

def my_encoder(data):
    bin_rep = ''.join(format(ord(i), 'b') for i in x)
    return bin_rep.replace('0', '.').replace('1', '!')
def our_decoder(data):
    # the previous function converts a string to a bitmap
    # and then changes 0 and 1 to . and ! respectively

    # reverse the string replacement
    data = data.replace('.', '0').replace('!', '1')

    # change the bitmap to numbers
    data = [int(data[i:i+8], 2) for i in range(0, len(data), 8)]

    # change the numbers to a string
    return ''.join([chr(i) for i in data])

Running this on data gave us the flag Flag{TMCTFy0urDec0de0f!@nd.Is@ma7ing}.

Decoded flag

GoldASM — Malware Reverse Engineering

We were given an assembly file GoldASM.asm that described a function with many repetitions of:

mov     rax, QWORD PTR [rbp-24]
mov     eax, DWORD PTR [rax]
cmp     eax, 70
jne     .L2
mov     rax, QWORD PTR [rbp-24]
add     rax, 4
mov     eax, DWORD PTR [rax]
cmp     eax, 76
jne     .L2

The code was walking across a string and comparing each byte against hardcoded values. Stripping the repetition makes it obvious:

0:
compare     eax, 70   ; 'F'
4:
compare     eax, 76   ; 'L'

Some positions did arithmetic before comparing:

8:
add eax, eax
compare eax, 130     ; we want 65, or 'A'

24:
subtract eax, 75
compare eax, 2       ; we want 77, or 'M'

After accounting for the edge cases, we got [70, 76, 65, 71, 123, 95, 75, 51, 101, 98, 95, 48, 110, 95, 83, 104, 49, 110, 105, 110, 103, 95, 125]. Converted to characters: FLAG{_K3eb_0n_Sh1ning_}.


I don’t have screenshots of the other challenges — they were web-based and CyberTalents took them down when the hackathon ended.

All in all this hackathon was a lot of fun and I’m interested in doing it again. Next time I’ll make sure to document the whole thing.