BuckeyeCTF is jeopardy-style CTF hosted by the Cybersecurity Club at The Ohio State University.

My First Hash

Here's your flag: 8f163b472e2164f66a5cd751098783f9 Psyc! Its encrypted. You think I'd give it to you that easily? Definitely don't look at my code tho -><- (when you find the flag, put it in bctf{} format)

str = hashlib.md5(str.encode())

Using a MD5 reverse lookup site gives us the string orchestra, which would yield the given hash. Hence the flag is bctf{orchestra}

% echo -n "orchestra" | md5 
8f163b472e2164f66a5cd751098783f9

Real Smooth

I know you're not supposed to leave passwords in plain text so I encrypted them. The flag is in the format btcf, not bctf due to a typo.

We are given a text file that consists of 11,430 passwords that are encoded with the ChaCha20 stream cipher scheme. However, we can see that the key and nonce are reused for encoding, which means that every one of the passwords is XORed with the same random material. We also can see that each password is padded on the right with spaces until it reaches the length of 18 characters.

    def main():
        lines = open("passwords.txt", "rb").readlines()
        key = get_random_bytes(32)
        nonce = get_random_bytes(8)
        lines = [x.ljust(18) for x in lines]
        lines = [encrypt(key, nonce, x) for x in lines]
        open("database.txt", "wb").writelines(lines)

First, we do some optimization as we notice that several of the entries in the database are duplicates. So, we need to keep only the unique values. Also, we can see that the right half (actually 22 hex characters) are the same for a large number of entries. This seems to indicate that these are encoded values of the spaces that are used to pad a password that is shorter than 18 characters.

# we only need to keep 1298 unique entries out of 11,430 entries in the database
% sort database.txt| uniq  | wc
    1298    1298   48026

# search and catalog the last 22 hex characters and see that nearly half of them share the same values.
% cut -c 15-  database.txt | sort | uniq -c | sort -r -n
4844 24f2dc11e8510fc249ad48         <=== this should be all spaces
2975 0ef2dc11e8510fc249ad48
 297 61d8dc11e8510fc249ad48
from pwn import *

'''
# store unique values in a db_uniq.txt file. 
% sort database.txt| uniq > db_uniq.txt
 
keys = [167, 78, 245, 199, 198, 76, 2, 4, 210, 252, 49, 200, 113, 47, 226, 105, 141, 104]
'''

def guess_character(hex_str, keypos):
    print(keys)
    print(*[hex(x) for x in hex_str])
    pos, char = input("Pos:Char ?").strip().split(":")
    position = int(pos)
    keys[position] = x[position] ^ ord(char)
    keypos -= 1
    return keypos

hex_lines = []
spaces_CT = unhex('24f2dc11e8510fc249ad48')  # 11 bytes representing encoded spaces

# keys = [167, 78, 245, 199, 198, 76, 2, 4, 210, 252, 49, 200, 113, 47, 226, 105, 141, 104]
# first_known_key = 0

keys = [b'\xFF']*18
first_known_key = 7 #we know PT and CT for 11 consecutive spaces

with open('db_uniq.txt', 'r') as F:
    for line in F:
        hex_lines.append(unhex(line.strip()))

print(f"Read {len(hex_lines)} lines from file")

# initialize the last 11 bytes of the key using space as the padding character. 
for i, x in enumerate(spaces_CT):
    keys[7+i] = x ^ ord(' ')

print(keys)

# work backwards by guessing the next character of any password that is easy to guess. 
# I used a password that was all sevens.
while (first_known_key > 0):
    print(f"First known key position : {first_known_key}")
    for x in hex_lines:
        print(xor(x[first_known_key:], keys[first_known_key:]))
        cmd = input()
        if (cmd.strip().lower() == 'g'):
            first_known_key = guess_character(x, first_known_key)
            break

# ok ... all keys are known ... dump the passwords
print(f"Keys: {keys}")

for x in hex_lines:
    passwd = xor(x, keys)
    if (b'btcf' in passwd or b'}' in passwd):
        print(passwd)           # btcf{w3_d0_4_l1773_kn0wn_pl41n73x7}

# b'3_kn0wn_pl41n73x7}'
# b'btcf{w3_d0_4_l177l'

Needle in the Wifi Stack

We are given a network capture file. The protocol statistics show that it contains about 100 thousand frames of WLAN SSID broadcasts.

% tshark -r frames.pcap -z io,phs
===================================================================
Protocol Hierarchy Statistics
Filter: 

radiotap                                 frames:101000 bytes:11316000
  wlan_radio                             frames:101000 bytes:11316000
    wlan                                 frames:101000 bytes:11316000
      wlan.mgt                           frames:101000 bytes:11316000
===================================================================

The SSID field of frame seems to contain a base64 string. So, the conjecture is that the flag is hidden in one of the base64 SSID strings. The following bash pipeline automates searching for this needle in the haystack.

