Skip to main content

HTB

HTB - UniCtf 2024- Signaling Victorious

Intro

HTB CTF Platform | Find & Play CTFs!
Prove your cybersecurity skills on the official Hack The Box Capture The Flag (CTF) Platform! Play solo or as a team. Jeopardy-style challenges to pwn machines.

This is a write-up for the forensics challenge <Signaling Victorious> from the 2024 hack the box university CTF. This challenge was rated as hard and ended up with not many solves. There are multiple ways, some unintended, for this challenge.

It required the analysis of a Windows 10 memory dump and a Windows 'Users' folder that is initially 7zipped and password protected. Both resources had to be combined to work through multiple layers of encryption in order to retrieve a username and password from a Signal messenger database. Using this username:password combination, one could login to the docker container that was provided along with the data-set. The container hosted an Empire post exploitation framework with the Starkiller web front-end, where the flag could be found.

Write-up

Challenge Description

"In a recent raid with your fellow bounty hunters you managed to recover a memory dump and a backup archive of the Frontier Board's Operation Center! The Board knows the Starry Spurr lies within your grasp and they are getting even more desperate... Uncover whatever secrets lie within the artefacts you are given and find a way to halt the Board's plans!!
Note: Carefully read the `readme.txt` in the downloadables!"

Tools used

  • volatility2 & volatility 3
  • pypykatz
  • sqlcipher
  • objdump
  • python
  • memprocfs

Initial Assessment

Checking the running processes

```
python3 ~/volatility3/vol.py -f win10_memdump.elf windows.pslist

...
7152    748     Signal.exe      0xb90b8a9da080  53      -       1       False   2024-11-13 00:54:38.000000      N/A     Disabled
6820    7152    Signal.exe      0xb90b8a9c7080  20      -       1       False   2024-11-13 00:54:39.000000      N/A     Disabled
6800    7152    Signal.exe      0xb90b8a2812c0  18      -       1       False   2024-11-13 00:54:39.000000      N/A     Disabled
7028    7152    Signal.exe      0xb90b8a6670c0  24      -       1       False   2024-11-13 00:54:40.000000      N/A     Disabled
...
7056    748     powershell.exe  0xb90b8a9c4080  20      -       1       False   2024-11-13 00:55:29.000000      N/A     Disabled
...
7392    7056    backuper.exe    0xb90b8a69d2c0  4       -       1       False   2024-11-13 00:55:58.000000      N/A     Disabled

```

Above the most interesting processes are listed, Signal.exe is a huge hint to the challenge name, backuper.exe could be related to the 7z backup files that came with the challenge and powershell.exe can be anything. In this case the windows.cmdline module can be used to verify the command line arguments with which the respective programs were started.

python3 ~/volatility3/vol.py -f win10_memdump.elf windows.cmdline
...
7056    powershell.exe  "PowerShell.exe" -noexit -command Set-Location -literalPath 'C:\Users\frontier-user-01\Desktop'
...
7392    backuper.exe    "C:\Users\frontier-user-01\Desktop\backuper.exe"

Nothing spectecular here, so why not analyze backuper.exe. I had to use volatility 2 because of a dependency (capstone) that was missing and not fixable in a moment's time.

vol.py -f win10_memdump.elf --profile=Win10x64_19041 procdump --pid 7392 --dump-dir ./

file -s executable.7392.exe 
executable.7392.exe: PE32+ executable (console) x86-64, for MS Windows, 10 sections

Next a quick peek at the imports from the PE:

objdump -x executable.7392.exe | less

...
The Import Tables (interpreted .idata section contents)
 vma:            Hint    Time      Forward  DLL       First
                 Table   Stamp     Chain    Name      Thunk
 000e8460       000e84a0 00000000 00000000 000e895e 000e8000

        DLL Name: ADVAPI32.dll
        vma:     Ordinal  Hint  Member-Name  Bound-To
        000e8000  <none>  01a0  LsaClose
        000e8008  <none>  01c9  LsaOpenPolicy
        000e8010  <none>  01db  LsaRetrievePrivateData
        000e8018  <none>  01c7  LsaNtStatusToWinError
        000e8020  <none>  01b1  LsaFreeMemory
...

Very interesting, it can be seen that the PE imports all the necessary functions to interact with the Windows Security subsystem.

Diving into the backup

If backuper.exe with LSA (Local Security Authority) is used to create the backup.7z, there might be a key in memory.

python3 ~/volatility3/vol.py -f win10_memdump.elf -o ~/Desktop/htb/signal windows.lsadump

Volatility 3 Framework 2.7.1
Progress:  100.00               PDB scanning finished                        
Key     Secret  Hex

