VS反汇编分析

Posted czsharecode

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了VS反汇编分析相关的知识,希望对你有一定的参考价值。

X86和X87汇编指令

数据传输指令

  • 它们在存贮器和寄存器、寄存器和输入输出端口之间传送数据.

  • 通用数据传送指令.
    MOV     传送字或字节.
    MOVSX   先符号扩展,再传送.
    MOVZX   先零扩展,再传送.
    PUSH    把字压入堆栈.
    POP     把字弹出堆栈.
    PUSHA   把AX,CX,DX,BX,SP,BP,SI,DI依次压入堆栈.
    POPA    把DI,SI,BP,SP,BX,DX,CX,AX依次弹出堆栈.
    PUSHAD  把EAX,ECX,EDX,EBX,ESP,EBP,ESI,EDI依次压入堆栈.
    POPAD   把EDI,ESI,EBP,ESP,EBX,EDX,ECX,EAX依次弹出堆栈.
    BSWAP   交换32位寄存器里字节的顺序
    XCHG    交换字或字节.(至少有一个操作数为寄存器,段寄存器不可作为操作数)
    CMPXCHG 比较并交换操作数.(第二个操作数必须为累加器AL/AX/EAX)
    XADD    先交换再累加.(结果在第一个操作数里)
    XLAT    字节查表转换.----BX指向一张256字节的表的起点,AL为表的索引值(0-255,即0-FFH);返回AL为查表结果.([BX+AL]->AL)
  • 输入输出端口传送指令.
    • IN I/O端口输入. ( 语法: IN 累加器, {端口号│DX} )
    • OUT I/O端口输出. ( 语法: OUT {端口号│DX},累加器 )输入输出端口由立即方式指定时, 其范围是 0-255; 由寄存器 DX 指定时,其范围是 0-65535.
  • 目的地址传送指令.
    LEA     装入有效地址.例: LEA DX,string ;把偏移地址存到DX.
    LDS     传送目标指针,把指针内容装入DS.例: LDS SI,string   ;把段地址:偏移地址存到DS:SI.
    LES     传送目标指针,把指针内容装入ES.例: LES DI,string   ;把段地址:偏移地址存到ES:DI.
    LFS     传送目标指针,把指针内容装入FS.例: LFS DI,string   ;把段地址:偏移地址存到FS:DI.
    LGS     传送目标指针,把指针内容装入GS.例: LGS DI,string   ;把段地址:偏移地址存到GS:DI.
    LSS     传送目标指针,把指针内容装入SS.例: LSS DI,string   ;把段地址:偏移地址存到SS:DI.
  • 标志传送指令.
    • LAHF 标志寄存器传送,把标志装入AH.
    • SAHF 标志寄存器传送,把AH内容装入标志寄存器.
    • PUSHF 标志入栈.
    • POPF 标志出栈.
    • PUSHD 32位标志入栈.
    • POPD 32位标志出栈.

算术运算指令

    ADD     加法.
    ADC     带进位加法.
    INC     加 1.
    AAA     加法的ASCII码调整.
    DAA     加法的十进制调整.
    SUB     减法.
    SBB     带借位减法.
    DEC     减 1.
    NEG     求反(以    0 减之).
    CMP     比较.(两操作数作减法,仅修改标志位,不回送结果).
    AAS     减法的ASCII码调整.
    DAS     减法的十进制调整.
    MUL     无符号乘法.结果回送AH和AL(字节运算),或DX和AX(字运算),
    IMUL    整数乘法.结果回送AH和AL(字节运算),或DX和AX(字运算),
    AAM     乘法的ASCII码调整.
    DIV     无符号除法.结果回送:商回送AL,余数回送AH, (字节运算);或 商回送AX,余数回送DX, (字运算).
    IDIV    整数除法.结果回送:商回送AL,余数回送AH, (字节运算);或 商回送AX,余数回送DX, (字运算).
    AAD     除法的ASCII码调整.
    CBW     字节转换为字. (把AL中字节的符号扩展到AH中去)
    CWD     字转换为双字. (把AX中的字的符号扩展到DX中去)
    CWDE    字转换为双字. (把AX中的字符号扩展到EAX中去)
    CDQ     双字扩展. (把EAX中的字的符号扩展到EDX中去)

