[免杀学习]动态链接库初探(下)

这篇文章会详细的讲述动态链接库的运行时动态链接机制,如果没有基础的师傅建议先看上篇文章《 [免杀学习]动态链接库初探(下) 》

知识点回顾

上篇文章中主要讲解了动态链接类型里的加载时动态链接,与本节要讲的运行时动态链接的区别在:

理论层面

加载时动态链接是在程序编译时就把对应的DLL文件加载进了程序,而运行时动态链接则是在程序运行时,也就是代码执行到了 LoadLibrary这个函数时才会把对应的DLL文件加载进程序,然后处理一下才可以使用

这里的处理指的是运行时动态链接需要通过一些额外的步骤才能开始使用DLL中的函数,而加载时动态链接则不需要额外的步骤,在导入DLL后就可以直接用函数名称来使用函数

虽然看起来运行时动态链接加载时动态链接静态链接在使用和理解上要难一丢丢,但是在免杀方面有很大的优势,如

  1. 运行时动态链接可以让攻击者在需要的时候才加载和调用DLL函数,而不是在程序启动时就加载所有的DLL,这样可以减少可疑的行为和被检测的风险。
  2. 运行时动态链接可以让攻击者使用API函数来获取DLL函数的地址,而不是使用导入表或者重定位表,这样可以避免被静态分析工具发现和拦截
  3. 运行时动态链接可以让攻击者使用反射技术来加载和执行DLL文件,而不是使用系统提供的加载器,这样可以绕过一些安全机制和监控工具。、
  4. 运行时动态链接不需要lib倒入库,只要能找到DLL即可利用

代码层面

加载时动态链接:

需要在代码中显示调用,并且需要手动导入lib导入库,或者说明查找路径

#pragma comment(lib,"MyDll.lib" )
__declspec(dllimport) int add(int a, int b);

运行时动态链接:

不需要显示调用,但需要额外处理

// 定义一个函数指针
typedef int (* func)(int,int);

// 加载DLL
HMODULE hModule = LoadLibrary("MyDll2.dll");

// 获取add方法的地址,并赋予函数指针使其可用
func add = (func)GetProcAddress(hModule, "add");

// 调用add方法
cout << add(1,2) << endl;

说完了区别,开始系统的讲解一下运行时动态链接的编写和使用

基础知识

函数指针

函数指针顾名思义就是指向函数指针

说的再本质一点就是指针中存储的地址是一个函数的地址

函数指针的基本定义格式为:

//定义一个函数指针变量
返回值类型 (*指针名称) (参数1类型,........);

看个例子来理解

#include <iostream>
#include <stdio.h>
using namespace std;

//声明一个函数指针变量
int (*funcPtr) (int, int);

int add(int a, int b) {
	return a + b;
}

int main() {
	// 把add函数的地址赋予funcPtr函数指针
	funcPtr = &add;//这里的&可以省略,因为add本来就代表函数的地址
    
    //通过函数指针调用add方法
	int c=funcPtr(1, 2);
	cout << c << endl;

}

从这个例子可以看出,这种声明方式相当于是声明了一个函数指针变量,这个变量适用于返回值为int,且有2个int参数的函数

但这种方式有个缺点,就是无法复用,如果我们有多个符合这种函数特征的方法需要用函数指针调用,那是不是还得多次声明多个函数指针变量?

为了解决这个问题,只需要在开头加个typedef,这样就相当于把funcPtr当做一个新的类型来使用,可以通过这个新的类型来声明函数指针变量

上述代码可以修改为

#include <iostream>
#include <stdio.h>
using namespace std;

//声明一个函数指针类型,类型名称为funcPtr
typedef int (*funcPtr) (int, int);

int add(int a, int b) {
	return a + b;
}

int main() {
	// 创建一个funcPtr类型的函数指针变量func,并将add函数的地址赋予func函数指针
	funcPtr func = add;
    
    //通过函数指针func调用add方法
	int c=func(1, 2);
	cout << c << endl;

}

函数指针刚开始看可能会觉得有点抽象,但理解后其实很容易看透其本质

Windows Api编码问题

win的api函数一般会提供2个版本,一个是函数名+A,一个是函数名+W

函数名+A结尾:支持ANSI编码,也就是ASCII编码,也可以说是多字节字符集编码

函数名+W结尾:支持Unicode编码,也就是宽字节编码

在Visual studio2022中,使用的编码方式为ASCII编码,所以如果要使用W结尾的函数,那么必须在字符串型参数的双引号前加"L",表明以Unicode编码这个字符串

