|
吾爱游客
发表于 2023-7-11 13:37
1、申 请 I D : iMonitorSDK2、个人邮箱: admin@iMonitorSDK.com
DLL挟持原理、分析、应用动态库基础很多程序,基本都不是仅仅程序本身就可以直接运行,经常会依赖到其他的动态库(Dynamic Link Library)。比如最基本的ntdll.dll、kernel32.dll。程序对动态库的依赖分为两种:- 静态依赖一般程序在编译的时候会依赖一个lib,编译后在程序的导入表就会生成相应的依赖信息
- 动态加载指通过LoadLibrary、LdrLoadDll的方式进行手动加载动态库的方式
在一个DLL的加载过程,默认会通过以下的目录进行查找:- 程序所在目录
- 程序设置的加载目录(SetCurrentDirectory)
- 系统目录
- PATH环境变量的目录
更多的可以参考LoadLibrary的API说明。DLL挟持的原理为什么会造成DLL挟持,正是因为DLL的加载过程会搜索不同的路径导致。比如搜索的路径依次A、B、C,而需要的DLL在C位置,如果有恶意的软件把相同名字的DLL放在了A目录,就会导致优先加载A目录的DLL,从而导致DLL挟持的发生。几乎所有的程序都会存在DLL挟持的风险。比如古老的LPK病毒就是利用了这个原理。KnownDLLs系统为了解决这个问题,同时加快系统动态库的加载效率,会建立一份信任列表KnownDLLs,在这份列表的DLL,会直接从已经加载的镜像里面映射一份,不需要重新经过查找、加载的过程。KnownDLLs注册表项的路径为 HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\KnownDLLs。(针对KnownDlls的攻击本文不介绍,有兴趣的可以自行了解)但是仍然有很多其他的DLL没有在这个列表里面,从而还是会存在挟持的风险。DLL挟持可能带来的风险- 程序的完整性会被破坏
- 程序的逻辑会被篡改
- 程序信任关系被盗用比如利用白 + 黑的病毒、绕过某些安全软件的信任逻辑、强制结束带自保护的软件
而且DLL挟持是没法简单通过数字签名来得到解决的。DLL挟持分析使用iMonitor(冰镜)的最新版本,可以直接分析出哪些程序存在DLL挟持(包括静态依赖、动态加载)的风险。DLL挟持应用如果已经知道一个程序存在DLL挟持,一般是怎么应用的呢?这个具体得挟持的目的是什么,常见的有下面的几种方式。拷贝DLL到程序目录的方式比如,基本的程序都会依赖version.dll(其他的DLL都可以),而version.dll是系统的DLL,但是加载搜索的过程,优先级最高是程序的当前目录,那么就可以自己制作一个假的version.dll(为了功能可以,需要同时把调用映射回真实的version.dll,如果不会操作,后续可以提供对应的工具),然后放到程序的目录下面,这样程序启动就会自动加载这个假的DLL。在这个DLL里面可以做一些Hook操作,这样就可以篡改程序的逻辑,比如修改日期绕过试用期达到破解的目的。之前还有一些奇葩的安全厂商,提交了针对某会议、某聊天工具的所谓漏洞,竟然是利用DLL挟持来实现。DLL挟持是几乎无法避免的,这样说的话所有软件都会存在漏洞了。 这种方式的DLL挟持,其实比较容易添加一些保护。比如在程序的目录添加自保护(如果需要自保护功能,可以接入iMonitorSDK快速实现),防止其他人拷贝恶意DLL进来,同时可以添加一个对当前目录DLL的扫描,删除不是自己的其他DLL。白 + 黑的方式拷贝一个存在DLL挟持的存在信任签名的程序,然后制作一个可以被他加载的DLL放到一起,这样启动这个程序的时候,就会自动加载这个DLL了。因为这个程序存在信任签名,这样就可以绕过一些安全软件的检测,从而提高权限、或者执行恶意逻辑。比如:某知名安全软件的自保护控制,只允许自己的固定一个进程来操作,驱动会校验通信的进程名,进程的签名是否有效,这样只要把这个信任的进程拷贝出来,然后跟关闭自保护的恶意DLL放一起,就可以轻松关闭自保护。拷贝DLL到PATH目录的方式上面的方式都是主动发起挟持注入DLL的,达到利用条件的前提是具备程序目录的文件拷贝权限、或者执行的时候已经不是完整的程序了。另外一种挟持方式是程序依赖的DLL在程序的目录根本没有,而且不是系统DLL。这种在搜索DLL的过程,就会找到PATH目录,但是因为很多软件可能会修改PATH目录,导致里面的条目非常多,这样就会从很多位置尝试去加载DLL。如果存在一个低权限的PATH目录,那么拷贝一个DLL过去,会到导致程序启动的时候自动加载这个DLL,恶意DLL就有可能达到提权的目的,从而做出更多恶意行为。建议在软件发布前,都分析程序中是否存在DLL丢失的情况,只要存在DLL丢失,就会潜在一个很大的挟持风险。红蓝对抗中,很多是通过这样的方式来达到提权的目的的。如果因为某一款软件的漏洞引发系统被入侵,这款软件可能会被抛弃的,特别是对等保要求比较高的企业。
DLL挟持模块的生成原理程序对一个模块有静态依赖和动态依赖两种。静态依赖 编译的时候,会把依赖的模块信息写入到PE结构的导入表中。加载的时候,会根据导入表,加载对应的模块,然后从该模块的导出表检索函数,修复PE映像的导入函数调用地址,这样调用函数的时候就会跳转模块里面执行。动态依赖程序对模块不会有直接依赖的关系,而是执行过程中,通过LoadLibrary + GetProcAddress的方式加载模块,然后获取需要调用的函数去执行。挟持模块无论是哪种依赖关系,制作一个挟持模块去替换原始的模块,如果要保证程序运行正常,挟持模块需要提供程序所依赖的全部函数,通常挟持模块会导出被挟持模块的全部导出函数。怎么查看一个模块的导出函数呢?可以通过dumpbin查看,也可以通过其他的PE工具。下面的通过dumpbin查看version.dll的导入函数的命令。dumpbin /exports C:\windows\system32\version.dll
​
Dump of file C:\windows\system32\version.dll
​
File Type: DLL
​
Section contains the following exports for VERSION.dll
​
00000000 characteristics
90A1B58A time date stamp
0.00 version
1 ordinal base
17 number of functions
17 number of names
​
ordinal hint RVA name
​
1 0 00001EE0 GetFileVersionInfoA
2 1 000023E0 GetFileVersionInfoByHandle
3 2 00001F00 GetFileVersionInfoExA
4 3 00001070 GetFileVersionInfoExW
5 4 00001010 GetFileVersionInfoSizeA
6 5 00001F20 GetFileVersionInfoSizeExA
7 6 00001090 GetFileVersionInfoSizeExW
8 7 000010B0 GetFileVersionInfoSizeW
9 8 000010D0 GetFileVersionInfoW
10 9 00001F40 VerFindFileA
11 A 000025A0 VerFindFileW
12 B 00001F60 VerInstallFileA
13 C 00003390 VerInstallFileW
14 D VerLanguageNameA (forwarded to KERNEL32.VerLanguageNameA)
15 E VerLanguageNameW (forwarded to KERNEL32.VerLanguageNameW)
16 F 00001030 VerQueryValueA
17 10 00001050 VerQueryValueW知道所有的导出函数了,那么下一步就是实现这些函数。大部分场景,我们并不关心这些函数具体的功能,也没有必要完整实现这些函数,只需要把这些函数映射到原来模块的函数就好了。方式一:使用导出表重定向的方式(比如上面的:VerLanguageNameA (forwarded to KERNEL32.VerLanguageNameA) )VC提供#pragma的编译指令,可以手动导出一个指向其他模块的函数。比如:导出GetFileVersionInfoA实际是指定到original_version.dll里面的GetFileVersionInfoA#pragma comment(linker,"/export:GetFileVersionInfoA=original_version.GetFileVersionInfoA")这种方式在替换模块的时候,存在一个问题,就是引入了一个新的模块依赖(原始模块),需要从原始模块拷贝一份重命名后放在一起才能正常加载。这种方式操作会比较麻烦,因为没法提前把原始模块打包在一起(每个系统可能不一样),而是需要在目标机器上手动拷贝一份。方式二:通过动态依赖原始模块,然后GetProcAddress + JMP的方式。// 初始化的时候,获取到全部需要的原始函数
// original_GetFileVersionInfoA = GetProcAddress(original_version, "GetFileVersionInfoA");
​
__declspec(naked) GetFileVersionInfoA
{
__asm jmp original_GetFileVersionInfoA;
}但是在x64编译器上,不支持直接内联汇编的方式。为了通用,直接去掉了手动编写函数,而是使用编辑字节码的方式。#ifdef _M_IX86
​
struct JMPStub
{
//
// jmp address
//
BYTE JMP;
ULONG Address;
};
​
#else
​
struct JMPStub
{
//
// mov rax, address
// jmp rax
//
USHORT MOVRax;
ULONGLONG Address;
USHORT JMPRax;
};
​
#endif完整代码#pragma pack(push, 1)
//******************************************************************************
#ifdef _M_IX86
//******************************************************************************
struct JMPStub
{
BYTE JMP;
ULONG Address;
​
bool Hook(HMODULE module, const char* Name)
{
PVOID addr = GetProcAddress(module, Name);
​
if (addr == NULL)
return false;
​
JMP = 0xE9;
Address = (ULONG)addr - (ULONG)this - 5;
​
return true;
}
};
//******************************************************************************
#else
//******************************************************************************
struct JMPStub
{
USHORT MOVRax;
ULONGLONG Address;
USHORT JMPRax;
​
bool Hook(HMODULE module, const char* Name)
{
PVOID addr = GetProcAddress(module, Name);
​
if (addr == NULL)
return false;
​
MOVRax = 0xB848;
Address = (ULONGLONG)addr;
JMPRax = 0xE0FF;
​
return true;
}
};
//******************************************************************************
#endif
//******************************************************************************
#pragma pack(pop)
//******************************************************************************
#pragma data_seg(".jmp")
__declspec(selectany) HMODULE g_jmp_module = NULL;
#pragma data_seg()
#pragma comment(linker, "/SECTION:.jmp,RWE")
//******************************************************************************
class JMPSetModule
{
public:
JMPSetModule(LPCTSTR Name)
{
//
// 默认替换系统的镜像,如果需要其他的可以自行修改
//
TCHAR path[MAX_PATH] = {};
GetWindowsDirectory(path, MAX_PATH);
PathAppend(path, Name);
g_jmp_module = LoadLibrary(path);
}
};
//******************************************************************************
class JMPSetHook
{
public:
JMPSetHook(JMPStub& stub, const char* Name)
{
stub.Hook(g_jmp_module, Name);
}
};
//******************************************************************************
#define CONCAT_RAW_(a, b) a##b
#define CONCAT_(a, b) CONCAT_RAW_(a, b)
//******************************************************************************
#define BEGIN_EXPORT_MAP(module) JMPSetModule CONCAT_(module_, __COUNTER__)(_T(module));
#define EXPORT_MAP(name) \
namespace JMPStubPrivate \
{ \
extern "C" __declspec(dllexport) __declspec(allocate(".jmp")) JMPStub name = {}; \
JMPSetHook sethook_##name(name, #name); \
};
#define END_EXPORT_MAP()
//******************************************************************************最终使用效果添加下面的代码,编译后就可以自动生成一个替换version.dll的挟持模块。BEGIN_EXPORT_MAP("system32\\version.dll")
EXPORT_MAP(GetFileVersionInfoA)
EXPORT_MAP(GetFileVersionInfoW)
EXPORT_MAP(GetFileVersionInfoByHandle)
EXPORT_MAP(GetFileVersionInfoExA)
EXPORT_MAP(GetFileVersionInfoExW)
EXPORT_MAP(GetFileVersionInfoSizeA)
EXPORT_MAP(GetFileVersionInfoSizeW)
EXPORT_MAP(GetFileVersionInfoSizeExA)
EXPORT_MAP(GetFileVersionInfoSizeExW)
EXPORT_MAP(VerFindFileA)
EXPORT_MAP(VerFindFileW)
EXPORT_MAP(VerInstallFileA)
EXPORT_MAP(VerInstallFileW)
EXPORT_MAP(VerLanguageNameA)
EXPORT_MAP(VerLanguageNameW)
EXPORT_MAP(VerQueryValueA)
EXPORT_MAP(VerQueryValueW)
END_EXPORT_MAP()
|
|
发帖前要善用【论坛搜索】功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。 |
|
|
|
|