The engineers next door cried greedily for my reverse engineering IDA and said they would rub my back and pinch my feet

Posted by StewardManscat on Thu, 20 Jan 2022 07:20:46 +0100


primary coverage

According to the monitoring requirements of process behavior, many security software used Hook technology to intercept key system calls to intercept malware process creation. However, under x64 architecture, the system kernel has done a lot of security detection measures, especially technologies like KDP, which makes the Hook method no longer effective. Therefore, OS has launched a behavior monitoring scheme based on callback implementation. In this paper, the implementation principle of this technology is analyzed inversely with the help of IDA, and the key data structure and call chain are given. The correctness of the data structure and call chain is verified by dual machine kernel debugging.

The contents involved are as follows:

1. Kernel object and kernel object management;

2. Process callback;

3. Kernel debugging;

4. Windbg double click debugging;

introduction

In recent years, a variety of new variants of malware emerge in endlessly, with a variety of attack methods and means, resulting in huge economic losses. As the first link of defense, it is able to identify the actions created by malicious processes, and process creation monitoring technology is a technology that allows security software to have the opportunity to intercept this action. The security software judges whether to allow the process to be created according to the matching algorithm, so as to protect the security of user data.

[click here to get the reverse document]

Based on reverse engineering and kernel debugging technology, this paper analyzes the specific implementation of this technology and the additional data detection mechanism of the system. With the help of the reverse tool IDA, this paper analyzes the internal actions and specific implementation of the key API of the system, the relevant data structure, and obtains the call source actually triggered by the technology and the whole call chain. Build a dual machine debugging environment with VMWare, use Windbg to dynamically debug the system kernel, view the key data involved in the system, and make a comparative analysis with the data given by PCHunter to verify the correctness of the analysis conclusion. In addition, through the breakpoints of key functions in the call chain and stack backtracking technology, the whole call chain and trigger time are dynamically observed. The key data structure and the detection and verification algorithm of the system can be used to detect the table items maliciously constructed by viruses, Trojans and other software. [Reverse Engineering]

1 process callback principle analysis

1.1 reverse analysis of installation and unloading

According to the instructions on Microsoft's official technical document MSDN, install a process creation and exit notification callback routine through PsSetCreateProcessNotifyRoutine, PsSetCreateProcessNotifyRoutineEx and PsSetCreateProcessNotifyRoutineEx2. When a process is created or exited, the system will call back the function specified in the parameter. Taking PsSetCreateProcessNotifyRoutine as an example, reverse analyze the specific implementation of the API based on IDA. As shown in Figure 1, it can be seen from the figure that the API simply calls another function and itself is only a stub. The specific implementation is in PspSetCreateProcessNotifyRoutine. The key implementation of the installation callback routine of this function is shown in the figure.


Call ExAllocateCallBack to create a callback object, and pass pNotifyRoutine and bremove as parameters to initialize the callback object. The code is shown in the figure; pNotifyRoutine is the function routine that needs to be called back. bRemovel here is false, indicating that the callback routine is currently installed.

Then call ExCompareExchangeCallBack to add the initialized CallBack object to the global array maintained by PspCreateProcessNotifyRoutine. It is worth noting that there is a special operation on the CallBack routine when installing the CallBack routine in ExCompareExchangeCallBack, as shown in the figure.

Or operation is performed with 0x0F, which is equivalent to setting all the lower 4 bits to 1; If the ExCompareExchangeCallBack fails, the next cycle continues. According to the code in line 66 in Figure 2, the maximum number of cycles is 0x40. If it fails all the time, you can call ExFreePoolWithTag to free up the memory occupied by pCallBack and return 0xC000000D error code. [receive reverse document]

Then, according to the value of v3, judge which callback is installed in the above three API s to update the corresponding global variables.
PspCreateProcessNotifyRoutineExCount and PspCreateProcessNotifyRoutineCount record the number of callback routines currently installed through PsSetCreateProcessNotifyRoutineEx and pssetcreateprocessnotifyroute respectively.

PspNotifyEnableMask is used to indicate whether a callback routine is installed in the current array. This value is used to judge whether the array is empty and speed up the execution efficiency of the program when the system traverses the callback array and executes the callback routine.

In addition to installing callback routines, these three API s can also uninstall the specified callback routines. Take PsSetCreateProcessNotifyRoutine as an example to analyze the key parts of its implementation, as shown in the figure.

Traverse the PspCreateProcessNotifyRoutine array through a while loop, and call ExReferenceCallBackBlock to retrieve each item in the array. Some inspection actions will be performed inside the API, and special processing is also done for the returned data, as shown in the figure. In Figure 6, * pCallBackObj is the function address of the callback routine in the callback object. Check some data by judging whether the lower 4 bits are 1, as shown in line 17.

