通用寄存器
- EAX:(针对操作数和结果数据的)累加器(Accumulator)
- EBX:(DS段的数据指针)基址寄存器(Base Register)
- ECX:(字符串和循环操作的)计数器(Count Register)
- EDX:(I/O指针)数据寄存器(Data Register)
用作内存指针的特殊寄存器
- ESI:(字符串操作源指针)源变址寄存器
- EDI:(字符串操作目标指针)目的变址寄存器
- EBP:(SS段中栈内数据指针)扩展基址指针寄存器,即栈底指针寄存器,与rbp相同
- ESP:栈顶指针寄存器与rsp相同
段寄存器
- CS:代码段寄存器(code s)
- SS:栈段寄存器(stack s)
- DS:数据段寄存器(data s)
- FS:数据段寄存器
- ES:附加数据寄存器
- GS:数据段寄存器
对段寄存器的理解
段寄存器就是存储一个代码段开头的一部分,可以理解为一个内存被分为了多个块,而段寄存器记录的就是一个段的开头。段寄存器一般与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的值进行跳转
栈帧
栈帧就是函数在内存中的一个执行空间,里面存储着函数的信息
PUSH EBP ;函数开始
MOV EBP,ESP ;将栈顶地址存入EBP中
.... ;函数执行,期间EBP地址不变
MOV ESP,EBP ;基准点地址给到ESP
POP EBP ;栈状态恢复,弹出EBP
RETN ;
ida汇编分析
.text:0000000000401550 push rbp ;栈中压入rbp
.text:0000000000401551 mov rbp, rsp ;记录栈底指针
.text:0000000000401554 sub rsp, 90h ;栈顶地址减去90h,即规定栈顶地址,规划栈帧
.text:000000000040155B call __main ;调用函数
.text:0000000000401560 lea rcx, Buffer ; "input your flag:" ;加载字符串到rcx
.text:0000000000401567 call puts ;调用函数输出字符串
.text:000000000040156C lea rax, [rbp+Str] ;加载一段缓冲区,并把地址加载到rax。这里的str为一个偏移量,那么[rbp+Str]指向的就是栈内的str的开始地址
.text:0000000000401570 mov rdx, rax ;把rax的地址转到rdx中
.text:0000000000401573 lea rcx, Format ; "%s",获取输入类型到rcx
.text:000000000040157A call scanf ;调用函数输入
.text:000000000040157F lea rax, [rbp+Str] ;再次加载str的缓冲区
.text:0000000000401583 mov rcx, rax ; Str,把str的地址加载到rax
.text:0000000000401586 call strlen ;调用strlen函数获取str的长度
.text:000000000040158B cmp rax, 23h ; 比较str的长度
.text:000000000040158F jz short loc_40159B ;根据返回zf值进行跳转
.text:0000000000401591 mov eax, 0 ;如果没有跳转那么把返回值设为0,以表示函数返回
.text:0000000000401596 jmp loc_40162B ;跳转到末尾
.text:000000000040159B ; ---------------------------------------------------------------------------
.text:000000000040159B
.text:000000000040159B loc_40159B: ; CODE XREF: main+3F↑j
.text:000000000040159B mov [rbp+var_4], 0 ;[rbp+var_4]即是var_4的开始地址,并把0赋值给var_4
.text:00000000004015A2
.text:00000000004015A2 loc_4015A2: ; CODE XREF: main+C8↓j
.text:00000000004015A2 cmp [rbp+var_4], 22h ; 这里判断var_4的值是不是等于22h,根据下文了解,估计是一个循环计数器
.text:00000000004015A6 jg short loc_40161A ;如果var_4>34,那么跳转到40161A,输出正确,推测下面代码存在检测,如果检测失败,那么就会跳转到错误。
.text:00000000004015A8 mov eax, [rbp+var_4] ;把var_4的值存入eax
.text:00000000004015AB cdqe ;把eax拓展成rax
.text:00000000004015AD movzx eax, [rbp+rax+Str] ;[rbp+rax+Str]可以理解成rbp栈底的地址加上str的索引再加上rax的偏移值,那么就是str[rax]
.text:00000000004015B2 xor eax, 52h ;把eax的值与52h进行异或,异或的结果会存为eax中
.text:00000000004015B5 mov edx, eax ;把eax中的值赋值给edx
.text:00000000004015B7 mov eax, [rbp+var_4] ;把Var_4的值赋值给eax,根据下面来看,是要把eax拓展成rax然后作为str的索引
.text:00000000004015BA cdqe ;把eax拓展成rax
.text:00000000004015BC mov [rbp+rax+Str], dl ;把str[rax]赋值为dl,dl就是edx_low即是edx的低八位(一个字符),由上面可知此时的dl为异或后的数据
.text:00000000004015C0 mov eax, [rbp+var_4] ;把var_4的值赋值给eax
.text:00000000004015C3 cdqe ;老样子
.text:00000000004015C5 movzx eax, [rbp+rax+Str] ;把str[rax]赋值给eax,并无符号拓展(unsigned char)
.text:00000000004015CA add eax, 5 ;把eax加上5,即str[rax]+5
.text:00000000004015CD mov edx, eax ;把处理后的值保存到edx
.text:00000000004015CF mov eax, [rbp+var_4] ;再获取var_4的值到eax
.text:00000000004015D2 cdqe ;继续拓展为rax
.text:00000000004015D4 mov [rbp+rax+Str], dl ;赋值edx的低八位给str[rax],即把加5后的str[rax重新赋值]
.text:00000000004015D8 mov eax, [rbp+var_4] ;获取str[rax]的值到eax
.text:00000000004015DB cdqe ;继续把eax拓展成rax
.text:00000000004015DD movzx eax, [rbp+rax+Str] ;获取str[rax]到eax,并无符号拓展
.text:00000000004015E2 movsx eax, al ;把eax的低字节拓展为
.text:00000000004015E5 mov edx, [rbp+var_4] ;再次获取var_4的值到edx
.text:00000000004015E8 movsxd rdx, edx ;带符号拓展为赋值到rdx
.text:00000000004015EB lea rcx, ds:0[rdx*4]
;ds是一个说明,说明访问的数据段。0[]表示没有额外偏移量,表示从获取到的地址开始rax*4,就是获取rax*4的指针,4为一个数组的字节大小,rax的值就是偏移量,如果rax是1,那么表示的是获取到4索引的指针,即4字节数组的第二个元素。这里rax为0,那么rcx就是0,即rcx记录了一个由var_4决定的指针偏移量
.text:00000000004015F3 lea rdx, res ;获取res的指针到rdx
.text:00000000004015FA mov edx, [rcx+rdx] ;获取res的第一个字节到edx
.text:00000000004015FD cmp eax, edx ;把str与res进行比较判断
.text:00000000004015FF jz short loc_401614 ;相等则进行跳转
.text:0000000000401601 lea rcx, aWrong ; "Wrong!"
.text:0000000000401608 call puts
.text:000000000040160D mov eax, 0
.text:0000000000401612 jmp short loc_40162B
.text:0000000000401614 ; ---------------------------------------------------------------------------
.text:0000000000401614
.text:0000000000401614 loc_401614: ; CODE XREF: main+AF↑j
.text:0000000000401614 add [rbp+var_4], 1 ;相等则跳转到这里,把var_4的值加上1,
.text:0000000000401618 jmp short loc_4015A2 ;返回循环起点继续循环
.text:000000000040161A ; ---------------------------------------------------------------------------
.text:000000000040161A
.text:000000000040161A loc_40161A: ; CODE XREF: main+56↑j
.text:000000000040161A lea rcx, aGood ; "Good!"
.text:0000000000401621 call puts
.text:0000000000401626 mov eax, 0
.text:000000000040162B
.text:000000000040162B loc_40162B: ; CODE XREF: main+46↑j
.text:000000000040162B ; main+C2↑j
.text:000000000040162B add rsp, 90h ;把栈顶恢复成栈底,回收栈帧
.text:0000000000401632 pop rbp ;弹出rbp
.text:0000000000401633 retn ;函数返回
.text:0000000000401633 main endp ;函数结束
hint = 0x21,0x6,0x6,0x16,0xb,0x19,0x2e,0x65,0x35,0x6a,0x6f,0x38,0x36,0x84,0x70,0x3b,0x39,0x65,0x38,0x35,0x84,0x6f,0x36,0x3c,0x6a,0x38,0x68,0x84,0x66,0x70,0x3b,0x38,0x6a,0x36,0x34,
上面汇编的加密逻辑和逆向脚本
#include <stdio.h>
char str[] = { 0x21,0x6,0x6,0x16,0xb,0x19,0x2e,0x65,
0x35,0x6a,0x6f,0x38,0x36,0x84,0x70,0x3b,0x39,0x65
,0x38,0x35,0x84,0x6f,0x36,0x3c,0x6a,0x38,0x68,0x84,
0x66,0x70,0x3b,0x38,0x6a,0x36,0x34 };
int enc() {
int len = 0x23;
for (int i = 0; i < len; i++) {
str[i] ^= 0x52;
str[i] += 5;
}
return 0;
}
int dec() {
int len = 0x23;
for (int i = 0; i < len; i++) {
str[i] -= 5;
str[i] ^= 0x52;
}
return 0;
}
int main() {
dec();
printf("%s", str);
}
汇编进阶理解与堆栈
栈操作与函数执行
push a; 把esp-4,并把a的值赋值给esp指向的位置
pop a; 把当前esp赋值给a,再把esp+4
我们取一段函数
PUSH EBP
MOV EBP,ESP
sub esp,n
call enc;
....
MOV ESP,EBP
POP EBP
RETN
假设我们的这个函数是由别的地方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的下一条指令的地址。