HackVent 2023 (The Easy)

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).

[HV23.01] A letter from Santa #

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?

Solution #

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:

  for c in {a..y}; do
    cat $pwd/templates/santa.j2 | grep -o ...{{$c | cut -c1 | tr 'ab' '10' | tr -d '\n'
    echo
  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}

[HV23.02] Who am I? #

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>}

Solution #

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}

[HV23.03] Santa's grille #

While contemplating the grille and turning some burgers, Santa decided to send all the hackers worldwide some season's greetings.

Solution #

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)
  (for i in {1..4}; do
    echo $data | tr ' ' '\n' | sed -n "$(echo $key | sed 's/ \|$/p;/g')"
    key=$(echo $key | tr ' ' '\n' | awk '{ print 1+(4-($1-1)%5)*5+(int(($1-1)/5)) }')
  done) | tr -d '\n'
  echo
source

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'

[HV23.04] Bowser #

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?

Solution #

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}

[HV23.05] Aurora #

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?

Solution #

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 $out
source

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)"

[HV23.06] Santa should use a password manager #

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

Solution #

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 $virtaddr
source

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}

[HV23.07] The golden book of Santa #

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.

Solution #

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 -p
source
  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}

[HV23.H1] Kringle's Secret #

Can you feel it? I feel like there's a... hidden flag in one of the easy challenges!

Solution #

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 -r
source

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}