The system also plays a protective role in this process, preventing malicious construction of data to fill in the table and hijacking the normal system call process. In addition, when the code at line 33 in the figure returns the callback routine to the parent call, it also clears all the lower 4 bits of the callback routine. Otherwise, the returned address is wrong, and the call immediately triggers a CPU exception.

After ExReferenceCallBackBlock has successfully returned, it calls ExGetCallBackBlockRoutine to retrieve callback routines from the callback object, and determines whether the item is currently specified to be unloaded. If it is, it calls the ExDereferenceCallBackBlock decrement reference count, and then uses ExFreePoolWithTag to release the memory occupied by Callback. The value of PspCreateProcessNotifyRoutineExCount or PspCreateProcessNotifyRoutineCount will also be updated during. According to the source code, there are 64 items in the array, that is, only 64 callback routines can be installed. If the 64 items of the array are still not found after traversing, the 0xC000007A error code is returned.

1.2 OS execution callback routine analysis

After the callback routine is installed, if a new process is created or exited, the kernel will traverse the array to execute each callback routine installed therein. Through the cross reference function of IDA, you can analyze the cross references to PspCreateProcessNotifyRoutine in other parts of the kernel, as shown in the figure

A total of 5 places involve this variable. Pspcallprocessnotifyroutes is a function that directly calls the callback routine. The key part of the function is shown in the figure.


Through the while loop, traverse all callback routines installed in the PspCreateProcessNotifyRoutine array and execute them in turn.

The operation of pspnotifyenablemask & 2 is to judge whether callback routines are installed in the current array and speed up the execution efficiency of the program. The value of this variable is set when installing callback routines in PsSetCreateProcessNotifyRoutine. The if branch of bremove & 2 is used to determine whether the current callback routine is installed through PsSetCreateProcessNotifyRoutine or PsSetCreateProcessNotifyRoutineEx. Because the prototypes of callback routines installed by the two API s are different, the parameters passed in during actual call are also different. The callback routines of the two are:

void PcreateProcessNotifyRoutine(HANDLE ParentId,HANDLE ProcessId,BOOLEAN Create)and void PcreateProcessNotifyRoutineEx(PEPROCESS Process,HANDLE ProcessId,PPS_CREATE_NOTIFY_INFO CreateInfo). 

In addition, the pseudo-C code routinefun ((unsigned _int64) routinefun) given by IDA in Figure 8 is obviously wrong, because the number of parameters of the callback routine is 3, while the parameters analyzed by Ida are only 1, which is obviously a problem. Directly look at the disassembly code, as shown in the figure

According to the calling convention under x64, the first four parameters of the function are passed through rcx, rdx, r8 and r9 registers. The figure shows the first three parameters of the callback routine_ guard_dispatch_icall will call directly from the value of rax, and the value of rax is the function address of the callback routine returned by the ExGetCallBackBlockRoutine call.

The second function in the above figure involving the PspCreateProcessNotifyRoutine array is the pspenumeratiecallback function, which is an internal function of the system and has not been exported. Its specific implementation is shown in the figure.

This function judges which array you want to enumerate according to dwEnumType. From the code analysis, the system kernel maintains three callback related arrays: image loading callback array, process creating exit callback array and thread creating exit array. Similar to the previous function verification, it also detects whether the index exceeds 0x40. If it exceeds 0x40, it returns 0 to indicate failure.

1.3 call chain analysis of trigger call

The previous section analyzes the direct call to the superior function of the callback routine. This section analyzes the whole call chain, mainly the call source and the key functions involved in the call process. The cross reference diagram given by IDA is shown in the figure.

There are many function calls involved, and many irrelevant ones are also included, which is not convenient for analysis. The key API s in the call chain after manual analysis and sorting are shown in the figure.

The part above the dotted line is the user state program, and the part below the dotted line is the kernel state program. All the APIs marked in red are standard exported APIs.

According to figure 12, when the user state process calls RtlCreateUserProcess, rtlcreateuserprocessex or RtlExitUserProcess, the kernel will traverse the PspCreateProcessNotifyRoutine array, execute the callback routine in turn, and notify the driver to do the corresponding processing.

After the driver takes over, it can do security verification, analyze the parent process of the process or further analyze the process chain. In addition, it can also do defense means such as feature code matching, PE fingerprint identification and import table detection for the child process to be pulled up.

