Hey! How’s it been, I know it’s been a while since my last update. I’ve been busy with school, but I’m back with some writeups for UMDCTF 2024. I hope you enjoy them!
These were some great challenges, though my solves for the rev might be a bit non-conventional. Still, hope you find them helpful!
Pwn
The Voice
pwn/the_voice - aparker - 98 solves / 399 points
If you want the flag, command me to give it to you.
nc challs.umdctf.io 31192
They give us source, so let’s take a look at it.
And here’s checksec
:
This looks pretty simple, we have a BOF in the gets
call and we can overwrite some offset in the g
array. In this case, since the g
array is __thread
, it’s stored in the thread-local storage, meaning the stack canary is located relative to it.
With our overwrite, we can overwrite the stack canary, then perform a simple ret2win to get the flag. Finding the exact offset to overwrite the canary just requires a peek in GDB, and we also need to add a ret gadget to align the stack properly.
Here’s the exploit:
[+] Opening connection to challs.umdctf.io on port 31192: Done
[*] Switching to interactive mode
If you want the flag, command me to give it to you.
UMDCTF{pwn_g3ss3r1t_sk1ll5_d0nt_tak3_a5_many_y3ar5_t0_l3arn_pau1}
Mentat Question
pwn/mentat_question - cephi - 71 solves / 432 points
Thufir Hawat is ready to answer any and all questions you have. Unless it’s not about division…
nc challs.umdctf.io 32300
Here’s the source:
This time, we don’t have a canary, but we do have PIE. Additionally, in the calculate
function, we have a both a BOF and a format string vulnerability. We can use the format string vulnerability to leak an address to break PIE, then use the BOF for a simple ret2win.
Here’s the exploit. First we leak the address of main
which just so happens to be left on the stack for us. Then, we overwrite the return address with the address of secret
to get the flag.
[+] Opening connection to challs.umdctf.io on port 32300: Done
[*] Switching to interactive mode
Was that a YesAAAAAAAAAAAAAAAAAAAAA\x1a\xc0ph\xdcU I heard?
$ cat flag.txt
UMDCTF{3_6u1ld_n4v16470r5_4_7074l_0f_1.46_m1ll10n_62_50l4r15_r0und_7r1p}
$
[*] Closed connection to challs.umdctf.io port 32300
The Spice
pwn/the_spice - relkochta - 45 solves / 460 points
House Harkonnen’s spice harvesters keep getting overrun by Atreides pwners. Help keep their riches secure using exotic techniques.
nc challs.umdctf.io 31721
This one is a lot larger. Some things to note:
There’s no offset check here, meaning we have an arbitary read.
We also have a buffer overflow here, since we can set len
to be any value.
Finally, we also have a syscall gadget.
First, we can use the arbitary read to leak the canary and stack. Since we’re not given libc, we can assume its not necessary, and PIE is off so we don’t need to leak any addresses.
To abuse the syscall
gadget properly, we need to control rax. Thankfully, the binary just so happens to give us a nice function that sets rax to a value relative of rbp.
We can use this to set rax to 0xf
, then perform SROP to jump back to the syscall
with the proper arguments to pop a shell.
Here’s the full exploit:
[+] Opening connection to challs.umdctf.io on port 31721: Done
Canary: 0xd55b629010f55d00
Stack leak: 0x7ffe93b36b38
RSP: 0x7ffe93b36930
[*] Switching to interactive mode
Choose an option:
(1) Add a buyer
(2) Update a buyer's spice allocation
(3) View a buyer
(4) Deploy a hunter-seeker
(5) Sell the spice
> You sold the spice, and have 79 tons remaining. You live to see another day.
UMDCTF{use_the_spice_to_see_into_the_srop_future}
Rev
CMSC430 / travel the dunes… with OCaml!
rev/cmsc430 - aparker - 80 solves / 421 points
This binary was compiled by an hand-crafted, artisan racket compiler, courtesy of UMD’s very own CMSC430 class.
rev/travel the dunes… with OCaml! - assgent - 72 solves / 431 points
ocaml_executable
is a flag checker compiled and assembled from OCaml using ocamlopt. Can you rev it?Hint: The flag is 123 characters in length.
These two are grouped together because you can instruction count side channel both.
From what I can tell, CMSC430 checks a byte at a time by reading in one character, multiplying it by 2, then comparing it with a constant. If it’s not equal, the function returns false early. This can be abused to instruction count side channel.
For the OCaml one, it does something similar, where it checks one byte at a time by accessing elements of a list one after the other, with failures exiting early. This can also be abused to instruction count side channel.
Typecheck
rev/Typecheck - aparker - 29 solves / 476 points
My C++ code won’t type check. Can you fix that for me?
Note: you will need to set
-ftemplate-depth=10000
when compiling.
We’re given 2 files, templates.hpp
, and main.cpp
:
These are both huge files, so I’ll give a quick rundown.
First, main.cpp
is the actual file that is “run”. In this case, checking the flag involves being able to compile the file.
To compile the file, we need to ensure that vm_t<nil_t, prog_t, flag_t>
is a valid type. To do so, we’ll have to take a look at templates.hpp
.
VM Instructions
templates.hpp
is a huge file, but from the looks of it, we seem to be working with a VM, with the large int_list_t
being the instructions that the VM will execute.
Looking at templates.hpp
, there are a few “instruction-like” structs:
These seem to check the first element of the instruction list, execute the corresponding instruction, then move on to the next instruction.
Here are the actual instructions.
First, we have an addition instruction mapped to 0
that pops the top two elements off the stack and pushes their sum:
Next, we have a multiplication instruction mapped to 1
, which pops the top two elements off the stack and pushes their product:
Then, we have an instruction that pushes a literal onto the stack. The literal that is pushed is the second element of the instruction list:
Next, we have an instruction that gets the value of the N
th element of the input list and pushes it onto the stack. It recursively reads the first element of the input list N
times, until it reaches the N
th element where it will return that value:
Finally, we have an instruction that checks if the top element of the stack is equal to argument. This check uses std::enable_if
, so if the element is not equal, the program will terminate and thus the file cannot be compiled:
Skimming over the instructions, we can see that it seems to basically taking linear combinations of the input list and checking if it equals a certain value.
We can then simply write a script that parses out these equations and solves them using Z3 (or linear algebra but Z3 is easier).
Solution
Here was my solution:
Running gives us the flag:
sat
UMDCTF{c++_templates_are_the_reason_for_the_butlerian_jihad}
Flavors
rev/Flavors - edwfeng - 19 solves / 485 points
ah, elixirs, the sweet liquid flavor that brings a little spice to my life
desired output is
AD38A5970B000E1500041F0B00011617AA85109204082D1485040326051D13012716BF081189AB990E2D0F182CA824
This time, we’re only given Elixir.Flavors.beam
, a compiled BEAM file. We can run it using the Elixir shell:
Without peaking too hard into the disassembly, we see that main
gets our input, creates a map using that input, then calls a/1
, then b/1
, and then prints some result.
At the start of a/1
, there’s a check to make sure the map has 47 elements:
This means our input has to be 47 characters long, which matches the fact that the desired output is 94 = 47 * 2 characters long.
Let’s see what happens when we run the program with a 47 character input:
Great, now we’re actually getting a response. Here’s what it looks like lined up with the desired output:
AD38A5970B000E1500041F0B00011617AA85109204082D1485040326051D13012716BF081189AB990E2D0F182CA824
AD38A5972EA92EA2A928A22428222FAA2D312C312FABAA14A22824A828A9A22EA92E3122A1312C312DAA2F222CA824
Notice how the first 8 characters are the same, as well as the last 6. This corresponds to the flag format we’ve put in, which is 7 characters, since 7 * 2 = 14 = 8 + 6 hex characters.
This means that at some point, our input is being shuffled around.
The next step requires a bit of intuition. We know we have 7 correct characters, and 4 correct at the start and 3 in the back. By inputting in more guesses, we can see that it takes one character at a time, puts it at the start, then takes the next character and prepends it to the end.
iex(1)> Flavors.main
Flag: UMDCTF{AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA}
AD38A5972EA92EA2A928A22428222FAA2D312C312FABAA14A22824A828A9A22EA92E3122A1312C312DAA2F222CA824
:ok
iex(2)> Flavors.main
Flag: UMDCTFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA}
AD38A5312EA92EA2A928A22428222FAA2D312C312FABAA14A22824A828A9A22EA92E3122A1312C312DAA2F222CA824
:ok
iex(3)> Flavors.main
Flag: AMDCTF{AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA}
2C38A5972EA92EA2A928A22428222FAA2D312C312FABAA14A22824A828A9A22EA92E3122A1312C312DAA2F222CA824
:ok
iex(4)> Flavors.main
Flag: UADCTF{AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA}
AD38A5972EA92EA2A928A22428222FAA2D312C312FABAA14A22824A828A9A22EA92E3122A1312C312DAA2F222CA828
:ok
Here is a reimplementation in Python:
Then all we have to do is unshuffle the output and brute force each character until we get the flag:
$ python solve.py
100%|████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 76/76 [00:49<00:00, 1.54it/s]
UMDCTF{what_about_melange_but_in_elixir_form_?}