逻辑运算指令

    AND     与运算.
    OR      或运算.
    XOR     异或运算.
    NOT     取反.
    TEST    测试.(两操作数作与运算,仅修改标志位,不回送结果).
    SHL     逻辑左移.
    SAL     算术左移.(=SHL)
    SHR     逻辑右移.
    SAR     算术右移.(=SHR)
    ROL     循环左移.
    ROR     循环右移.
    RCL     通过进位的循环左移.
    RCR     通过进位的循环右移.
    以上八种移位指令,其移位次数可达255次.
    移位一次时, 可直接用操作码. 如 SHL AX,1.
    移位>1次时, 则由寄存器CL给出移位次数.
    如 MOV CL,04 SHL AX,CL

串指令

    DS:SI   源串段寄存器 :源串变址.
    ES:DI   目标串段寄存器:目标串变址.
    CX 重复次数计数器.
    AL/AX   扫描值.
    D标志 0表示重复操作中SI和DI应自动增量; 1表示应自动减量.
    Z标志 用来控制扫描或比较操作的结束.
    MOVS    串传送.( MOVSB 传送字符. MOVSW 传送字. MOVSD 传送双字. )
    CMPS    串比较.( CMPSB 比较字符. CMPSW 比较字. )
    SCAS    串扫描.把AL或AX的内容与目标串作比较,比较结果反映在标志位.
    LODS    装入串.把源串中的元素(字或字节)逐一装入AL或AX中.( LODSB 传送字符. LODSW 传送字.    LODSD 传送双字. )
    STOS    保存串.是LODS的逆过程.
    REP         当CX/ECX<>0时重复.
    REPE/REPZ   当ZF=1或比较结果相等,且CX/ECX<>0时重复.
    REPNE/REPNZ 当ZF=0或比较结果不相等,且CX/ECX<>0时重复.
    REPC        当CF=1且CX/ECX<>0时重复.
    REPNC       当CF=0且CX/ECX<>0时重复.

程序转移指令

  • 无条件转移指令 (长转移)
    • JMP 无条件转移指令
    • CALL 过程调用
    • RET/RETF 过程返回.
  • 条件转移指令 (短转移,-128到+127的距离内)( 当且仅当(SF XOR OF)=1时,OP1<OP2 )
    JA/JNBE     不小于或不等于时转移.
    JAE/JNB     大于或等于转移.
    JB/JNAE     小于转移.
    JBE/JNA     小于或等于转移.
        以上四条,测试无符号整数运算的结果(标志C和Z).
    JG/JNLE     大于转移.
    JGE/JNL     大于或等于转移.
    JL/JNGE     小于转移.
    JLE/JNG     小于或等于转移.
        以上四条,测试带符号整数运算的结果(标志S,O和Z).
    JE/JZ       等于转移.
    JNE/JNZ     不等于时转移.
    JC          有进位时转移.
    JNC         无进位时转移.
    JNO         不溢出时转移.
    JNP/JPO     奇偶性为奇数时转移.
    JNS         符号位为 "0" 时转移.
    JO          溢出转移.
    JP/JPE      奇偶性为偶数时转移.
    JS          符号位为 "1" 时转移.
  • 循环控制指令(短转移)
    • LOOP CX不为零时循环.
    • LOOPE/LOOPZ CX不为零且标志Z=1时循环.
    • LOOPNE/LOOPNZ CX不为零且标志Z=0时循环.
    • JCXZ CX为零时转移.
    • JECXZ ECX为零时转移.
  • 中断指令
    • INT 中断指令
    • INTO 溢出中断
    • IRET 中断返回
  • 处理器控制指令
    HLT 处理器暂停, 直到出现中断或复位信号才继续.
    WAIT 当芯片引线TEST为高电平时使CPU进入等待状态.
    ESC 转换到外处理器.
    LOCK 封锁总线.
    NOP 空操作.
    STC 置进位标志位.
    CLC 清进位标志位.
    CMC 进位标志取反.
    STD 置方向标志位.
    CLD 清方向标志位.
    STI 置中断允许位.
    CLI 清中断允许位.

    伪指令

    DW 定义字(2字节).
    PROC 定义过程.
    ENDP 过程结束.
    SEGMENT 定义段.
    ASSUME 建立段寄存器寻址.
    ENDS 段结束.
    END 程序结束.

处理机控制指令:标志处理指令

CLC     进位位置0指令
CMC     进位位求反指令
STC     进位位置为1指令
CLD     方向标志置1指令
STD     方向标志位置1指令
CLI     中断标志置0指令
STI     中断标志置1指令
NOP     无操作
HLT     停机
WAIT    等待
ESC     换码
LOCK    封锁

