note20200629

notes about chapter12

DLL注入

通过干预输入表处理过程加载目标DLL

输入表相关

  1. 输入函数调用

PE文件内部有一组数据结构,对应于被输入的DLL,每一个这样的结构都给出了输入的DLL的名称并指向一组函数指针。这组函数指针为IAT(输入地址表),每一个被引入的API在IAT里都有保留的位置,API被Windows加载器在该位置写入输入函数的地址。

一旦模块被载入,IAT中将包含要调用的输入函数的地址。

默认情况下,输入API的调用采用下列这种低效的形式:

1
2
3
4
    Call 00401164
............
:00401164
Jmp dword ptr [00402010]

可以在编写程序时使用一个前缀”__declspec(dllimport)”来告诉编译器这个函数来自另外一个DLL,这样上述指令就会变成:

1
Call dword ptr [00402010]

示例如下:

1
__declpec(dllimport) void Foo(void);
  1. 输入表的结构

PE文件头的可选映像头中,数据目录表的第二个成员指向输入表。输入表以一个IMAGE_IMPORT_DESCRIPTOR(IID)数组开始。每个被PE文件隐式链接的DLL都有一个IID,在这个数组中,没有字段指出这个数组的项数,但是每个元素的最后一个单元是NULL,可以由此计算数组的项数。IID结构:

1
2
3
4
5
6
7
8
9
10
IMAGE_IMPORT_DESCRIPTOR STRUCT
union ;00h
Characteristics DWORD ?
OriginalFirstThunk DWORD ?
end
TimeDateStamp DWORD ? ;04h
ForwardChain DWORD ? ;08h
Name DWORD ? ;0Ch
FirstThunk DWORD ? ;10h
IMAGE_IMPORT_DESCRIPTOR ENDS
  • OriginalFirstThunk(Characteristics):包含指向输入名称表(INT)的RVA。INT(IMAGE_THUNK_DATA结构数组)指向IMAGE_IMPORT_BY_NAME结构,数组以一个内容为0的IMAGE_THUNK_DATA结构结束。
  • TimeDateStamp:一个32位的时间标志。
  • ForwardChain:第一个被专向的API的索引,一般为0.在一个程序引用DLL中的APOI,而这个API又在引用其他的DLL的API时使用。
  • Name:DLL名字的指针,是一个以”00”结尾的ASCII字符的RVA地址。
  • FirstThunk:包含指向输入表地址(IAT)的RVA。IAR是一个IMAGE_THUNK_DATA结构的数组。

输入API方式:

两种数组都使用了IMAGE_THUNK_DATA结构,IMAGE_THUNK_DATA结构是一个指针大小的联合。每个IMAGE_THUNK_DATA元素对应于一个从可执行文件输入的函数:

1
2
3
4
5
6
7
8
IMAGE_THUNK_DATA STRUCT
union u1
ForwarderString DWORD ? ;指向一个转向者字符串的RVA
Function DWORD ? ;被输入的函数的内存地址
Ordinal DWORD ? ;被输入的API的序数值
AddressOfData DWORD ? ;指向IMAGE_IMPORT_BY_NAME
ends
IMAGE_THUNK_DATA ENDS

IMAGE_IMPORT_BY_NAME结构:

1
2
3
4
IMAGE_IMPORT_BY_NAME STRUCT
Hint WORD ? ;本函数所在DLL的序号
Name BYTE ? ;含有输入函数的函数名
IMAGE_IMPORT_BY_NAME ENDS

修改IID进行干预加载情况

  1. 复制一份IID数组

  1. 构造新的IID

  1. 填充新的IID

  1. 修正PE文件头

  1. 清零Bound Import Table

使用PEidtor直接添加输入表

改变程序流程使其主动加载DLL

CreateRemoteThread法

在目标进程申请一块内存并向其中写入DLL路径,然后调用CreateRemoteThread在目标进程中创建一个线程。线程的函数地址就是LoadLibraryA(W),参数就是存放DLL路径的内存指针。这时需要目标进程的4个权限:PROCESS_CREATE_THREAD,PROCESS_QUERY_INFORMATION,PROCESS_VM_OPERATION,PROCESS_VM_WRITE。

核心函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
BOOL WINAPI InjectDllToProcess(DWORD dwTargetPid, LPCTSTR DllPath) {


HANDLE hProc = NULL;
hProc = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwTargetPid);//获取PROCESS_ALL_ACCESS权限,没有继承的handle

if (hProc == NULL) {
cout << "打开目标进程失败!\n";
return FALSE;
}

LPTSTR psLibFileRemote = NULL;

