反编译与反汇编简介
反编译器以可读形式表示可执行二进制文件 span>。 更准确地说,它将二进制代码转换为软件开发人员可以读取和修改的文本。 软件安全行业依靠这种转换来分析和验证程序。 该分析是对二进制代码执行的,因为传统上源代码(软件的文本形式)不可用,因为它被认为是商业秘密。
将二进制代码转化为文本形式的程序一直存在。将处理器指令代码简单地一对一映射成指令口令是由反汇编器完成的。市场上有许多反汇编程序,包括免费的和商业的。最强大的反汇编程序是我们自己的IDA Pro。它可以处理大量处理器的二进制代码,并且具有开放的架构,允许开发人员编写附加的分析模块。
反编译器在一个非常重要的方面与反编译器不同。 两者都生成人类可读的文本,而反编译器生成更高级的文本 span>,它更简洁 span>和更容易阅读 span>。
与低级汇编语言相比,高级语言表示具有以下优点:
- 它很简洁
- 它是结构化的
- 它并不要求开发人员了解汇编语言
- 它能识别低级成语并将其转换为高级概念
- 它的混乱程度较低,因此比较容易理解
- 它的重复性较低,不易分散注意力
- 它使用了数据流分析
我们来详细考虑一下这几点。
通常反编译器的输出比反汇编器的输出短五到十倍。例如,一个典型的现代程序包含400KB到5MB的二进制代码。反汇编器对这样一个程序的输出将包括大约5-100MB的文本,这可能需要几周到几个月的时间才能完全分析。由于经济原因,分析人员不可能在一个程序上花费这么多时间。
典型程序的反编译器输出为400KB至10MB。 尽管这仍然是很大的阅读和理解量(大约是一本厚书的大小),但分析时间所需的时间除以10或更多。
第二个大区别是反编译器输出是结构化的 span>。 代替使每行与所有其他行都相似的线性指令流,对文本进行缩进以使程序逻辑明确。 控制流构造 span>(例如条件语句,循环和开关)都标记有适当的关键字。
由于反编译器的输出是高级别的,因此它比反汇编程序的输出更易于理解。 为了能够使用反汇编程序,分析人员必须知道目标处理器的汇编语言。 主流程序员并不将汇编语言用于日常任务,但如今几乎每个人都使用高级语言。 反编译器消除了典型的编程语言和输出语言之间的差距。 更多的分析师可以使用反编译器而不是反汇编程序。
反编译器将汇编级习语转换为高级抽象。有些习语的分析时间可能相当长,而且很耗时。以下是一行代码
x = y / 2;
可以被编译器转化为一系列20-30条处理器指令。一个有经验的分析人员至少需要15-30秒的时间才能识别出这种模式,并在心里将其替换成原来的行。如果代码中包含许多这样的成语,分析师就不得不做笔记,并将每个模式用它的简短表示法标记出来。所有这些都极大地减慢了分析速度。反编译器消除了分析人员的这种负担。
要分析的汇编器指令数量巨大。它们看起来彼此非常相似,并且它们的模式非常重复。读取反汇编程序输出与读取引人入胜的故事完全不同。在编译器生成的程序中,95%的代码将非常无聊,无法阅读和分析。对于分析师来说,将两个看起来相似的代码片段混淆起来非常容易,而在输出中就完全迷路了。这两个因素(文本的大小和无聊的性质)导致以下现象:从未对二进制程序进行全面分析。分析人员尝试使用一些启发式方法和一些自动化工具来定位可疑部分。当程序很小或分析人员花费不成比例的大量时间进行分析时,就会发生异常。反编译器可以缓解这两个问题:它们的输出较短且重复性较低。输出仍然包含一些重复,但是可以由人管理。此外,这种重复可以通过自动化分析来解决。
二进制代码中的重复模式需要一个解决方案。一个显而易见的解决方案是利用计算机来寻找模式,并以某种方式将其缩减为更短的、更容易为人类分析人员所掌握的东西。一些拆解器(包括IDA Pro)提供了一种自动分析的手段。然而,可用的分析模块数量一直不多,所以重复的代码仍然是一个问题。主要原因是,识别二进制模式是一项出人意料的困难任务。任何 "简单 "的动作,包括基本的算术运算,如加法和减法,都可以用无穷无尽的方式用二进制形式表示。编译器可能用加法运算符来做减法,反之亦然。它可以在内存中的某个地方存储常数,并在需要时加载它们。它可以利用经过一些操作后,可以证明寄存器的值是一个已知的常数,只需使用寄存器而不重新初始化它。使用方法的多样性解释了可用的分析模块数量少的原因。
有了反编译器,情况就不同了。自动化变得更加容易,因为反编译器为分析人员提供了高层次的概念。许多模式会被自动识别并被抽象的概念所取代。由于反编译器引入了形式化的概念,剩余的模式可以很容易地被检测出来。例如,函数参数和调用约定的概念被严格地形式化。反编译器使我们极易找到任何函数调用的参数,即使这些参数的初始化距离调用指令很远。如果使用反汇编器,这是一项艰巨的任务,它需要单独处理每种情况。
反编译器,与反汇编器相反,对输入进行广泛的数据流分析。这意味着,诸如 "变量在哪里初始化?"和 "这个变量被使用了吗?"这样的问题可以立即得到回答,而不需要对函数进行任何广泛的搜索。分析师经常提出并回答这些问题,有了答案就能立即提高他们的工作效率。
反汇编和反编译的并列比较
下面你会发现反汇编和反编译输出的并列比较。以下是一些例子:
此页面上显示以下示例:
- 一分为二 li>
- 够简单吗? li>
- 我的变量在哪里? li>
- 算术不是火箭科学 li>
- 示例窗口过程 li>
- 短路评估 li>
- 内联字符串操作 li>
一分为二
只要注意大小上的差异! 尽管反汇编输出不仅需要您知道编译器会为有符号除法和模运算生成此类复杂的代码,而且还必须花费时间来识别模式。 不用说,反编译器使事情变得非常简单。
汇编代码
; =============== S U B R O U T I N E =======================================
; Attributes: bp-based frame
; mod_ll(long long)
public __Z6mod_llx
__Z6mod_llx proc near
var_10 = dword ptr -10h
var_C = dword ptr -0Ch
arg_0 = qword ptr 8
push ebp
mov ebp, esp
push ebx
sub esp, 0Ch
mov ecx, dword ptr [ebp+arg_0]
mov ebx, dword ptr [ebp+arg_0+4]
mov eax, ecx
mov edx, ebx
mov eax, edx
mov edx, eax
sar edx, 1Fh
sar eax, 1Fh
mov eax, edx
mov edx, 0
shr eax, 1Fh
add eax, ecx
adc edx, ebx
shrd eax, edx, 1
sar edx, 1
mov [ebp+var_10], eax
mov [ebp+var_C], edx
mov eax, [ebp+var_10]
mov edx, [ebp+var_C]
shld edx, eax, 1
add eax, eax
sub ecx, eax
sbb ebx, edx
mov [ebp+var_10], ecx
mov [ebp+var_C], ebx
mov eax, [ebp+var_10]
mov edx, [ebp+var_C]
add esp, 0Ch
pop ebx
pop ebp
retn
__Z6mod_llx endp
伪代码
__int64 __cdecl mod_ll(__int64 a1)
{
return a1 % 2;
}
够简单吗?
类似的问题
- 函数可能的返回值有哪些?
- 该函数是否使用任何字符串?
- 该函数的作用是什么?
几乎可以在看反编译器输出的瞬间得到答案。不用说,它看起来更好,因为我重命名了局部变量。在反汇编器中,很少重命名寄存器,因为它隐藏了寄存器的用途,会导致混乱。
汇编代码
; =============== S U B R O U T I N E =======================================
; int __cdecl sub_4061C0(char *Str, char *Dest)
sub_4061C0 proc near ; CODE XREF: sub_4062F0+15p
; sub_4063D4+21p ...
Str = dword ptr 4
Dest = dword ptr 8
push esi
push offset aSmtp_ ; "smtp."
push [esp+8+Dest] ; Dest
call _strcpy
mov esi, [esp+0Ch+Str]
push esi ; Str
call _strlen
add esp, 0Ch
xor ecx, ecx
test eax, eax
jle short loc_4061ED
loc_4061E2: ; CODE XREF: sub_4061C0+2Bj
cmp byte ptr [ecx+esi], 40h
jz short loc_4061ED
inc ecx
cmp ecx, eax
jl short loc_4061E2
loc_4061ED: ; CODE XREF: sub_4061C0+20j
; sub_4061C0+26j
dec eax
cmp ecx, eax
jl short loc_4061F6
xor eax, eax
pop esi
retn
; ---------------------------------------------------------------------------
loc_4061F6: ; CODE XREF: sub_4061C0+30j
lea eax, [ecx+esi+1]
push eax ; Source
push [esp+8+Dest] ; Dest
call _strcat
pop ecx
pop ecx
push 1
pop eax
pop esi
retn
sub_4061C0 endp
伪代码
signed int __cdecl sub_4061C0(char *Str, char *Dest)
{
int len; //
int i; //
char *str2; //
signed int result; //
strcpy(Dest, "smtp.");
str2 = Str;
len = strlen(Str);
for ( i = 0; i < len; ++i )
{
if ( str2[i] == 64 )
break;
}
if ( i < len - 1 )
{
strcat(Dest, &str2[i + 1]);
result = 1;
}
else
{
result = 0;
}
return result;
}
我的变量在哪里?
IDA高亮了当前的标识符。事实证明,这个功能在高层次的输出中更加有用。在这个示例中,我试图追踪检索到的函数指针是如何被函数使用的。 在反汇编输出中,许多错误的eax出现被高亮显示,而反编译器却完全按照我的要求做了。
汇编代码
; =============== S U B R O U T I N E =======================================
; int __cdecl myfunc(wchar_t *Str, int)
myfunc proc near ; CODE XREF: sub_4060+76p
; .text:42E4p
Str = dword ptr 4
arg_4 = dword ptr 8
mov eax, dword_1001F608
cmp eax, 0FFFFFFFFh
jnz short loc_10003AB6
push offset aGetsystemwindo ; "GetSystemWindowsDirectoryW"
push offset aKernel32_dll ; "KERNEL32.DLL"
call ds:GetModuleHandleW
push eax ; hModule
call ds:GetProcAddress
mov dword_1001F608, eax
loc_10003AB6: ; CODE XREF: myfunc+8j
test eax, eax
push esi
mov esi, [esp+4+arg_4]
push edi
mov edi, [esp+8+Str]
push esi
push edi
jz short loc_10003ACA
call eax ; dword_1001F608
jmp short loc_10003AD0
; ---------------------------------------------------------------------------
loc_10003ACA: ; CODE XREF: myfunc+34j
call ds:GetWindowsDirectoryW
loc_10003AD0: ; CODE XREF: myfunc+38j
sub esi, eax
cmp esi, 5
jnb short loc_10003ADD
pop edi
add eax, 5
pop esi
retn
; ---------------------------------------------------------------------------
loc_10003ADD: ; CODE XREF: myfunc+45j
push offset aInf_0 ; "\\inf"
push edi ; Dest
call _wcscat
push edi ; Str
call _wcslen
add esp, 0Ch
pop edi
pop esi
retn
myfunc endp
伪代码
size_t __cdecl myfunc(wchar_t *buf, int bufsize)
{
int (__stdcall *func)(_DWORD, _DWORD); // [email protected]
wchar_t *buf2; // [email protected]
int bufsize; // [email protected]
UINT dirlen; // [email protected]
size_t outlen; // [email protected]
HMODULE h; // [email protected]
func = g_fptr;
if ( g_fptr == (int (__stdcall *)(_DWORD, _DWORD))-1 )
{
h = GetModuleHandleW(L"KERNEL32.DLL");
func = (int (__stdcall *)(_DWORD, _DWORD))
GetProcAddress(h, "GetSystemWindowsDirectoryW");
g_fptr = func;
}
bufsize = bufsize;
buf2 = buf;
if ( func )
dirlen = func(buf, bufsize);
else
dirlen = GetWindowsDirectoryW(buf, bufsize);
if ( bufsize - dirlen >= 5 )
{
wcscat(buf2, L"\\inf");
outlen = wcslen(buf2);
}
else
{
outlen = dirlen + 5;
}
return outlen;
}
算术不是火箭科学
算术不是一门火箭科学,但是如果有人为您处理算术,它总是更好。 您还有更多重要的事情要关注。
汇编代码
; =============== S U B R O U T I N E =======================================
; Attributes: bp-based frame
; sgell(__int64, __int64)
public @sgell$qjj
@sgell$qjj proc near
arg_0 = dword ptr 8
arg_4 = dword ptr 0Ch
arg_8 = dword ptr 10h
arg_C = dword ptr 14h
push ebp
mov ebp, esp
mov eax, [ebp+arg_0]
mov edx, [ebp+arg_4]
cmp edx, [ebp+arg_C]
jnz short loc_10226
cmp eax, [ebp+arg_8]
setnb al
jmp short loc_10229
; ---------------------------------------------------------------------------
loc_10226: ; CODE XREF: sgell(__int64,__int64)+Cj
setnl al
loc_10229: ; CODE XREF: sgell(__int64,__int64)+14j
and eax, 1
pop ebp
retn
@sgell$qjj endp
伪代码
bool __cdecl sgell(__int64 a1, __int64 a2)
{
return a1 >= a2;
}
样本窗口程序
反编译器识别出switch语句,很好地表示了窗口过程。 没有这些帮助,用户将不得不自己计算消息号。 没什么特别困难的,只是费时又无聊。 如果她犯错了怎么办?...
汇编代码
; =============== S U B R O U T I N E =======================================
wndproc proc near ; DATA XREF: sub_4010E0+21o
Paint = tagPAINTSTRUCT ptr -0A4h
Buffer = byte ptr -64h
hWnd = dword ptr 4
Msg = dword ptr 8
wParam = dword ptr 0Ch
lParam = dword ptr 10h
mov ecx, hInstance
sub esp, 0A4h
lea eax, [esp+0A4h+Buffer]
push 64h ; nBufferMax
push eax ; lpBuffer
push 6Ah ; uID
push ecx ; hInstance
call ds:LoadStringA
mov ecx, [esp+0A4h+Msg]
mov eax, ecx
sub eax, 2
jz loc_4013E8
sub eax, 0Dh
jz loc_4013B2
sub eax, 102h
jz short loc_401336
mov edx, [esp+0A4h+lParam]
mov eax, [esp+0A4h+wParam]
push edx ; lParam
push eax ; wParam
push ecx ; Msg
mov ecx, [esp+0B0h+hWnd]
push ecx ; hWnd
call ds:DefWindowProcA
add esp, 0A4h
retn 10h
; ---------------------------------------------------------------------------
loc_401336: ; CODE XREF: wndproc+3Cj
mov ecx, [esp+0A4h+wParam]
mov eax, ecx
and eax, 0FFFFh
sub eax, 68h
jz short loc_40138A
dec eax
jz short loc_401371
mov edx, [esp+0A4h+lParam]
mov eax, [esp+0A4h+hWnd]
push edx ; lParam
push ecx ; wParam
push 111h ; Msg
push eax ; hWnd
call ds:DefWindowProcA
add esp, 0A4h
retn 10h
; ---------------------------------------------------------------------------
loc_401371: ; CODE XREF: wndproc+7Aj
mov ecx, [esp+0A4h+hWnd]
push ecx ; hWnd
call ds:DestroyWindow
xor eax, eax
add esp, 0A4h
retn 10h
; ---------------------------------------------------------------------------
loc_40138A: ; CODE XREF: wndproc+77j
mov edx, [esp+0A4h+hWnd]
mov eax, hInstance
push 0 ; dwInitParam
push offset DialogFunc ; lpDialogFunc
push edx ; hWndParent
push 67h ; lpTemplateName
push eax ; hInstance
call ds:DialogBoxParamA
xor eax, eax
add esp, 0A4h
retn 10h
; ---------------------------------------------------------------------------
loc_4013B2: ; CODE XREF: wndproc+31j
push esi
mov esi, [esp+0A8h+hWnd]
lea ecx, [esp+0A8h+Paint]
push ecx ; lpPaint
push esi ; hWnd
call ds:BeginPaint
push eax ; HDC
push esi ; hWnd
call my_paint
add esp, 8
lea edx, [esp+0A8h+Paint]
push edx ; lpPaint
push esi ; hWnd
call ds:EndPaint
pop esi
xor eax, eax
add esp, 0A4h
retn 10h
; ---------------------------------------------------------------------------
loc_4013E8: ; CODE XREF: wndproc+28j
push 0 ; nExitCode
call ds:PostQuitMessage
xor eax, eax
add esp, 0A4h
retn 10h
wndproc endp
伪代码
LRESULT __stdcall wndproc(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam)
{
LRESULT result; //
HWND h; //
HDC dc; //
CHAR Buffer; // [sp+40h] [bp-64h]@1
struct tagPAINTSTRUCT Paint; // [sp+0h] [bp-A4h]@10
LoadStringA(hInstance, 0x6Au, &Buffer, 100);
switch ( Msg )
{
case 2u:
PostQuitMessage(0);
result = 0;
break;
case 15u:
h = hWnd;
dc = BeginPaint(hWnd, &Paint);
my_paint(h, dc);
EndPaint(h, &Paint);
result = 0;
break;
case 273u:
if ( (_WORD)wParam == 104 )
{
DialogBoxParamA(hInstance, (LPCSTR)0x67, hWnd, DialogFunc, 0);
result = 0;
}
else
{
if ( (_WORD)wParam == 105 )
{
DestroyWindow(hWnd);
result = 0;
}
else
{
result = DefWindowProcA(hWnd, 0x111u, wParam, lParam);
}
}
break;
default:
result = DefWindowProcA(hWnd, Msg, wParam, lParam);
break;
}
return result;
}
短路评估
这是一个大函数的节选,用来说明短路评估。复杂的事情发生在长函数中,有反编译器以人性化的方式来表示事情是非常方便的。请注意,散落在地址空间的代码是如何在两个if
语句中简洁地显示出来的。
汇编代码
loc_804BCC7: ; CODE XREF: sub_804BB10+A42j
mov [esp+28h+var_24], offset aUnzip ; "unzip"
xor eax, eax
test esi, esi
setnz al
mov edx, 1
mov ds:dword_804FBAC, edx
lea eax, [eax+eax+1]
mov ds:dword_804F780, eax
mov eax, ds:dword_804FFD4
mov [esp+28h+var_28], eax
call _strstr
test eax, eax
jz loc_804C4F1
loc_804BCFF: ; CODE XREF: sub_804BB10+9F8j
mov eax, 2
mov ds:dword_804FBAC, eax
loc_804BD09: ; CODE XREF: sub_804BB10+9FEj
mov [esp+28h+var_24], offset aZ2cat ; "z2cat"
mov eax, ds:dword_804FFD4
mov [esp+28h+var_28], eax
call _strstr
test eax, eax
jz loc_804C495
loc_804BD26: ; CODE XREF: sub_804BB10+99Cj
; sub_804BB10+9B9j ...
mov eax, 2
mov ds:dword_804FBAC, eax
xor eax, eax
test esi, esi
setnz al
inc eax
mov ds:dword_804F780, eax
.............................. SKIP ............................
loc_804C495: ; CODE XREF: sub_804BB10+210j
mov [esp+28h+var_24], offset aZ2cat_0 ; "Z2CAT"
mov eax, ds:dword_804FFD4
mov [esp+28h+var_28], eax
call _strstr
test eax, eax
jnz loc_804BD26
mov [esp+28h+var_24], offset aZcat ; "zcat"
mov eax, ds:dword_804FFD4
mov [esp+28h+var_28], eax
call _strstr
test eax, eax
jnz loc_804BD26
mov [esp+28h+var_24], offset aZcat_0 ; "ZCAT"
mov eax, ds:dword_804FFD4
mov [esp+28h+var_28], eax
call _strstr
test eax, eax
jnz loc_804BD26
jmp loc_804BD3D
; ---------------------------------------------------------------------------
loc_804C4F1: ; CODE XREF: sub_804BB10+1E9j
mov [esp+28h+var_24], offset aUnzip_0 ; "UNZIP"
mov eax, ds:dword_804FFD4
mov [esp+28h+var_28], eax
call _strstr
test eax, eax
jnz loc_804BCFF
jmp loc_804BD09
伪代码
dword_804F780 = 2 * (v9 != 0) + 1;
if ( strstr(dword_804FFD4, "unzip") || strstr(dword_804FFD4, "UNZIP") )
dword_804FBAC = 2;
if ( strstr(dword_804FFD4, "z2cat")
|| strstr(dword_804FFD4, "Z2CAT")
|| strstr(dword_804FFD4, "zcat")
|| strstr(dword_804FFD4, "ZCAT") )
{
dword_804FBAC = 2;
dword_804F780 = (v9 != 0) + 1;
}
内联字符串操作
反编译器尝试识别经常内联的字符串函数,例如strcmp,strchr,strlen等。在此代码段中,已识别出对 strlen code>函数的调用。
汇编代码
mov eax, [esp+argc]
sub esp, 8
push ebx
push ebp
push esi
lea ecx, ds:0Ch[eax*4]
push edi
push ecx ; unsigned int
call @Z ; operator new(uint)
mov edx, [esp+1Ch+argv]
mov ebp, eax
or ecx, 0FFFFFFFFh
xor eax, eax
mov esi, [edx]
add esp, 4
mov edi, esi
repne scasb
not ecx
dec ecx
cmp ecx, 4
jl short loc_401064
cmp byte ptr [ecx+esi-4], '.'
jnz short loc_401064
mov al, [ecx+esi-3]
cmp al, 'e'
jz short loc_401047
cmp al, 'E'
jnz short loc_401064
loc_401047: ; CODE XREF: _main+41j
mov al, [ecx+esi-2]
cmp al, 'x'
jz short loc_401053
cmp al, 'X'
jnz short loc_401064
loc_401053: ; CODE XREF: _main+4Dj
mov al, [ecx+esi-1]
cmp al, 'e'
jz short loc_40105F
cmp al, 'E'
jnz short loc_401064
loc_40105F: ; CODE XREF: _main+59j
mov byte ptr [ecx+esi-4], 0
loc_401064: ; CODE XREF: _main+32j _main+39j ...
mov edi, esi
or ecx, 0FFFFFFFFh
xor eax, eax
repne scasb
not ecx
add ecx, 3
push ecx ; unsigned int
call @Z ; operator new(uint)
mov edx, eax
伪代码
v4 = operator new(4 * argc + 12);
v5 = *argv;
v77 = strlen(*argv);
v3 = v77 - 1;
if ( (signed int)(v77 - 1) >= 4 )
{
if ( v5[v3 - 4] == '.' )
{
chr = v5[v3 - 3];
if ( chr == 'e' || chr == 'E' )
{
v7 = v5[v3 - 2];
if ( v7 == 'x' || v7 == 'X' )
{
v8 = v5[v3 - 1];
if ( v8 == 'e' || v8 == 'E' )
v5[v3 - 4] = 0;
}
}
}
}
v9 = operator new(strlen(v5) + 3);