LOADING

加载过慢请开启缓存 浏览器默认开启

汇编语言初步学习

## 汇编指令编基础

通用寄存器

  • 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的下一条指令的地址。