常用寄存器 
    
        
            | 寄存器 
 | 名称 
 | 常见用途(未完) 
 | 
        
            | eax 
 | 累加器(Accumulator) | 函数返回值 
 | 
        
            | ebx | 基址寄存器(Base) | 可作为存储指针来使用 
 | 
        
            | ecx 
 | 计数器(Counter) 
 | 在循环和字符串操作时,用来控制循环次数 __thiscall中传递this指针
 | 
        
            | edx 
 | 数据寄存器(Data) 
 | 
 | 
        
            | esp 
 | 堆栈指针寄存器(Stack) 
 | 
 | 
        
            | ebp 
 | 基地址指针寄存器(Base) 
 | 
 | 
        
            | esi 
 | 源地址寄存器(Source Index) 
 | 
 | 
        
            | edi 
 | 目的地址寄存器(Destination) 
 | 
 | 
    
常用汇编指令
    
        
            | push | 把一个32位的操作数压入堆栈,这个操作会导致esp减4. | 
        
            | pop | 与push相反,esp加4,一个数据出栈 | 
        
            | call | 调用函数。将下一条指令的地址压栈,然后跳转到所调用函数的开始处,本质相当于push+jump | 
        
            | ret | 与call相对应,跳转到栈顶数据所指的地址,本质相当于pop+jump。对于_cdecl 调用的函数,通常会在ret之后进行exp-[n],用于清理调用参数堆栈 | 
        
            | xor | 异或,常用于清零操作,例如: xor eax eax | 
        
            | lea | 取得地址(第二个参数)后放入前面的寄存器中。 | 
        
            | stosw | 将eax中的数据传送给edi,之后edi+4。常与rep一起使用,用于初始化内存段 | 
        
            | rep | 当eax>0时,重复后面的指令 | 
        
            | jp,jl,jge | 根据eax中值与0的关系跳转 | 
        
            | cmp | 比较指令,将结果放入eax中,往往是jp,jl,jge之类跳转指令的执行条件 | 
    
函数调用规则
    
        
            | 调用方式 
 | 简要说明 
 | 堆栈清理 | 参数传递规则 
 | 
        
            | _cdecl | C 编译器的默认调用规则 | Caller 
 | 从右到左 | 
        
            | _stdcall | 又称为WINAPI | Callee 
 | 从右到左 | 
        
            | __thiscall | C++成员函数调用方式 
 | Callee | this放入ecx,其他从右到左 | 
        
            | __fastcall 
 | 
 | Callee 
 | 前两个等于或者小于DWORD大小的参数放入ecx和edx,其他参数从右到左 
 | 
    
 _cdecl调用通常的asm代码: 
被调用方:
1.保存ebp。ebp总是用来保存这个函数执行之前的esp值。执行完毕之后,我们用ebp回复esp;同时,调用此函数的上层函数也用ebp做同样的事情。
2.保存esp到ebp中。
 ;保存ebp,并把esp放入ebp中,此时ebp与esp都为这次函数调用的栈顶
;保存ebp,并把esp放入ebp中,此时ebp与esp都为这次函数调用的栈顶
 push ebp
push ebp
 mov  ebp,esp
mov  ebp,esp
3.在堆栈中预留一个区域用于保存局部变量。方法是将esp减少一个数值,这样就等于压入了一堆变量。要恢复的时候直接把esp回复成ebp保存的数据就可以了。
4.保存ebx、esi、edi到堆栈中,函数调用完成后恢复。
 ;把esp往下移动一个范围,等于在堆栈中预留一片新的空间来保存局部变量
;把esp往下移动一个范围,等于在堆栈中预留一片新的空间来保存局部变量
 sub  esp,010h
sub  esp,010h
 push ebx
push ebx
 push esi
push esi
 push edi
push edi
5.(debug版)把局部变量全部初始化为0xcccccccch.
 ;将保存局部变量的区域全部初始化为0xcccccccch
;将保存局部变量的区域全部初始化为0xcccccccch
 lea  edi,[ebp-010h]
lea  edi,[ebp-010h]
 mov  ecx,33h
mov  ecx,33h
 mov  eax,0xcccccccch
mov  eax,0xcccccccch
 rep  stos dword ptr [edi]
rep  stos dword ptr [edi]
6.然后执行函数的具体逻辑。传入参数的获取为:ebp+4为函数的返回地址;ebp+8为第一个参数,ebp+12为第二个参数,以此类推。
7.回复ebx、esi、edi、esp、ebp,最后返回。如果有返回值,在返回之前将保存在eax中,供调用方式用。
 pop  edi ;恢复edi、esi、ebx
pop  edi ;恢复edi、esi、ebx
 pop  esi
pop  esi
 pop  ebx
pop  ebx
 mov  esp, ebp ;恢复原来的ebp和esp
