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