This blog post is part of my write-up for HackVent 2023, an advent-calendar style CTF. During the first 24 days of December, each day a new challenge is released, with the difficulty increasing as we get closer to the 24th. This part of the write-up covers the easy challenges from the 1st to the 7th. There is also a post about the medium challenges. The remaining write-up is currently in the works.
All my solution scripts are available in my ctf-notes git repo. It provides a flake that can be used to build and run the solutions:
nix run git+https://git.sr.ht/~lgcl/ctf-notes\?ref=refs/tags/hv23#hv23.day-01
The result is usually a script that runs my solution in a minimal container using crun. Some the solution scripts require arguments, such as the target IP address or the local IP address for reverse shells. This will be pointed out in the write-up.
I plan on writing another blog post on how this setup functions soon (tm).
Finally, after 11 months of resting, Santa can finally send out his presents and challenges again. He was writing a letter to his youngest baby elf, who's just learning his ABC/A-Z's. Can you help the elf read the message?
We are given a web service that lets us select a letter and enter some text. As a response we get the entered text repeated, sometimes with white and sometimes with black background.
After some time, the A-Z
in the description got me to print, for each letter, the order of the white/black backgrounds:
forc done
1111111010111111101111111 1000001001110100001000001 1011101011111100101011101 1011101011011101001011101 1011101011110100001011101 1000001011111101001000001 1111111010101010101111111 0000000000010100100000000 0011111100110011110111101 1010100000110010011011001 0111011000101011111111011 1011000100001000001110000 0010111011110101101001011 1000110100000111000001110 1110011010010110100101101 0100010110101010001010100 1010111111010111111111010 0000000001001010100011011 1111111010111011101011011 1000001010100111100011100 1011101010101011111110010 1011101011110100010001000 1011101010101010111010001 1000001001001110001001001 1111111000101111011011111
Looking at this I noticed the distinctive finder patterns of a QR code (the ring with a square in the middle you get on three of the four corners of a QR code). Re-constructing and then scanning the QR code yields the flag:
nix run git+https://git.sr.ht/~lgcl/ctf-notes\?ref=refs/tags/hv23#hv23.day-01
: HV23{qr_c0des_fun}
Have you ever wished for an efficient dating profile for geeks? Here's a great example:
G d--? s+: a+++ C+++$ UL++++$ P--->$ L++++$ !E--- W+++$ N* !o K--? w O+ M-- V
PS PE Y PGP++++ t+ 5 X R tv-- b DI- D++ G+++ e+++ h r+++ y+++
Find the flag using the code block provided in the introduction.
Flag format: HV23{<Firstname Lastname>}
After some googling, I stumbled across Geek Code. Using an online decoder got me a name - Philip Zimmerman and with it the flag:
nix run git+https://git.sr.ht/~lgcl/ctf-notes\?ref=refs/tags/hv23#hv23.day-02
: HV23{Philip Zimmerman}
While contemplating the grille and turning some burgers, Santa decided to send all the hackers worldwide some season's greetings.
Together with the challenge, we also get this image:
It is from the Grille (cryptography) Wikipedia article, so it assumed that a grille cipher was used to encrypt the flag. Knowing the flag starts with HV23{
and that the mask should have six holes, which must not overlap when turning the mask, we can construct the only viable mask:
. . . . . . . O O . O . . . . . . . . . . O . O O
With the mask, extracting the flag is easily done.
nix run git+https://git.sr.ht/~lgcl/ctf-notes\?ref=refs/tags/hv23#hv23.day-03
: HV23{m3rry_h8ckvent2023}
I wrote an overcomplicated bash script for it :)
key=$(echo $mask | tr ' ' '\n' | grep -n 'O' | cut -d: -f1) ( ) | tr -d '\n' echosource
Where data
and mask
are defined as follows:
data=$'8 c t k 3\n2 r H V r\n2 y % 0 v\n2 e n 3 _\n} 3 h { m' mask=$'. . . . .\n. . O O .\nO . . . .\n. . . . .\n. O . O O'
Santa has heard that some kids appreciate a video game as a christmas gift. He would rather have the kids solve some CTF challenges, so he took some inspiration and turned it into a challenge. Can you save the princess?
We are given a binary that asks for a password. A quick scan of the control flow graph in cutter reveals a strcmp
with mario
:
rizin -A -e 'scr.color=0' -qc 'pdb @ dbg.main+0xd6' bowser.elf
: │ 0x000013e6 mov rax, qword [argv] ; bowser.c:46 : │ 0x000013ea add rax, 8 : │ 0x000013ee mov rax, qword [rax] : │ 0x000013f1 lea rsi, str.mario ; 0x36bd ; "mario" ; const char *s2 : │ 0x000013f8 mov rdi, rax ; const char *s1 : │ 0x000013fb call sym.imp.strcmp ; int strcmp(const char *s1, const char *s2) : │ 0x00001400 test eax, eax : │ ┌─< 0x00001402 je 0x1417
Trying this password, the binary still outputs no flag:
./bowser.elf mario | tail -n1
: Sorry, your flag is in another castle.
Looking back at the disassembly, in the begining of main
, the local variable flag
is filled with a lot of data:
rizin -A -e 'scr.color=0' -qc 'pdb @ dbg.main' bowser.elf
; DATA XREF from entry0 @ 0x10e1 ;-- main: ┌ int dbg.main(int argc, char **argv); │ ; arg char **argv @ stack - 0x88 │ ; arg int argc @ stack - 0x7c │ ; var uint8_t *c @ stack - 0x70 │ ; var uint8_t [73] flag @ stack - 0x68 │ ; var int64_t canary @ stack - 0x10 │ 0x00001310 endbr64 ; bowser.c:37 ; int main(int argc, char **argv); │ 0x00001314 push rbp │ 0x00001315 mov rbp, rsp │ 0x00001318 add rsp, 0xffffffffffffff80 │ 0x0000131c mov dword [argc], edi ; argc │ 0x0000131f mov qword [argv], rsi ; argv │ 0x00001323 mov rax, qword fs:[0x28] │ 0x0000132c mov qword [canary], rax │ 0x00001330 xor eax, eax │ 0x00001332 movabs rax, 0x86dfd3868d8d90ac ; bowser.c:39 │ 0x0000133c movabs rdx, 0x989e9399df8d8a90 │ 0x00001346 mov qword [flag[0]], rax │ 0x0000134a mov qword [flag[8]], rdx │ 0x0000134e movabs rax, 0x9edf9196df8c96df │ 0x00001358 movabs rdx, 0x9cdf8d9a978b9091 │ 0x00001362 mov qword [flag[16]], rax │ 0x00001366 mov qword [flag[24]], rdx │ 0x0000136a movabs rax, 0xb7ffd19a938b8c9e │ ; DATA XREF from dbg.bowser @ 0x1205 │ 0x00001374 movabs rdx, 0xa08a90a684cccda9 │ 0x0000137e mov qword [flag[32]], rax │ 0x00001382 mov qword [flag[40]], rdx │ 0x00001386 movabs rax, 0x899eaca09a899eb7 │ 0x00001390 movabs rdx, 0xafa09a978ba09b9a │ 0x0000139a mov qword [flag[48]], rax │ 0x0000139e mov qword [flag[56]], rdx │ 0x000013a2 movabs rax, 0x828c8c9a9c91968d │ 0x000013ac mov qword [flag[64]], rax │ 0x000013b0 mov byte [flag[72]], 0 │ 0x000013b4 mov eax, 0 ; bowser.c:41 │ 0x000013b9 call dbg.bowser │ 0x000013be cmp dword [argc], 2 ; bowser.c:42 │ ┌─< 0x000013c2 je 0x13e6
And this data is then inverted later on:
rizin -A -e 'scr.color=0' -qc 'pdb @ dbg.main+0x111' bowser.elf
: │ 0x00001421 mov rax, qword [c] ; bowser.c:52 : │ 0x00001425 movzx eax, byte [rax] : │ 0x00001428 not eax : │ 0x0000142a mov edx, eax : │ 0x0000142c mov rax, qword [c] : │ ; DATA XREF from dbg.bowser @ 0x1211 : │ 0x00001430 mov byte [rax], dl : │ 0x00001432 add qword [c], 1 ; bowser.c:51
The above assembly is executed in a loop and c
points to flag
in the beginning.
Looking at the data, 0xb7ffd19a938b8c9e
contains a ff
byte, which inverted is a null byte, causing puts
to stop printing. To extract the flag, I used the following monstrosity:
xxd -c2 -p $elf \ | sed -n '/48b/,+4{/^4/D;p}' \ | tr 0-9a-f fedcba9876543210 \ | xxd -p -r \ | cut -b40-source
The command matches the movabs
instructions used to load the data in a hex dump of the binary and uses sed
to extract the 8 bytes encoding the immediate value. tr
is used to invert the hex data. Running it, gives the flag:
nix run git+https://git.sr.ht/~lgcl/ctf-notes\?ref=refs/tags/hv23#hv23.day-04
: HV23{You_Have_Saved_the_Princess}
The Northern Lights appeared at exceptionally low latitudes this year due to the high level of solar activity. But from Santa's grotto at the North Pole, it's not unusual at all to see them stretching across the sky. Snowball the elf tried to capture a video of the aurora for his Instagram feed, but his phone doesn't work well in poor light, and the results were rather grainy and disappointing. Is there anything you can do to obtain a clearer image?
We are given a video with a lot of static. Averaging all frames yields a clear image with flag readable:
ffmpeg -i $src -filter:v tmix=frames=250:weights=1 -update 1 $outsource
From this, we can just read the flag:
HV23{M4gn3t0sph3r1c_d1sturb4nc3}
For this challenge, the solution in the repository is a derivation, which builds the image, instead of a script extracting and printing the flag:
feh "$(nix build --no-link --print-out-paths \ git+https://git.sr.ht/~lgcl/ctf-notes\?ref=refs/tags/hv23#hv23.day-05)"
Santa is getting old and has troubles remembering his password. He said password Managers are too complicated for him and he found a better way. So he screenshotted his password and decided to store it somewhere handy, where he can always find it and where its easy to access.
Santa recommends the volatility profile Win10x64_18362
The challenge provided a windows event trace log. Using volatility3, I was able to extract the wallpaper, which contains a QR code of the flag.
virtaddr="$( \ vol -f $dump -o files --offline -s $symbols windows.filescan \ | grep 'Pictures\\wallpaper.png' \ | awk '{ print $1; exit }')" vol -f $dump -o files --offline -s $symbols windows.dumpfile --virtaddr $virtaddrsource
The solutions script requires the memory dump to be passed as file-descriptor 4:
nix run git+https://git.sr.ht/~lgcl/ctf-notes\?ref=refs/tags/hv23#hv23.day-06 4<>memory.raw
: HV23{FANCY-W4LLP4p3r}
An employee found out that someone is selling secret information from Santa's golden book. For security reasons, the service for accessing the book was immediately stopped and there is now only a note about the maintenance work. However, it still seems possible that someone is leaking secret data.
Hint #1: Santa recommends to initiate a direct connection to the server without any proxy in between
Hint #2: You should stop spamming connections because you already have everything you need
We are given a web-server that always replies with a "Service Maintenance" image. Looking at the traffic, it is using chunked
as a transfer-encoding and the chunks have varying sizes, all in of the form 9xx
(hex). Extracting the lower byte of each chunk size yields the flag:
echo | ncat "$1" 80 | grep -a '^9' | cut -c2- | xxd -r -psource
nix run git+https://git.sr.ht/~lgcl/ctf-notes\?ref=refs/tags/hv23#hv23.day-07 -- $target_ip
: HV23{here_is_your_gift_in_small_pieces}
Can you feel it? I feel like there's a... hidden flag in one of the easy challenges!
This flag was hidden in day 6. Looking at the least significant bits of the color channels, only the top left of the red channel as any bits set. Extracting the LSBs from the channel yields the flag:
echo "obase=16;ibase=2;$( \ convert wallpaper.png ppm:- \ | xxd -c1 -p \ | sed "$(echo {,,}0,/0a/d\;)" \ | cut -b2 \ | tr 02468ace13579bdf '[0*8]1' \ | tr -d \\n \ | sed -E 's/0{8}//g' \ )" | bc | xxd -p -rsource
Again, the memory dump needs to be passed as file-descriptor 4:
nix run git+https://git.sr.ht/~lgcl/ctf-notes\?ref=refs/tags/hv23#hv23.hidden-1 4<>memory.raw
: HV23{no_ctf_without_stego}