mov  esp, ebp ;恢复原来的ebp和esp
 pop  ebp
pop  ebp
 ret
ret
调用方:
 mov  eax,dword ptr [b]
mov  eax,dword ptr [b]
 push eax
push eax
 move ecx,dword ptr [a]
move ecx,dword ptr [a]
 push ecx
push ecx
 call myfunction
call myfunction
 add  esp,8              ;回复堆栈
add  esp,8              ;回复堆栈 
常见的基础代码结构
for循环
    for(int i = 0; i < 20; ++i )
0040B93E  mov         dword ptr [i],0 
0040B945  jmp         wmain+30h (40B950h) 
0040B947  mov         eax,dword ptr [i] 
0040B94A  add         eax,1 
0040B94D  mov         dword ptr [i],eax 
0040B950  cmp         dword ptr [i],14h 
0040B954  jge         wmain+38h (40B958h) 
    {
    }
0040B956  jmp         wmain+27h (40B947h) 
可以看到主循环主要由这么几条指令来实现:mov进行初始化;jmp跳过修改循环变量的代码;cmp实现跳转判断;jge根据条件跳转。用jmp回到修改循环变量的代码进行下一次循环。大体结构如下:
    mov  <循环变量>,<初始值>     ;给循环变量赋值
    jmp  A                     ;跳到第一次循环处
A:     (改动循环变量)            ;修改循环变量
    
B:  cmp  <循环变量>,<限制变量>   ;检查循环变量
    jge  跳出循环
    (循环体)
    
    jmp  A                     ;跳回修改循环变量 
do循环
    int i = 0;
0040B93E  mov         dword ptr [i],0 
    do 
    {
        ++i;
0040B945  mov         eax,dword ptr [i] 
0040B948  add         eax,1 
0040B94B  mov         dword ptr [i],eax 
    } while (i<10);
0040B94E  cmp         dword ptr [i],0Ah 
0040B952  jl          wmain+25h (40B945h) 
上面的do循环就是用一个简单的条件比较指令跳转回去:
cmp  <循环变量><限制变量>
jl   <循环开始>
while循环
int i = 0;
0040B93E  mov         dword ptr [i],0 
    while (i<10)
0040B945  cmp         dword ptr [i],0Ah 
0040B949  jge         wmain+36h (40B956h) 
    {
        ++i;
0040B94B  mov         eax,dword ptr [i] 
0040B94E  add         eax,1 
0040B951  mov         dword ptr [i],eax 
    }
0040B954  jmp         wmain+25h (40B945h) 
while要复杂一些,因为wile除了开始的时候判断循环条件之外,后面还要有一条无条件跳转指令:
A:  cmp  <循环变量>,<限制变量>
    jge  B
    (循环体)
    
    jmp  A 
B:  (跳出循环) 
if-else判断分支
int i = 0;
0040B93E  mov         dword ptr [i],0 
    int j = 0;
0040B945  mov         dword ptr [j],0 
    if ( i < 10 )
0040B94C  cmp         dword ptr [i],0Ah 
0040B950  jge         wmain+3Bh (40B95Bh) 
    {
        j = 10;
0040B952  mov         dword ptr [j],0Ah 
0040B959  jmp         wmain+51h (40B971h) 
    }
    else if (i < 20 )
0040B95B  cmp         dword ptr [i],14h 
0040B95F  jge         wmain+4Ah (40B96Ah) 
    {
        j = 20;
0040B961  mov         dword ptr [j],14h 
    }
    else
0040B968  jmp         wmain+51h (40B971h) 
    {
        j = 30;
0040B96A  mov         dword ptr [j],1Eh 
    }
    return 0;
0040B971  xor         eax,eax 
if 判断都是使用cmp加上条件跳转指令。
所以开始的反汇编为:
    if ( i < 10 )
0040B94C  cmp         dword ptr [i],0Ah     ;判断点
0040B950  jge         wmain+3Bh (40B95Bh)     ;跳转到下一个else if
else if和else的特点是,在开始的地方都有一条无条件跳转指令,跳转到判断结束处,阻止前面的分支执行结束后,直接进入这个分支的可能,这个分支执行的唯一条件为前面的判断不满足。else则在jmp之后直接执行操作,而else if则开始重复if之后的操作,用cmp比较,然后用条件质量进行跳转。
0040B959  jmp         wmain+51h (40B971h)     ;跳转到判断块外
    }
    else if (i < 20 )
0040B95B  cmp         dword ptr [i],14h     
0040B95F  jge         wmain+4Ah (40B96Ah)     ;比较,条件跳转,目标为下一个分支
    {
        j = 20;
0040B961  mov         dword ptr [j],14h 
    }
