您的位置:首页 > 其它

通用且简单的扩充和改造PE中的call的功能的方法

2007-03-21 09:07 405 查看
技术基础:会编写加壳程序、了解PE结构、X86指令系统。
开发工具:MASM、VC++

1、我们有时候在改造一个PE的时候,对其中的某些call要做大量的改动,这时
候,往往通过手动增加一个节,在这个节中完成这个call的改造,再返回原来的地方继续执行。此法虽然可行,但工作量实在太大,且调试十分不方便。

2、“通用简单的方法”之实现原理:
使用jmp far address 和 call far address的机器码长度相同,且仅仅第一字节不同的原则,jmp第一字节是0xE9,而call第一字节是0xE8,后面4字节相同。
这种方法就是:通过给PE加个小壳,同时在加壳的时候,将PE中要改造的call 修改成调用外壳中的call,在外壳中的call中调用原来PE中的call,而外壳中的call我们在编写的时候较容易实现功能扩充和改造。
在外壳中的call中,我们有两种扩充功能的方式:
(1)在调用原来PE中的call之前扩充功能;
(2)在调用原来PE中的call之后扩充功能;
扩充功能可以直接在外壳call中实现,但较难调试和复杂。
简单通用的方法是这个外壳中的新call仅仅完成:
1、加载一个外部补丁DLL;
2、调用这个外部补丁DLL中的函数,完成原来PE中的call的功能扩充和改造。
3、调用原来PE中的call的;
是先调用原来PE中的call还是后调用,视具体需要决定。这样我们就可以很简单的写扩充的功能代码了,因为这个外部补丁DLL可以用高级语言来写,且调试十分简单。

3、外壳中函数的具体实现:
(一) 首先,在外壳中设定几个变量:
(1) 保存外部DLL的各个函数地址的变量(DWORD):
pfnExtPatchFun_1--pfnExtPatchFun_n,n为多大具体看要改造PE中几个函数。
(2) 保存原来PE中的call的“地址差”变量(DWORD):
dwOrgCallDifference_1--dwOrgCallDifference_n,n为多大具体看要改造PE中几个函数。
dwOrgCallDifference_x = 原来call的机器码的后4个字节(双字)。
(3) 保存原来PE中的call的RVA地址的变量(DWORD):
dwOrgCallRVA_1--dwOrgCallRVA_n,n为多大具体看要改造PE中几个函数。
(4) 保存外部DLL的hModule的变量(DWORD):
hExtPatchDll。
;-----------------------------------------------------------------
举例说明:
如:原来PE的ImageBase=0x00400000,且有下面代码(IDA显示):
.
.
004010CB 50 push eax
004010CC 57 push edi
004010CD E8 CE 08 00 00 call sub_4019A0 ;需要改造的call,注意机器码后4字节
004010D2 85 C0 test eax, eax
.
.
则:dwOrgCallRVA_1 = 004010CD - 0x00400000 = 0x000010CD
dwOrgCallDifference_1 = 0x000008CE
;------------------------------------------------------------------
;
(二) 在外壳程序中编写这些call,需要改造几个函数,就有几个call,格式如下:
;*****************************************************************************
;---------------------------------------------------;
; 外壳中的call,用于替换原来PE中的call ;
;---------------------------------------------------;
align 4
wjq_API_SMC_Label_1:
GeneralPEShellCall_1 proc
;{
;原PE中的call的入口参数,我们通过堆栈传递到Dll中的新call,此时需注意EBP在本call中不要改变。
@nPara1 EQU [ebp+0ch] ;原来PE中的Call的参数,也可以通过esp取得
@nPara2 EQU [ebp+8h]
;
pusha
call GetPatchDllFunctions ;获取外部DLL中的所有函数地址并保存
call GetAddressDifference ;获取地址差
.if [pfnExtPatchFun_1+eax] != 0 ;如果功能函数1实现了,就调用
push @nPara1 ;原来PE中的Call的参数
push @nPara2
call [pfnExtPatchFun_1+eax] ;调用外部DLL中的补丁call
.endif
;------------调用原来的call-------------------------
call GetAddressDifference ;获取地址差
lea esi,[TempCall_1+eax] ;现在call的VA
sub esi,[Image_Base+eax] ;转换成RVA
sub esi,[dwOrgCallRVA_1+eax] ;减去原来PE中的call的RVA = 两个call的地址差
;
mov ebx,[dwOrgCallDifference_1+eax] ;原来call xxxxxxxx 指令的偏移差
.if ebx < 0 ;负数,原来call向上调用
add ebx,esi
.else ;正数,原来call向下调用
sub ebx,esi
.endif
;
mov [TempCallDifference_1+eax],ebx ;修正后的差值
popa
;
;下面等同于一个 jmp far xxxxxxxx 语句,调用原来的call
;这里之所以采用这种方式,而不是用call,是为了通用,因为PE中的call有些是__stdcall,有些是___cdecl,如果一概采用call实现,要考虑出栈问题,不是很通用了。
TempCall_1 db 0E9h
TempCallDifference_1 dd 0
ret
;}
GeneralPEShellCall_1 endp

