Joined the UMDCTF after the CTF already had started. Nice varieties in this CTF. I especially enjoyed the Hardware category.
Crypto
CBC-MAC 1
"Team Rocket told me CBC-MAC with arbitrary-length messages is safe from forgery. I don't think they can be trusted, so I built this oracle for it to be tested before I use it for my own important needs."
This set of challenges turned out to be a really good refreshers for the CBC cryptography.
The basic structure of Cipher Block Chaining is given by the diagram above. The message is chopped up into blocks of equal size (with necessary padding). The first block is XORed with an initialization vector and the resulting block is encrypted using a secret key. The encrypted output becomes the initialization vector for the next block, and so on.
With Message Authentication Code, the idea is that an entire message is treated with CBC and only the cipher output of the last block is provided as a proof of authenticity. The principle is that if the message is tampered in any way, the intermediate ciphers will change, creating a cascading effect and invalidate the final cipher.
As you will see if the scheme allows for variable length messages, it is vulnerable to forgery.
For the CBC-MAC schemes, the IV for the first block is always 0.
The challenge server is running a program that presents a menu when we connect to it.
Team Rocket told me CBC-MAC with arbitrary-length messages is safe from forgery. If you manage to forge a message you haven't queried using my oracle, I'll give you something in return.
What would you like to do?
(1) MAC Query
(2) Forgery
(3) Exit
Choice:
The approach is that you can request MACs for upto 10 messages that you can send to the server. The goal is to send a message to the server, using option 2, and predict the MAC tag to get the flag.
The most important part of the server code is this bit where the MAC tag is calculated and returned. As we see here, IV = 0 and the incoming message (which is validated to be aligned to the block size), is passed to the cipher function. The last 16 bytes of the cipher is returned as the MAC tag.
def cbc_mac(msg, key):
iv = b'\x00'*BS
cipher = AES.new(key, AES.MODE_CBC, iv=iv)
t = cipher.encrypt(msg)[-16:]
return hexlify(t)
The approach to forge the message is as follows.
- We will send one block of known plain text A (b’aaaaaaaaaaaaaaaa’) and recieve a tag (T1)
- We will calculate the XOR value of the plain text and the tag (E = A xor T1)
- We will append this calculated value to the original plain text and send it for the forgery option, with the expected tag of T1
- Why does this work ?
T1 = Encryption(A , key)
E = A xor T1
When we send a message A||E,
A being the first block, gets treated the same as before, yeilding T1 as the cipher.
Now this cipher is XORed with E, the next block.
But, T1 xor E ==> T1 xor T1 xor A => A
Hence the Tag from the second encryption function is Tag = Encryption(E xor T1, key) = Encryption(A, key) = T1
Thus, the tag is predicable.
The full solution is provided here:
A = b'A'*16
def getTag(p, msg):
p.recvuntil(b'Choice: ')
p.sendline(b'1')
p.recvuntil(b'msg (hex): ')
hexed_msg = hexlify(msg)
p.send(hexed_msg)
p.recvuntil(b'(msg):')
tag = p.recvline().strip()
print(f"{msg =}\nX:{tag= }")
return unhexlify(tag)
def forgeMessage(p, msg, tag):
p.recvuntil(b'Choice: ')
p.sendline(b'2')
p.recvuntil(b'msg (hex): ')
p.send(hexlify(msg))
p.recvuntil(b'tag (hex): ')
p.send(hexlify(tag))
p = remote('0.cloud.chals.io', 12769)
t1 = getTag(p, A)
E = strxor(A, t1)
print(hexlify(t1))
forgeMessage(p, A + E, t1)
p.interactive()
flag: UMDCTF{Th!s_M@C_Sch3M3_1s_0nly_S3cur3_f0r_f!xed_l3ngth_m3ss4g3s_78232813}
CBC-MAC 2
"Okay so I came up with a scheme that might just work. Definitely not trusting those TR peeps anymore. I'm not sure how to prove the security of this besides jsut having you test it again."
As we saw with the previous challenge, CBC-MAC schemes are vulnerable to forgery if messages of arbitrary lengths are allowed. So, one of the protections designed for this scheme is to calculate and use the length of the message (or the number of blocks), as part of the message. So that message cannot be forged. This approach works only when the length is in the FIRST block of the message.
The most important part of the challenge server again is the MAC calculation function here :
def cbc_mac(msg, key):
iv = b'\x00'*BS
cipher = AES.new(key, AES.MODE_CBC, iv=iv)
ct = cipher.encrypt(msg + l2b(len(msg)//BS, BS))
t = ct[-16:]
return hexlify(t)
Here we see that the length of the message is calculated, but appended at the END of the message. This makes it vulnerable to message code forgery.
I relied on the following paper to formulate my attack.
The attack consists of these steps
t1 = getTag(p, A+block_one+B+B+B)
Construct a message ofA1BBB
and get its tag (t1). Assume each letter is a full block.t2 = getTag(p, A)
Construct a message of just A and get its tag (t2). Note that the server will automatically add a block with1
to this messaget3 = getTag(p, C)
Construct a message of same length with a differet content, say C. Get its tag (t3)t2_xor_t3 = strxor(t2 ,t3)
CalculateXOR(t2, t3)
E = strxor(B, t2_xor_t3)
CalculateE = XOR(B, XOR(t2, t3))
forgeMessage(p, C + block_one + E + B + B, t1)
Construct a message likeC1EBB
and send it with the tag t1
The complete solution is as below:
A = b'A'*16
B = b'B'*16
C = b'C'*16
block_one = b'\x00'*15 + b'\x01'
def getTag(p, msg):
p.recvuntil(b'Choice: ')
p.sendline(b'1')
p.recvuntil(b'msg (hex): ')
hexed_msg = hexlify(msg)
p.send(hexed_msg)
p.recvuntil(b'<|msg|>):')
tag = p.recvline().strip()
print(f"{msg =}\nX:{tag= }")
return unhexlify(tag)
def forgeMessage(p, msg, tag):
p.recvuntil(b'Choice: ')
p.sendline(b'2')
p.recvuntil(b'msg (hex): ')
p.send(hexlify(msg))
p.recvuntil(b'tag (hex): ')
p.send(hexlify(tag))
#p = remote('localhost', 60002)
p = remote('0.cloud.chals.io', 31220)
t1 = getTag(p, A+block_one+B+B+B)
t2 = getTag(p, A)
t3 = getTag(p, C)
t2_xor_t3 = strxor(t2 ,t3)
E = strxor(B, t2_xor_t3)
# for testing
# t4 = getTag(p, C + block_one + E + B + B )
# print(hexlify(t4)) # should be same as t1
print(hexlify(t1))
forgeMessage(p, C + block_one + E + B + B, t1)
p.interactive()
flag: UMDCTF{W3lp_l00k5_l!k3_I_n33d_t0_ch4ng3_th1s_ag41n_s4d_f4ce_3m0j1_927323}
Pokecomms
Comms are vital to winning matches. Pikachu looks a little angry. You should figure out what he's saying before he bytes you
We are given a text file filled with statements like:
CHU! PIKA CHU! PIKA CHU! PIKA CHU! PIKA
CHU! PIKA CHU! CHU! PIKA PIKA CHU! PIKA
CHU! PIKA CHU! CHU! CHU! PIKA CHU! CHU!
CHU! PIKA CHU! CHU! CHU! CHU! PIKA PIKA
CHU! PIKA CHU! PIKA CHU! PIKA CHU! CHU!
Time for a one-line solution.
% sed -e 's/CHU!/0/g' pokecomms.txt | sed -e 's/PIKA/1/g' | tr -d ' ' | perl -lpe '$_=pack"B*",$_' | tr -d "\n"
A very simple cipher that can be solved by replacing PIKA
with 1
and CHU!
with 0
. The payoff is perhaps the longest flag I have ever seen in a CTF.
flag: UMDCTF{P1K4CHU_Once_upon_a_time,_there_was_a_young_boy_named_Ash_who_dreamed_of_becoming_the_world's_greatest_Pokemon_trainer._He_set_out_on_a_journey_with_his_trusty_Pokemon_partner,_Pikachu,_a_cute_and_powerful_electric-type_Pokemon._As_Ash_and_Pikachu_traveled_through_the_regions,_they_encountered_many_challenges_and_made_many_friends._But_they_also_faced_their_fair_share_of_enemies,_including_the_notorious_Team_Rocket,_who_were_always_trying_to_steal_Pikachu._Despite_the_odds_stacked_against_them,_Ash_and_Pikachu_never_gave_up._They_trained_hard_and_battled_even_harder,_always_looking_for_ways_to_improve_their_skills_and_strengthen_their_bond._And_along_the_way,_they_learned_valuable_lessons_about_friendship,_determination,_and_the_power_of_believing_in_oneself._Eventually,_Ash_and_Pikachu's_hard_work_paid_off._They_defeated_powerful_opponents,_earned_badges_from_Gym_Leaders,_and_even_competed_in_the_prestigious_Pokemon_League_tournaments._But_no_matter_how_many_victories_they_achieved,_Ash_and_Pikachu_never_forgot_where_they_came_from_or_the_importance_of_their_friendship._In_the_end,_Ash_and_Pikachu_became_a_legendary_team,_admired_by_Pokemon_trainers_around_the_world._And_although_their_journey_may_have_had_its_ups_and_downs,_they_always_knew_that_as_long_as_they_had_each_other,_they_could_overcome_any_obstacle_that_stood_in_their_way}
Hardware
Bleep 1
Toss the flag.enc contents into the ROM and press play :)
The description says it all. That’s it. That is the writeup.
- Fire up Logisim-evolution. The circuit file is a XML file and has the link to the Github site.
- Open up the ROM and load the contents of the given encoded file (after converting it to binary first)
- Start the simulation and the clock. Profit!
The criss-cross of green wires at the top of the diagram unscrambles the orders of the bits for each character (byte), which is the decoding mechanism. The blue box is the TTY which shows the characters, which happens to be our flag.
flag: UMDCTF{w3lc0me_t0_l0g1s1m_yeet}
Clutter
I wrote this machine code but Giovanni wiped my memory! I'm all scatter-brained and can't remember where I wrote the flag to :(
We are given a link to VeSP - a barebones micro-controller simulator. The attached VESP file are the opcodes for a simple program that enter two values into the registers, adds them and stores them at a memory address.
I ran the program, redirecting the output to a file. Subsequently, I went through the output file to pick out the MOV instructions that were storing the result of the ADD operations to random memory locations (Hence the name clutter
, I think)
The full output for each MOV operation was something like this:
DECODE SUBCYCLE
Decoded instruction is: MOV
Clock cycle = 11
EXECUTE SUBCYCLE
Clock cycle = 13
*************Begin[Machine Level]*****************
A = 0055, B = 0003, Z = 0, S = 0, C = 0, F = 0
MAR = 0A09, PC = 0A0B, IR = 315B, reset = 0
add = 0 complement = 0
Memory[015B] = 0055 <=== This is the statement of interest for us
$ grep Memory vesp_out.txt | grep -v "\[0000\]" | grep -v "\[0001\]"
Memory[015B] = 0055
Memory[0285] = 004D
Memory[0185] = 0044
Memory[022A] = 0043
Memory[011D] = 0054
Memory[0153] = 0046
Memory[02E9] = 007B
Memory[0266] = 0055
Memory[029E] = 0078
Memory[02A7] = 0031
Memory[0213] = 0033
Memory[0298] = 002D
Memory[0121] = 0075
Memory[01C7] = 0073
Memory[01B5] = 0033
Memory[0256] = 002D
Memory[0265] = 006D
Memory[0150] = 0033
Memory[0159] = 006D
Memory[028C] = 0030
Memory[0134] = 0072
Memory[0268] = 0079
Memory[02E7] = 002D
Memory[01DE] = 0077
Memory[0214] = 0031
Memory[023A] = 0070
Memory[019C] = 0033
Memory[0171] = 0021
Memory[012A] = 007D
Memory[0153] = 000A
We can further refine the solution to give the flag directly.
$ grep Memory vesp_out.txt | grep -v "\[0000\]" | grep -v "\[0001\]" | cut -d = -f2 | xxd -r -p
UMDCTF{Ux13-us3-m3m0ry-w1p3!}
flag: UMDCTF{Ux13-us3-m3m0ry-w1p3!}
beep-boop
We are given a sound file and a Matlab build script that created that sound file.
%Build script to beep-boop (UMDCTF2023, author: Assgent)
%{
A flag was encoded into a sound file using the script below.
Analyze the script and reverse-engineer the flag!
%}
close
clear all
flag = fileread("flag.txt");
Fs = 8192;
sound = string_to_sound(flag, Fs, 1, 0.5);
sound_normalized = sound / (max(abs(sound)));
audiowrite("sound.wav", sound_normalized, Fs);
function freq = get_frequency_1(char)
freq = char * 13;
end
function freq = get_frequency_2(char)
freq = (char - 50) * 11;
end
% Fs is the samples/sec.
% T is the duration of each key. (in seconds)
% Tpause is the pause between keys. (in seconds)
function x = string_to_sound(keys,Fs,T,Tpause)
t = (0:fix(T*Fs)).'/Fs ;
zp = zeros(fix(Tpause*Fs/2),1) ;
x = [];
for r = 1:length(keys(:))
char = keys(r);
x = [x ; zp ; cos(2*pi*get_frequency_1(char)*t) + cos(2*pi*get_frequency_2(char)*t) ; zp];
end
end%
The most important part of the build script is that there are two frequencies that are chosen based on the ASCII value of the character of the flag.
- One frequency is 13 times the character value (this is the higher frequency)
- The second frequency is 11 times (character value - 50) (this is the lower frequency)
In the spectrogram view of the sound file, the two frequencies are clearly visible. The tones are 1 second in length and have a 0.5 second pause between them.
The plot spectrum analysis of the segment in Audacity, clearly shows the frequency distribution of the section. I hacked together several scripts to dump the frequency that has the peak amplitude for each 1 second segment, jump 0.5 seconds and repeat.
The frequencies were dumped into a text file and I wrote the following script to calculate the original characters and display it. Since the frequencies were not exact multiples, I provided some room for error. Also, even though I needed only one frequency to determine the character, I calculated using both the frequencies to check them against each other.
flag1 = ""
flag2 = ""
with open("freqs.txt", "r") as F:
for l in F.readlines():
low,hi = map(int, l.strip().split(','))
print(f"Low: {low} {low%11}")
print(f"Hi : {hi} {hi%13}")
if (low%11 <=2):
flag1 += chr(50 + (low - low%11)//11 )
if (hi%13 <=2):
flag2 += chr((hi - (hi%13))//13)
print(flag1)
print(flag2)
flag: UMDCTF{do_you_actually_enjoy_signal_processing_???}
Rev
Introduction to C
*Welcome to CMSC216. Weeeeee have a lecture worksheet that the TAs will now hand out. You must write your name, student ID, and discussion session **CORRECTLY** at the top of the worksheet. I have 3**69** students, so any time I need to spend finding out who to grade will cause YOU to lose credit.* **(Larry Herman)**
Which came first? Python or C ?
In this case, Python certainly came before C. We are given a tiny image (32 x 31 pixels) and a text file with some look up values.
This knowledge might prove useful:
Key: 0, Value: RGB(0, 0, 0)
Key: 1, Value: RGB(210, 126, 15)
Key: 2, Value: RGB(164, 252, 30)
Key: 3, Value: RGB(118, 122, 45)
Key: 4, Value: RGB(72, 248, 60)
...
The obvious approach is to go through the image, pick the color value for each pixel. Use it as an index to pick the corresponding key. After printing the initial set, it was apparent that the keys were making up the ASCII codes. So, I collected all the letters and printed it out as a string.
from PIL import Image
im = Image.open('intro_to_c.png')
pix = im.load()
h,w = im.size # Get the width and hight of the image for iterating over
lookup_keys = {}
def init_dictionary():
with open('intro_to_c.txt', 'r') as F:
for l in F.readlines():
if 'Key' in l:
tokens = l.strip().split()
key = int(tokens[1].replace(',',''))
red = int(tokens[3].replace('RGB(','').replace(',', ''))
green = int(tokens[4].replace(',', ''))
blue = int(tokens[5].replace(')', ''))
color = (red, green, blue)
lookup_keys[color] = key
lookup_keys[(255,255,255)] = 10 # map white to newline.
print("# of entries loaded : ", len(lookup_keys.keys()))
init_dictionary()
prog = ""
for col in range(w):
for row in range(h):
prog+= chr(lookup_keys[pix[row,col]])
print(prog)
The printed string is a small C program.
#include <stdio.h>
#define LEN(array) sizeof(array) / sizeof(*array)
#define SALT_1 97
#define SALT_2 4563246763
const long numbers[] = {4563246815, 4563246807, 4563246800, 4563246797, 4563246816, 4563246802, 4563246789, \
4563246780, 4563246783, 4563246850, 4563246843, 4563246771, 4563246765, 4563246825, 4563246781, 4563246784, \
4563246796, 4563246784, 4563246843, 4563246765, 4563246825, 4563246786, 4563246844, 4563246803, 4563246800, \
4563246825, 4563246775, 4563246852, 4563246843, 4563246778, 4563246825, 4563246781, 4563246849, 4563246782, \
4563246843, 4563246778, 4563246769, 4563246825, 4563246796, 4563246782, 4563246769, 4563246781, 4563246821, \
4563246823, 4563246827, 4563246827, 4563246827, 4563246791};
int main(void)
{
size_t i;
char undecyphered_char;
for (i = 0; i < LEN(numbers); i++)
{
undecyphered_char = (char)((numbers[i] - SALT_2) ^ 97);
printf("%c", undecyphered_char);
}
printf("\n");
return 0;
}
Compiling and running the program gave us the flag.
flag: UMDCTF{pu61ic_st@t1c_v0ID_m81n_s7r1ng_@rgs[]!!!}
Other
Mirror Unknown
- Use dCode to translate the glyphs using the Pokemon alphabet. We should mirror the sybols.
HOJNIS
SNIUR
- Due to the reference to a
mirror
, we must reverse the order of the letters, givingSINJOH
RUINS
- The flag is
SINJOHRUINS
flag: UMDCTF{SINJOHRUINS}
Fire Type Pokemon Only
Some wannabe trainer with no pokemon left their PC connected to the internet. Watch as I hack this nerd lol
We are given a network capture file in pcapng
format. Before we start on this challenge, notice that the title of the challenge hints to FTP Only
.
As usual, I use the protocol-hierarchy statistics to see the data in the capture. Nearly 85% of the traffic is TLS. Except in rare circumstances, we would not be asked to decrypt TLS traffic. So, it is safe to ignore. Given this fact and because of the hint from the challenge name, I decided to focus on FTP next.
$ tshark -r fire-type-pokemon-only.pcapng -z io,phs
Protocol Hierarchy Statistics
Filter:
eth frames:34235 bytes:34205924
ip frames:34210 bytes:34203112
udp frames:1435 bytes:806033
data frames:119 bytes:25096
mdns frames:17 bytes:1894
ssdp frames:6 bytes:1178
quic frames:1251 bytes:773290
quic frames:246 bytes:165711
quic frames:77 bytes:74202
dns frames:34 bytes:3780
llmnr frames:4 bytes:276
nbns frames:3 bytes:276
nbdgm frames:1 bytes:243
smb frames:1 bytes:243
mailslot frames:1 bytes:243
browser frames:1 bytes:243
tcp frames:32735 bytes:33385239
tls frames:14954 bytes:20300524
tcp.segments frames:1528 bytes:12490280
tls frames:1443 bytes:12143373
http frames:22 bytes:17182
ocsp frames:14 bytes:14951
data-text-lines frames:2 bytes:622
ftp frames:112 bytes:9701
ftp.current-working-directory frames:112 bytes:9701
ftp-data frames:122 bytes:3231223
ftp-data.setup-frame frames:122 bytes:3231223
ftp-data.setup-method frames:122 bytes:3231223
ftp-data.command frames:122 bytes:3231223
ftp-data.command-frame frames:122 bytes:3231223
ftp-data.current-working-directory frames:122 bytes:3231223
data-text-lines frames:6 bytes:2348
icmp frames:40 bytes:11840
data frames:8 bytes:2760
quic frames:32 bytes:9080
ipv6 frames:21 bytes:2590
udp frames:21 bytes:2590
mdns frames:17 bytes:2234
llmnr frames:4 bytes:356
arp frames:4 bytes:222
===================================================================
Filtering on FTP as the protocol shows the FTP commands as well as the FTP-Passive transfers initiated by the client 192.168.16.129
. The relevant commands are shown below. From this interaction, we can see that the user pokemonfan1
retrieved several files from the FTP server.
12476 43.815884924 192.168.16.129 192.168.16.130 FTP 84 Request: USER pokemonfan1
12576 45.011050717 192.168.16.129 192.168.16.130 FTP 77 Request: PASS pika
14205 54.775654966 192.168.16.129 192.168.16.130 FTP 88 Request: CWD secret_documents
14490 59.871546649 192.168.16.129 192.168.16.130 FTP 84 Request: RETR Diglett.png
14804 64.976928456 192.168.16.129 192.168.16.130 FTP 77 Request: RETR hmmm
15290 69.553040363 192.168.16.129 192.168.16.130 FTP 87 Request: RETR secretpic1.png
28927 100.082641502 192.168.16.129 192.168.16.130 FTP 79 Request: RETR secret
An easy way to retrieve all the files from the packet capture is to use the Export Objects feature. Wireshark --> File --> Export Objects --> FTP-DATA
, shows the files that are available to be retrieved. Export the four files.
Examining these files, we can see that three of them are small PNG files and one zip file. I tried the usual stego techniques for PNG files (zsteg, binwalk, etc
), but they did not seem to hold any additional information. So, I tried to extract the files from the zip archive, and I was prompted for a password.
$ file *
Diglett.png: PNG image data, 70 x 70, 8-bit/color RGBA, non-interlaced
hmmm: PNG image data, 70 x 70, 8-bit/color RGBA, non-interlaced
secret: Zip archive data, at least v2.0 to extract
secretpic1.png: PNG image data, 70 x 70, 8-bit/color RGBA, non-interlaced
$ unzip -l secret
Archive: secret
Length Date Time Name
--------- ---------- ----- ----
3205229 04-27-2023 12:12 wisdom.mp4
--------- -------
3205229 1 file
$ unzip -x secret
Archive: secret
[secret] wisdom.mp4 password:
I tried the filenames of the PNG files, Diglett, hmmm and secretpic1
each as the password, but was unsuccessful. I then used the password that was used for the FTP transfer pika
as the password and was successful in extracting wisdom.mp4
. The video was a 6 second clip of the Pokemon TV show and the flag was overlaid on the bottom of the video.
flag: UMDCTF{its_n0t_p1kachu!!}
A TXT for You and Me
Used the following online tool to get the TXT records for the subdomain, which contained the flag.
Type Domain Name TTL Record
TXT a-txt-for-you-and-me.chall.lol 35 days "UMDCTF{just_old_school_texting}"
Chungus Bot v3
Searching for Github for ChungusBot
brought us to this repository. Looking through the sourcecode of the Bot, tells us that the flag is split into 4 parts and each part can be obtained by a different command.
//attach https://tenor.com/view/sigma-sigma-male-sigma-rule-b2k-sigma-expression-gif-27239871
flag0: UMDCTF{Chungu
---> flip tails tails heads
flag1: 5_4ppr3c1@t3s
---> numbers 53000 481 20 5
flag2: _y0ur_l0y@lty
---> bee It's a bee law. <snip>"You like jazz?" No, that's no good.
flag3: _a61527bd8ec}
flag: UMDCTF{Chungu5_4ppr3c1@t3s_y0ur_l0y@lty_a61527bd8ec
After the CTF
EP-815
This was another VESP chall. I could not solve it during the CTF. However inspecting the program afterwards leads to an easier, definitely unintended solution
$ awk '!/^(2000|0000|0001|2001|2010|1000|3001)/' EP_815_program.vsp | sed -e 's/^[7|0]//g' | tr -d '\n' | xxd -r -p
UMDCTF{smuggl3d_n0m5}
Beep Boop
I had hacked together some scripts to solve the Beep Boop challenge during the CTF. This is a more refined solution. For future reference.
import scipy
import numpy as np
from scipy.io import wavfile
fps, data = wavfile.read("sound.wav")
print(f"Read data {data.size} FPS:{fps}")
tone_dur = 1
pause_dur = 0.5
start = 0
while (data[start] == 0):
start += 1
print(f"Starting to get data at position {start}")
flag_low = ""
flag_hi = ""
for i in range(start, data.size, int((tone_dur+pause_dur)*fps)):
begin, end = i, int(i+(tone_dur * fps))
# print(f"Processing {begin} to {end}")
signal = data[begin:end]
freqs = np.fft.fftfreq(signal.size, d=1/fps)
amplitudes = np.fft.fft(signal) # produces a complex number.
amp = abs(amplitudes.real[:])
# get frequencies where the amplitude is near maximum
f = freqs[np.where((amp > max(amp)*0.04) & (freqs > 0))]
fLow, fHi = int(f[0]), int(f[1])
flag_low += chr(50+(fLow//11))
flag_hi += chr(fHi//13)
print(flag_low)
print(flag_hi)
Writeups
- https://txnn3r.github.io/UMDCTF : Construct an ELF binary to meet YARA rules, multiple QRs in a GIF, PDF dot extraction and decode
- https://github.com/yarml/Writeups/blob/main/TamuCTF2023/Embedded-Courier/README.md : Communicating over UART with an unknown device emulated with
qemu
- https://dree.blog/posts/umd-ctf-2023/
- Challenge sources : https://github.com/UMD-CSEC/UMDCTF-Public-Challenges
By Benjamin D. Esham (bdesham) - Own work based on: Cbcmac.png by en:User:PeterPearson. Own work by bdesham using: Inkscape., Public Domain, https://commons.wikimedia.org/w/index.php?curid=2277179. ↩︎
https://www.csa.iisc.ac.in/~arpita/Cryptography15/CT3.pdf ↩︎