DPAPI_SYSTEM    ,Ô¢¢7}ÔYVª!¾^T+£)àÏMºf]¦p#c=ÏÇ¿L3&      2c 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01 00 00 00 d4 a2 a2 37 7d 10 04 d4 59 56 aa 21 1d be 5e 14 54 2b a3 29 e0 cf 4d ba 66 01 5d a6 70 23 63 9f 3d cf c7 bf 4c 9f 33 26 00 00 00 00
NL$KM   @´ùhf" ÷Zúè;j¬ÂèÕ&¡ûmvaZÍÚâ`HõáLõÑáAúÓY)r
                                                 ãVÌ©2YjiAdv3oÞö%H£ÙùÞ[H        40 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 b4 f9 68 66 22 20 f7 5a fa 82 e8 82 3b 6a 9a 90 ac 0f c2 e8 d5 26 a1 19 fb 6d 76 8d 9a 61 5a cd da e2 60 48 f5 e1 4c f5 11 d1 e1 41 fa d3 59 29 72 0b e3 56 cc a9 32 59 13 6a 69 41 64 76 33 6f 15 de f6 02 25 10 48 12 a3 d9 f9 de 83 1e 5b 48
OfflineBackupKey        (yYj8g5Pk6h!-K3pBMSxF   28 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 79 00 59 00 6a 00 38 00 67 00 35 00 50 00 6b 00 36 00 68 00 21 00 2d 00 4b 00 33 00 70 00 42 00 4d 00 53 00 78 00 46 00 00 00 00 00 00 00 00 00

The format is a bit messed up, but there is an OfflineBackupKey shown. It is important to not that the ( is not part of the key, this can be seen in the hex representation. Depedning on the shell that is used, the ! has to be escaped, e.g. zsh in Kali.

7z x backup.7z -p"yYj8g5Pk6h\!-K3pBMSxF"

This leaves us with a decrypted Users folder full of goodies. There is only one non-standard user: frontier-user-01. After some rummaging with find, grep and due to the name of the challenge, as well as the existing processes I concluded that the next step would be to decrypt the signal database found under Users\frontier-user-01\AppData\Roaming\Signal\sql\db.sqlite. I was aware of a flaw in the Signal desktop app that allowed someone with access to the config.json in the Signal root directory to unlock the database. This flaw was patched mid 2024, and the electron safeStorage API was implemented, which uses DPAPI under Windows. After some research I found out that the key from config.json was encrypted with the chromium based os_crypt, which could be confirmed by the v10 prefix:

cat ./frontier-user-01/AppData/Roaming/Signal/config.json | jq .encryptedKey | tr -d '"' | xxd -p -r | head -c 3
v10              

Decrypting the Signal database

Retrieving the necessary keys

To decrypt the key from config.json we need to get the retrieve the AES key. The AES key can be found in the local state file from the signal process memory or directory, which has to be decrypted with the DPAPI masterkey. which can be retrieved from memory too, more specifically from the lsass process. This can be achieved via multiple ways and tools. I used volatility3 with the pypykatz plugin.

python3 ~/volatility3/vol.py -f win10_memdump.elf -o ~/Desktop/htb/signal windows.pypykatz
Volatility 3 Framework 2.7.1
ERROR    pypykatz    : Failed to prcess TSPKG package! Reason: Page Fault at entry 0x0 in table page directory

credtype        domainname      username        NThash  LMHash  SHAHash masterkey       masterkey(sha1) key_guid        password

msv     DESKTOP-6MBJBAP frontier-user-01        1d3e3e030ba1a179e1281406efd980bf                ded871d3a3992be2179840890d061c9f30a59a77
dpapi                                           791ca70e650987684b043745c6f4b1c0f97eb2369317302c6c60f9cda19e1b4864fbece48341141501606d8d359ff7f54ee71e4a2b821d3df69582927742809f        8d53efa8456b9ba43206f4c3a6dc1c957d26105a        ab71b6fc-d0b8-4d7b-aa12-6ece19ff1917
msv     DESKTOP-6MBJBAP frontier-user-01        1d3e3e030ba1a179e1281406efd980bf                ded871d3a3992be2179840890d061c9f30a59a77

extracting the DPAPI masterkey

Now retrieving the key from the local state:

cat Users/frontier-user-01/AppData/Roaming/Signal/Local\ State 
{"os_crypt":{"audit_enabled":true,"encrypted_key":"RFBBUEkBAAAA0Iyd3wEV0RGMegDAT8KX6wEAAAD8tnGruNB7TaoSbs4Z/xkXEAAAABIAAABDAGgAcgBvAG0AaQB1AG0AAAAQZgAAAAEAACAAAACKakPvCWDeRdef30ik+0RfHTUXhQrfAdfcEOuzfv8sDQAAAAAOgAAAAAIAACAAAAAad9BHSVFuYmI0D8QG9924xL4pzewU1LemGmaTlTzcOjAAAAAg0SNGW/NP4egaKEv0Tgl9JE3d0tFQpx6G6lMcoOlF3EyR/dr0hbbBbQksTEkECcxAAAAAHaurRLkbh4yTcD+/hxG67Vfa0zLEIJpQOAWw6BIDUw+jRHY3AuIU0wdyxy5lv6CZEYmIQqUbyJSXzPIPpqYn6w=="}}

To confirm:

echo 'RFBBUEkBAAAA0Iyd3wEV0RGMegDAT8KX6wEAAAD8tnGruNB7TaoSbs4Z/xkXEAAAABIAAABDAGgAcgBvAG0AaQB1AG0AAAAQZgAAAAEAACAAAACKakPvCWDeRdef30ik+0RfHTUXhQrfAdfcEOuzfv8sDQAAAAAOgAAAAAIAACAAAAAad9BHSVFuYmI0D8QG9924xL4pzewU1LemGmaTlTzcOjAAAAAg0SNGW/NP4egaKEv0Tgl9JE3d0tFQpx6G6lMcoOlF3EyR/dr0hbbBbQksTEkECcxAAAAAHaurRLkbh4yTcD+/hxG67Vfa0zLEIJpQOAWw6BIDUw+jRHY3AuIU0wdyxy5lv6CZEYmIQqUbyJSXzPIPpqYn6w==' | base64 -d | head -c 5
DPAPI  

This key has to be saved as in hexadecimal format and WITHOUT the first 5 bytes (DPAPI string) in a file like so:

01000000d08c9ddf0115d1118c7a00c04fc297eb01000000fcb671abb8d07b4daa126ece19ff191710000000120000004300680072006f006d00690075006d0000001066000000010000200000008a6a43ef0960de45d79fdf48a4fb445f1d3517850adf01d7dc10ebb37eff2c0d000000000e80000000020000200000001a77d04749516e6262340fc406f7ddb8c4be29cdec14d4b7a61a6693953cdc3a3000000020d123465bf34fe1e81a284bf44e097d244dddd2d150a71e86ea531ca0e945dc4c91fddaf485b6c16d092c4c490409cc400000001dabab44b91b878c93703fbf8711baed57dad332c4209a503805b0e81203530fa344763702e214d30772c72e65bfa09911898842a51bc89497ccf20fa6a627eb

The DPAPI masterkey and its GUID have to be saved into json like follows:

{
    "masterkeys": {
        "ab71b6fc-d0b8-4d7b-aa12-6ece19ff1917": "791ca70e650987684b043745c6f4b1c0f97eb2369317302c6c60f9cda19e1b4864fbece48341141501606d8d359ff7f54ee71e4a2b821d3df69582927742809f"
    },
    "backupkeys": {}
}

And finally, the AES key can be decrypted with pypykatz:

pypykatz dpapi blob dpapi.json dpapi.bin                                             
Error! non-hexadecimal number found in fromhex() arg at position 1
HEX: 7582f084a7d00872eebe919c2c02da0a8f4d8e67e648bb55805e8994a8a165ef
STR: 艵蓰킧爈뻮鲑Ȭ૚䶏枎䣦喻庀钉ꆨ

With this AES key, the key for the signal database can be decrypted. This would be possible with cyberchef, but quite tedious, so python it is:

from Cryptodome.Cipher import AES

def decrypt_password(encrypted_password, master_key):
    try:
        iv = encrypted_password[3:15]
        ciphertext = encrypted_password[15:-16]
        tag = encrypted_password[-16:]
        cipher = AES.new(master_key, AES.MODE_GCM, iv)
        decrypted_password = cipher.decrypt_and_verify(ciphertext, tag)
        return decrypted_password.decode()
    except Exception as e:
        return e 

def main():
    sig_pw = bytes.fromhex("763130cc1843cbf3949e872b373031e89c85f8e8d6e9ec3bd9340bb9c6fd844ca424d7e666feac3663f6c2810d6ddbdfb82f7faa4456eda119bacd2709fc2404eeeb74e69b2b3f2f71e765b74a068c5549a1871559d537de08a25c700a97cd")
    key = bytes.fromhex("7582f084a7d00872eebe919c2c02da0a8f4d8e67e648bb55805e8994a8a165ef")
    print(decrypt_password(sig_pw,key))

if __name__ == "__main__":
    main()

Which returns the key to decrypt the database.

Decrypting the Database

The database is located at the following path: Users/frontier-user-01/AppData/Roaming/Signal/sql/db.sqlite and can be opened with sqlcipher:

sqlcipher Users/frontier-user-01/AppData/Roaming/Signal/sql/db.sqlite

To unlock the database:

PRAGMA key = "x'65f77c5912a1456af299975228bb45857144ee8fb546683c9274e11a1617fa65'";

This should return ok

The messages are in the messages table, and since I do not like sql, the table can be dumped as follows:

.headers on
.mode csv
.output messages.csv
SELECT * FROM messages;
.output stdout

Followed by:

grep -iE 'password|pass|user|username' messages.csv

Username: empireadmin
Password: eNSrj4qHp0pG#075D98@

Remembering the readme.txt, navigating to <IP>:<PORT>/index.html, adding the IP:PORT combo, username and password. The flag can be found on the left-hand side menu under 'credentials'.

All in all I really liked the challenge, even though it was really time-consuming and research-intensive.