程序内存

  • 代码段CS:保存程序代码文本
  • 数据段DS:保存初始化的全局变量和静态变量
  • BSS(Block Started by Symbol): 保存未初始化的全局变量和静态变量
  • 堆(heap):动态分配内存,向地址增大的方向增长
  • 栈(stack):存放局部变量,向地址减小的方向增长

    技术图片

常用指令

  • add:加法指令,第一个是目标操作数,第二个是源操作数,格式为:目标操作数 = 目标操作数 + 源操作数;

  • sub:减法指令,格式同 add;
  • call:调用函数,一般函数的参数放在寄存器中;
  • ret:跳转会调用函数的地方。对应于call,返回到对应的call调用的下一条指令,若有返回值,则放入eax中;
  • push:把一个32位的操作数压入堆栈中,这个操作在32位机中会使得esp被减4(字节),esp通常是指向栈顶的
  • pop:与push相反,esp每次加4(字节),一个数据出栈。pop的参数一般是一个寄存器,栈顶的数据被弹出到这个寄存器中;
  • mov:数据传送。第一个参数是目的操作数,第二个参数是源操作数,就是把源操作数拷贝到目的一份。
  • xor:异或指令,这本身是一个逻辑运算指令,但在汇编指令中通常会见到它被用来实现清零功能。用 xor eax,eax这种操作来实现 mov eax,0,可以使速度更快,占用字节数更少。
  • lea:取得第二个参数地址后放入到前面的寄存器(第一个参数)中。然而lea也同样可以实现mov的操作,例如:lea edi,[ebx-0ch],方括号表示存储单元,也就是提取方括号中的数据所指向的内容,然而lea提取内容的地址,这样就实现了把(ebx-0ch)放入到了edi中,但是mov指令是不支持第二个操作数是一个寄存器减去一个数值的。
  • stos:串行存储指令,它实现把eax中的数据放入到edi所指的地址中,同时edi后移4个字节,这里的stos实际上对应的是stosd,其他的还有stosb,stosw分别对应1,2个字节。
  • jmp:无条件跳转指令,对应于大量的条件跳转指令。
  • jg:条件跳转,大于时成立,进行跳转,通常条件跳转之前会有一条比较指令(用于设置标志位)。
  • jl:小于时跳转
  • cmp:比较大小指令,,CMP OPR1 , OPR2.相当于(OPR1)-(OPR2),它并不保存运算结果,只是根据结果设置相关的条件标志位(SF、ZF、CF、OF)。CMP指令后往往跟着条件转移指令,实现根据比较的结果产生不同的程序分支的功能。
  • rep 根据ECX寄存器的值进行重复循环操作

寄存器

  • EAX:累加(Accumulator)寄存器,常用于函数返回值,它是很多加法乘法指令的缺省寄存器。
  • EBX:基址(Base)寄存器,以它为基址访问内存
  • ECX:计数器(Counter)寄存器,常用作字符串和循环操作中的计数器
  • EDX:数据(Data)寄存器,常用于乘除法和I/O指针
  • ESI:源变址寄存器
  • DSI:目的变址寄存器
  • ESP:堆栈(Stack)指针寄存器,指向堆栈顶部
  • EBP:基址指针寄存器,指向当前堆栈底部
  • EIP:指令寄存器,指向下一条指令的地址
  • ESI,EDI,分别是16位寄存器SI和DI的32位扩展,它们是源变址寄存器,和目的变址寄存器,用于串操作指令中。同时,它们也可以作为通用寄存器使用。

技术图片

