一
介绍
二
前置知识
动态库的加载方式
隐式加载
隐式加载的优点是使用简单,程序启动时按照系统中一定的搜索顺序自动加载所有依赖,不需要在代码中进行额外的加载操作。缺点是程序启动时会加载所有依赖的库,即使这些库中的一些函数可能永远不会被调用,这可能会导致额外的内存消耗。此外,如果依赖的库在系统上不存在,程序将无法启动。
从PE文件格式对应的就是导入表,注意,导入表内的动态库在程序初始化的时候会被加载进内存,如果没找到就会报错。但动态库里面的导出函数并不一定会被调用。
显式加载
动态库的加载顺序
● 检测加载的dll是否在Known DLLs内
● 检测应用当前目录下是否存在dll
● 检测System32目录下是否存在dll
● 检测当前执行目录下是否存在dll
● 检测%PATH%环境变量下是否存在dll
这里再介绍下Known DLLs
Known DLLs标记的dll默认会从system32路径下加载,注册表位置
HKEY_LOCAL_MACHINESYSTEMCurrentControlSetControlSession ManagerKnownDLLs
动态库被加载的执行点
dllmain
You should never perform the following tasks from within DllMain:
● Call LoadLibrary or LoadLibraryEx (either directly or indirectly). This can cause a deadlock or a crash.
● Call GetStringTypeA, GetStringTypeEx, or GetStringTypeW (either directly or indirectly). This can cause a deadlock or a crash.
● Synchronize with other threads. This can cause a deadlock.
● Acquire a synchronization object that is owned by code that is waiting to acquire the loader lock. This can cause a deadlock.
● Initialize COM threads by using CoInitializeEx. Under certain conditions, this function can call LoadLibraryEx.
● Call the registry functions.
● Call CreateProcess. Creating a process can load another DLL.
● Call ExitThread. Exiting a thread during DLL detach can cause the loader lock to be acquired again, causing a deadlock or a crash.
● Call CreateThread. Creating a thread can work if you do not synchronize with other threads, but it is risky.
● Call ShGetFolterPathW. Calling shell/known folder APIs can result in thread synchronization, and can therefore cause deadlocks.
● Create a named pipe or other named object (Windows 2000 only). In Windows 2000, named objects are provided by the Terminal Services DLL. If this DLL is not initialized, calls to the DLL can cause the process to crash.
● Use the memory management function from the dynamic C Run-Time (CRT). If the CRT DLL is not initialized, calls to these functions can cause the process to crash.
● Call functions in User32.dll or Gdi32.dll. Some functions load another DLL, which may not be initialized.
● Use managed code.
导出函数
三
隐式加载的劫持方式
OEP劫持
具体利用方式参考:https://github.com/9bie/iatHijackGenerate(很粗暴)
伪代码如下:
BOOL WINAPI DllMain(HMODULE hModule, DWORD dwReason, PVOID pvReserved)
{
if (dwReason == DLL_PROCESS_ATTACH)
{
MODULEINFO moduleInfoe;
SIZE_T bytesWritten;
GetModuleInformation(GetCurrentProcess(), GetModuleHandle(NULL), &moduleInfoe, sizeof(moduleInfoe));
unsigned char shellcode[] = { xx, xx...}
int shellcode_size = xx;
HANDLE currentProcess = GetCurrentProcess();
WriteProcessMemory(currentProcess, moduleInfoe.EntryPoint, (LPCVOID)&shellcode, shellcode_size, &bytesWritten);
}
else if (dwReason == DLL_PROCESS_DETACH){}
return TRUE;
}
● 改良方案:申请新内存,将shellcode代码拷贝过去,修改oep处代码跳转执行或找到E8、E9指令,修改偏移地址劫持执行流。
导入函数劫持
四
显式加载的劫持方式
导入函数劫持
ghost dll hijacking
注意:不同的系统版本可利用此方式劫持的动态库不同。
挖掘思路
第二步:选择一种在开机启动时可以动态全局hook所有三环进程的方式。
方案一:从内核做,由于PG的存在,想要在内核做模块加载的监控,首选肯定是LoadImage内核回调,但又大概率认为三环模块加载失败时是不会触发LoadImage的内核回调(可恶,应该去测一下。插一嘴,这个点对后续绕LoadImage回调触发调用栈检测给了我不一样的思路。我就不再这里赘述了)。后续和其他大哥们聊天知道了很多安全产品从零环做三环UMH(User Mode Hook)的思路与接触了InfinityHook拦截系统调用等方式,后续有时间开帖子讲。
方案二:在三环通过Appinit方式注入dll从而hook LoadLibrary函数。方案可行,但有缺陷。凡是加载appinit注册表中dll的程序前提是会加载user32.dll。(这个点在我做测试的时候是没有想到的),所以依靠三环还是无法做到全局hook所有进程的api调用,欢迎师傅们提供三环全局hook的思路。
来看一下方案二实现的伪代码。
64位 HKEY_LOCAL_MACHINESOFTWAREMicrosoftWindows NTCurrentVersionWindowsAppInit_DLLs
32位 HKEY_LOCAL_MACHINESOFTWAREWow6432NodeMicrosoftWindows NTCurrentVersionWindowsAppInit_DLLs
重启机器等待进程的加载。
五
通用dllmain利用
利用难题
解决思路
第一种解决思路肯定是参考文章所述通过一定方式去解锁,不详细赘述。
既然程序在退出前会进行解锁,那我们不必纠结于在dllmain解锁的问题,我们直接劫持栈上的返回地址。其中的难点就是在dllmain中遍历自己的栈空间,找到应用模块的返回地址进行覆盖。随后便完成劫持,等待函数返回即可。
这里附上遍历调用栈的逻辑。
六
总结
看雪ID:F4our444
https://bbs.kanxue.com/user-home-833287.htm
# 往期推荐
球分享
球点赞
球在看
点击阅读原文查看更多
原文始发于微信公众号(看雪学苑):Windows攻防对抗-DLL侧载篇