This method does not need to go to any API of Hook, nor does it need to do repetitive and cumbersome work such as feature code positioning. It is completely based on the callback mechanism provided by the system, and can be seamlessly connected in Windows system. And there is no competition among various security manufacturers, which greatly reduces the risk of system blue screen.

In Figure 12, NtCreateUserProcess calls ppinsertthread because the main thread of the process will be created inside the API that created the process. It is more reasonable to unify the work of traversing the callback routine array into PspInsertThread and call the underlying pspcallprocessnotifyroutes.

2 experiment

2.1 observe the callback routines installed in the system

The experimental environment is shown in Table 1. Dual machine debugging is carried out with the help of VMWare.

Guest OS Build 10.0.16299.125
Host OS Build 10.0.17134.885
Windbg Version 10.0.17134.1
VMWare 14.1.1 build-7528167
PCHunter V1.56

Observe the PspCreateProcessNotifyRoutine array in Windbg. There are 14 valid data items in total, as shown below;

1: kd> dd PspCreateProcessNotifyRoutineCount  l1
fffff802`151f4e78  00000009

1: kd> dd PspCreateProcessNotifyRoutineExCount l1
fffff802`151f4e7c  00000005

1: kd> dq PspCreateProcessNotifyRoutine l40
fffff802`14da2a80  ffffcc8b`d884b9bf  ffffcc8b`d8d9c96f
fffff802`14da2a90  ffffcc8b`d939975f  ffffcc8b`da00044f
fffff802`14da2aa0  ffffcc8b`d9bd382f  ffffcc8b`da41e8df
fffff802`14da2ab0  ffffcc8b`da53815f  ffffcc8b`da5ca8bf
fffff802`14da2ac0  ffffcc8b`dac5178f  ffffcc8b`dbef624f
fffff802`14da2ad0  ffffcc8b`dce333af  ffffcc8b`dcec67df
fffff802`14da2ae0  ffffcc8b`dc735def  ffffcc8b`dcabd32f

Disassemble the first item and find its corresponding callback routine, as follows:

1: kd> dq ffffcc8b`d884b9b0 l3
ffffcc8b`d884b9b0  00000000`00000020 fffff802`13fd6268
ffffcc8b`d884b9c0  00000000`00000000

It can be seen that the starting address of the installed callback routine is fffff802`13fd6268, and you can also know that Remove is 0, that is, this is already installed. Find the driver module corresponding to the callback routine, as follows:

1: kd> u fffff802`13fd6268
360qpesv64+0x26268:
fffff802`13fd6268  mov  qword ptr [rsp+08h],rbx
fffff802`13fd626d  mov  qword ptr [rsp+10h],rbp
fffff802`13fd6272  mov  qword ptr [rsp+18h],rsi
fffff802`13fd6277  push rdi

1: kd> lmvm 360qpesv64
start              end                 module name
fffff802`13fb0000 fffff802`14002000 360qpesv64
Loaded symbol image file: 360qpesv64.sys
Image path: 360qpesv64.sys
Image name: 360qpesv64.sys
Timestamp:  Wed May 27 20:13:22 2020 (5ECF2C52)
CheckSum:   00054A2A
ImageSize:  00052000

It can be seen that the callback routine is officially provided by 360. With the help of PCHunter, the data given is shown in the figure


2.2 dynamic debugging callback routine
Taking item 14 of the table item as an example, the contents are as follows,

1: kd> dq ffffcc8b`dcabd320 l3
ffffcc8b`dcabd320  00000000`00000020 fffff802`13d795b4
ffffcc8b`dcabd330  00000000`00000006

1: kd> bp fffff802`13d795b4
1: kd> g

When the breakpoint hits, view the related information of the parent process, as shown below,

Breakpoint 0 hit
fffff802`13d795b4 48895c2408      mov     qword ptr [rsp+8],rbx
1: kd> dt _EPROCESS @$proc -yn ImageFileName
nt!_EPROCESS
  +0x450 ImageFileName : [15]  "svchost.exe"

It can be seen that it is svchost Exe, the parent process, creates or destroys a child process. More specific information is analyzed as follows; View the current context;

1: kd> r
rax=fffff80213d795b4 rbx=ffffcb8050526c80 rcx=ffffcc8bdd67e080
rdx=0000000000001f28 rsi=000000000000000d rdi=ffffcc8bdd67e080
rip=fffff80213d795b4 rsp=ffffcb8050526c38 rbp=ffffcb8050526ca9
r8=ffffcb8050526c80  r9=ffffcc8bdc735de0 r10=ffff9401cdcc2760
r11=0000000000000000 r12=0000000000000001 r13=0000000000000000
r14=ffffcc8bdcabd320 r15=fffff80214da2ae8
iopl=0         nv up ei pl zr na po nc
cs=0010  ss=0018  ds=002b  es=002b  fs=0053  gs=002b             efl=00000246

according to x64 According to the calling convention of, rcx Stored in the register is EPROCESS Object pointer, which stores the relevant information of the sub process to be created. The key information that can be obtained as identity recognition or security detection is as follows:
1: kd> dt _EPROCESS ffffcc8bdd67e080 -yn ImageFile
ntdll!_EPROCESS
   +0x448 ImageFilePointer : 0xffffcc8b`dc97c5c0 _FILE_OBJECT
   +0x450 ImageFileName : [15]  "UpdateAssistan"