switch-case 判断分支
switch的特点是有多个判断。因为switch显然不会判断大于小于,所以都是je,分别跳转到每个case处,最有一个是无条件跳转,直接跳到default处。
对于break,会增加一个无条件跳转语句,跳转至结尾
 int i = 0;
int i = 0;
 0040B93E  mov         dword ptr [i],0
0040B93E  mov         dword ptr [i],0 
 int j = 0;
    int j = 0;
 0040B945  mov         dword ptr [j],0
0040B945  mov         dword ptr [j],0 
 switch (i)
    switch (i)
 0040B94C  mov         eax,dword ptr [i]
0040B94C  mov         eax,dword ptr [i] 
 0040B94F  mov         dword ptr [ebp-0DCh],eax
0040B94F  mov         dword ptr [ebp-0DCh],eax 
 0040B955  cmp         dword ptr [ebp-0DCh],0
0040B955  cmp         dword ptr [ebp-0DCh],0     
 0040B95C  je          wmain+49h (40B969h)         ;判断case 1
0040B95C  je          wmain+49h (40B969h)         ;判断case 1
 0040B95E  cmp         dword ptr [ebp-0DCh],1
0040B95E  cmp         dword ptr [ebp-0DCh],1 
 0040B965  je          wmain+52h (40B972h)         ;判断case 2
0040B965  je          wmain+52h (40B972h)         ;判断case 2
 0040B967  jmp         wmain+59h (40B979h)         ;跳转到default
0040B967  jmp         wmain+59h (40B979h)         ;跳转到default

 
     {
{
 case 0:
    case 0:
 j = 0;
        j = 0;
 0040B969  mov         dword ptr [j],0
0040B969  mov         dword ptr [j],0 
 break;                                    ;跳转到结束
        break;                                    ;跳转到结束
 0040B970  jmp         wmain+60h (40B980h)
0040B970  jmp         wmain+60h (40B980h) 
 case 1:
    case 1:
 j = 1;
        j = 1;
 0040B972  mov         dword ptr [j],1
0040B972  mov         dword ptr [j],1 
 default:
    default:
 j = 3;
        j = 3;
 0040B979  mov         dword ptr [j],3
0040B979  mov         dword ptr [j],3 
 }
    }

 return 0;
    return 0;
 0040B980  xor         eax,eax
0040B980  xor         eax,eax  cmp
cmp
 je
je标志着可能是swith语句
访问结构体数组成员
对于以下代码:
 struct A
struct A 


 {
{
 int a;
    int a;
 int b;
    int b;
 int c;
    int c;
 };
};

 int wmain(int argc, wchar_t* argv[])
int wmain(int argc, wchar_t* argv[])


 {
{
 A    ar[3];
    A    ar[3];
 for (int i=0;i<3;++i)
    for (int i=0;i<3;++i)

 
     {
{
 ar[i].a    = 0;
        ar[i].a    = 0;
 ar[i].b    = 0;
        ar[i].b    = 0;
 ar[i].c    = 0;
        ar[i].c    = 0;
 }
    }

 return 0;
    return 0;
 }
}for循环中所对应的汇编为
  ar[i].a = 0;
0040B956  mov      eax,dword ptr [i]  ;访问第i个数据
0040B959  imul     eax,eax,0Ch    ;0ch为结构体的大小,这里得到访问第i个机构体的地址偏移
0040B95C  mov      dword ptr ar[eax],0  ;取得第i个结构体的第一个元素地址
  ar[i].b = 0;
0040B964  mov      eax,dword ptr [i] 
0040B967  imul     eax,eax,0Ch 
0040B96A  mov      dword ptr [ebp+eax-24h],0 
  ar[i].c = 0;
0040B972  mov      eax,dword ptr [i] 
0040B975  imul     eax,eax,0Ch 
0040B978  mov      dword ptr [ebp+eax-20h],0 
对于结构体数组的访问有个很明显的特征:使用imul取得某个数组元素的地址偏移,然后在加上所要访问结构体成员的地址偏移。同时,大多数情况下结构的的大小都是在编译期决定的,imul的最后一个参数会是个常量。
阅读汇编代码的一些技巧
 
1.将指令分类:
    首先F(function)类指令:是函数调用相关代码,这些代码用于函数或者作为一个函数数被调用。几乎凡是堆栈操作(备份集陈启或者压入参数)可全部归入此类。此外还有call指令、堆栈恢复。
    然后C(control)类指令    :设计判断和跳转指令,以及对循环变量操作的指令。这些代码用于循环、判断语句。
    剩余D(data)类指令:数据处理指令,应该不包含函数调用,多半不含有堆操作,也不会含有跳转。
2.翻译D类指令。
3.表达式的合并与控制流程的结合。
Reference:
学 Win32 汇编[29] - 串指令: MOVS*、CMPS*、SCAS*、LODS*、REP、REPE、REPNE 等
《天书夜读-从汇编语言到Windows内核编程》