How to analyze DLL address acquisition process

Sachiel
6 min readJan 23, 2021

Overview

Windows programs use APIs to use OS functions. Regular programs are built to use the import table when it is built. Also, if the program uses the APIs dynamically, it can use the “LoadLibrary” and “GetProcAddress” API.

Malware also uses APIs. However, if the malware simply used the API, it would be easily detected. Therefore, they devise to hide the use of APIs. They understand how the Windows OS works and use that knowledge to get APIs addresses. They hide their malicious intent by not leveraging import tables or the “LoadLibrary” and “GetProcAddress” APIs.

This article describes how to find the DLL address that is often used in malware. And I will describe the analysis method. This technique is already widely known, so many may know it. I would like to write down the steps of analysis, how to collect information, and the problems that occurred at that time. I hope that you will not only gain knowledge of the technique, but will also be a resource for learning the steps of analysis.

Figure 1. Relation of Structures

1st Step — Win32 Thread Information Block

When the program is loaded, that information must be managed. Information about the DLL loaded by the program must also be managed. Where can we find this information?

Windows OS has a management mechanism called Thread Information Block. Wikipedia had more information about Thread Information Block. It is important to collect such publicly available information for analysis.

Win32 Thread Information Block
https://en.wikipedia.org/wiki/Win32_Thread_Information_Block

As written on Wikipedia, “Thread Information Block” stores information about the currently running threads. I couldn’t find this information from Microsoft’s official MSDN homepage. However, this information may be provided by many researchers. Researchers have shown that Thread Information Block can be accessed using “FS segment” in Win32 processes.

What information in the FS segment do we need to get the address of the DLL? In this case, we refer to the “Process Environment Block” (PEB) in “FS: [0x30]”. Many malware references “FS: [0x30]” to get the address of the DLL. If you find a reference to “FS: [0x30]” in the code you are analyzing, you should anticipate the DLL addressing process.

In order to understand how to analyze DLL address acquisition process, you must first know “Thread Information Block” and “Process Environment Block”, and then know “FS: [0x30]” as the access method.

Figure 2. Thread Information Block (FS:[0x30])

2nd Step — Analysis of “Process Environment Block”

Once we know the address of the “Process Environment Block”(PEB), analyze that data. There is official Microsoft documentation on PEB.

PEB structure (winternl.h)
https://docs.microsoft.com/en-us/windows/win32/api/winternl/ns-winternl-peb

typedef struct _PEB {
BYTE Reserved1[2];
BYTE BeingDebugged;
BYTE Reserved2[1];
PVOID Reserved3[2];
PPEB_LDR_DATA Ldr;
PRTL_USER_PROCESS_PARAMETERS ProcessParameters;
PVOID Reserved4[3];
PVOID AtlThunkSListPtr;
PVOID Reserved5;
ULONG Reserved6;
PVOID Reserved7;
ULONG Reserved8;
ULONG AtlThunkSListPtr32;
PVOID Reserved9[45];
BYTE Reserved10[96];
PPS_POST_PROCESS_INIT_ROUTINE PostProcessInitRoutine;
BYTE Reserved11[128];
PVOID Reserved12[1];
ULONG SessionId;
} PEB, *PPEB;

Ldr is a pointer to a PEB_LDR_DATA structure, as described in the members section of the documentation. PEB_LDR_DATA structure contains information about the loaded modules for the process. We need to refer to the contents of PEB_LDR_DATA structure to analyze DLL address acquisition process. For the address of Ldr, refer to the value of offset 0x0C from the address of _PEB.

Figure 3. “Ldr” in PEB structure

3nd Step — Analysis of “PEB_LDR_DATA”

There is official Microsoft documentation on PEB_LDR_DATA.

PEB_LDR_DATA structure (winternl.h)
https://docs.microsoft.com/ja-jp/windows/win32/api/winternl/ns-winternl-peb_ldr_data

typedef struct _PEB_LDR_DATA {
BYTE Reserved1[8];
PVOID Reserved2[3];
LIST_ENTRY InMemoryOrderModuleList;
} PEB_LDR_DATA, *PPEB_LDR_DATA;

“PEB_LDR_DATA” has only one address to the list of modules loaded as members. The list contains two addresses for bidirectional reference. The address indicated by “InMemoryOrderModuleList” is a pointer to the list of “LDR_DATA_TABLE_ENTRY” structure. The other parameters are “Reserved”.

However, the malware I analyzed was referencing offset “0x0C”. It looks like it refers to the “Reserved2” address. Also, when I looked up the structure data in the debugger, it seemed that the value was set to “Reserved”. What is this value?

Figure 4. List pointers in PEB_LDR_DATA structure

I referred to other documentation about “PEB_LDR_DATA”. In some documents, “Reserved” was defined with a different value. Please refer to the following web page.

