代码移植:无限的代码重用






4.83/5 (27投票s)
使用现有的已编译机器码为项目添加功能。
- 下载示例1源代码 - 3.43 KB
- 下载示例2源代码 - 3.91 KB
- 下载示例3源代码 - 24.9 KB
- 下载示例4源代码 - 24.4 KB
- 下载示例5源代码 - 42.3 KB
- 下载汇编器源代码 - 2.02 KB
- 下载列表 - 校验和映射文件 - 1.27 KB
- 下载列表 - 校验和 - 1.28 KB
引言
本文将演示如何使用ASM源文件将已编译的机器码集成到现有项目中。汇编源文件将从已编译的机器码创建。此外,本文还将消除Imagehlp.dll的单线程安全限制,并展示将已编译的STDCALL过程转换为C-CALL汇编语言例程的技术。
示例一将呈现一个标准的控制台应用程序。该程序将两个数字相加,然后返回结果。该示例将介绍将遇到的几个障碍。示例二将使用第一个示例中Add()的已编译机器码来集成移植。
最后,示例三将使用来自imagehlp.dll的外部代码来补充PEChecksum程序。这将消除项目对imagehlp库的依赖性要求,并充分演示这些技术。PEChecksum程序已在Windows PE校验和算法分析中介绍。熟悉x86汇编、WinDbg和IDA Pro的读者应从示例三开始。
这些示例将使用标准的C语言。对于有兴趣重用已编译C++的读者,请参阅Paul Vincent Sabanal和Mark Vincent Yason在BlackHat 2007上的演示文稿Reversing C++。
优化
为了保持前两个示例的学术性,将禁用优化。通常,带有优化的发布代码缺乏本文示例所需的结构。发布示例将以禁用优化(/Od)的方式构建。请参阅图1。
|
图1:禁用优化
|
启用优化后,某些函数将被内联。例如,图2显示没有与源代码中存在的函数对应的调用。wmain
在0x00401000处进入。遇到的第一个函数调用位于0x40101D。0x40101D是调用cout
。在这种情况下,Add()
已被优化掉。
|
图2:由于优化导致的函数调用丢失
|
另一个不希望的优化是帧指针省略。为了便于理解示例,没有使用帧指针省略。帧指针通常通过下面所示的指令序列创建。缺少帧指针给原本学术性的练习增加了一点小麻烦。帧指针将在“堆栈、ESP和EBP”部分讨论。
全局变量
全局于进程的变量被放置在.data
段或(如果存在).bss
段中。这些段分别对应于已初始化和未初始化的数据段。通常,程序通过地址而不是基于相对寻址(如局部变量中使用的EBP
)来引用全局变量。例如,请注意图3中访问全局临时变量的代码。
|
图3:全局变量存储访问
|
使用的地址是0x00417000。在PE Browse中检查程序时,该变量在地址0x00417000处的.data
段下列出。请参阅图4。
|
图4:全局变量的分配
|
图5显示了未初始化全局变量的反汇编和PE头。地址0x00403374处的四个字节值是垃圾:CR、LF、[空格]和[空格]。
|
图5:未初始化全局变量的分配
|
push ECX
的含义将在“局部变量”部分进行探讨。
局部变量
局部变量存储在线程的堆栈上。图5显示了一个单独的ECX
压栈指令,尽管ECX
在main()
本身中并未被使用。这是编译器为变量i
创建局部存储的一种技术。与其发出一个三字节的sub ESP, 4
操作码(0x83 0xEC 0x04),不如使用一个一字节的push ECX
操作码(0x51)。
|
图6:单个变量的局部分配
|
当需要更多变量进行存储分配时,将使用sub ESP, n
(其中n是所需的字节数)。例如,图7显示了五个DWORD
变量的分配。编译器不发出五个push ECX
指令,而是发出一个sub ESP, 0x14
。
|
图7:多个变量的局部分配
|
堆栈、ESP和EBP
堆栈是线程在程序执行期间用作“临时区域”的内存区域。进程中的每个线程都有自己的堆栈。通常,人们将堆栈的内存视为一个从低地址开始并顺序移动到大地址的连续区域。这类似于在堆或数组中寻址——A[0]位于比A[63]低的地址。然而,与大多数内存访问操作不同,堆栈是向下增长的。
ESP是堆栈指针,由处理器维护。EBP(如果使用)由线程维护。当处理器遇到push n
指令时,将按以下顺序执行两个操作:
- 处理器将ESP减去机器字大小
- 将值n放在ESP处的堆栈上
这意味着ESP始终指向放在堆栈上的最后一个值。
|
图8:参数大小和调用堆栈
|
由于压入的值始终是机器字大小,压入五个连续的**字节**会在堆栈上占用20个字节(0x14)——即使局部分配只需要两个DWORD
(8个字节)。最后,对于截断的字节压栈,没有0扩展。寄存器高三字节中的任何内容都会作为函数参数出现,即使只对低字节感兴趣。这体现在指令序列mov al, byte ptr [a]; push eax
中。请参阅图8。
关于代码生成的最后一个评论是编译器对处理器中多个流水线的认识。与其重用eax
通过发出
mov al, byte ptr [a]
push eax
...
mov al, byte ptr [d]
push eax
...
编译器将旋转寄存器使用(eax
、ecx
、edx
),以便执行流水线保持满载。这种优化对于性能至关重要,因为没有可能因分支预测错误而导致执行停顿的分支。因此,我们可以预期看到以下情况:
mov al, byte ptr [a]
push eax
...
mov dl, byte ptr [d]
push edx
...
在创建基于地址引用的堆栈帧时,编译器将发出标准的指令对。该帧创建了一个定义明确的“函数上下文”。堆栈操作的典型序言是序列:
push EBP
mov EBP, ESP
上述序列在每次调用的函数中发出,以防止线程(函数)意外破坏堆栈指针(ESP)。相反,当函数退出时,通常会遇到堆栈和EBP的恢复。这是必需的,因为调用函数有不同的参考点来工作。
pop EBP
ret
应得出结论,EBP可以相对于函数(如果使用),而ESP相对于线程。这意味着当看到EBP-0xn时,函数是指函数内的局部存储。EBP+0xn表示线程正在访问由调用函数或调用链中的函数创建的局部变量。
OR和XOR
在查看反汇编时,遇到xor eax
、eax
和or eax, 0xFFFFFFFF
是很常见的。第一条指令等同于mov eax, 0
,第二条指令等同于mov eax, -1
。它们是生成代码的优化版本。通常,指令序列比其等效的近亲使用的空间更少。
代码移植1
代码移植1是其余示例将扩展的基础。由于编译器和链接器的行为,第一个示例比预期的要复杂一些。以下将详细介绍遇到的问题,并概述本文中使用的通用解决方法。
第一个示例的源代码如下。main()
调用Add()
,后者将两个数字相加。然后将结果显示在标准输出上。
int main( )
{
DWORD Augend = 32437; // 0x7EB5
DWORD Addend = 15369; // 0x3C09
DWORD Sum = 0; // 0xBABE
Sum = Add ( Augend, Addend );
cout << _T("Augend: ") << Augend << endl;
cout << _T("Addend: ") << Addend << endl;
cout << _T(" Sum: ") << Sum << endl;
return 0;
}
DWORD Add( DWORD Augend, DWORD Addend )
{
DWORD result = Augend + Addend;
return result;
}
遇到的第一个问题是存储布局不遵循源代码声明。请参阅图9。
|
图9:存储布局与源代码声明
|
变量按以下顺序声明和初始化:
被加数
加数
Sum
然而,内存中的布局是:
Addend
(EBP-0x04
) - 高内存Augend
(EBP-0x08
)Sum
(EBP-0x0C
) - 低内存
遇到的第二个问题是Add()
函数对变量的使用。Add()
创建一个临时变量(result
)并将两个值相加。然后将结果返回给main()
。由于Add()
接受两个参数——Augend
和Addend
——人们期望该函数操作EBP+0x04和EBP+0x08。EBP+0x04和EBP+0x08是预期的相对基地址,因为假设它们已被压入堆栈。对于临时结果,期望该值会通过以下两种方式之一返回:
- 通过使用
Sum
变量在EBP+0x00处 - 通过使用
EAX
然而,在Add()
中停止执行时,观察到了一种不同的情况。请参阅图10。
|
图10:存储布局与源代码声明
|
在执行Add()
的指令之前(但在进入函数之后),堆栈如下所示。请参阅图11。
|
图11:调用Add()后的堆栈布局
|
一旦函数执行完毕(但在执行返回指令之前),堆栈布局如图12所示。
|
||
图12:堆栈布局
|
表1解释了这些值相对于其地址(RVA)的含义:
项目 |
地址 |
值 |
注释 |
1 |
0x12FF5C |
BABE |
Add::result (由Add 创建) |
2 |
0x12FF60 |
0012FF7C |
main的EBP |
3 |
0x12FF64 |
00401028 |
返回地址 |
4 |
0x12FF68 |
7EB5 |
|
5 |
0x12FF6C |
3C09 |
|
6 |
0x12FF70 |
0 |
|
7 |
0x12FF74 |
7EB5 |
main::Augend |
8 |
0x12FF78 |
3C09 |
main::Addend |
表1:堆栈布局 |
代码移植2
示例二为代码移植1,但删除了C++源文件中的Add()
函数。WinDbg下代码移植1生成的Add()
代码如下所示。
00401cc0 55 push ebp
00401cc1 8bec mov ebp, esp
00401cc3 51 push ecx
00401cc4 8b4508 mov eax, dword ptr [ebp+8]
00401cc7 03450c add eax, dword ptr [ebp+0Ch]
00401cca 8945fc mov dword ptr [ebp-4], eax
00401ccd 8b45fc mov eax, dword ptr [ebp-4]
00401cd0 8be5 mov esp, ebp
00401cd2 5d pop ebp
00401cd3 c3 ret
移植
此时,供体(代码移植1)为受体(代码移植1)提供了20字节的代码。集成该功能的最简单方法是通过一个汇编文件(带有自定义构建步骤),该文件已添加到项目中。这种方法的好处是允许集成x32和x64例程,因为没有使用内联汇编。
还有其他方法可以集成移植。第一种方法是在WinDbg中直接编辑内存。这会创建一个临时示例。第二种方法是可以使用内联汇编来发出指令序列。其缺点是x64平台不支持内联汇编。第三种选择是修补。修补可执行文件通常属于病毒和破解者的范畴。修补留给读者作为练习。成功链接可执行文件需要两个更改:
- 将
Add()
更改为Addition()
- 将
Addition()
的函数原型更改为extern "C"
将Add()
更改为Addition()
是因为add
是汇编器(MASM)中的保留字。添加extern "C"
是由于名称修饰和链接错误LNK2001: unresolved external symbol "unsigned long __cdecl Addition(unsigned long,unsigned long)" (?Addition@@YAKKK@Z)。几乎未更改的C++文件如下所示:
extern "C" DWORD Addition( DWORD, DWORD );
int main( int argc, char* argv[] )
{
DWORD Augend = 32437; // 0x7EB5
DWORD Addend = 15369; // 0x3C09
DWORD Sum = 0;
Sum = Addition ( Augend, Addend );
cout << _T("Augend: ") << Augend << endl;
cout << _T("Addend: ") << Addend << endl;
cout << _T(" Sum: ") << Sum << endl;
return 0;
}
首先,在项目目录中创建一个名为Addition.asm的文件。然后,将该文件添加到项目中。请参阅图13。
|
||
图13:将ASM文件添加到项目
|
在Visual Studio的后续版本中,环境会询问是否使用masm.rules自定义构建规则。选择“确定”。请参阅图14。
|
||
图14:MASM自定义构建规则
|
如果自定义构建规则不可用,请添加以下内容作为自定义构建步骤:
- 调试命令行
- ml -c -Zi "-Fl$(IntDir)\$(InputName).lst" "-Fo$(IntDir)\$(InputName).obj" "$(InputPath)"
- 发布命令行
- ml -c "-Fl$(IntDir)\$(InputName).lst" "-Fo$(IntDir)\$(InputName).obj" "$(InputPath)"
- 输出
- $(IntDir)\$(InputName).obj
将ASM文件添加到项目后,项目将显示如图15所示。
|
图15:添加ASM文件
|
接下来,将以下内容添加到Addition.asm中。下面的代码演示了汇编过程的最低要求:
PUBLIC Addition
.486
.MODEL FLAT, C
.CODE
Addition PROC
push ebp ; Save Caller's EBP
mov ebp, esp ; Grab our Frame Reference
push ecx ; Storage for local 'result'
mov eax, dword ptr [ebp+8] ; Augend
add eax, dword ptr [ebp+0Ch] ; Addend
mov dword ptr [ebp-4], eax ; Temporary 'result'
mov eax, dword ptr [ebp-4] ; ??? Already in EAX
mov esp, ebp ; Clean 'push ECX' from stack
pop ebp ; Resotre Caller's EBP
ret
Addition ENDP
END ; End of .CODE
PUBLIC Addition
通知链接器过程Addition
可供任何模块使用。.486
是处理器指令。.MODEL
是简化段指令,它指示MASM为特定的内存模型生成代码。语言(“C”)告知汇编器调用约定。
.CODE
是另一个简化段指令。.CODE
开始代码段,而END
标记代码段的结束。还存在其他段,例如.DATA
。PROC
和ENDP
是过程指令,它们构成了Addition
函数的前后。其他过程将以类似的方式用不同的标签框起来。位于Addition
过程中的是从代码移植1复制粘贴的代码。
编译和链接后,首先注意到的是第二个可执行文件比第一个程序小0x200字节(一个段)。请参阅图14。
|
||
图14:可执行文件大小比较
|
然而,就main()
和Addition()
的执行而言,生成的代码保持不变。请参阅图15。
|
图15:WinDbg中的源代码分析
|
代码移植3
代码移植3将演示从外部可执行文件进行代码移植。具体来说,它将重用Imagehlp.dll中的CheckSumMappedFile()
。Imagehlp.dll是一个单线程库,因此这是一个改进该函数的机会。PE校验和算法的近乎完整的处理已在Windows PE校验和算法分析中介绍。
首先,下载PE校验和源代码。打开StdAfx.h并注释掉对imagehlp.dll的引用;并为CheckSumMemMapFile()添加原型。由于与Imagehlp.lib(即使未指定)链接,因此纳入了名称更改。
extern "C" {
PIMAGE_NT_HEADERS /*WINAPI*/ CheckSumMemMapFile(
PVOID BaseAddress,
DWORD FileLength,
PDWORD ExistingCheckSum,
PDWORD CalculatedCheckSum
);
}
由于名称修饰和链接错误LNK1190: invalid fixup found, type 0x0002,需要extern "C"
。另外请注意,缺少WINAPI
(__stdcall
的宏)。这是因为链接对象文件时出现链接错误。我怀疑这可能是打包问题,但尚未进一步调查。
LNK1190:找到无效的修复,类型0x0002
由于模型不再是STDCALL
,因此需要转换来自Imagehlp.dll的例程。三个最常见的问题是:
- STDCALL到C调用转换(堆栈清理)
- 添加局部帧引用(EBP)
- 伪影清理
将一个名为“CheckSum.asm”的汇编文件添加到项目中。在CheckSum.asm中创建三个过程:CheckSumMemMapFile
、_ChkSum
和_ImageNtHeader
。从imagehlp.dll获取CheckSumMappedFile()
和ChkSum()
的汇编代码,并将它们放在CheckSum.asm的相应过程中。暂时将_ImageNtHeader
留空。_ImageNtHeader
将是一个手动编码的替换,用于替代Imagehlp.dll对NTDLL.DLL的RtlpImageNtHeader()
的调用。
唯一需要PUBLIC
属性的过程是CheckSumMemMapFile
。这使得_ChkSum
和_ImageNtHeader
成为CheckSumMemMapFile
可以使用的“私有”过程。
或者,使用本文提供的函数的列表文件。文件是CheckSummMappedFile.listing和ChkSum.listing。列表文件是通过在WinDbg中检查原始CodeGraft.exe时进行复制粘贴操作创建的。请参阅图16。
|
图16:WinDbg复制粘贴
|
Labels
此时,列表包括内存地址、操作码和助记符。为遇到的任何跳转创建标签(将CheckSumMappedFile
更改为CheckSumMemMapFile
)。例如,在0x76c96f3b处是以下指令:
76c96f3b eb1d jmp imagehlp!CheckSumMappedFile+0x4f (76c96f5a)
跳转目标是0x76C9F5A。在该位置创建一个标签。请注意,标签名称基于反汇编提供的地址(“+”已更改为“_”)
76c96f3b eb1d jmp imagehlp!CheckSumMappedFile+0x4f (76c96f5a)
...
76c96f57 8b7de4 mov edi,dword ptr [ebp-1Ch]
CheckSumMemMapFile_0x4f:
76c96f5a 85c0 test eax,eax
最后,清理原始指令以与跳转到标签的指令保持一致。
76c96f3b eb1d jmp CheckSumMemMapFile_0x4f
构件
代码中存在一些看起来是伪影的区域。例如,检查0x76c96fc8。由于没有生成操作码的汇编助记符,因此使用DB
指令创建代码。请注意,在MASM中使用十六进制表示法时,数字前应加上“0”。DUP
是一个运算符,用于按请求的次数创建数据字节。
;; 76c96fc8 ff ???
;; 76c96fc9 ff ???
;; 76c96fca ff ???
DB 3 DUP(0FFh)
;; 76c96fcb ff426f inc dword ptr [edx+6Fh]
DB 0FFh, 042h, 06Fh
;; 76c96fce c9 leave
DB 0C9h
;; 76c96fcf 764b jbe imagehlp!MapFileAndCheckSumA+0x43 (76c9701c)
DB 076h, 04Bh
;; 76c96fd1 6f outs dx,dword ptr [esi]
DB 06Fh
;; 76c96fd2 c9 leave
DB 0C9h
;; 76c96fd3 7690 jbe imagehlp!CheckSumMappedFile+0x5a (76c96f65)
DB 076h, 090h
;; 76c96fd5 90 nop
;; 76c96fd6 90 nop
;; 76c96fd7 90 nop
;; 76c96fd8 90 nop
DB 4 DUP(090h)
最后,我们可以删除列表中不需要的内容。要删除列表中的项目,只需注释掉它即可。
;; push 10h
;; push offset `string'+0x3c (76c96fc8)
;; call _SEH_prolog (76c934b9)
mov esi,dword ptr [ebp+10h]
and dword ptr [esi],0
mov eax,dword ptr [ebp+0Ch]
shr eax,1
push eax
push dword ptr [ebp+8]
push 0
; 76c96f1e e856d6ffff call ChkSum (76c94579)
call _ChkSum
其他修复
原始代码在入口时安装了一个结构化异常处理程序。CodeGraft.exe代码将代码包装在处理程序中,因此可以跳过安装。通过注释掉上面的调用来实现处理程序的移除。这会创建一个堆栈不平衡,将在STDCALL到C调用转换中解决。
STDCALL到C调用转换
这一步需要最多的分析。这是因为缺少帧指针。因此,每个过程都将接收常规的:
push ebp
mov ebp, esp
一旦遇到额外的压栈,就必须注意代码/堆栈依赖关系。CheckSumMemMapFile
如下所示。大写字母的指令是为了堆栈管理而添加的。注释掉的行已被删除。最后,STDCALL
执行ret n
,其中n是对ESP
的调整。C-CALL使用标准的ret
,由被调用者执行堆栈调整。清理的结果可在CodeGraft4.zip中找到。
CheckSumMemMapFile
CheckSumMemMapFile PROC
;;push 10h
;;push offset `string'+0x3c (76c96fc8)
; Inspecting 0x76c96fc8 shows this is '-1'...
; push 0FFFFFFFFh
;; 76c96f08 e8acc5ffff call _SEH_prolog (76c934b9)
PUSH EBP ; Reference
MOV EBP, ESP
SUB ESP, 10h ; Space for 4 Temporary Variables
; T1: EBP-10h use in place of ebp-18h
; T2: EBP-0Ch use in place of ebp-1Ch
; T3: EBP-08h use in place of ebp-20h
; T4: EBP-04h use in place of ebp-04h
mov esi,dword ptr [ebp+10h] ; Header CheckSum Variable (Read From PE Header)
and dword ptr [esi],0 ; Header CheckSum = 0
mov eax,dword ptr [ebp+0Ch] ; File Size
shr eax,1 ; File Size = File Size / 2
push eax ; Parameter 3: File Size
push dword ptr [ebp+8] ; Parameter 2: Source (pBaseAddress)
push 0 ; Parameter 1: Partial Sum
; 76c96f1e e856d6ffff call _ChkSum@4(76c94579)
call _ChkSum
;; No Longer STDCALL
;; Clean the parameters from the Stack
ADD ESP, 0Ch
mov edi,eax ; EDI = Return from _ChkSum
mov dword ptr [EBP-0Ch],edi ; Sum
and dword ptr [EBP-04h],0 ; File Size = 0???
;; push dword ptr [ebp+8]
;; 76c96f2f e81ed2ffff call RtlpImageNtHeader (76c94152)
push [ebp+8] ; Source (pBaseAddress)
call _ImageNTHeader
ADD ESP, 4 ; Stack Maintenance - No longer STDCALL
mov dword ptr [EBP-08h],eax
or dword ptr [EBP-04h],0FFFFFFFFh ; EBP-04h = -1
jmp _CheckSum_0x4f
;; Retain the Noise Bytes
DB 5 DUP (090h)
xor eax,eax
inc eax
ret
;; Retain the Noise Bytes
DB 5 DUP (090h)
mov esp,dword ptr [EBP-10h] ; Local Temporary Storage
xor eax,eax
or dword ptr [EBP-04h],0FFFFFFFFh ; Local Temporary Storage
mov esi,dword ptr [ebp+10h] ; Local Temporary Storage
mov edi,dword ptr [EBP-0Ch] ; Local Temporary Storage
_CheckSumMemMapFile_0x4f:
test eax,eax
je _CheckSum_0x90
cmp eax,dword ptr [ebp+8]
je _CheckSum_0x90
mov cx,word ptr [eax+18h]
cmp cx,10Bh
je _CheckSum_0x6a
cmp cx,20Bh
jne _CheckSum_0xb5
_CheckSumMemMapFile_0x6a:
lea ecx,[eax+58h] ; Existing (Header) Checksum
mov edx,dword ptr [ecx] ; This routine removes the existing
mov dword ptr [esi],edx ; Checksum from the calculated value
xor edx,edx ;
mov dx,word ptr [ecx] ; Notice the use of Subtract with Borrow (sbb)
cmp di,dx ;
sbb esi,esi ; This is consistent with the Documnetation stating
neg esi ; 'Calculate the checksum of the file
add esi,edx ; with the the existing taken as 0.'
sub edi,esi
movzx ecx,word ptr [ecx+2]
cmp di,cx
sbb edx,edx
neg edx
add edx,ecx
sub edi,edx
_CheckSumMemMapFile_0x90:
mov ecx,dword ptr [ebp+0Ch]
test cl,1
je _CheckSumMemMapFile_0xa3
mov edx,dword ptr [ebp+8]
movzx dx,byte ptr [edx+ecx-1]
add edi,edx
_CheckSumMemMapFile_0xa3:
movzx edx,di
add edx,ecx
mov ecx,dword ptr [ebp+14h]
mov dword ptr [ecx],edx
;; 76c96fb8 e83cc5ffff call _SEH_epilog (76c934f9)
ADD ESP, 10h
POP EBP
;; No Longer STDCALL
;; ret 10h
ret
CheckSumMemMapFile ENDP
遇到的第一个更改是移除SEH。程序将操作包装在处理程序中,因此在此级别添加SEH机制已被放弃。下一个添加是引用push ebp
和mov ebp, esp
。
SUB ESP, 10h ; Space for 4 Temporary Variables
; T1: EBP-10h use in place of ebp-18h
; T2: EBP-0Ch use in place of ebp-1Ch
; T3: EBP-08h use in place of ebp-20h
; T4: EBP-04h use in place of ebp-04h
原始代码会访问EBP-0x1C
,而不保留堆栈空间。分析表明,堆栈需要容纳四个DWORD
。上述操作完成了任务。下面,手动堆栈调整完成了过程和EBP
的恢复。
ADD ESP, 10h
POP EBP
;; No Longer STDCALL
;; ret 10h
ret
正如Joe Partridge所指出的,原始移植错过了上面对ESI
的使用。由于寄存器被使用,必须保存和恢复它。在函数调用期间,必须保留EBX
、ESI
、EDI
和EBP
。EAX
、ECX
和EDX
是临时寄存器。
ChkSum
此过程基本未更改。由于该过程将压入堆栈的值(参数)移动到寄存器中,因此未创建局部帧引用。转换的明显效果是将ret 0Ch
更改为ret
,因为调用者现在正在清理堆栈。_ChkSum
可以在Windows PE校验和算法分析中详细检查。
_ChkSum PROC
push esi
mov ecx,[esp+10h] ; File Size / 2
mov esi,[esp+0Ch] ; Source (pBaseAddress)
mov eax,[esp+8] ; Partial Sum
shl ecx,1 ; File Size = File Size * 2
je _ChkSum_0x16e
test esi,2
je _ChkSum_0x2d
sub edx,edx
mov dx,[esi]
add eax,edx
adc eax,0
add esi,2
sub ecx,2
...
_ChkSum_0x16e:
mov edx,eax ;; Fold 32 bits in 16
shr edx,10h
and eax,0FFFFh
add eax,edx
mov edx,eax
shr edx,10h
add eax,edx
and eax,0FFFFh
pop esi
;; No longer STDCALL
;; ret 0Ch
ret
_ChkSum ENDP
_ChkSum
没有使用局部堆栈帧——它正在访问ESP
处的参数。
push esi
mov ecx,[esp+10h] ; File Size / 2
mov esi,[esp+0Ch] ; Source (pBaseAddress)
mov eax,[esp+8] ; Partial Sum
可以使用以下方法将其转换为使用局部帧引用(带有适当的序言):
PUSH EBP
MOV EBP, ESP
push esi
;; Stack Based
;; mov ecx,[esp+10h] ; File Size / 2
;; mov esi,[esp+0Ch] ; Source (pBaseAddress)
;; mov eax,[esp+8] ; Partial Sum
;; Frame Based
mov ecx,[EBP+10h] ; File Size / 2
mov esi,[EBP+0Ch] ; Source (pBaseAddress)
mov eax,[EBP+08h] ; Partial Sum
在上面的转换中,通过EBP
和ESP
引用值的偏移量是相同的。在这个例子中,这纯粹是巧合。情况可能并非总是如此。
_ImageNtHeader
_ImageNtHeader
是原始调用RtlpImageNtHeader()
的手动编码替换。该过程获取指向内存映射文件的指针,并为其加上IMAGE_DOS_HEADER
的e_lfanew
值。函数在成功时返回总和(指向IMAGE_NT_HEADER
的指针),在失败时返回NULL
。
_ImageNtHeader PROC
push ebp
mov ebp, esp
push esi
;; ESI = pBaseAdddress
mov eax, dword ptr[ ebp+08h ]
mov esi, eax
;; pBaseAdddress == NULL?
cmp esi, 0
je NULLRETURN
;; pBaseAdddress == 0xFFFFFFFF?
cmp esi, 0FFFFFFFFh
je NULLRETURN
;; MZ Signature
cmp byte ptr [ESI], 'M'
jne NULLRETURN
cmp byte ptr [ESI+01h], 'Z'
jne NULLRETURN
;; ESI is a pointer to IMAGE_DOS_HEADER
;; Grab the e_lfanew DWORD
;
; IMAGE_DOS_HEADER
; is 64 bytes (0x40) long
;
; e_lfanew occupies bytes
; IMAGE_DOS_HEADER[60-63]
;
; ESI+060 is _not_ Hex!!!
;
mov eax, esi
add eax, dword ptr[ ESI+060 ] ; value at e_lfanew
mov esi, eax
;; PE Signature
cmp byte ptr [ESI], 'P'
jne NULLRETURN
cmp byte ptr [ESI+01h], 'E'
jne NULLRETURN
cmp byte ptr [ESI+02h], 0
jne NULLRETURN
cmp byte ptr [ESI+03h], 0
jne NULLRETURN
;;
;; EAX = IMAGE_NT_HEADER pointer
;;
jmp CLEANSTACK
NULLRETURN:
mov eax, 0
CLEANSTACK:
pop esi
pop ebp
ret
_ImageNtHeader ENDP
代码移植5
第五个示例结合了前面的示例,并为CheckSumMemMapFile
和_ChkSum
添加了优化。
优化后的CheckSumMemMapFile
通过观察代码中无用的局部变量,可以进一步清理CheckSumMemMapFile
。此外,如果执行路径被发送到cmp cx,20Bh (IMAGE_NT_OPTIONAL_HDR64_MAGIC)
之后的“Abort”跳转,则可以消除伪影。清理后的例程可在示例四中找到。
CheckSumMemMapFile PROC
PUSH EBP ; Create Local Stack Frame
MOV EBP, ESP
PUSH ESI
mov esi,dword ptr [ebp+10h] ; Header CheckSum Variable (Read from PE Header)
and dword ptr [esi],0 ; Header CheckSum = 0
mov eax,dword ptr [ebp+0Ch] ; File Size
shr eax,1 ; File Size = File Size / 2
push eax ; Parameter 3: File Size
push dword ptr [ebp+8] ; Parameter 2: Source (pBaseAddress)
push 0 ; Parameter 1: Partial Sum
call _ChkSum
ADD ESP, 0Ch ; C-CALL, adjust stack
mov edi,eax ; EDI = Return from _ChkSum
push [ebp+8] ; Source (pBaseAddress)
call _ImageNTHeader
ADD ESP, 4 ; C-CALL, adjust stack
test eax,eax ; Return from _ImageNTHeader. Is it NULL?
je _CheckSum_0x90 ; Abort
cmp eax,dword ptr [ebp+8] ; pBaseAddress == _ImageNTHeader
je _CheckSum_0x90 ; Abort
mov cx,word ptr [eax+18h] ; IMAGE_OPTIONAL_HEADER.Magic
cmp cx,10Bh ; IMAGE_NT_OPTIONAL_HDR32_MAGIC
je _CheckSum_0x6a
cmp cx,20Bh ; IMAGE_NT_OPTIONAL_HDR64_MAGIC
jne _CheckSum_0x90 ; Abort
_CheckSum_0x6a:
lea ecx,[eax+58h] ; ADDRESSOF(IMAGE_OPTIONAL_HEADER.Checksum)
mov edx,dword ptr [ecx] ; IMAGE_OPTIONAL_HEADER.Checksum (dereference)
mov dword ptr [esi],edx ; Save To Callee parameter dwHeaderCheckSum
xor edx,edx ; EDX = 0
mov dx,word ptr [ecx] ; 2 bytes at IMAGE_OPTIONAL_HEADER.Checksum
cmp di,dx ; DI = result of _ChkSum
sbb esi,esi
neg esi
add esi,edx
sub edi,esi
movzx ecx,word ptr [ecx+2]
cmp di,cx
sbb edx,edx
neg edx
add edx,ecx
sub edi,edx
_CheckSum_0x90:
mov ecx,dword ptr [ebp+0Ch] ; File Size
test cl,1
je _CheckSum_0xa3
mov edx,dword ptr [ebp+8]
movzx dx,byte ptr [edx+ecx-1]
add edi,edx
_CheckSum_0xa3:
movzx edx,di
add edx,ecx
mov ecx,dword ptr [ebp+14h]
mov dword ptr [ecx],edx
POP ESI
POP EBP
ret
CheckSumMemMapFile ENDP
优化后的_ChkSum
可以在_ChkSum
的主求和循环中实现最后的“窥孔”优化。这个补充将利用处理器调度并行指令的能力。较小的求和(0x40个DWORD
、0x20个DWORD
、0x10个DWORD
等)将被跳过,因为它们在该例程的执行过程中最多只遇到一次。
由于该例程大部分时间都花在执行下面的循环(消耗0x80个DWORD
)上,进一步的优化将包括一次执行push ebx
和push edx
。一旦求和完成,在jne _ChkSum_0xe8
处退出之前执行相应的pop。
_ChkSum_0xe8:
PUSH EBX
PUSH EDX
XOR EBX, EBX
XOR EDX, EDX
add eax,dword ptr [esi]
adc EBX,dword ptr [esi+4]
adc EDX,dword ptr [esi+8]
adc eax,dword ptr [esi+0Ch]
adc EBX,dword ptr [esi+10h]
adc EDX,dword ptr [esi+14h]
adc eax,dword ptr [esi+18h]
adc EBX,dword ptr [esi+1Ch]
adc EDX,dword ptr [esi+20h]
adc eax,dword ptr [esi+24h]
adc EBX,dword ptr [esi+28h]
adc EDX,dword ptr [esi+2Ch]
adc eax,dword ptr [esi+30h]
adc EBX,dword ptr [esi+34h]
adc EDX,dword ptr [esi+38h]
adc eax,dword ptr [esi+3Ch]
adc EBX,dword ptr [esi+40h]
adc EDX,dword ptr [esi+44h]
adc eax,dword ptr [esi+48h]
adc EBX,dword ptr [esi+4Ch]
adc EDX,dword ptr [esi+50h]
adc eax,dword ptr [esi+54h]
adc EBX,dword ptr [esi+58h]
adc EDX,dword ptr [esi+5Ch]
adc eax,dword ptr [esi+60h]
adc EBX,dword ptr [esi+64h]
adc EDX,dword ptr [esi+68h]
adc eax,dword ptr [esi+6Ch]
adc EBX,dword ptr [esi+70h]
adc EDX,dword ptr [esi+74h]
adc eax,dword ptr [esi+78h]
adc EBX,dword ptr [esi+7Ch]
ADC EAX, EBX
ADC EAX, EDX
adc eax,0
POP EDX
POP EBX
add esi,80h
sub ecx,80h
jne _ChkSum_0xe8
...
校验和
- CodeGraft1.zip
- CodeGraft2.zip
- CodeGraft3.zip
- CodeGraft4.zip
- CodeGraft5.zip
- CheckSumAsm.zip
- CodeGraft.zip
- PEChecksum.zip
MD5: F8958E18071F9FFDE17286AC4243C514
SHA-1: ECE1C15BA469CCABFF922C453A26C0BD6593CEEF
MD5: B457ED277E848A106F20F94B1CE275F4
SHA-1: 241E70C0660A652D4015C7787850DBA0684F62F8
MD5: 5DD1A1B16D47385577C8D7FF1DD49041
SHA-1: 98C5EFE3F2EA6CF5214C8A739FF99E1D60FD56EA
MD5: AC3800CF5714922D9930D7A2EAFCBD5C
SHA-1: 273C14760D4A438518513677424CE9A54E29294E
MD5: 8F8B25301DB6C77683FF8918CD679B21
SHA-1: 95D52336EE4EEC1A46D00ACD2BBC10C79489D41B
MD5: 35EA1BBC97F1A23E8F0B7D943BA0F9F3
SHA-1: B0BE1D8BF772958191114A55FFB343B5B829E240
MD5: c0d4468002f6ff82228323dd226093b5
SHA: 42bf918481881819fa8a1cc8f519303185964e15
MD5: C0D4468002F6FF82228323DD226093B5
SHA-1: 42BF918481881819FA8A1CC8F519303185964E15
修订
- 2008年6月3日:总体修订和文章格式设置。
- 2007年11月20日:Bug修复 - 为
CheckSumMemMapFile
添加了ESI保存。 - 2007年11月5日:初始发布。