通用且简单的扩充和改造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
开发工具: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
相关文章推荐
- C# ASP.NET 最常用的通用权限的3个方法例子展示(每个功能一行代码实现)
- Redrain 通用菜单控件使用方法和说明(增加动态添加功能、附源码和demo)
- AVR简单通用的串口配置方法
- 一个函数返回两值的通用实现方法(简单,备忘)
- 实用知识:摇一摇功能的方法使用(真简单??)
- 开启虚拟主机配置php.ini功能的通用方法(所有支持SSH的主机都可以)
- 【Unity】简单聊天室功能的实现方法教程
- iOS简单登录LoginViewController、注册RegisterViewController等功能实现方法
- 简单数据库查询通用方法
- 使用Java实现简单的server/client回显功能的方法介绍
- 扩充PE文件功能
- javaCV开发详解之7:让音频转换更加简单,实现通用音频编码格式转换、重采样等音频参数的转换功能(以pcm16le编码的wav转mp3为例)
- 移动硬盘上安装WIN PE最简单的方法
- IOS开发简单登录LoginViewController、注册RegisterViewController、UcenterViewController功能实现方法
- JavaScript的String类型replace()方法介绍和使用replace()方法实现简单html模板替换功能
- 通过Object.prototype扩充功能,增加对所有对象都可用的方法
- 移动硬盘上安装WIN PE最简单的方法http://dzh.mop.com/topic/main/readSubMain_6459066_0.html
- Android——非常好用、简单实用的通用dialog实现方法
- python 之简单聊聊 析构函数和特殊的__call__方法
- Summernote实现图片上传功能的简单方法