struct PEB_LDR_DATA
https://www.nirsoft.net/kernel_struct/vista/PEB_LDR_DATA.html

Interestingly, the offset 0x0C indicates an “InLoadOrderModuleList”. It seems that it is only in a different order compared to “InMemoryOrderModuleList”. Microsoft may have removed it from the documentation to discontinue anything other than the “InMemoryOrderModuleList” in the future. However, the values were still set, so the malware was able to work properly.

We need to refer to the address in the list of loaded modules to analyze DLL address acquisition process. For the address to the list of loaded modules, refer to the value of offset 0x0C for “InLoadOrderModuleList”, offset 0x14 for “InMemoryOrderModuleList”, and offset 0x1C for “InInitializationOrderModuleList”. These are pointers to a list of “LDR_DATA_TABLE_ENTRY” structure.

4th Step — Analysis of “LDR_DATA_TABLE_ENTRY”

As a final step, analyze “LDR_DATA_TABLE_ENTRY”. There is official Microsoft documentation on “LDR_DATA_TABLE_ENTRY”.

PEB_LDR_DATA structure (winternl.h)
https://docs.microsoft.com/en-us/windows/win32/api/winternl/ns-winternl-peb_ldr_data

typedef struct _LDR_DATA_TABLE_ENTRY {
PVOID Reserved1[2];
LIST_ENTRY InMemoryOrderLinks;
PVOID Reserved2[2];
PVOID DllBase;
PVOID EntryPoint;
PVOID Reserved3;
UNICODE_STRING FullDllName;
BYTE Reserved4[8];
PVOID Reserved5[3];
union {
ULONG CheckSum;
PVOID Reserved6;
};
ULONG TimeDateStamp;
} LDR_DATA_TABLE_ENTRY, *PLDR_DATA_TABLE_ENTRY;

“DllBase” in “LDR_DATA_TABLE_ENTRY” is the address of the DLL. We can find the address of the desired DLL from the chain list of “LDR_DATA_TABLE_ENTRY”. To achieve that, we need to find the target “LDR_DATA_TABLE_ENTRY” by DLL name.”LDR_DATA_TABLE_ENTRY” contains a pointer to the DLL name of the Unicode string. The process can find the DLL by determining if the string is the same as the desired DLL name.

  • If it is the desired DLL, the address of the DLL is stored in “DllBase” of “LDR_DATA_TABLE_ENTRY”.
  • If it is not the desired DLL, refer to the following “LDR_DATA_TABLE_ENTRY” in “InMemoryOrderLinks”.

We will be able to find out the address of the DLL with a simple DLL name determination and a “DllBase” reference.

However, when I was analyzing a malware, I found something strange. The malware was referencing offset 0x30 for “LDR_DATA_TABLE_ENTRY”. It is defined as “Reserved” in Microsoft documentation. I referred to other documentation again.

struct LDR_DATA_TABLE_ENTRY
https://www.nirsoft.net/kernel_struct/vista/LDR_DATA_TABLE_ENTRY.html

“BaseDllName” is defined at offset 0x2C of “LDR_DATA_TABLE_ENTRY”. “BaseDllName” is the “UNICODE_STRING” structure. Therefore, 0x30 indicates “* Buffer” of “BaseDllName”.

Figure 5. LDR_DATA_TABLE_ENTRY structure
Figure 6. FullDllName (0x02562968)
Figure 7. BaseDllName (0x776D9B34)

Referring the actual data, the address of the buffer was set in “BaseDllName” which is “Reserved”. It showed the string of DLL filenames. Therefore, the malware was able to work properly. It’s unclear why the parameters differ from the official Microsoft documentation.

Conclusion

We were able to find out the address of the DLL from the “Thread Information Block”. This is a technique that utilizes the mechanism of the Windows OS. This allows us to know the address of the DLL loaded without the API. However, this is not a substitute for “Load Library” API. Because this technique only knows the address of the loaded DLL. If we want to know the address of an unloaded DLL, we will probably use the “LoadLibrary” API. In most cases, we can expect “ntdll.dll” and “kernel32.dll” to be loaded.

This technique is common in malware. Because it is a well-known technique, I do not analyze it in detail every time. If you know this technique for the first time, you can skip the analysis of one feature. However, when I analyzed it, the parameters often did not match the official Microsoft documentation. This is one of the factors that makes analysis difficult. In this case, we were able to find the answer from some unofficial documents. If the official documentation does not provide the correct information, we will have to refer to the documentation of other researchers or guess and analyze the values.

This article has written techniques and knowledge for finding DLL addresses without APIs, analysis steps, and documentation references. I hope it helps you in your analysis.

--

--

Sachiel

Security Analyst in Japan. GIAC GREM (Gold) #165237