//开辟buffer存放DLL名称
psLibFileRemote = (LPSTR)VirtualAllocEx(hProc, NULL, lstrlen(DllPath) + 1, MEM_COMMIT, PAGE_READWRITE);//使用MEM_COMMIT表示分配物理空间,PAGE_READWRITE用于在页中读写数据

if (psLibFileRemote == NULL) {
cout << "页分配失败!\n";
return FALSE;
}

//使用WriteProcessMemory函数将DLL的路径名复制到远程的内存空间
if (WriteProcessMemory(hProc, psLibFileRemote, (void*)DllPath, lstrlen(DllPath) + 1, NULL) == 0) {//判断页可写
cout << "页无法写入数据\n";
return FALSE;
}

//获取LoadLibraryA地址
PTHREAD_START_ROUTINE pfnStartAddr = NULL;
pfnStartAddr = (PTHREAD_START_ROUTINE)GetProcAddress(GetModuleHandle("Kernel32"), "LoadLibraryA");

if (pfnStartAddr == NULL) {
cout << "获取LoadLibraryA地址失败!\n";
return FALSE;
}

//创建线程,线程内容为LoadLibraryA(psLibFileRemote)
HANDLE hThread = NULL;
hThread = CreateRemoteThread(hProc, NULL, 0, pfnStartAddr, psLibFileRemote, 0, NULL);

if (hThread == NULL) {
cout << "线程创建失败!\n";
return FALSE;
}

cout << "DLL注入成功!\n"
<<"DLL "<<DllPath<<" 注入进程 "<<dwTargetPid<<"\n";

return TRUE;
}

Hint:64位注入64位,32位注入32位。。。。。

RtlCreateUserThread

与CreateRemoteThread不同,RtlCreateUserThread的使用最后需要自己结束创建的线程。因为CreateRemoteThread函数有kernel32.dll中的函数关闭相关线程,而RtlCreateUserThread无。

关闭的shellcode:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
__declspec(naked)//标记为汇编
VOID ShellCodeFun(VOID){
__asm {
call Loop1
Loop1:
pop ebx
sub ebx,5
push dword ptr ds:[ebx+0x24]//lpParameter
call dword ptr ds:[ebx+0x20]//lpThreadStartRoutine
xor eax,eax
push eax
push -2
call dword ptr ds:[ebx+0x28]//AddrOfZwTerminateThread
nop
}
}

注入的核心代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
HANDLE RtlCreateRemoteThread(
IN HANDLE hProcess,
IN LPSECURITY_ATTRIBUTES lpThreadAttributes,
IN DWORD dwStackSize,
IN LPTHREAD_START_ROUTINE lpStartAddress,
IN LPVOID lpParameter,
IN DWORD dwCreationFlags,
OUT LPDWORD lpThreadId
)
{
NTSTATUS status = STATUS_SUCCESS;
CLIENT_ID Cid;
HANDLE hThread = NULL ;
SIZE_T dwIoCnt = 0 ;

if (hProcess == NULL
|| lpStartAddress == NULL)
{
return NULL;
}

//获取Native API函数地址
RtlCreateUserThread =(PCreateThread)GetProcAddress(GetModuleHandle("ntdll"),"RtlCreateUserThread");

if(RtlCreateUserThread == NULL)
{
return NULL;
}

//在目标进程中申请内存写入ShellCode
PBYTE pMem = (PBYTE)VirtualAllocEx(hProcess,NULL,0x1000,MEM_COMMIT,PAGE_EXECUTE_READWRITE);
if (pMem == NULL)
{
return NULL;
}

printf("[*] pMem = 0x%p\n",pMem);

//构造并填充INJECT_DATA
INJECT_DATA Data;
ZeroMemory(&Data,sizeof(INJECT_DATA));
PrepareShellCode(Data.ShellCode);
Data.lpParameter = lpParameter;
Data.lpThreadStartRoutine = lpStartAddress;
Data.AddrOfZwTerminateThread = GetProcAddress(GetModuleHandle("ntdll"),"ZwTerminateThread");


//写入ShellCode
if (!WriteProcessMemory(hProcess,pMem,&Data,sizeof(INJECT_DATA),&dwIoCnt))
{
printf("[-] WriteProcessMemory Failed!\n");
VirtualFreeEx(hProcess,pMem,0,MEM_RELEASE);
return NULL;
}

printf("[*] ShellCode Write OK.\n");

status = RtlCreateUserThread(
hProcess,
lpThreadAttributes, //ThreadSecurityDescriptor
TRUE, //CreateSuspend
0, //ZeroBits
dwStackSize, //MaximumStackSize
0, //CommittedStackSize
(PUSER_THREAD_START_ROUTINE)pMem,//pMem开头就是Shellcode
NULL,
&hThread,
&Cid);

if (status >= 0)
{
printf("[*] 线程创建成功!\n");
if (lpThreadId != NULL)
{
*lpThreadId = (DWORD)Cid.UniqueThread;
}

if (!(dwCreationFlags & CREATE_SUSPENDED))
{
ResumeThread(hThread);
}
}


return hThread;
}


