HTB - UniCtf 2024- Signaling Victorious
Intro
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.
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.