DLL替代法

三、DLL替代法

 

终于要动笔写这篇文章了。离上篇文章有十几天时间了,这段时间都是在准备这篇文章。因为DLL替代法是上次写文章时想起来的。查资料、写代码化了很长时间。还有一点需要说明:本方法只是完成了大部分,并没有全部完成,但是已有雏形了。

 

1.               技术与实现

 

我们知道每个DLL输出函数都有一个输出节(Export Section)。在这个节中,dll中每个需要输出的函数都有一个对应的项。这些概念PE文章中都有详细描述。

 

DLL替换法,就是要手工生成一个和原来DLL有相同的输出节的DLL文件。我们称新生成的DLLSUBDLL;而原来的DLL,我们称之为ORGDLLSUBDLLORGDLL有相同的输出节(输出的函数相同);SUBDLL的每个输出函数在执行一段PROXYFUN的代码后,将调用ORGDLL中对应的函数(相同的函数)。因此在SUBDLL中还要有一个输入节(IMPORT SECTION)。它将输入ORGDLL中每个输出的函数。根据这个理论,下面描绘了一张图。

下面目标明确了:手工生成一个DLL文件,它有三个节:输出节、输入节和代码节。根据它们之间的互相调用关系:先生成输入节,然后生成代码节,最后输出节。

 

1)  输入节

输入节是最简单的,首先将原来的DLL改名,假设名称由原来的AB.DLL改成_B.DLL(因为需要同时修改原来的AB.DLL的输出节的名称,若增加字符数则需要重新分配空间)

 

在输入节的输入DLL上写上_B.DLL,表示本输入节是从_B.DLL的输出节中输入的。然后将每个输入的函数的名称或ORD填好。注意此时FirstThunk指向的数组中存放的数据和OrgFirstThunk指向的数组一样,都是IMAGE_IMPORT_BY_NAME结构的指针。

 

当动态链接库SUBDLL(也就是新的AB.DLL)加载时,加载器(Loader)会检测到AB.DLL需要使用_B.DLL的。于是Loader会加载_B.DLL,然后将_B.DLL中的输出函数地址填充到响应的AB.DLL的输入节的位置的。

 

2)  代码节

DLL替代法的代码节和前面的两个方法大致一样。每个输出函数都有一个对应的HOOKAPIPROXY结构,这个结构中将函数名称的地址、原函数地址保存到一个堆栈中,然后所有的结构都跳转到一个PROXYFUN代码段中,在这个代码段中,需要保存函数名称和函数参数,并且要调用原来的函数。

 

但是DLL替代法的这两个结构与前面两个方法也有不一样的。因为前面两个方法替代时,所有的函数代码都已经加载进内存地址空间了,所以在堆栈中直接压入函数的地址和函数名称地址就可以了;而这种方法在DLL替代法中是不行的:因为在手工生成新的DLL时,并不知道这个DLL将在以后调用时,被加载到什么地址空间中,(虽然可以在OptionalHeader中指定BaseOfImage,但是所有熟悉PE结构的人都知道这个地址是不可靠的。)因此压入堆栈的地址不能是函数的地址和函数名地址,那么可以用什么方法表示呢?——我们用相对地址。绝对地址不行,我们只好使用相对地址。下面是HOOKAPIPROXY的结构:

 

typedef struct {

      byte PushCode1;  //0x68

      ULONG OrgAddr;   //偏移

      byte PushCode2;  //0x68

      ULONG NameAddr;  //偏移

      byte CallCode;    //0xe8 

      ULONG CallParam;    //CALL rel

}*PHOOKAPIPROXY, HOOKAPIPROXY;

 

使用这个结构,于是我们将OrgAddr(原函数地址,其实是存放原函数地址的指针)NameAddr(函数名称地址)表示成本HOOKAPIPROXY结构的下一个结构之间的偏移。而下一个结构地址是什么呢?CPU会在执行CALL执行是同时压入堆栈中。下面的代码段就是对这个结构利用的具体过程:

pHookApiProxy = pTextSection;

pDllName = (PCHAR)(pImportDesc + 2);

pFirstThunk = (PULONG)((ULONG)pDllName + strlen(pDllName) + 1);

 

while(*pFirstThunk)

{

//保存函地址的偏移

pHookApiProxy->PushCode1 = 0x68;

pHookApiProxy->OrgAddr = ((ULONG)pFirstThunk - (ULONG)pImportDesc + pImportRVA) - ((ULONG)(pHookApiProxy + 1) - (ULONG)pTextSection + pTextRVA);

 

//保存函称值

pHookApiProxy->PushCode2 = 0x68;

if (*pFirstThunk < 0x80000000)

pHookApiProxy->NameAddr = ((ULONG)((PIMAGE_IMPORT_BY_NAME)*pFirstThunk)->Name -(ULONG)pImportDesc + pImportRVA) - ((ULONG)(pHookApiProxy + 1) - (ULONG)pTextSection + pTextRVA);

else

pHookApiProxy->NameAddr = *pFirstThunk;

 

//CALL

pHookApiProxy->CallCode = 0xe8;

pHookApiProxy->CallParam = (ULONG)pTextSection + sizeof(HOOKAPIPROXY) * pSourExportDir->NumberOfFunctions - (ULONG)(pHookApiProxy + 1);

 

pFirstThunk ++;

pHookApiProxy ++;

}

 

在所哟的HOOKAPIPROXY结构后面是PROXYFUN代码段。这个代码段是对上面保存在堆栈中的地址进行分析并保存一些参数,注意:下面的代码段并没有像前面的方法一样将参数保存在文件之中,这个功能看样子要在以后实现了。

 

//函数HOOK API

_declspec(naked) void ProxyFun()