;---------------------------------------------------------------------
; 获取外部DLL中的所有函数地址并保存
; 此函数中使用的W_LoadLibraryA等W_开始的函数,是外壳自建引入表是实现的
;---------------------------------------------------------------------
GetPatchDllFunctions proc
pusha
call GetAddressDifference
mov edx,eax
.if [hExtPatchDll+edx] == 0 ;如果已经装载了,就直接返回。
;
lea ebx,[pExtPatchDll+edx]
push edx ;保存地址差
;--------------------------
push ebx
call [W_LoadLibraryA+edx]
;--------------------------
pop edx ;恢复地址差
mov [hExtPatchDll+edx],eax
.if eax != 0
;-------------;
; function_1 ;
;-------------;
push edx ;保存地址差
lea ebx,[pExtPatchFun_1+edx]
push ebx ; 功能名的字符偏移
push dword ptr [hExtPatchDll+edx] ; 模块的句柄
call [W_GetProcAddress+edx] ; 调用Kernel32!GetpProcAddress以获得功能调用地址
pop edx ;恢复地址差
mov [pfnExtPatchFun_1+edx],eax
;-------------;
; function_2 ;
;-------------;
push edx ;保存地址差
lea ebx,[pExtPatchFun_2+edx]
push ebx ; 功能名的字符偏移
push dword ptr [hExtPatchDll+edx] ; 模块的句柄
call [W_GetProcAddress+edx] ; 调用Kernel32!GetpProcAddress以获得功能调用地址
pop edx ;恢复地址差
mov [pfnExtPatchFun_2+edx],eax
.
.
;-直到取得n个函数地址。
.endif
.endif
popa
ret
GetPatchDllFunctions endp

;-------------------------------------------------------------------
;该函数返回:地址差
;-------------------------------------------------------------------
align 4
GetAddressDifference proc
call @F
@@:
pop eax ;获得实际偏移
sub eax, offset @B ;减去偏移=地址差
ret
GetAddressDifference endp
;*****************************************************************************

4、加壳部分的实现:
相对来说,加壳部分的实现加简单,只要按照上面计算call的偏移差的方法,计算出新的偏移差,改写这个call的机器码的后4字节为新的偏移差,使其调用我们外壳中的call就可以了。
;
具体计算过程如下:
mov eax,dwOrgCallRVA_1 ;原来PE中call处的RVA,对应上面例子就是:dwOrgCallRVA_1 = 0x000010CD
mov edx,Sheller_RVA ;外壳程序入口的RVA
add edx,API_CALL_ADJUST_1 ;API_CALL_ADJUST_1 = wjq_API_SMC_Label_1 - Sheller_Start
sub edx,eax ;差值
sub edx,5 ;减去Call指令的长度5
mov eax,edx ;此时后的eax就是要修改的原PE中的call的后4字节(偏移差),对应上面例子就是:004010CD E8 CE 08 00 00 的 CE 08 00 00,变为双字就是0x000008CE,这个值改为eax中的值。

需要注意的是:API_CALL_ADJUST_1的计算要使用wjq_API_SMC_Label_1标号,而不是函数名字GeneralPEShellCall_1。

5、外部DLL的编写格式(使用高级语言,快速编写,十分方便的调试):
//具体参数格式,请根据原来PE中的call的入口参数确定
//调用约定请参照外壳中的call,主要看出栈规则,我用的是__stdcall,也可以用___cdecl,主要根据外壳call中的使用方式,这个不能错,否则堆栈就乱套了。
extern "C" DWORD __stdcall ExtPatchFun_1(DWORD nPara1,DWORD nPara2)
{
//实现功能扩充。。。
return;
}
//
extern "C" DWORD __stdcall ExtPatchFun_2(DWORD nPara1,DWORD nPara2)
{
//实现功能扩充。。。
return;
}

6、关于补丁DLL的卸载
我们在外壳call中只加载外部DLL,但不去卸载。这会不会有问题呢?回答是否定的,因为PE在结束进程时候,会自动卸载所有加载到本进程的DLL,所以,不用关心这个DLL不会卸载掉。而且,我们通过只加载方式使用这个DLL,会给我们带来很大方便,因为,只要在一处加载了这个外部DLL,我们就可以在原PE的任何地方调用其中的函数。

7、上面介绍的是先调用功能扩展部分,再调用原来的call。反之实现的方法类似,可自行发挥。

排版不是很好,凑合着看吧。。呵呵。
Spring.W
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