% tshark -r frames.pcap -T fields -e wlan.ssid | xxd -p -r | base64 -d | grep bctf
bctf{tw0_po1nt_4_g33_c0ng3s7i0n}
bctf{tw0_po1nt_4_g33_c0ng3s7i0n}
...

Pong

We are just given an ip address and nothing more. Taking a clue from the title of the challenge, we ping the server, which produces an interesting error message.

% ping 18.191.205.48        
PING 18.191.205.48 (18.191.205.48): 56 data bytes
128 bytes from 18.191.205.48: icmp_seq=0 ttl=41 time=37.165 ms
wrong total length 148 instead of 84
128 bytes from 18.191.205.48: icmp_seq=1 ttl=41 time=37.589 ms
wrong total length 148 instead of 84

The wrong total length message seems to indicate that the response ICMP packet contains 64 bytes more than expected. So, we set out to capture the responses.

shell 1% sudo tcpdump -c 10 -vvv -XX -i any icmp 
12:38:10.112932 IP (tos 0x0, ttl 41, id 42760, offset 0, flags [none], proto ICMP (1), length 148)
    ec2-18-191-205-48.us-east-2.compute.amazonaws.com > localhost : ICMP echo reply, id 37292, seq 0, length 128
	0x0000:  9c00 0000 0100 0000 0100 0000 656e 3000  ............en0.
	0x0010:  0000 0000 0000 0000 0000 0000 0000 0000  ................
	0x0020:  0000 0000 0100 0000 0200 0000 0e00 0000  ................
	0x0030:  0000 0000 ffff ffff 0000 0000 0000 0000  ................
	0x0040:  0000 0000 0000 0000 0000 0000 0000 0000  ................
	0x0050:  0600 0000 ffff ffff 0000 0000 0000 0000  ................
	0x0060:  0000 0000 0000 0000 0000 0000 0000 0000  ................
	0x0070:  0000 0000 0000 0000 0000 0000 0000 0000  ................
	0x0080:  0000 0000 0000 0000 0000 0000 0000 0000  ................
	0x0090:  0000 0000 0000 0000 0000 0000 985a eb8f  .............Z..
	0x00a0:  0766 485d 3666 551c 0800 4500 0094 a708  .fH]6fU...E.....
	0x00b0:  0000 2901 47e8 12bf cd30 c0a8 01e1 0000  ..).G....0......
	0x00c0:  0e32 91ac 0000 651a f1f2 0001 1e80 0809  .2....e.........
	0x00d0:  0a0b 0c0d 0e0f 1011 1213 1415 1617 1819  ................
	0x00e0:  1a1b 1c1d 1e1f 2021 2223 2425 2627 2829  .......!"#$%&'()
	0x00f0:  2a2b 2c2d 2e2f 3031 3233 3435 3637 8950  *+,-./01234567.P          <--- PNG header
	0x0100:  4e47 0d0a 1a0a 0000 000d 4948 4452 0000  NG........IHDR..
	0x0110:  0258 0000 0258 0806 0000 00be 6698 dc00  .X...X......f...
	0x0120:  0000 c57a 5458 7452 6177 2070 726f 6669  ...zTXtRaw.profi
	0x0130:  6c65 2074 7970 6520 6578 6966 0000       le.type.exif..

shell 2% ping 18.191.205.48

Now, that we have established that we have 64 bytes of a PNG file appended to the ICMP response, we set out to capture all ICMP packets until we receive the IEND chunk or all of the PNG image.

# To capture the ICMP responses to a PCAP file
% sudo tcpdump -vvv -XX -i any icmp -w pong.pcapng 
% tshark -f icmp -w pong.pcapng
% Wireshark -> Capture -> Start

# Analyze the PCAP file
% tshark -r pong.pcapng -T fields -e data -Y "ip.src == 18.191.205.48" | cut -c 97- | tr -d '\n' | xxd -p -r > pong.png

I had missed two ICMP response packets that were lost due to a timeout. I manually copied a frame from a similar chunk and used pngfix to fix the CRC. At the end of the day, the flag was visible in the bottom most section of the image.

Challenges

Expand to see the list of challenges
CategoryChallengeDescription
cryptoElectronical
cryptoMy First Hash
cryptoReal Smooth
cryptoRivest-Shamir-Adleman
cryptoSecret Code
cryptoTurtle Tree
cryptocoding 2
cryptocoding
miscBirdwatching
miscBreaking Away
miscJust Hang, Man
miscNeedle in the Wifi Stack
miscNew Management
miscOgrechat
miscParkour
miscSmederij
miscaNyFT
miscnada
miscpong
miscreplace-me
misctypscrip
miscweather
pwnBeginner Menu
pwnBugsworld
pwnFrog Universe in C (FUC)
pwnIgpay Atinlay Natoriay 3000
pwnStarter Buffer
pwnchat_app
pwnflag-sharing
pwnlossless
pwnsaas
rev8ball
revCurrency Converter
revEmotional Damage
revSkribl
revTape
webFont Review
webOhio Instruments 84
webSentiment
webSpa
webStray
webText Adventure API
webTriple D Columbus
webarea51
webcerts
web∞!