#include "pch.h"
#include <iostream>
using namespace std;
int Add(int x, int y)
{
00A117E0 55                   push        ebp  //栈基址寄存器内容送栈顶
00A117E1 8B EC                mov         ebp,esp  //栈顶寄存器内容为当前栈基址 (函数调用压栈后更新栈基址为原先的栈顶,原先的栈基址已经保存在栈顶,故而能够在函数调用返回后找到上一层函数的起始地址)
00A117E3 81 EC CC 00 00 00    sub         esp,0CCh  //esp-0cch,相当于栈往下长了0CCH(51*4)个单位,/*用于预留空间给函数临时变量*/
00A117E9 53                   push        ebx  //基址寄存器压栈
00A117EA 56                   push        esi  //源变址寄存器压栈
00A117EB 57                   push        edi  //目的变址寄存器压栈
00A117EC 8D BD 34 FF FF FF    lea         edi,[ebp-0CCh]  //ebp-0cch这个值送到edi中,也就是说把目的地址寄存器的值设置成了栈基址往下0cch个字节处
00A117F2 B9 33 00 00 00       mov         ecx,33h  //计数器设置为33H,用于循环计数(51)
00A117F7 B8 CC CC CC CC       mov         eax,0CCCCCCCCh  //eax设置成0CCCCCCCCH
00A117FC F3 AB                rep stos    dword ptr es:[edi]  //rep是循环,stos是串行存储指令,它实现把eax中的数据放入到edi所指的地址中,同时edi后移4个字节
00A117FE B9 09 C0 A1 00       mov         ecx,offset _3B624A16_test.cpp (0A1C009h)  
00A11803 E8 19 FA FF FF       call        @__CheckForDebuggerJustMyCode@4 (0A11221h)  
    int z = 0;
00A11808 C7 45 F8 00 00 00 00 mov         dword ptr [z],0  //ptr指针指向变量z的地址,在这个地址上写入双字大小表示的0
    z = x + y;
00A1180F 8B 45 08             mov         eax,dword ptr [x]  //ptr指针指向变量x的地址,取这个地址双字内容送入eax,相当于就是把变量x的内容送eax
00A11812 03 45 0C             add         eax,dword ptr [y]  //ptr指针指向变量y的地址,取这个地址的双字内容与eax内容相加,结果还在eax中
00A11815 89 45 F8             mov         dword ptr [z],eax  //把eax内容送入z中

    return z;
00A11818 8B 45 F8             mov         eax,dword ptr [z]  //z内容送入eax作为返回值
}
00A1181B 5F                   pop         edi   //edi出栈
00A1181C 5E                   pop         esi  //esi出栈
00A1181D 5B                   pop         ebx  //ebx出栈
00A1181E 81 C4 CC 00 00 00    add         esp,0CCh  //栈顶指针减去0CCH,相当于退栈清除填充内容
00A11824 3B EC                cmp         ebp,esp  //检查ebp是否小于esp,调用__RTC_CheckEsp检查
00A11826 E8 00 FA FF FF       call        __RTC_CheckEsp (0A1122Bh)  
00A1182B 8B E5                mov         esp,ebp  //该函数返回后,其基地址变成当前栈中函数的栈顶地址
00A1182D 5D                   pop         ebp  //该函数的基地址已经没用了,弹出该函数的基地址
00A1182E C3                   ret  //返回指令
--- 无源文件 -----------------------------------------------------------------------
00A1182F CC                   int         3  //断点
00A11830 CC                   int         3  
00A11831 CC                   int         3  
00A11832 CC                   int         3  
00A11833 CC                   int         3  
00A11834 CC                   int         3  
00A11835 CC                   int         3  
00A11836 CC                   int         3  
00A11837 CC                   int         3  
00A11838 CC                   int         3  
00A11839 CC                   int         3  
00A1183A CC                   int         3  
00A1183B CC                   int         3  
00A1183C CC                   int         3  
00A1183D CC                   int         3  
00A1183E CC                   int         3  
00A1183F CC                   int         3  
00A11840 CC                   int         3  
00A11841 CC                   int         3  
00A11842 CC                   int         3  
00A11843 CC                   int         3  
00A11844 CC                   int         3  
00A11845 CC                   int         3  
00A11846 CC                   int         3  
00A11847 CC                   int         3  
00A11848 CC                   int         3  
00A11849 CC                   int         3  
00A1184A CC                   int         3  
00A1184B CC                   int         3  
00A1184C CC                   int         3  
00A1184D CC                   int         3  
00A1184E CC                   int         3  
00A1184F CC                   int         3  


