note20200629
发表于更新于
字数总计:2.1k阅读时长:8分钟 中国
notes about chapter12
DLL注入
通过干预输入表处理过程加载目标DLL
输入表相关
- 输入函数调用
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);
|
- 输入表的结构
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 Characteristics DWORD ? OriginalFirstThunk DWORD ? end TimeDateStamp DWORD ? ForwardChain DWORD ? Name DWORD ? FirstThunk DWORD ? 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 ? Function DWORD ? Ordinal DWORD ? AddressOfData DWORD ? ends IMAGE_THUNK_DATA ENDS
|
IMAGE_IMPORT_BY_NAME结构:
1 2 3 4
| IMAGE_IMPORT_BY_NAME STRUCT Hint WORD ? Name BYTE ? IMAGE_IMPORT_BY_NAME ENDS
|
修改IID进行干预加载情况
- 复制一份IID数组
- 构造新的IID
- 填充新的IID
- 修正PE文件头
- 清零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);
if (hProc == NULL) { cout << "打开目标进程失败!\n"; return FALSE; }
LPTSTR psLibFileRemote = NULL;
psLibFileRemote = (LPSTR)VirtualAllocEx(hProc, NULL, lstrlen(DllPath) + 1, MEM_COMMIT, PAGE_READWRITE);
if (psLibFileRemote == NULL) { cout << "页分配失败!\n"; return FALSE; }
if (WriteProcessMemory(hProc, psLibFileRemote, (void*)DllPath, lstrlen(DllPath) + 1, NULL) == 0) { cout << "页无法写入数据\n"; return FALSE; }
PTHREAD_START_ROUTINE pfnStartAddr = NULL; pfnStartAddr = (PTHREAD_START_ROUTINE)GetProcAddress(GetModuleHandle("Kernel32"), "LoadLibraryA");
if (pfnStartAddr == NULL) { cout << "获取LoadLibraryA地址失败!\n"; return FALSE; }
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] call dword ptr ds:[ebx+0x20] xor eax,eax push eax push -2 call dword ptr ds:[ebx+0x28] 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; } RtlCreateUserThread =(PCreateThread)GetProcAddress(GetModuleHandle("ntdll"),"RtlCreateUserThread"); if(RtlCreateUserThread == NULL) { return NULL; }
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 Data; ZeroMemory(&Data,sizeof(INJECT_DATA)); PrepareShellCode(Data.ShellCode); Data.lpParameter = lpParameter; Data.lpThreadStartRoutine = lpStartAddress; Data.AddrOfZwTerminateThread = GetProcAddress(GetModuleHandle("ntdll"),"ZwTerminateThread");
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, TRUE, 0, dwStackSize, 0, (PUSER_THREAD_START_ROUTINE)pMem, 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; } if (Thread32First(thread_snap, &te32)) { do{ if (te32.th32OwnerProcessID == process_pid) { HANDLE h_thread = OpenThread(THREAD_ALL_ACCESS, FALSE, te32.th32ThreadID); if (h_thread) { 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法
- 创建进程时注入;
- 将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的接口的进程都会调用它。