在编写了自己的反汇编程序后,我现在希望使其汇编列表更具可读性,例如来自(人工)示例
push ebp
mov ebp, esp
sub esp, 10h
mov eax, dword ptr [55431824h]
imul eax, dword ptr [ebp+8]
add eax, dword ptr [ebp+0ch]
mov dword ptr [ebp-4], eax
mov eax, dword ptr [ebp-4]
leave
ret 10h
通过(>
标记一个自动识别的序言/尾声):
>push ebp
>mov ebp, esp
>sub esp, 10h
mov eax, dword ptr [_global_55431824]
imul eax, dword ptr [arg_0]
add eax, dword ptr [arg_4]
mov dword ptr [local_4], eax
mov eax, dword ptr [local_4]
>leave
>ret 10h
向
eax = 10
eax *= arg_0
eax += arg_4
local_4 = eax
eax = local_4
return
此时,每条伪指令仍与其原始的反汇编表示相关联。打印时,我可以扫描某些序列,因此我将前两行替换为
eax = 10 * arg_0
然后跳过打印下一行。但是,我能做到的程度是有限的。连接下一个操作,这将导致
eax = 10 * arg_0 + arg_4
需要向前看2个指令,并且将只为这个特定组合的工作mov
,imul
和add
,同时确保相同的目的地寄存器仍然有针对性的和中间的指令对其他寄存器没有持久的影响。
目标是最终得到这样的结果:
local_4 = 10 * arg_0 + arg_4
eax = local_4
return
其中显然eax
在 a 之前专门分配了一个值return
,所以最后一行可以是
return eax
最后整个建筑可以折叠成
return 10 * arg_0 + arg_4
(这非常接近我在最初的普通 C 程序中开始的内容)。我正在努力处理复合线的内部表示,例如最后一条。否则非常可靠的反编译器页面backerstreet.com/creating_statements在原始汇编器和类 C 复合语句之间随意切换:
if(!expr1 && !expr2)
...
无需解释此中间步骤如何存储在内存中并可以创建输出字符串。
我应该查看哪种类型的中间存储形式,其中 (a) 可以从原始反汇编指令构建,(b) 可以组合和重新排列其元素,以及 (c) 可以转换为人类可读的输出,例如最后一行?
值得一提的是,我的目标并不是创建最终的通用反编译器 :) 我的测试输入是用 Delphi Pascal 的一个古老版本(很可能是 1993 年之前的)编译的,但它的程序集并没有真正优化并且相当可读首先,我仍然想加倍努力,让我的计算机做它最擅长的事情,并使代码更容易理解。
更长的真实世界示例
下面是一些实际的拆解。基本块编号后跟一个支配位集(loops
十六进制编号;我生成了它们但尚未使用该数据)。的ENTRY
和EXIT
数字将基本块再次和用于生成功能的.DOT图。
; ## BLOCK 0; loops: 01; EXIT to 1, 4
401966A8 ( 0) 8B 15 42201590 mov edx,dword ptr [42201590]
401966AE ( 0) 8B 12 mov edx,dword ptr [edx]
401966B0 ( 0) 80 BA 39 01 00 00 02 cmp byte ptr [edx+139h],2
401966B7 ( 0) 75 11 jnz label_4
; ## BLOCK 1; loops: 03; ENTER from 0; EXIT to 2, 4
401966B9 ( 0) 83 78 24 02 cmp dword ptr [eax+24h],2
401966BD ( 0) 7D 0B jge label_4
; ## BLOCK 2; loops: 07; ENTER from 1; EXIT to 3=RET
401966BF ( 0) BA 02 00 00 00 mov edx,2
401966C4 ( 0) 2B 50 24 sub edx,dword ptr [eax+24h]
401966C7 ( 0) 8B C2 mov eax,edx
; ## BLOCK 3 (epilog); loops: 0F; ENTER from 2
401966C9 >C3 retn
; ## BLOCK 4; loops: 11; ENTER from 0, 1; EXIT to 5, 7
label_4:
401966CA ( 0) 8B 15 42201590 mov edx,dword ptr [42201590]
401966D0 ( 0) 8B 12 mov edx,dword ptr [edx]
401966D2 ( 0) 80 BA 39 01 00 00 01 cmp byte ptr [edx+139h],1
401966D9 ( 0) 75 0D jnz label_7
; ## BLOCK 5; loops: 31; ENTER from 4; EXIT to 6, 7
401966DB ( 0) 83 78 24 00 cmp dword ptr [eax+24h],0
401966DF ( 0) 75 07 jnz label_7
; ## BLOCK 6; loops: 71; ENTER from 5; EXIT to 8=RET
401966E1 ( 0) B8 01 00 00 00 mov eax,1
401966E6 ( 0) EB 02 jmp label_8
; ## BLOCK 7; loops: 91; ENTER from 4, 5; EXIT to 8=RET
label_7:
401966E8 ( 0) 33 C0 xor eax,eax
; ## BLOCK 8 (epilog); loops: 0111; ENTER from 6, 7
label_8:
401966EA >C3 retn
401966EB align 4
这是 .dot 图像;clear if
s 是黄色的,节点包含它们的支配位集,所以我可以尝试理解它们(一个待办事项)。这解释了流程,但您看不到每个节点代表多少代码。
反汇编被解析为以下伪代码。一些笔记是manually added
.
; #### 已解析 401966A8(0)edx = Main.UnitList@20551314; 401966AE(0)edx =(双字)[edx]; 401966B0(0)如果((字节)[edx + 139h]!= 2)转到label_4; 401966B9(0)如果((dword)[eax + 24h] >= 2)转到label_4; 401966BF(0)edx=2; 401966C4(0)edx-=(双字)[eax + 24h]; 401966C7(0)返回edx;MOV EAX,.. 后面跟着一个退出块 返回 ; .. 而这一行是由实际的退出块生成的 标签_4: 401966CA(0)edx = Main.UnitList@20551314; 401966D0(0)edx =(双字)[edx]; 401966D2(0)如果((字节)[edx + 139h]!= 1)转到label_7; 401966DB(0)如果((dword)[eax + 24h]!= 0)转到label_7; 401966E1(0)eax=1; 401966E6(0)返回; 这是一个跳转到退出块,所以错过了 标签_7: 401966E8(0)eax=0;这是一个 XOR,而不是一个 MOV,所以错过了 标签_8: 返回 ;
冗长的损失已经值得付出努力:从 21 行代码到 16 行,即使在RET
两次失败之前检查 EAX 是否被正确写入的前瞻也是如此。
然而,很明显,两个连续的if
s at401966B0
可以用 OR 组合成一个。反转条件,整个块可以是一个单一的if
并支撑到return
, 删除label_4
。可以将from开始
的操作连接到单个语句中。
此外,at 条件可以合并为一个条件并放入一个块中。因为在它的末尾有一个,所以那里没有必要。手动重建:edx
401966BF
return
if
401966D2
if
return
else
401966A8 ( 0) edx = Main.UnitList@20551314 ;
401966AE ( 0) edx = (dword)[edx] ;
401966B0 ( 0) if ((byte)[edx + 139h] == 2 &&
401966B9 ( 0) (dword)[eax + 24h] < 2)
{
401966BF ( 0) return 2 - (dword)[eax + 24h] ;
}
401966CA ( 0) edx = Main.UnitList@20551314 ;
401966D0 ( 0) edx = (dword)[edx] ;
401966D2 ( 0) if ((byte)[edx + 139h] == 1 &&
401966DB ( 0) (dword)[eax + 24h] == 0)
{
401966E1 ( 0) return 1 ;
}
401966E8 ( 0) return 0 ;
将原来的 21 行减少到仅仅 11 行。此外,寄存器值传播解决了对全局类的引用Main.UnitList
。我手动为其元素创建了名称;401966A8
并向前折叠成
401966B0 ( 0) if (Main.UnitList.flag == 2 &&
401966B9 ( 0) Main.UnitList.count < 2)
这使得代码实际上具有可读性。
eax
在写入之前访问在这里不是错误。该函数是一个类函数,并eax
指向一个类实例,因此(dword)[eax + 24h]
读取结构的数据成员。Main.UnitList
是一个指向全局声明类实例的指针,因为它指向 BSS。