APC注入法

使用用户态的APC,即在进程其他线程从等待状态苏醒时,将APC嵌入使系统执行APC过程,前面的思路与CreateRemoteThread注入相同,然后对目标进程的每个线程使用QueueUserAPC函数进行注入。

核心代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
DWORD inject_module_to_process_by_pid(DWORD process_pid, char*dll_full_path) {
SIZE_T st_size = 0;
BOOL b_status = FALSE;
LPVOID lp_data = NULL;
SIZE_T path_len = lstrlen(dll_full_path) + 1;

HANDLE h_process = OpenProcess(PROCESS_ALL_ACCESS, FALSE, process_pid);
if (h_process) {
//分配空间
lp_data = VirtualAllocEx(h_process, NULL, path_len, MEM_COMMIT, PAGE_READWRITE);
if (lp_data) {
b_status = WriteProcessMemory(h_process,lp_data,dll_full_path,path_len,&st_size);
}
else {
cout << "空间分配失败!错误代码:" << GetLastError() << endl;
return FALSE;
}
CloseHandle(h_process);
}

if (b_status == FALSE) {
cout << "获取DLL完整字符串失败!错误代码:" << GetLastError() << endl;
return FALSE;
}

//创建线程快照
THREADENTRY32 te32 = { sizeof(THREADENTRY32) };
HANDLE thread_snap = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
if (thread_snap == INVALID_HANDLE_VALUE) {
cout << "线程快照获取失败!错误代码:" << GetLastError() << endl;
return FALSE;
}
//获取所有线程,并向所有线程添加APC
if (Thread32First(thread_snap, &te32)) {
do{
if (te32.th32OwnerProcessID == process_pid) {//判断当前线程是否属于目标进程
HANDLE h_thread = OpenThread(THREAD_ALL_ACCESS, FALSE, te32.th32ThreadID);
if (h_thread) {
//向线程添加用户态APC:LoadLibraryA(dll_full_path)
DWORD dw_ret = QueueUserAPC((PAPCFUNC)LoadLibraryA, h_thread, (ULONG_PTR)lp_data);
if (dw_ret > 0)
b_status = TRUE;
CloseHandle(h_thread);
}
}
} while (Thread32Next(thread_snap, &te32));
}

CloseHandle(thread_snap);
return b_status;
}

SetThreadContext法

正在执行的线程被SuspendThread暂停后,向线程中写入shellcode,把线程的CONTEXT的eip设置为shellcode的地址,当执行ResumeThread时,就会执行shellcode。

内核中通过HOOK/Notify干预执行流程

与修改线程CONTEXT类似。如系统服务NtResumeThread,当线程恢复执行时,写入shellcode然后修改CONTEXT即可。

纯WriteProcessMemory法

  1. 创建进程时注入;
  2. 将DLL注入到运行的进程中;

使用系统机制加载DLL

SetWindowHookEx消息钩子注入

钩子类型以及适用时期:

使用在SetWindowsHookEx函数中的idHook参数:

1
2
3
4
5
6
HHOOK SetWindowsHookEx(
int idHook,
HOOKPROC lpfn,
HINSTANCE hMod,
DWORD dwThreadId
);

将dwThreadId设置为0,安装一个全局钩子,指定第三个参数hMod。系统在调用HOOKPROC的函数时,发现目标DLL未加载就会去加载。

AppInit_DLLs注册表注入

修改:

1
HKEY LOCAL MACHINE\Software\Microsof七\Windows NT\CurrentVersion\Windows\Appinit—DLLs 

值为指定DLL,当进程加载User32.dll,也会加载这个DLL。

输入法注入

实质是使用IME写相关DLL,并伪装成输入法,系统安装后就会触发注入。

ShimEngine注入

ShimEngine引擎是Windows向下兼容AOPI而设置的引擎,该引擎默认使用ShimEng.dll,可以仿写该DLL来进行注入。

实测实用性不高,安全软件各种报警。。。

Explorer Shell扩展注入

即鼠标右键的COM DLL,注册为Shell扩展后,所有调用Shell的接口的进程都会调用它。