Description
An IR team contained an active intrusion on a Windows host. They yanked the machine off the network while the intruder was typing a note. The system was triaged and powered down. You’ve been handed the artifacts exactly as they were collected.
FileProvided: Lazy.zip Alternative download link: Lazy.zip
Solution
Lazy.zip contains a file called mem.raw. Clearly, we will be dealing with memory forensics, let’s start by enumerating the Windows version we are dealing with:
vol -f mem.raw windows.info
...
Is64Bit True
IsPAE False
...
SystemTime 2025-10-01 02:26:36+00:00
NtSystemRoot C:\WINDOWS
NtProductType NtProductWinNt
NtMajorVersion 10
NtMinorVersion 0
...
PE Machine 34404
PE TimeDateStamp Fri Mar 9 22:53:21 2085We are dealing with a Windows 10 x64. The next step I followed was to list the running processes (output truncated):
vol -f mem.raw windows.pslist
PID PPID ImageFileName Offset(V) Threads Handles SessionId Wow64 CreateTime ExitTime File output
4 0 System 0x9484c06a7040 171 - N/A False 2025-10-01 02:10:49.000000 UTC N/A Disabled
96 4 Registry 0x9484c073c080 4 - N/A False 2025-10-01 02:10:44.000000 UTC N/A Disabled
400 4 smss.exe 0x9484c26eb040 2 - N/A False 2025-10-01 02:10:49.000000 UTC N/A Disabled
...
6980 1256 ctfmon.exe 0x9484c5e80080 11 - 1 False 2025-10-01 02:11:01.000000 UTC N/A Disabled
1888 788 NisSrv.exe 0x9484c7ddd080 6 - 0 False 2025-10-01 02:11:03.000000 UTC N/A Disabled
7096 940 smartscreen.ex 0x9484c5961080 7 - 1 False 2025-10-01 02:11:04.000000 UTC N/A Disabled
...
5196 940 FileCoAuth.exe 0x9484c8467080 5 - 1 False 2025-10-01 02:11:43.000000 UTC N/A Disabled
7400 4424 cmd.exe 0x9484c823f080 3 - 1 False 2025-10-01 02:11:46.000000 UTC N/A Disabled
7376 7400 conhost.exe 0x9484c823d080 4 - 1 False 2025-10-01 02:11:46.000000 UTC N/A Disabled
9076 940 OpenConsole.ex 0x9484c823a080 9 - 1 False 2025-10-01 02:11:46.000000 UTC N/A Disabled
3960 940 WindowsTermina 0x9484c6ea3080 20 - 1 False 2025-10-01 02:11:46.000000 UTC N/A Disabled
...
1200 788 VSSVC.exe 0x9484c627e080 5 - 0 False 2025-10-01 02:24:49.000000 UTC N/A Disabled
1104 788 svchost.exe 0x9484c70b8080 6 - 0 False 2025-10-01 02:24:49.000000 UTC N/A Disabled
6040 788 svchost.exe 0x9484c92d8080 6 - 0 False 2025-10-01 02:24:55.000000 UTC N/A Disabled
5708 1332 MoUsoCoreWorke 0x9484c9ca90c0 9 - 0 False 2025-10-01 02:25:55.000000 UTC N/A Disabled
4512 788 svchost.exe 0x9484c6925080 7 - 0 False 2025-10-01 02:25:56.000000 UTC N/A Disabled
1416 788 TrustedInstall 0x9484c8578080 9 - 0 False 2025-10-01 02:25:56.000000 UTC N/A Disabled
1972 940 TiWorker.exe 0x9484cbbe80c0 9 - 0 False 2025-10-01 02:25:56.000000 UTC N/A Disabled
7772 4424 Notepad.exe 0x9484c7b0f080 23 - 1 False 2025-10-01 02:25:58.000000 UTC N/A Disabled
4708 4424 Lazy.exe 0x9484c7ae8080 1 - 1 False 2025-10-01 02:26:07.000000 UTC N/A Disabled
...Within the output, and given the challenge name as well as the description pointing to the intruder writing a note, there are two process that stood out: Lazy.exe and Notepad.exe. We can grab the executable with the following command:
vol -f mem.raw windows.dumpfiles --pid 4708 --filter Lazy.exeIf we execute the file, it will ask for an hex string, then a last XOR in hex, then terminate:
Enter hex string: fffff
Enter last XOR (hex): aa
I opened the file in Ghidra and eventually found a function with the following code:
}
for (; puVar4 != puVar2; puVar4 = (uint *)((longlong)puVar4 + 1)) {
*(byte *)puVar4 = ((byte)*puVar4 ^ 0x55) + 1 ^ param_3;
}
If we translate the entire function to pseudo-code, it would be something similar to:
void encrypt_buffer(uint8_t *out, const uint8_t *in, size_t len, uint8_t key) {
// copy input to out
memcpy(out, in, len);
for (size_t i = 0; i < len; i++) {
uint8_t plain = out[i];
uint8_t tmp = (plain ^ 0x55) + 1;
out[i] = tmp ^ key;
}
}In other words, it does the following:
- Take a byte:
plain = *(byte *)puVar4 - XOR with
0x55:plain ^ 0x55 - Add 1:
+ 1 - XOR with
param_3(the user‑supplied key):^ param_3
The above can be decrypted by reversing the operations:
def decrypt_hex(hex_string: str, key: int) -> bytes:
data = bytes.fromhex(hex_string) # convert hex → raw bytes
out = bytearray()
for c in data:
tmp = c ^ key # undo final XOR
tmp = (tmp - 1) & 0xFF
plain = tmp ^ 0x55 # undo initial XOR
out.append(plain)
return bytes(out)What we are missing now is the encrypted string and the key. Let’s focus on Notepad.exe now.
We can use the Volatility plugin filescan to locate open files and recently accessed files:
vol -f mem.raw windows.filescan > filescan.txtIn the output, we are interested in checking Notepad’s recovery artifacts (TabStates):
cat filescan.txt| grep -i notepad | grep -i tabstate
0x9484c6fc20a0 \Users\Mokeyy\AppData\Local\Packages\Microsoft.WindowsNotepad_8wekyb3d8bbwe\LocalState\TabState\a3e1e609-7d54-4899-823f-a074f5acb841.bin
0x9484c6fe3250 \Users\Mokeyy\AppData\Local\Packages\Microsoft.WindowsNotepad_8wekyb3d8bbwe\LocalState\TabState\120fa387-feeb-46ab-8acd-1270c86c2a05.bin
0x9484c6fe86b0 \Users\Mokeyy\AppData\Local\Packages\Microsoft.WindowsNotepad_8wekyb3d8bbwe\LocalState\TabState\120fa387-feeb-46ab-8acd-1270c86c2a05.bin.tmp
0x9484c8fa8610 \Users\Mokeyy\AppData\Local\Packages\Microsoft.WindowsNotepad_8wekyb3d8bbwe\LocalState\TabState\88d18893-54ed-447a-a886-819e167076ce.binWe can dump the files using the windows.dumpfiles plugin with the virtual addresses:
vol -f mem.raw windows.dumpfiles --virtaddr 0x9484c6fc20a0
vol -f mem.raw windows.dumpfiles --virtaddr 0x9484c6fe3250
vol -f mem.raw windows.dumpfiles --virtaddr 0x9484c6fe86b0
vol -f mem.raw windows.dumpfiles --virtaddr 0x9484c8fa8610If we check with strings all the dumped files, we get:
strings -el file.0x9484c*
C:\Users\Mokeyy\Desktop\What is it.txt
8da3acaab6fef7f8a8f8f4f4fbfdfeacf7fea8f4f7fff1f7acf8abfdfcacfbf4abfaf4feadb0cf
Thank Me Later 0x99
Thank Me Later 0x99
C:\Users\Mokeyy\Desktop\What is it.txt
8da3acaab6fef7f8a8f8f4f4fbfdfeacf7fea8f4f7fff1f7acf8abfdfcacfbf4abfaf4feadb0cfLooks like the data we were missing is:
ciphertext = "8da3acaab6fef7f8a8f8f4f4fbfdfeacf7fea8f4f7fff1f7acf8abfdfcacfbf4abfaf4feadb0cf"
key = 0x99
Now we need all the ingredients to recover the plaintext:
>>> def decrypt_hex(hex_string: str, key: int) -> bytes:
... data = bytes.fromhex(hex_string) # convert hex → raw bytes
... out = bytearray()
...
... for c in data:
... tmp = c ^ key # undo final XOR
... tmp = (tmp - 1) & 0xFF
... plain = tmp ^ 0x55 # undo initial XOR
... out.append(plain)
...
... return bytes(out)
...
>>>
>>> ciphertext = "8da3acaab6fef7f8a8f8f4f4fbfdfeacf7fea8f4f7fff1f7acf8abfdfcacfbf4abfaf4feadb0cf"
>>> key = 0x99
>>> plaintext = decrypt_hex(ciphertext, key)
>>> print (plaintext)
b'Flag{385e599463a83e98028a5d61a49d793f}\x00'flag: Flag{385e599463a83e98028a5d61a49d793f}