int main()
{
00A118F0 55                   push        ebp  //栈基址寄存器内容压栈
00A118F1 8B EC                mov         ebp,esp  //esp称为新的栈基址
00A118F3 81 EC E4 00 00 00    sub         esp,0E4h  //栈下移0E4H个字节的空间
00A118F9 53                   push        ebx  //ebx esi edi入栈
00A118FA 56                   push        esi  
00A118FB 57                   push        edi  
00A118FC 8D BD 1C FF FF FF    lea         edi,[ebp-0E4h]  //edi设定
00A11902 B9 39 00 00 00       mov         ecx,39h  //循环计数器设定
00A11907 B8 CC CC CC CC       mov         eax,0CCCCCCCCh  //eax赋值
00A1190C F3 AB                rep stos    dword ptr es:[edi]  //填充0CCCCCCCCH
00A1190E B9 09 C0 A1 00       mov         ecx,offset _3B624A16_test.cpp (0A1C009h)  
00A11913 E8 09 F9 FF FF       call        @__CheckForDebuggerJustMyCode@4 (0A11221h)  
    int a = 3;
00A11918 C7 45 F8 03 00 00 00 mov         dword ptr [a],3  //a = 3
    int b = 5;
00A1191F C7 45 EC 05 00 00 00 mov         dword ptr [b],5  // b= 5
    int ret = 0;
00A11926 C7 45 E0 00 00 00 00 mov         dword ptr [ret],0  // ret = 0

    ret = Add(a, b);
00A1192D 8B 45 EC             mov         eax,dword ptr [b]  //eax累加寄存器赋值b中内容eax = b = 5
00A11930 50                   push        eax  //eax入栈
00A11931 8B 4D F8             mov         ecx,dword ptr [a] //a中内容送ecx ,ecx = a = 3 
00A11934 51                   push        ecx  //ecx入栈
00A11935 E8 60 F8 FF FF       call        Add (0A1119Ah)//调用Add函数  
00A1193A 83 C4 08             add         esp,8  //esp栈顶寄存器+8,因为一个参数+4,这里有两个参数传入,并且函数参数是向栈上方涨的
00A1193D 89 45 E0             mov         dword ptr [ret],eax  //eax送入ret变量所处内存地址。注意,此处eax是存储了Add函数的返回值,故而ret = 8
    cout << ret << endl;
00A11940 8B F4                mov         esi,esp  //栈顶寄存器内容送esi
00A11942 68 4E 12 A1 00       push        offset std::endl<char,std::char_traits<char> > (0A1124Eh)  // "push variable" means push the value of the variable and "push offset variable" means push the offset of the variable.
00A11947 8B FC                mov         edi,esp  //栈顶指针送edi
00A11949 8B 45 E0             mov         eax,dword ptr [ret]  //ret内容送eax
00A1194C 50                   push        eax  //eax入栈
00A1194D 8B 0D A8 B0 A1 00    mov         ecx,dword ptr [_imp_?cout@std@@3V?$basic_ostream@DU?$char_traits@D@std@@@1@A (0A1B0A8h)]  //ecx赋值,打印长度确定,循环打印字符,接下来调用打印函数打印字符
00A11953 FF 15 9C B0 A1 00    call        dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (0A1B09Ch)]  //调用打印函数,不断打印栈顶的值,栈顶是eax,其值等于ret
00A11959 3B FC                cmp         edi,esp    //检查edi<esp  每个过程函数调用结束后都要比较
00A1195B E8 CB F8 FF FF       call        __RTC_CheckEsp (0A1122Bh)  
00A11960 8B C8                mov         ecx,eax  //eax送ecx
00A11962 FF 15 A0 B0 A1 00    call        dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (0A1B0A0h)]  //调用打印函数 打印endl
00A11968 3B F4                cmp         esi,esp //检查esi<esp  
00A1196A E8 BC F8 FF FF       call        __RTC_CheckEsp (0A1122Bh)  
    return 0;
00A1196F 33 C0                xor         eax,eax  //eax清零
}
//退栈函数返回
00A11971 5F                   pop         edi  
00A11972 5E                   pop         esi  
00A11973 5B                   pop         ebx  
00A11974 81 C4 E4 00 00 00    add         esp,0E4h  
00A1197A 3B EC                cmp         ebp,esp  
00A1197C E8 AA F8 FF FF       call        __RTC_CheckEsp (0A1122Bh)  
00A11981 8B E5                mov         esp,ebp  
00A11983 5D                   pop         ebp  
00A11984 C3                   ret  

以上是关于VS反汇编分析的主要内容,如果未能解决你的问题,请参考以下文章

VS2015使用技巧 调试-反汇编 查看C语言代码对应的汇编代码

VS2010查看源码对应的汇编语言

如何构建反汇编代码?

VS Code 是不是有用于 C++ 扩展的内存查看器和/或反汇编器?

自制反汇编逆向分析工具 迭代第三版本

通过反汇编一个简单的C程序,分析汇编代码理解计算机是如何工作的