1: kd> dt 0xffffcc8b`dc97c5c0 _FILE_OBJECT -yn FileName
ntdll!_FILE_OBJECT
   +0x058 FileName : _UNICODE_STRING "\Windows\UpdateAssistant\UpdateAssistant.exe"

1: kd> .process /p ffffcc8bdd67e080; !peb 186ef07000
Implicit process is now ffffcc8b`dd67e080
.cache forcedecodeuser done
PEB at 000000186ef07000
    CurrentDirectory:  'C:\Windows\system32\'
    WindowTitle:  'C:\Windows\UpdateAssistant\UpdateAssistant.exe'
    ImageFile:    'C:\Windows\UpdateAssistant\UpdateAssistant.exe'
CommandLine:  'C:\Windows\UpdateAssistant\UpdateAssistant.exe /ClientID Win10Upgrade:VNL:NHV19:{} /CalendarRun'

You can obtain the EXE path of the process, the command line parameters at the time of creation, the PID of the parent process and other information, which are sufficient for the detection of security software.
The complete call stack of the parent process is as follows:,

1: kd> k
 # Child-SP          RetAddr           Call Site
00 ffffcb80`50526c38 fffff802`14ef4ae5 0xfffff802`13d795b4
01 ffffcb80`50526c40 fffff802`14ef752c nt!PspCallProcessNotifyRoutines+0x249
02 ffffcb80`50526d10 fffff802`14f2797b nt!PspInsertThread+0x5a4
03 ffffcb80`50526dd0 fffff802`14b79553 nt!NtCreateUserProcess+0x9c7
04 ffffcb80`50527a10 00007ffe`547d1654 nt!KiSystemServiceCopyEnd+0x13
05 0000002f`4b67d258 00007ffe`50b406df ntdll!NtCreateUserProcess+0x14
06 0000002f`4b67d260 00007ffe`50b3d013 KERNELBASE!CreateProcessInternalW+0x1b3f
07 0000002f`4b67dec0 00007ffe`5216ee0f KERNELBASE!CreateProcessAsUserW+0x63
08 0000002f`4b67df30 00007ffe`4ce0a136 KERNEL32!CreateProcessAsUserWStub+0x5f
09 0000002f`4b67dfa0 00007ffe`4ce0bdd9 UBPM!UbpmpLaunchAction+0xb36
0a 0000002f`4b67e280 00007ffe`4ce08ee0 UBPM!UbpmLaunchTaskExe+0x279
0b 0000002f`4b67e490 00007ffe`4ce10a86 UBPM!UbpmpLaunchOneTask+0x6c0
0c 0000002f`4b67e8f0 00007ffe`4ce0b8bc UBPM!UbpmpHandleGroupSid+0x236
0d 0000002f`4b67ea10 00007ffe`4ce0b78b UBPM!UbpmpLaunchExeAction+0xec
0e 0000002f`4b67eaf0 00007ffe`4ce0b5a3 UBPM!UbpmpTakeAction+0xeb
0f 0000002f`4b67eb50 00007ffe`4ce0b193 UBPM!UbpmpPerformTriggerActions+0x293
10 0000002f`4b67eca0 00007ffe`4ce1316c UBPM!UbpmpHandleTriggerArrived+0x563
11 0000002f`4b67ef50 00007ffe`508c32d0 UBPM!UbpmpRepetitionArrived+0x1c
12 0000002f`4b67ef90 00007ffe`508c3033 EventAggregation!EaiSignalAggregateEvent+0x16c
13 0000002f`4b67f060 00007ffe`508c27aa EventAggregation!EaiSignalCallback+0xe7
14 0000002f`4b67f140 00007ffe`508c253e EventAggregation!EaiProcessNotification+0x1aa
15 0000002f`4b67f270 00007ffe`508caef8 EventAggregation!WnfEventCallback+0x506
16 0000002f`4b67f3a0 00007ffe`5476769f EventAggregation!AggregateEventWnfCallback+0x38
17 0000002f`4b67f3f0 00007ffe`54767a51 ntdll!RtlpWnfWalkUserSubscriptionList+0x29b
18 0000002f`4b67f4e0 00007ffe`5476b510 ntdll!RtlpWnfProcessCurrentDescriptor+0x105
19 0000002f`4b67f560 00007ffe`54766b59 ntdll!RtlpWnfNotificationThread+0x80
1a 0000002f`4b67f5c0 00007ffe`54764b70 ntdll!TppExecuteWaitCallback+0xe1
1b 0000002f`4b67f600 00007ffe`52171fe4 ntdll!TppWorkerThread+0x8d0
1c 0000002f`4b67f990 00007ffe`5479ef91 KERNEL32!BaseThreadInitThunk+0x14
1d 0000002f`4b67f9c0 00000000`00000000 ntdll!RtlUserThreadStart+0x21

Since the first four parameters are passed through registers, they cannot be directly traced back to the parameters through the stack, but can be obtained by manual analysis. Analyze ntdll! The assembly code at the return address of the calling parent function of ntcreateuserprocess is as follows:

1: kd> ub 00007ffe`50b406df
KERNELBASE!CreateProcessInternalW+0x1b11:
00007ffe`50b406b1 488b842440040000 mov     rax,qword ptr [rsp+440h]
00007ffe`50b406b9 4889442420      mov     qword ptr [rsp+20h],rax
00007ffe`50b406be b800000002      mov     eax,2000000h
00007ffe`50b406c3 448bc8          mov     r9d,eax
00007ffe`50b406c6 448bc0          mov     r8d,eax
00007ffe`50b406c9 488d942448010000 lea     rdx,[rsp+148h]
00007ffe`50b406d1 488d8c24e0000000 lea     rcx,[rsp+0E0h]
00007ffe`50b406d9 ff1521901600    call    qword ptr [KERNELBASE!_imp_NtCreateUserProcess (00007ffe`50ca9700)]

You know, NtCreateUserProcess The first parameter and the second parameter rsp+0xE0 and rsp+0x148 Division; Check the data here as follows:
1: kd> dpu 0000002f`4b67d260+E0 0000002f`4b67d260+148 
0000002f`4b67d340  00000000`00000000
0000002f`4b67d348  00000000`00000004
0000002f`4b67d350  00000100`00000000
0000002f`4b67d358  00000000`00000020
0000002f`4b67d360  000001f2`d9b87cc0 "C:\Windows\UpdateAssistant\UpdateAssistant.exe"
0000002f`4b67d368  00000000`00000000
0000002f`4b67d370  00000000`00000000
0000002f`4b67d378  0000002f`00000000
0000002f`4b67d380  000001f2`d8d43580 "C:\Windows\UpdateAssistant\UpdateAssistant.exe /ClientI"
0000002f`4b67d388  00000000`00000000
0000002f`4b67d390  00000000`00008664
0000002f`4b67d398  000001f2`d9d73c40 "ALLUSERSPROFILE=C:\ProgramData"
0000002f`4b67d3a0  00000000`00000000
0000002f`4b67d3a8  00000000`00000000

Therefore, the child process pulled up by svchost is updateassistant Exe, which is also consistent with the parameters obtained by previous analysis. It can be seen from the call stack that the child process updateassistant is created in svchost Exe, and notify the driver software to do corresponding processing.

I am a penetration testing worker who loves penetration and penetrates attentively. In order to make it easier for everyone to study and read, I sort out the documents in the article, including penetration testing and attack and defense documents. Documents are required I'll pick it up!!!

3 conclusion

This paper analyzes the internal principle of the system's process callback security mechanism in detail, reverses the system image file with the help of IDA tool, analyzes the key code part of the implementation, and obtains the key data structure and the additional data detection and verification algorithm of the system. The role of key global variables is also explained in detail.

In addition, through reverse analysis, the call source and call chain of the whole mechanism are given. Finally, based on the dual machine debugging environment, the process callback routine table maintained in the kernel is dynamically viewed, and the breakpoint actually dynamically debugs the whole process.

For driver development, researchers related to kernel security provide the implementation principle and mechanism of this technology. Based on the obtained key data structure and system data inspection and protection algorithm, it can detect the malicious code in the table item after decrypting the key fields. It can also be used for security manufacturers to manually build the table item completely away from the API provided by the system in the process of confrontation, so as to achieve the purpose of monitoring the system behavior.

Topics: Cyber Security penetration test Information Security security hole