This is more a note than it is a blog post. Recently, I followed this report about a malware named BLISTER, a current loader. This malware features a walk of the Process Environment Block (PEB) and API hashing to eventually load the functions necessary for its operation from ntdll
. I followed this report: https://www.elastic.co/security-labs/blister-loader
As I found the report to be excellent, I aimed to replicate the findings to learn / get better at reversing. At some point, the report states that the malware “enumerates and hashes each export of ntdll…” - That means walking the PEB (Process Environment Block) and API hashing. I wanted to understand this in detail, it’s been a while since I reversed this technique, so I started to dig in. Here are my notes and a comprehension of links I used to figure this out. Maybe they’re helpful to someone else. Also, I wanted to share the awesome links I found explaining PEB walk etc. be sure to check the references! :)
Sample SHA256: afb77617a4ca637614c429440c78da438e190dd1ca24dc78483aa731d80832c2
()
Below, you can see the start of the PEB walk. I added numbers you can follow.
1 17173852 64a130000000 mov eax, dword [fs:0x30]
17173858 53 push ebx {__saved_ebx} {0x0}
17173859 57 push edi {__saved_edi}
1717385a 8975f4 mov dword [ebp-0xc {var_10_1}], esi {0x0}
2 1717385d 8b400c mov eax, dword [eax+0xc]
3 17173860 8b401c mov eax, dword [eax+0x1c]
17173863 c745e8004ab6f3 mov dword [ebp-0x18 {v_xorkey}], 0xf3b64a00
4 1717386a 8b5808 mov ebx, dword [eax+0x8] // *flink
1717386d 8b433c mov eax, dword [ebx+0x3c] // e_lfanew
17173870 8b441878 mov eax, dword [eax+ebx+0x78]
- this fetches a pointer to the PEB, which is stored in
eax
PEB + 0x0c
is the offset inside the PEB pointing toPPEB_LDR_DATA LoaderData
[1]PEB + 0x1c
is the offset insidePEB_LDR_DATA
[2] to the variableInInitilizationOrder
, the list of modules in initialization order- The list is doubly linked [3] what we have in
eax
is*Flink
- now comes the part that got me confused: where doeseax+0x8
point to? If you look at theLDR_MODULE
structure [4], you will see that the base address of NTDLL, which we want to fetch, is not at offset0x8
, but at the offset0x18
instead:
Each LIST_ENTRY
is 8 bytes, thus, the base address SHOULD be at offset 0x18
, why use the offset 0x08
then? The blog post [2] helps out here - without it, that would’ve been a long debug session. We’re actually 0x10
bytes into the LDR_MODULE
struct with the InInitalizationOrderModuleList
, thus, relatively speaking, the offset the to the base address is at 0x08
! I added the absolute and relative offsets to the struct below:
typedef struct _LDR_MODULE {
/* offset */
-16 00 LIST_ENTRY _InLoadOrderModuleList_;
-08 08 LIST_ENTRY _InMemoryOrderModuleList_;
00 10 LIST_ENTRY _InInitializationOrderModuleList_;
08 18 PVOID _BaseAddress_;
PVOID _EntryPoint_;
ULONG _SizeOfImage_;
UNICODE_STRING _FullDllName_;
UNICODE_STRING _BaseDllName_;
ULONG _Flags_;
SHORT _LoadCount_;
SHORT _TlsIndex_;
LIST_ENTRY _HashTableEntry_;
ULONG _TimeDateStamp_;
} LDR_MODULE, *PLDR_MODULE;
Now it all makes sense. Shoutout to the author, dzzie for [2]. The PEB walk is available as C/ASM code and documented here: [5] - check it out if you want to compile a minimal version with source code to reverse and learn from.
References:
[1] http://undocumented.ntinternals.net/UserMode/Undocumented%20Functions/NT%20Objects/Process/PEB.html
[2] http://sandsprite.com/CodeStuff/Understanding_the_Peb_Loader_Data_List.html
[3] https://learn.microsoft.com/en-us/windows/win32/api/ntdef/ns-ntdef-list_entry
[4] http://undocumented.ntinternals.net/UserMode/Structures/LDR_MODULE.html
0xca7