1. Introduction
DLL injection is a common attack technique of malware, and a series of new variants have been developed. Different from previous DLL injection methods, the new DLL injection method prevents the victim process from calling the system API to load the malicious DLL module so that there is no information about the injected DLL in the LDR list of the victim process. Meanwhile, the new DLL injection method may tamper with the properties of the Virtual Address Descriptor (VAD), realizing the covert injection of malicious DLL modules. Driver-level injection is difficult to implement because Windows requires drivers to have trusted signatures, so the main target of the new DLL injection is the user-space process, and the injection method is more complicated and hidden. The representative injection methods include the reflective code inject technology proposed by Stephen et al. [
1], the Process Hollowing technology proposed by Mieleke et al. [
2], and the VAD remapping technology proposed by Palutke et al. [
3]. Moreover, some malware uses similar attack techniques, such as Duqu2.0 (2015), Dyre banking Trojan (2015), Conti ransomware (2020), etc., which cause widespread and serious harm [
4], so the detection of new types of DLL injection is very important.
Existing security software uses static and dynamic detection techniques and focuses on the detection of disk files and the process of injection, but the software has insufficient detection capability for malicious DLLs in memory after an attack occurs. Detection technology based on memory forensics can solve this problem. Memory forensics can find, extract, and analyze volatile evidence from physical memory and page swap files, and focuses more on the traceability of attacks after they occur. However, the existing memory forensics methods only consider the memory management structure in the virtual memory space, and these methods may be ineffective when the malware tampers with the memory management structure. For example, the VAD remapping method proposed by Palutke et al. [
3] can modify the protection attribute and address range of VAD so that the VAD node corresponding to the malicious code page is associated with the benign page. The malfind plugin of Volatility relies on the VAD attribute for detection, so it cannot detect VAD remapping attacks. Srivastava et al. [
5] proposed a method based on the combined detection of the thread call stack and VAD attributes, but the method cannot detect injected code modules that do not trigger execution. Block et al. [
6] proposed a method to detect hidden executable pages based on PTE attributes. Though the method can detect a variety of new injection attacks, it causes too many false-positive pages, making it difficult to accurately locate malicious memory pages.
This paper proposes a code injection covert memory page detection and forensic algorithm named MRCIF (Memory Reverse based Code Injection Forensics). First, the physical memory pages containing DLL features from the memory image are located, and a sub-algorithm for mapping physical memory space and virtual memory space is designed, thus realizing reverse reconstruction of the physical page subset corresponding to the DLL code module. Then, in the virtual memory space, the LDR linked list structure of the process is reversely reconstructed, and a reverse reconstruction algorithm of the DLL virtual page subset is developed to reconstruct its virtual space. Finally, a DLL injection covert page detection sub-algorithm is designed based on the physical memory page subset and virtual space page subset. The experimental results indicate that MRCIF achieves a higher accuracy than that of the traditional DLL module injection detection method, and only MRCIF can accurately detect the VAD remapping attack. In practice, MRCIF helps to quickly determine the direction of investigation for forensic analysis because of its higher accuracy.
The rest of this paper is organized as follows.
Section 2 introduces the related work;
Section 3 proposes a code injection forensics detection framework and its sub-algorithms based on the reverse analysis of the memory structure;
Section 4 presents the experiments and result discussion and analysis; finally,
Section 5 concludes this paper.
2. Related Works
The practice of memory forensics technology started in 2005, and was launched by DFRWS for the Windows system memory forensics analysis challenge [
7]. Afterward, memory forensics technology began to develop rapidly. Schuster proposed a pool tag search method to extract processes and threads from pool memory [
8]; Dolan-Gavitt developed a method to reconstruct the VAD tree [
9]; Kornblum analyzed the PTE structure and proposed a virtual address to physical address translation method [
10]. These studies lay the foundation for Windows memory forensics.
In terms of the research on memory reconstruction, Guo et al. proposed a method to reconstruct WinXP system memory based on the KPCR structure [
11]; Zhang et al. improved the KPCR method and applied it to the Win7 system [
12]. However, with the update of Windows versions, the methods based on pool tag scanning and KPCR scanning are difficult to generalize. To address this issue, Cohen et al. proposed a general Windows memory reconstruction method based on PDB [
13], and the memory forensics framework Rekall implemented this method [
14].
After being able to reconstruct the basic Windows structure, researchers continue to expand data sources for memory forensics. Cohen proposed a method to extract network connections from the Windows heap [
15]; Li et al. proposed a memory fragment file carving algorithm based on the reverse of the structure chain [
16]; Zhai et al. proposed a stack trace method that does not rely on process debug symbols [
17]. These examples of research on Windows memory structure reconstruction, including heap, stack, and other objects, analyze the relationship between Windows memory management objects, which is the foundation of detecting malware in memory.
Currently, the injection page detection method based on memory forensics mainly focuses on VAD objects (e.g., the malfind plugin of Volatility and Rekall) to detect the label, private state, and protection attribute of VAD. However, the detection conditions of this method are too rough, and it cannot detect malicious code that modifies the VAD protection attributes. Pshoul proposed a method to detect thread injection based on the call stack and developed the malthfind plugin [
18]. Srivastava et al. proposed a similar injection detection method based on call stack analysis, which can detect the injection code [
5] that modifies the VAD protection attribute, but the hidden injection page that has not been executed cannot be detected based on the stack call.
Considering the limitations of existing VAD and call stack-based detection methods, researchers turned to exploit the characteristics of physical memory pages. Cohen presented a method to match YARA signatures in logically discontinuous physical memory [
19], but this method requires input target signature of malware; Block et al. [
6] proposed to use the executable attribute of PTE as a feature of executable physical memory pages, but this method will report all modified memory maps, including many benign memory pages, so it cannot accurately locate malicious memory pages.
3. MRCIF Algorithm
In the new DLL injection method, the malware prevents the victim process from calling the system API to load the malicious DLL module so that there is no information about the injected DLL in the load module list (LDR linked list) of the victim process. Our code injection forensic aims to detect covert DLL code modules for new injection attacks from physical memory image files.
The principle of the code injection forensic detection method named MRCIF is as follows. First, the physical memory image file is preprocessed, and the original memory data are read and decomposed into physical memory pages. From a physical point of view, the corresponding physical memory pages are located in the memory image based on the DLL characteristics, and then the mapping between the physical memory space and the virtual memory space is performed. Then, a reverse search for the virtual memory page corresponding to the physical page of the DLL code module is performed. Subsequently, from the perspective of virtual memory space, the LDR linked list structure of the process is reversely reconstructed, and the virtual memory page of the code module is obtained.
Finally, the virtual memory page of the code module reversely searched from the physical page and that of the code module obtained from the LDR are compared, and the hidden virtual memory page of the injected code module is found, as shown in
Figure 1.
A formal description of the method is provided below. Denote a virtual memory page as . A virtual memory page is uniquely determined by the process and the virtual address. Denote the virtual space of a process as . Denote the subset belonging to the user space as and the set of virtual memory pages of the code module in the LDR linked list as . Then, the set of virtual memory pages of all processes is . Denote a physical memory page as . A physical memory page is uniquely determined by its physical address, and all physical memory pages constitute the set of physical memory spaces . Denote the subset of physical memory pages where the DLL code module is located as . Denote the virtual address to physical address translation as a mapping . Further, denote the set of memory pages where the DLL code module is located in the virtual memory as , which is the preimage set of .
Generally, the DLL code module in user space saves information in the LDR linked list of the corresponding process, i.e., the set should satisfy . When there is covert DLL injection, denote the set of injected DLL module pages as .
According to the expression to detect the covertly injected page set , the following detection method is proposed:
- (1)
Preprocess the memory image file to obtain the physical memory page set .
- (2)
Locate in the physical memory the set of all physical memory pages that contain the DLL header feature .
- (3)
Reverse the virtual memory space and establish the mapping from the virtual memory space to the physical memory space .
- (4)
According to the page map t, traverse each process to obtain and and find the preimage set . Finally, obtain according to the expression of the covertly injected page set .
3.1. File Preprocessing
The purpose of file preprocessing is to read the memory data of the original system from the memory image file. Different memory image formats require different preprocessing approaches. For example, Microsoft’s dmp format crash dump file adds metadata to the header, and the rest is the original memory image. The VMware memory snapshot captured by the virtual machine contains two files: the .vmem file is the raw memory data, and the .vmsn file contains the metadata. The memory image obtained by the EnCase forensics tool is in EWF format. The metadata and the compressed original memory data are in the same file, which needs a special tool for parsing.
After parsing the memory image file, the original physical memory data are obtained. The operating system generally manages memory pages at the size of 4 KB. Thus, the original memory data can be processed as a set of 4 KB memory pages, and the set of physical memory pages is formed.
3.2. Physical Locator Sub-Algorithm
The physical locator sub-algorithm is responsible for locating the load address of the code module in physical memory. DLL files are organized in the general format of PE files, whether normally loaded DLLs or covertly injected DLLs. These files need to be applied to memory pages in the virtual memory and loaded in blocks according to the PE file format. Meanwhile, these virtual memory pages must be mapped to physical memory before the DLL module can be executed.
The DLL header code module is less than 4 KB and will be completely loaded at the beginning of a page in memory, and the characteristic string and relative offset remain unchanged. Thus, the PE header is also unchanged in the physical memory space (as shown in
Figure 2).
Therefore, a direct search for the PE header character can be performed in the physical memory
to locate the physical memory address where all DLLs are located. The process is shown in detail in Algorithm 1.
Algorithm 1: Code Module Physical Locator Algorithm |
Input: Physical memory page set Output: DLL code module physical memory page set Init: for each do: if is PEheader () = True then end
Function is PEheader (): if [0,1] = 4d5ah then e_lfanew [0x3c,...,0x3f] if [e_lfanew, ..., e_lfanew + 4] = 0x50450000 then return True else return False |
3.3. Virtual Space Reverse Reconstruction Sub-Algorithm
The user-space process and its load module management structure, i.e., LDR linked list, exist in the virtual memory space. Modern operating systems generally use the Address Space Layout Randomization (ASLR) mechanism, so the physical memory pages are logically discontinuous. To access the virtual address, the system lookups at the page table of the process to find the physical address to access the data. The lookup mechanism of the page table is proposed by Russinovich et al. [
20]. In reverse analysis and reconstruction of the image, it is necessary to find the EPROCESS structure of the process, read the physical-address DTB of the page table (as shown in
Figure 3), and then construct the mapping
from virtual pages to physical pages from the memory page table.
According to Cohen’s work [
13], the structure of each version of the Windows kernel is almost the same. There are only 10 structural layouts of EPROCESS from WinXP to Win8.1. The structure of EPROCESS is easy to exhaust, so a specific process name can be searched for to locate EPROCESS. Taking the system process “smss.exe” as an example, the search steps are as follows:
(1) The ImageFileName field in EPROCESS indicates the process name, and it has a length of at least 15 bytes(as shown in
Figure 3). This work uses ‘\0’ to fill the end of the process name string “smss.exe” to 15 bytes to obtain the hexadecimal character string “73 6d 73 73 2e 65 78 65 00 00 00 00 00 00 00”. Then, this string is searched in the physical memory page set
.
(2) If the operating system version is known, the EPROCESS structure is uniquely determined; otherwise, all possible EPROCESS structures are constructed for each search result.
Figure 4 shows an EPROCESS structure constructed for a process name search result when the operating system is assumed to be 32-bit Win7.
(3) For each built EPROCESS structure, the system version assumption is verified via the _KUSER_SHARED_DATA structure. The virtual address of _KUSER_SHARED_DATA is 7ffe0000 in each system version, and must correspond to a physical memory page. The NtMajorVersion and NtMinorVersion fields represent the major and minor versions of the operating system, and the two values should correspond to the assumed operating system version.
Assuming that the operating system is 32-bit Win7, an EPROCESS structure is constructed for the search result of a process name (as shown in
Figure 4). Then, the DTB is read, and the virtual address 0x7ffe0000 of _KUSER_SHARED_DATA is converted to the physical address 0x1e2010 according to the page table conversion method of the 32-bit system (as shown in
Figure 5). Since the values of the NtMajorVersion and NtMinorVersion fields are 6 and 1, the system kernel version is 6.1, which is consistent with 32-bit Win7. Thus, it can be determined that the built EPROCESS structure is correct. Then, the mapping
between all virtual pages in the smss process space to physical pages can be constructed.
Next, the map
is established through all processes. The ActiveProcessLinks of the EPROCESS structure is a doubly linked list containing two virtual address pointers that point to the ActiveProcessLinks addresses of two adjacent EPROCESSs. As shown in
Figure 6, 0x3f2d0568 is the starting physical address of the smss process. The virtual addresses of two adjacent EPROCESS structures can be obtained by reading the virtual addresses of the two linked list items before and after the ActiveProcessLinks linked list and subtracting the offset of the relative starting position. The physical address corresponding to the virtual address 0x86049d40 obtained from the mapping
is 0x3f249d40. Based on this, a new EPROCESS structure is built, and it is known that the process is “csrss” from its ImageFileName field. Meanwhile, the DTB of the process is 0x3f2d2060, according to which the address mapping
can be established. The above steps are executed iteratively until the EPROCESS of all processes is traversed; finally, the mapping
from virtual pages to physical pages of all processes is obtained. Taking InInitializationOrderModuleList as an example, the offset of the linked list in the LDR is 0xc, and the virtual addresses of two adjacent linked list entries are 0x3b1790 and 0x3b1810, respectively.
Then, build the _LDR_DATA_TABLE_ENTRY structure at virtual address 0x3b1790, and the virtual address of the code module is read to be 0x484f0000,
. The beginning of the _LDR_DATA_TABLE_ENTRY structure is the virtual address of two adjacent linked list items, so the InInitializationOrderModuleList linked list can be traversed. The structures of the other two linked lists in the LDR have the same structure, and the address of the load module can be traversed and read in the same way (as shown in
Figure 7). Finally,
is obtained, and the LDR linked list is reconstructed for each process to obtain
.
3.4. Code Injection Covert Page Identification Sub-Algorithm
Hidden page identification aims to detect the virtual page set of the hidden code module in the user space and find the set containing the memory pages where the DLL code module is located in the virtual memory.
The user-space address range of a process in a 32-bit system is 0~0x7ffffffff, and in a 64-bit system, it is 0~0x7ffffffffff. According to the address range, the user space page set of the smss process is obtained as . The user space of other processes is obtained in a similar way. The set of user-space memory pages of all processes is represented as .
To reduce physical memory usage by the operating system, when the same module is used by multiple processes, multiple virtual memory pages are mapped to the same physical memory page . So, there is no inverse mapping for . Therefore, to find the preimage of the physical memory page , we can only traverse all the virtual memory pages and perform the mapping to convert the pages. In this way, the preimage set of , can be obtained.
The set
contains a part of the driver code module because the header characteristics of the driver module file are the same as that of the DLL file. However, the driver modules are loaded in the kernel space of virtual memory, and they do not appear in the LDR linked list. These pages where these driver modules are located need to be excluded from the detection, so the set of user-space code module virtual memory pages
is obtained. Finally, whether each virtual page appears in the set
is judged, and if a virtual page is not in
, the virtual page is a covertly injected page. The whole covert page detection process is shown in Algorithm 2.
Algorithm 2: Covert page detection sub-algorithm |
Input: Virtual memory page set , Code module page set in LDR linked list, Code module physical memory page set Output: Covert code module virtual page set Init: for each do: for each do: if and and then: end end
|
4. Experiment and Discussion
In this section, we first introduce the samples and experimental setup, and then we conducted a series of experiments to compare with the current commonly used methods.
4.1. Experiment Samples and Setup
The MRCIF algorithm has been implemented using Python 3.8 and some functions of volatility have been called, regardless of the operating system. The MRCIF algorithm was evaluated on 32-bit WindowXP, 32-bit Windows 7, and 64-bit Windows10 snapshots, using a machine with Intel i7-7700K and 16 GB RAM.
The experiment samples are shown in
Table 1, including the source of the samples and the version and size of the system memory image.
4.2. Experiment Results and Discussion
For the above test samples, the method proposed in this paper is compared with the commonly used detection methods, and the results are presented in
Table 2,
Table 3 and
Table 4. The process accuracy is calculated as the number of correctly reported injection processes divided by the total number of reported processes; the page accuracy rate is calculated as the number of correctly reported injected pages divided by the total number of reported pages. The target of malthfind is the thread, so this work uses the page where the calling code module is located as the hidden page of its report.
Note that in Sample 2 and 3, Reflective DLL will first read the payload DLL file as data directly into the memory during injection, perform this operation again on the memory space, and remap the payload code module in the executable code mode. So, there are two injection modules: one is the file data page, and the other is the executable memory page. Since injected data pages are usually freed, only executable memory pages are counted as detection target pages in the accuracy rate.
As shown in
Table 2 and
Table 3, the malfind method only detects the private attributes and protection attributes of VAD nodes, but the detection conditions are rough, which results in low accuracy.
The malthfind method discovers unknown calling modules by reconstructing the call stack. It has high accuracy in detecting malicious injected pages, such as samples 2, 5, 6, and 7, but it cannot detect potential injected pages that are not executed. As for samples 3 and 4, some of the injected pages are not executed, and there is no stack, so the malthfind method cannot detect these pages. In samples 3 and 6, malthfind fails to rebuild the stack on Windows10 because of the lack of the necessary objects in Volatility profile.
Ptemalfind only achieved slightly better results in the experiment of Windows10, such as Sample 3 and 6. It cannot run on samples 5, 6, and 7 because the PTE structure of the WinXP lacks the properties required for Ptemalfind detection. Meanwhile, this method can detect almost all the memory pages injected by the method, but its accuracy is extremely low, which makes it difficult to apply in practice.
In comparison, MRCIF can completely detect the hidden injected pages for samples 1~8 without false positives. Especially in sample 4, only MRCIF can detect the injected code module with high accuracy. The memory structures used in MRCIF, such as EPROCESS, LDR linked list, and PTE, are all necessary structures provided in Microsoft PDB files. Therefore, compared with malthfind and Ptemalfind, MRCIF is more widely applicable in different versions of Windows with higher accuracy. For sample 9, Coreflood’s injection code module erases the PE header, while MRCIF is characterized by the PE header, so the hidden injection page cannot be detected at all.
In terms of time consumption (as shown in
Table 4), MRCIF consumes the most time in all the methods. This is due to the translation of all virtual addresses of all processes. Because the detection process is not real-time, the time consumption should be worthwhile. Moreover, the translation of all virtual addresses can be executed in parallel by the process to improve the efficiency of hardware usage, but we have not realized it at present, which is our future work.
5. Conclusions
This paper proposes MRCIF, a code injection covert memory page detection and forensic detection forensic algorithm based on memory structure reverse analysis. First, the physical memory pages containing DLL features in the memory image are located, and a sub-algorithm is designed for mapping physical memory space and virtual memory space, thus realizing reverse reconstruction of the physical page subset corresponding to the DLL code module. Then, in the virtual memory space, the LDR linked list structure of the process is reversely reconstructed, and a reverse reconstruction algorithm of the DLL virtual page subset is designed to reconstruct its virtual space. Finally, a DLL injection covert page detection sub-algorithm is developed based on the physical memory page subset and virtual space page subset. The method proposed in this paper does not use the VAD structure during detection and is therefore immune to VAD attribute tampering attacks. The experimental results indicate that MRCIF achieves an accuracy of 88.89%, which is much higher than that of the traditional DLL module injection detection method, and only MRCIF can accurately detect the VAD remapping attack. In practice, the proposed method is a preferred method for detecting hidden memory pages because of its higher accuracy, and it helps to quickly determine the direction of investigation for forensic analysis. Further research will be conducted on improving the efficiency of MRCIF and the characteristics of executable code in physical memory to deal with PE header erasure.