当然,如果你不想在每个函数结尾都要手动声明A或者W的话,可以设置项目属性>配置类型->高级->字符集->多字节字符集,这样visual 就会默认使用A结尾的api函数

tips:如果需要用中文,那还是推荐使用Unicode编码,因为ansi对中文会乱码

LoadLibrary函数

要进行运行时的动态链接,通常顺序是引入DLL->查找DLL中的函数地址->使用函数->卸载DLL

LoadLibrary函数的作用就是引入DLL

HMODULE LoadLibraryA(
  [in] LPCSTR lpLibFileName
);

lpLibFileName:模块的名称,可以是DLL或EXE文件。如果指定了完整路径,只搜索该路径;如果指定了相对路径或没有路径的模块名,使用标准搜索策略。如果省略了文件扩展名,会追加默认的“.DLL”。

注释:LoadLibrary方法通常就是为了导入DLL模块,获取到DLL模块的句柄后就可以在后续获取其中想要使用的函数地址

在代码中通常是这样用的

// 加载DLL
HMODULE hModule = LoadLibrary("MyDll2.dll");

HMODULE是一个模块句柄,相当于获得了dll的地址。它是一个指针类型

GetProcAddress函数

当我们引入一个DLL后,我们就可以使用其句柄来查找存在于这个DLL中的函数的地址了(第二步),这个过程的实现主要就是通过使用GetProcAddress函数

FARPROC GetProcAddress(
  [in] HMODULE hModule,
  [in] LPCSTR  lpProcName
);

hModule:模块句柄

lpProcName:函数名

在代码中这样使用

typedef int (* func)(int,int);
...
...
...
func add = (func)GetProcAddress(hModule, "add");

因为GetProcAddress的返回值是一个FARPROC类型,它是一个通用函数指针,没有指定参数和返回值的类型。为了能够正确地调用add函数,需要将FARPROC转换为func类型,也就是指定了参数和返回值为int的函数指针。这样才能保证函数调用的安全和正确。

因为需要进行类型转换,所以必须使用typedef来创建一个函数指针的类型,而不能只定义一个函数指针变量

FreeLibrary函数

BOOL FreeLibrary(
  [in] HMODULE hLibModule
);

作用很简单,就是把导入的DLL卸载了

完整代码

首先把上篇生成的.dll文件放到当前项目下,然后在项目里新建一个cpp输入以下代码

#include <windows.h>
#include <stdio.h>
#include <iostream>
// 定义一个函数指针
typedef int (* func)(int,int);
using namespace std;

int main()
{
    // 加载DLL
    HMODULE hModule = LoadLibrary("MyDll2.dll");
    if (hModule == NULL)
    {
        printf("Failed to load dll\n");
        return -1;
    }

    // 获取add方法的地址,并赋予函数指针使其可用
    func add = (func)GetProcAddress(hModule, "add");
    if (add == NULL)
    {
        printf("Failed to get the address of add func \n");
        FreeLibrary(hModule);
        return -1;
    }

    // 调用add方法
    cout << add(1,2) << endl;

    // 卸载dll
    FreeLibrary(hModule);

    return 0;
}

成功运行

举一反三

我们在上篇说道,DLL库有DllMain入口函数,我们能否在加载DLL(LoadLibrary)或卸载DLL(FreeLibrary)时做一些恶意操作?

重新编写一下MyDll,在上篇我们已经演示过在引入DLL时执行额外操作,这次我们来尝试在卸载DLL时打开计算器

打开MyDll2.cpp,编写如下代码

//MyDll2.cpp
#include <stdio.h>
#include <iostream>
#include <Windows.h>
#include <cstdlib>
#include "MyDll2.h"
using namespace std;

BOOL WINAPI DllMain(
    HINSTANCE hinstDLL,  // handle to DLL module
    DWORD fdwReason,     // reason for calling function
    LPVOID lpvReserved)  // reserved
{
    // Perform actions based on the reason for calling.
    switch (fdwReason)
    {
    case DLL_PROCESS_ATTACH:
        
        break;

    case DLL_THREAD_ATTACH:
        break;

    case DLL_THREAD_DETACH:
        break;

    case DLL_PROCESS_DETACH:
        cout << "卸载DLL成功" << endl;
        cout << "calc.exe start" << endl;
        system("calc.exe");
        break;
    }
    return TRUE;  
}

int add(int a, int b) {
	return a + b;
}

重新生成项目(这里有问题的建议看看上篇),然后只需要把生成的dll文件放在Project目录下

运行main.cpp,成功执行并且按照我们的预期打开了calc.exe!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值