{

PCHAR pFunctionName;

ULONG Param[PARAMNO];

ULONG nParam;

ULONG Result;

 

_asm{

push ebp

mov ebp, esp

sub esp, __LOCAL_SIZE

push esi

push edi

push ebx

 

//先取出函数名称地址。

mov eax, [ebp + 4]

add eax, [ebp + 8]

mov pFunctionName, eax

 

//下面拷贝参数PARAMNO * 4

mov ecx, PARAMNO

mov esi, ebp

add esi, 16 + PARAMNO * 4 //4ebp 4hookapiproxy地址 + 4: NAME偏移 + 4: 原来的函数偏移,PARAMNO参数

 

nextmove:

mov eax, [esi]

push eax

sub esi, 4

dec ecx

jnz nextmove

 

mov nParam, esp

//调用原来的函数。

mov eax, [ebp + 4]

add eax, [ebp + 12]

call [eax]

mov Result, eax  //保存可能的返回值

 

mov ecx, esp

//先复原ESP

mov esp, nParam

add esp, PARAMNO * 4

//判断到底有几个参数。

sub ecx, nParam

shr ecx, 2

mov nParam, ecx

jcxz noparam   //若没有参数

 

//下面拷贝参数

lea esi, Param

mov edi, ebp

add edi, 20  //20 = 4(proxyFun地址) + 4(name偏移) + 4(原函数地址偏移) + 4(callor地址) + 4(EBP)

 

nextparam:

mov eax, [edi]

mov [esi], eax

add esi, 4

add edi, 4

loop nextparam

noparam: //若没有参数,便直接出来。

}

 

//准备返回

_asm{

 

mov eax, Result

mov ecx, nParam

shl ecx, 2

 

pop ebx

pop edi

pop esi

mov esp, ebp

pop ebp

 

add esp, 12  //4: EIP +  4:原来函数地址 + 4:FUNCTIONNAME PARAMNO参数

pop edx

add esp, ecx

push edx

ret

}

}

 

这就是代码节。

 

3)  输出节

 

输出节也比较简单,基本就是拷贝ORGDLL中的输出节,只是要改变所有输出函数的RVA地址。需要将每个输出函数的地址改成上面代码节中对应函数的HOOKAPIPROXY结构的RVA地址。

 

上面的三个节都生成了,将它们组合起来生成一个完整的DLL文件,就可以运行了。

 

4)资源节

但是正当对更多的DLL进行测试时,我们发现一个非常严峻的问题:基本上每个DLL都有一个资源节。而这些DLL在加载时,都要使用资源节中的信息。于是必须也要在新的SUBDLL中输出和原来的ORGDLL一样的资源节。首先可以拷贝ORGDLL的资源节,然后将ORGDLL中的RVA地址,改成SUBDLL中相应的RVA地址。

 

PE资源节中的三层结构修改RVA地址是比较方便的。不过让人不放心的是:假如在具体的信息中使用了RVA地址,则必须解析每个资源项,然后将其重新组织。不过我在程序中并没有解析每个结构,测试时并没有发生错误。但是这并不表示没有错。

 

考虑到解析每个资源项是一个浩大的工程,我们有一个替代的方法。直接拷贝原来的资源节到SUBDLL相同RVA地址处。与其他的节之间的空隙用0填充。这可能是解决这个问题的最好的方法了。

 

2.               优缺点

 

本方法和其他的API HOOK方法比较,最大的优点就是能完全截获并且不会有冲突现象。但是它最大的缺点就是:程序复杂。

 

新生成的DLL,若要替代原来的系统DLL,也是一个比较复杂的过程。我的做法是在DOS状态下将指定的DLL拷贝至系统目录。当然也有其他方法,有兴趣的读者可以参考下面的文章:

http://www.nsfocus.net/index.php?act=magazine&do=view&mid=2146(Win2000/XP上安静地替换正在使用的系统文件),希望它对你有所启发。

 

3.               总结

好了,终于到了结束的时候了。虽然它不够完美,但已接近了J 以后有时间再对它进行完善。聪明的你,若有什么想法,一定要给我回复,帮我来完善它。

 

 
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Delphi是一种编程语言,而DLL(Dynamic-Link Library)是一种模块化的文件格式,用于存储代码和数据,可以被多个应用程序共享。DLL注入是一种技术,它允许将DLL文件加载到正在运行的进程中,并使得该进程能够调用DLL中的函数和使用其中的数据。 在Delphi中实现DLL注入的方法有很多种。一种常见的方法是使用Windows API函数LoadLibrary和GetProcAddress。通过调用LoadLibrary函数,将DLL文件加载到进程的虚拟地址空间中。然后使用GetProcAddress函数获取DLL中导出函数的地址,并将其传递给需要调用的函数。通过这种方式,可以在运行时将DLL注入到目标进程中,并且通过调用DLL中的函数来扩展进程的功能。 DLL注入在实际应用中有多种用途。例如,可以使用DLL注入来为某个程序添加额外的功能或修改程序的行为。DLL注入还可以用于实现一些调试和监控的功能。通过注入DLL,可以截获程序的输入和输出,或者在程序执行某些指定的操作时进行额外的处理。 在Delphi中实现DLL注入需要一定的编程知识和技巧。需要考虑目标进程的架构和权限限制,以及如何管理注入的DLL的生命周期和资源管理。同时,还需要处理一些安全性和稳定性方面的问题,以确保注入过程不会对目标进程造成损害或崩溃。 总之,Delphi可以通过调用Windows API函数来实现DLL注入,从而扩展和修改进程的功能。但在实际应用中,需要考虑各种方面的问题,并且遵守相关的法律和规定,以确保注入操作的安全性和合法性。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值