首先,我要澄清我是一个学生,我对C,C ++和汇编器尚不具备广泛的知识,因此,我正在竭尽全力来理解它。
我有一段来自Intel x86-32位处理器的汇编代码。
我的目标是将其转换为源代码。
0x80483dc <main>: push ebp
0x80483dd <main+1>: mov ebp,esp
0x80483df <main+3>: sub esp,0x10
0x80483e2 <main+6>: mov DWORD PTR [ebp-0x8],0x80484d0
0x80483e9 <main+13>: lea eax,[ebp-0x8]
0x80483ec <main+16>: mov DWORD PTR [ebp-0x4],eax
0x80483ef <main+19>: mov eax,DWORD PTR [ebp-0x4]
0x80483f2 <main+22>: mov edx,DWORD PTR [eax+0xc]
0x80483f5 <main+25>: mov eax,DWORD PTR [ebp-0x4]
0x80483f8 <main+28>: movzx eax,WORD PTR [eax+0x10]
0x80483fc <main+32>: cwde
0x80483fd <main+33>: add edx, eax
0x80483ff <main+35>: mov eax,DWORD PTR [ebp-0x4]
0x8048402 <main+38>: mov DWORD PTR [eax+0xc],edx
0x8048405 <main+41>: mov eax,DWORD PTR [ebp-0x4]
0x8048408 <main+44>: movzx eax,BYTE PTR [eax]
0x804840b <main+47>: cmp al,0x4f
0x804840d <main+49>: jne 0x8048419 <main+61>
0x804840f <main+51>: mov eax,DWORD PTR [ebp-0x4]
0x8048412 <main+54>: movzx eax,BYTE PTR [eax]
0x8048415 <main+57>: cmp al,0x4b
0x8048417 <main+59>: je 0x804842d <main+81>
0x8048419 <main+61>: mov eax,DWORD PTR [ebp-0x4]
0x804841c <main+64>: mov eax,DWORD PTR [eax+0xc]
0x804841f <main+67>: mov edx, eax
0x8048421 <main+69>: and edx,0xf0f0f0f
0x8048427 <main+75>: mov eax,DWORD PTR [ebp-0x4]
0x804842a <main+78>: mov DWORD PTR [eax+0x4],edx
0x804842d <main+81>: mov eax,0x0
0x8048432 <main+86>: leave
0x8048433 <main+87>: ret
这是我从代码中了解的内容:
有4个变量:
a = [ebp-0x8] ebp
b = [ebp-0x4] eax
c = [eax + 0xc] edx
d = [eax + 0x10] eax
值:
0x4 = 4
0x8 = 8
0xc = 12
0x10 = 16
0x4b = 75
0x4f = 79
类型:
char (8 bits) = 1 BYTE
short (16 bits) = WORD
int (32 bit) = DWORD
long (32 bits) = DWORD
long long (32 bit) = DWORD
这是我能够创建的:
#include <stdio.h>
int main (void)
{
int a = 0x80484d0;
int b
short c;
int d;
c + b?
if (79 <= al) {
instructions
} else {
instructions
}
return 0
}
但是我被困住了。我也无法理解句子“ cmp al ..”与什么相比,什么是“ al”?
有人可以向我提供有关说明如何工作的解释吗?
x86汇编语言具有悠久的历史,并且在很大程度上保持了兼容性。我们需要回到8086/8088芯片开始的故事。它们是16位处理器,这意味着它们的寄存器的字长为16位。通用寄存器分别命名为AX,BX,CX和DX。 8086的指令可操纵这些寄存器的高8位和低8位,然后分别命名为AH,AL,BH,BL,CH,CL,DH和DL。 This Wikipedia page描述了这一点,请看一看。
这些寄存器的32位版本前面带有E
:EAX,EBX,ECX等
您提到的特定指令,例如cmp al,0x4f
正在将AX寄存器的低字节与0x4f进行比较。比较实际上与减法相同,但不保存结果,仅设置标志。
对于8086指令集,为there is a nice reference here。您的程序是32位代码,因此您至少需要80386指令参考。
您的组装完全疯了。这大致相当于C:
int main() {
int i = 0x80484d0; // in ebp-8
int *p = &i; // in ebp-4
p[3] += (short)p[4]; // add argc to the return address(!)
if((char)*p != 0x4f || (char)*p != 0x4b) // always true because of || instead of &&
p[1] = p[3] & 0xf0f0f0f; // note that p[1] is p
return 0;
}
应该立即很明显,这是糟糕透顶的代码,几乎可以肯定不会满足程序员的意图。
您已经分析了变量,这是一个很好的起点。您应尝试在开始时为其添加类型注释,大小,并在用作指针(例如b
)时使用指向哪种类型/大小的指针。
知道[ebp-4]
为b
,我可能会如下更新变量图:
c = [b + 0xc]
d = [b + 0x10]
e = [b + 0], size = byte
要分析的另一件事是控制流程。对于大多数指令,控制流是顺序的,但是某些指令有意地对其进行了更改。广义上讲,当PC向前移动时,它将跳过一些代码,而当PC向后移动时,它将重复它已经运行的某些代码。跳过代码用于构造if-then,if-then-else和中断循环的语句。回跳用于继续循环。
[某些指令,称为条件分支,在某些动态条件为true时:向前(或向后)跳过,在为false时,将简单的顺序前进到下一条指令(有时称为条件分支失败)。
这里的控制顺序:
...
0x8048405 <main+41>: mov eax,DWORD PTR [ebp-0x4] b
0x8048408 <main+44>: movzx eax,BYTE PTR [eax] b->e
0x804840b <main+47>: cmp al,0x4f b->e <=> 'O'
0x804840d <main+49>: jne 0x8048419 <main+61> b->e != 'O' skip to 61
** we know that the letter, a->e, must be 'O' here
0x804840f <main+51>: mov eax,DWORD PTR [ebp-0x4] b
0x8048412 <main+54>: movzx eax,BYTE PTR [eax] b->e
0x8048415 <main+57>: cmp al,0x4b b->e <=> 'K'
0x8048417 <main+59>: je 0x804842d <main+81> b->e == 'K' skip to 81
** we know that the letter, a->e must not be 'K' here if we fall thru the above je
** this line can be reached by taken branch jne or by fall thru je
0x8048419 <main+61>: mov eax,DWORD PTR [ebp-0x4] ******
...
控制流到达标记的最后一行,我们知道该字母不是'O'或不是'K'。
jne
指令用于跳过另一个测试的结构是短路||
运算符。因此,控件构造为:
if ( a->e != 'O' || a->e != 'K' ) {
then-part
}
由于这两个条件分支是函数中唯一的流控制修改,因此if中没有其他部分,也没有循环或其他if。
此代码似乎有一个小问题。
如果该值不为'O',则从第一项测试开始,然后触发部分。但是,如果我们进行第二次测试,我们已经知道字母为'O',那么对其进行'K'测试是很愚蠢的,并且是正确的('O'不是'K')。
因此,此if-then将始终触发。
或者效率很低,或者有一个错误,也许是(大概)字符串中的下一个字母应该测试'K'而不是完全相同的字母。