计算输入内容长度
00402865 |> /8A06 /mov al,byte ptr ds:[esi] ; 计算长度(esi)
00402867 |. |46 |inc esi ;esi每次+1,直到末端
00402868 |. |84C0 |test al,al ;当两个值不同时,al遇到0了,所以会不同,也就跳出循环
0040286A |.^\75 F9 \jnz short WannaFla.00402865
函数调用
;------------------------
;1.通过栈传递参数
;2.通过寄存器传递参数:快
;3.名称修饰约定
;
;下边的是通过栈传递参数的代码
;------------------------
;未优化的汇编代码
push par2 ;压入参数,根据调用约定不同,会改变push的参数的先后顺序
push par1
call test ;调用函数,执行call指令保存返回地址,即该处的下一条指令的地址
{
push ebp ;保护现场原来的函数指针?
mov ebp, esp ;将此时的栈顶指针保存在ebp中,在之后可以通过[ebp+offset]的方式调用各个参数
mov eax, dword ptr [ebp+0c] ;调用参数2,该调用约定下参数2先push,所以高地址(在下边的)加的offset更大。
mov ebx, dword ptr [ebp+08] ;调用参数1,为什么差的是0xc-0x8=4?因为push的都是4字节
sub esp, 8 ;因为有局部变量,局部变量压入栈,所以esp要往上,指到栈顶
...
add esp, 8 ;与sub esp,8相对应:栈顶指针往下走,即弹出局部变量
pop ebp ;恢复现场的ebp指针?
ret 8 ;ret后的指=参数个数*4h 平衡栈
}
;几种局部变量分配与清除栈的形式
| 形式1 | 形式2 | 形式3 |
|sub esp, n |add esp,-n |push reg |
|add esp, n |sub esp,-n |pop reg |
;优化的汇编代码, 优化选项为"Maximize Speed"
;会直接使用esp取各种参数
mov eax, dword ptr [esp+04] ;调用参数1
mov ecx, dword ptr [esp+08] ;调用参数2
全局变量
由于全局变量作用于整个程序,所以跟局部变量相比,全局变量会直接被放在一块内存区里。一般放在.data块。
mov eax, dword ptr [4084C0h] ;一个固定的硬编码地址,直接调用全局变量,位于该地址
数组
/*C语言代码*/
int main(void)
{
static int a[3] = {0x11,0x22,0x33};
int i,s=0,b[3];
for(i=0;i<3;i++)
{
s = s + a[i];
b[i] = s;
}
for(i=0;i<3;i++)
{
printf("%d\n",b[i]);
}
}
;对应的汇编代码
00401000 sub esp, 0c ; 给局部变量留空间
00401003 xor ecx, ecx ; s = 0
00401005 xor eax, eax ; i = 0
00401007 push esi
00401008 push edi
00401009 /mov edi, dword ptr[eax+407030] ;[eax+407030]-数组的起始位置 放入 edi中
0040100F |add eax, 4 ;数组的索引放在eax中,在最后与0xc比较,每次加4,共3:a[3]
;eax为什么是0x4,因为数组的每个值占0x4,[eax+407030]依次取到数组值
00401012 |add ecx, edi ;s=s+a[i]
00401014 |cmp eax, 0C ;i?>3
00401017 |mov dword ptr [esp+eax+4], ecx ;b[i]=s
0040101B \jl short 00401009
0040101D lea esi, dword ptr [esp+8]
00401021 mov edi,3 ;第二个i,计数
00401026 /mov eax, dword ptr [esi] ;[esi]指向数组b[]
00401028 |push eax
00401029 |push 40703C
0040102E |call 00401050 ;printf("%d\n",b[i])
00401033 |add esp, 8
00401036 |add esi, 4 ;同上,取下一个
00401039 |dec edi
0040103A \jnz short 00401026
;可知,数组寻址一般使用"基址+偏移量"的方式实现
;mov eax, [407030h + eax]
IF-THEN-ELSE
;if-else
;====================
cmp a, b
jz(jnz) xxxx
;====================
;z寄存器是是否为zero的标志为zero 则z为真 为1
;cmp指令有时会用 or test 指令代替
#include<stdio.h>
int main(void)
{
int a,b=5;
scanf("%d",&a);
if(a==0)
a=8;
return a+b;
}
00401000 push ecx ; 分配内存<=>sub esp,4
00401001 lea eax, dword ptr [esp] ;eax指向局部变量空间
00401005 push eax
00401006 push 00407030 ;push "%d"
0040100B call 00401030 ;调用scanf函数
00401010 mov eax,dword ptr[esp+8] ;输入的字符串放在[esp+8]这个地址,转出到eax
00401014 add esp,00000008 ;因为是_cdel调用约定,所以在函数外作栈平衡操作
00401017 test eax,eax ;test:若eax=0,则zf置1,否则zf置0
00401019 jne 00401020 ;jne:若zf=1就不跳转,否则跳转
0040101B mov eax,00000008 ;a = 8
00401020 add eax,00000005 ;a+b
00401023 pop ecx ;与push ecx相对应
00401024 ret
switch-case
#include<stdio.h>
int main(void)
{
int a;
scanf("%d", &a);
switch (a)
{
case 1:
printf("a=1");
break;
case 2:
printf("a=2");
break;
case 10:
printf("a=10");
break;
default:printf("a=default");
break;
}
return 0;
}
;汇编代码
00401000 push ebp
00401001 mov ebp, esp
00401003 sub esp, 00000008 ;局部变量分配内存
00401006 lea esp, [ebp-04]
00401009 push eax
0040100A push 00408030 ;指向字符"%d"
0040100F call 004010A2 /;scanf("%d",&a)
00401014 add esp,00000008 |scanf的特点,输入后在[ebp-xxx]处又开始拿出来
00401017 mov ecx,[ebp-04] \;将输入的结果传给ecx
0040101A mov [ebp-08],ecx ;再放入[ebp-08]
0040101D cmp [ebp-08],01 ;case 1
00401021 je 00401031 ;跳转处会调用printf(a=1)
00401023 cmp [ebp-08],02 ;case 2
00401027 je 00401040 ;跳转处会调用printf(a=2)
00401029 cmp [ebp-08],0A ;case 10
0040102D je 0040104F ;同上
0040102F jmp 0040104F ;同上
00401031 push 00408034
00401036 call 00401071 ;各种printf
0040103B add esp, 00000004
0040103E jmp 0040106B
00401040 push 00408038
00401045 call 00401071
0040104A add esp,00000004
0040104D jmp 0040106B
0040104F push 00408044
00401054 call 00401071
00401059 add esp,00000004
0040105C jmp 0040106B
0040105E push 00408044
00401063 call 00401071
00401068 add esp,00000004 ;与sub平衡
0040105C xor eax,eax
0040105E mov esp, ebp
00401063 pop ebp
00401000 ret
其实代码来看,都是各种逻辑判断和条件跳转语句。switch何尝不是ifesle(不优化的前提下)。
但是 继续看switch的代码
switch(a)
{
case 1:printf("a=1");break;
case 2:printf("a=2");break;
case 3:printf("a=2");break;
case 4:printf("a=2");break;
case 5:printf("a=2");break;
case 6:printf("a=2");break;
case 7:printf("a=2");break;
default:printf("a=default");break;
}
;如果!取值表示一个算数级数!编译器会使用一个跳转表来实现
jmp dword ptr [4*eax+004010B0] ;跳转表,eax的值影响取到的是具体那个
循环(包括while 和 for)
int main(void)
{
int sum=0,i=0;
for(i=0;i<=100;i++)
{
sum = sum + i;
}
return 0;
}
;汇编未优化代码
00401000 push ebp ;保护现场
00401001 mov ebp,esp ;栈顶放入ebp中
00401003 sub esp,00000008 ;局部变量的空间
00401006 mov [ebp-04],00000000 ;sum = 0
0040100D mov [ebp-08],00000000 ;i = 0
00401014 jmp 0040101F ;i<100的循环
00401016 mov eax,dword ptr [ebp-08] ;
00401019 add eax,00000001 ;与上条一起,构成i++
0040101C mov dword ptr [ebp-08],eax ;再放入[ebp-08]中,并在下条比较,看是否大于100
0040101F cmp dword ptr [ebp-08],64 ;如上
00401023 jg 00401030 ;如果大于100,跳到retrn 0
00401025 mov ecx,dword ptr [ebp-04]
00401028 add ecx,dword ptr [ebp-08] ;sum的计算
0040102B mov dword ptr [ebp-04],ecx
0040102E jmp 00401016
00401030 xor eax,eax
00401032 mov esp,ebp
00401034 pop ebp
00401035 ret
;for循环相对熟悉且易看。特征:从高地址往低地址返回再次执行这些指令 有计数器
;在优化后 +1会成 inc 且寄存器用得多了
滥用返回指针(花指令)
004011C0 sub_4011C0 pro near
004011C0
004011C0
004011C0 var_4 = byte ptr -4
004011C0
004011C0 call $+5 ;该指令push了该地址+5的地址,也就是4011C5,同时esp也等于该值
004011C5 add [esp+4+var_4],5 ;可以看到var_4=-4,所以该指令简化为 add [esp],5
将esp指向栈顶加5,因此esp在4011CA,栈顶变了
004011C9 retn ;pop出栈顶,即值4011CA,然后jmp到他
004011C9 sub_4011C0 endp ; sp-analysis failed ;因此这么一波花指令下来,其实就是调用4011CA处的代码,
造成了ida识别错误。
将这三行代码nop了,再把下边的创建函数。完成去花。
004011C9
004011CA ; ----------------------------------------------
004011CA push ebp
004011CB mov ebp,esp
004011CD mov eax,[ebp+8]
004011D0 imul eax,2Ah
004011D3 mov esp,ebp
004011D5 pop ebp
004011D6 retn