x86汇编语言初步学习
汇编指令编基础
通用寄存器
- EAX:(针对操作数和结果数据的)累加器(Accumulator)
- EBX:(DS段的数据指针)基址寄存器(Base Register)
- ECX:(字符串和循环操作的)计数器(Count Register)
- EDX:(I/O指针)数据寄存器(Data Register)
用作内存指针的特殊寄存器
- ESI:(字符串操作源指针)源变址寄存器
- EDI:(字符串操作目标指针)目的变址寄存器
- EBP:(SS段中栈内数据指针)扩展基址指针寄存器,即栈底指针寄存器,与rbp相同
- ESP:栈顶指针寄存器与rsp相同
段寄存器
段寄存器就是存储一个代码段开头的一部分,可以理解为一个内存被分为了多个块,而段寄存器记录的就是一个段的开头。段寄存器一般与IP一起用,IP可以认为时一个偏移量,与段寄存器一起使用可以寻找段内的具体内存,例如ce = e8000,ip=ef,cs:ip=e80ef,即CS:IP就是一个内存中的具体地址,指向的是cpu即将执行的地址。还会和jmp一起使用,用于使cpu读取其他地方的数据
程序状态与控制寄存器
- EFLAGS:标志寄存器,32个位元的01控制
- ZF(零标志器,运算结果为0时置1)
- CF(进位标志,运算结果向最高位以上进位时置1)
- OF(溢出标志),如果运算结果有溢出,那么值为1
- AF(辅助进位标志,运算结果在第3位的时候置1)
- SF(符号标志,有符号整型的符号位为1时置1),正数为0,负数为1
指令指针寄存器
- EIP / RIP:保存CPU要执行的指令地址
r与e的差别
- rax 是 64 位寄存器,能够存储 64 位(8 字节)的数据。
- eax 是 rax 的低 32 位部分,能够存储 32 位(4 字节)的数据
在某些情况下,使用 eax 只会影响低 32 位数据,而高 32 位将保持不变。 - 其他的如rbp和ebp就是64位和32位的区别
例如,执行 mov eax, 1 会将 rax 的低 32 位设置为 1,而高 32 位会被设置为 0。
rax 通常用于需要完整 64 位数据的操作,特别是在调用函数和处理返回值时。
在eax赋值给rax时只会赋值在低位,高位还会时原来rax的高位,例如0xffffffff<–0x1111 ==> 0xffff1111
常用指令
cdqe 用于拓展eax的值为更高位32位拓展为rax,64位
PUSH/POP:压栈/出栈
PUSHA/POPA 、 PUSHAD/POPAD
MOV/CALL/RET/LEA/INT/EMD:传送 / 调用 / 返回 / 加载 / 中断 / 结束
CMP/TEST:比较/测试(结果丢弃,只修改标志位寄存器)
JMP系列跳转指令
ADD/SUB/SHL/SHR(sar)/ROL/ROR:加 / 减 / 逻辑左移 / 逻辑右移 / 循环左移 / 循环右移
INC/DEC :加一 / 减一
MUL/IMUL:无符号乘法、整数乘法
DIV/IDIV:无符号除法、整数除法
AND/XOR/OR/NOT:与 / 异或 / 或 / 取反
movesx 有符号拓展,把要赋值的值进行有符号拓展成更多位,如果原来的值为负数,那么拓展的高位会补1,例如0xffff(负数)–>0xffffffff
movezx 无符号拓展,把要赋值的值拓展成更多位,高为补0;例如0xffff–>0x0000ffff
byte ptr:表示操作的数据类型是字节
rep stosd 会重复执行 stosd 指令,ecx 寄存器中的值决定了重复次数。
每次执行 stosd,它会将 eax 中的值存储到 edi 寄存器指向的地址,并将 edi 加 4(指向下一个双字位置)。然后 ecx 会减 1,直到 ecx 为 0。
lea获取的差别
lea rax, [rbp+Str] 中的str是一个偏移量,由栈底的地址加偏移量获取str的地址
lea rax, Str 中的str是一个地址,需要str被全局定义或再数据段中被定义
比较操作
在cmp操作中,有cmp ax,bx
其实cmp的操作就是ax-bx,当这个操作为0,即ax==bx时zf(zero flag)就会被标志为1,反之为1.
下面这些参考就行
如果(ax)=(bx)则(ax)-(bx)= 0,所以:zf = 1
如果(ax)!=(bx)则 (ax)-(bx)!= 0,所以:zf != 1;
如果(ax)<(bx)则(ax)-(bx)将产生借位,所以:cf = 1;
如果(ax)>=(bx)则(ax)-(bx)将不会产生借位,所以:cf = 0;
如果(ax)>(bx)则(ax)-(bx)即不借位,结果又不为零,所以:cf = 0,zf = 0;
如果(ax)<=(bx)则(ax)-(bx)即可能借位,结果可能为0,所以:cf = 1,zf = 1;
- INC 自增1 increase
- DEC 自减1 decrease
- JGE 前>=后 Jump if greater or equal
- JG 前>后 Jump if greater
- JLE 前<=后 Jump if less or equal
- JL 前<后 Jump if less
- JNE 前不等于后 Jump if not equal
- JE 前等于后 Jump if equal
- JZ,JE,jnz,jne 通过判断zf的值进行跳转
栈帧
栈帧就是函数在内存中的一个执行空间,里面存储着函数的信息
1 | PUSH EBP ;函数开始 |
ida汇编分析
1 |
|
上面汇编的加密逻辑和逆向脚本
1 |
|
汇编进阶理解与堆栈
栈操作与函数执行
push a; 把esp-4,并把a的值赋值给esp指向的位置
pop a; 把当前esp赋值给a,再把esp+4
我们取一段函数
1 | PUSH EBP |
假设我们的这个函数是由别的地方call进来的,那么在call的时候执行了一下步骤。
- 把call的下一条指令压入栈中,并把esp-4,即保存返回地址
- 修改eip的值为被调用函数的开头
- 进入被调用函数执行
- push ebp;把原函数的栈底指针压入栈,把esp-4,然后把ebp的值赋值给esp指向的地址即保存原来的栈底指针。
- mov ebp,esp;设置栈底指针为当前函数的开头。
- sub esp,n;用esp-n开辟新的栈帧,往下正常执行函数。
- …
- mov esp,ebp;回收栈帧,恢复栈顶指针到当前栈底
- pop ebp;弹出保存栈底指针,把当前esp赋值给ebp,再把esp+4
- retn ;修改eip的值为esp指向的值,esp此时指向call的下一条指令的地址。