实战:初读反汇编

汇编yulabu2026-09-13 16 次阅读

实战:初读反汇编

先大概整理下我现在要熟悉的指令

  • mov:数据搬运

  • lea:计算数据有效地址并存储到寄存器中;但是也可以用来计算,如 lea eax, [ecx + ecx*2],这里是把右式计算后传给eax,不是算地址

  • add、sub、imul有符号乘法(常用于数组方案)

  • xor按位异或,常见 xor edx, edx 的意思就是清零edx,edx = edx ^ edx

  • and按位与,如:and eax, ebx的意思就是:eax = eax & ebx,常用于判断奇偶

  • or按位或,如: or eax, ebx的意思就是:eax = eax | ebx

  • shl逻辑左移,shr逻辑右移(补0)

  • sal算数左移,sar算数右移(左边补符号位,左移和shl没区别,x86统一用shl)

  • cmp比较:常与如下指令配合

    asm 复制代码
    je
    jne
    jg
    jl
    jge
    jle
  • test位测试/设置标志

  • jmp无条件跳转

  • je(jump if equal)相等就跳转,常与cmp共同出现

  • jne(jump if not equal)不相等就跳转

  • jg(jump if greater)大于就跳转,cmp eax, 10可以看作if eax > 10

  • Jl(jump if less)小于就跳转,反过来

  • jle和jge也是差不多,大于等于和小于等于

  • 同时还有区分有符号和没符号

    复制代码
    jg / jl / jge / jle 有符号
    ja / jb / jae / jbe 无符号
  • call调用函数,配套的还有ret

  • push压栈/pop出栈

大概就这些,那么我们来实战吧

先解决一下之前看不懂的指令(

首先搞懂一个困扰我挺久的变量(就当这东西是变量吧),反汇编里会这么写

asm 复制代码
mov rax, qword ptr ds:[4042E0]

这什么雷霆,前面的指令我能看懂,后面的一串是啥?

先说第一个,本质上是在说明要操作多大的数据:

  • byte:1字节,8bit
  • word:2字节,16bit
  • dword(double word):4字节,32bit
  • qword(quad word):8字节,64bit
  • tword(

然后就是第二个,ptr->pointer,前面的byte等说明就是要告诉ptr要取多大的数据,ptr是用来明确操作大小的

至于第三个,ds几乎可以忽略,只看后面的地址就好理解了,带了个[]的基本就是要取里面的地址指向的数据

如果换成lea呢

asm 复制代码
lea rax, qword ptr ds:[4042E0]

我们知道lea是用来计算地址的,所以[4042E0]算完就是4042E0

初步熟悉c代码在汇编中的表示

c 复制代码
#include <stdio.h>
int main()
{
    printf("test");
    int a = 1;
    int b = 2;
    int c = a + b;
    char ch = '1';
    float f = 1;
    double d = 1;
    long double ld = 1;
    long long ll = 1;
    return 0;
}

我们用打印来定位主函数的位置,然后我们来观察下不同的数据类型在汇编中如何存储

image-20260912165154045

有点神圣啊,怎么又多了这么多指令,诶,好像多的也不多

  • movss:移动一个 32 位单精度浮点数
  • movsd:移动一个 64 位双精度浮点数 double
  • fld1:把浮点数 1.0 压入 x87 浮点栈(不用细究)
  • fstp:可以理解成把浮点栈中的值弹出来到内存地址

这下应该没有看不懂的了(

我们可以看到,最简单的a+b

c 复制代码
int a = 1;
int b = 2;
int c = a + b;

在汇编中的操作是;

asm 复制代码
mov dword ptr ss:[rbp-4],1              
mov dword ptr ss:[rbp-8],2              
mov edx,dword ptr ss:[rbp-4]            
mov eax,dword ptr ss:[rbp-8]            
add eax,edx                             
mov dword ptr ss:[rbp-C],eax            

我们会发现,简单的把常数1和2先放在栈帧提前划分好的局部变量空间里面:这其实是给栈上的一个局部变量赋值

然后在让我们的寄存器从栈空间中取出,执行add,然后再放回局部变量空间中

我们会发现放的位置又不同了,其实这个局部变量就是放在第三个槽位,即我们在c代码中写的变量c

理解完完整的加法操作,我们再来大概看一下其他数据类型是怎么赋值的:

c 复制代码
char ch = '1';

这个我们发现非常简单啊

asm 复制代码
mov byte ptr ss:[rbp-D],31

根据ASCII表,31号确实就是符号1,这里将这个常数以一字节的大小放入预留的栈空间

而且很有意思的一点就是:这里相对rbp的位置跟前面的int比起来就移了一位,和这个一字节对应上了

c 复制代码
float f = 1;

老实说 这个float之前已经演示过了:

asm 复制代码
movss xmm0,dword ptr ds:[404008]
movss dword ptr ss:[rbp-14],xmm0

就是用专属寄存器xmm0来存,至于为什么是从这个404008的位置取出来,我猜测应该是在编译的时候放在那了,估计是个放数据的区域,之前的字符串也是放在这种40开头的位置,然后进7位放在局部变量空间(应该是编译的时候考虑了对齐),这里dword已经说明这个数据就占4字节

c 复制代码
double d = 1;

这个double也没啥好说的,也是浮点数,也用xmm0,但是用的qword和movsd,也就是说精度到了64位,占8个字节

asm 复制代码
movsd xmm0,qword ptr ds:[404010]
movsd qword ptr ss:[rbp-20],xmm0

但是进了12位是为啥,我服了,我学个汇编容易吗,唉,编译器爱咋地咋地吧,应该是自己预留4个字节的padding了

c 复制代码
long double ld = 1;
long long ll = 1;

后面的long double就更离谱了

asm 复制代码
fld1 
fstp tword ptr ss:[rbp-30]
mov qword ptr ss:[rbp-38],1

不过也还算好理解,前面的fld自己压入了自己的栈,我就不管了,后面fstp就把这个值弹出来给当前局部变量空间(又吃16字节)

long long就正常留了8个字节的空间

算数运算在反汇编

我们使用以下的c语言代码

c 复制代码
#include <stdio.h>
int main()
{
    printf("test");
    int a = 6;
    int b = 3;
    int c;
    c = a + b;
    c = a - b;
    c = a * b;
    c = a / b;
    c = a % b;
    return 0;
}

编译后进入反汇编,我们找到以下指令

asm 复制代码
mov dword ptr ss:[rbp-4],6
mov dword ptr ss:[rbp-8],3
mov edx,dword ptr ss:[rbp-4]
mov eax,dword ptr ss:[rbp-8]
add eax,edx
mov dword ptr ss:[rbp-C],eax
mov eax,dword ptr ss:[rbp-4]
sub eax,dword ptr ss:[rbp-8]
mov dword ptr ss:[rbp-C],eax
mov eax,dword ptr ss:[rbp-4]
imul eax,dword ptr ss:[rbp-8]
mov dword ptr ss:[rbp-C],eax
mov eax,dword ptr ss:[rbp-4]
cdq 
idiv dword ptr ss:[rbp-8]
mov dword ptr ss:[rbp-C],eax
mov eax,dword ptr ss:[rbp-4]
cdq 
idiv dword ptr ss:[rbp-8]
mov dword ptr ss:[rbp-C],edx
mov eax,0
add rsp,30
pop rbp
ret 

我们继续一步步分析吧,加法没什么好说的,之前演示过了,我们看看加法后怎么处理减法的

asm 复制代码
mov dword ptr ss:[rbp-4],6
mov dword ptr ss:[rbp-8],3
mov edx,dword ptr ss:[rbp-4]
mov eax,dword ptr ss:[rbp-8]
add eax,edx
mov dword ptr ss:[rbp-C],eax
mov eax,dword ptr ss:[rbp-4]
sub eax,dword ptr ss:[rbp-8]
mov dword ptr ss:[rbp-C],eax

前四行代码把我的常量6、3压入局部变量空间,然后从栈中取出给寄存器,执行add,结果在eax

接下来的指令就先把计算结果压入栈,然后寄存器重新取出局部变量

asm 复制代码
mov dword ptr ss:[rbp-C],eax
mov eax,dword ptr ss:[rbp-4]
sub eax,dword ptr ss:[rbp-8]
mov dword ptr ss:[rbp-C],eax

做减法,计算结果继续压入栈,然后看看乘法

c 复制代码
c = a * b;

好像挺简单的

asm 复制代码
mov eax,dword ptr ss:[rbp-4]
imul eax,dword ptr ss:[rbp-8]
mov dword ptr ss:[rbp-C],eax

将变量a的数据取出来继续算,因为这里用的是同一个变量c来存,所以计算结果入栈的时候把之前的计算结果覆盖了

接着是除法:

c 复制代码
c = a / b;

等下,我刚夸你简单来着,怎么又背刺我

asm 复制代码
cdq 
idiv dword ptr ss:[rbp-8]
mov dword ptr ss:[rbp-C],eax
mov eax,dword ptr ss:[rbp-4]

这个cdq是什么鬼这个cdq还有一个长得很像的兄弟cqo,这俩哥们都是专门给除法做准备的,而且非常神秘(

  • cdq:把 EAX 的符号位扩展到 EDX:EAX
  • cqo:把 RAX 的符号位扩展到 RDX:RAX

还是好神秘,不懂是什么意思,这里的意思就是说,被除数不是单独一个寄存器存放:

以cdq为例,这里的被除数存在两个寄存器里面,从32为提升到64位,其中EDX存高32位的数据,EAX存低32位的数据

除法计算完之后,得到的结果这样保存

复制代码
EAX = 商
EDX = 余数

那就好理解的,等下,好理解在哪(

为啥idiv只有一个操作数啊,不兑,哦我懂了

cdq和idiv的关系就和cmp与je很像,差不多强行绑定的,cdq这一步实际上就是告诉下面的idiv:被除数是EDX:EAX,接下来接收的是除数

这就能说的通了,因为这波做的是整除,所以是eax的数据被压入栈了,最后恢复变量a

asm 复制代码
cdq 
idiv dword ptr ss:[rbp-8]
mov dword ptr ss:[rbp-C],edx

至于取余,那就是留edx的数据啦,后面就都是很经典的收尾工作了

位运算在反汇编

我们用以下示例代码

c 复制代码
    #include <stdio.h>
    int main()
    {
        printf("yulabu");
        int a = 12;
        int b = 10;
        int c;
        c = a & b;
        c = a | b;
        c = a ^ b;
        c = a << 2;
        c = a >> 1;
        return 0;
    }

大概常用的就这些位运算吧,反汇编大概长这样

asm 复制代码
mov dword ptr ss:[rbp-4],C
mov dword ptr ss:[rbp-8],A
mov eax,dword ptr ss:[rbp-4]
and eax,dword ptr ss:[rbp-8]
mov dword ptr ss:[rbp-C],eax
mov eax,dword ptr ss:[rbp-4]
or eax,dword ptr ss:[rbp-8]
mov dword ptr ss:[rbp-C],eax
mov eax,dword ptr ss:[rbp-4]
xor eax,dword ptr ss:[rbp-8]
mov dword ptr ss:[rbp-C],eax
mov eax,dword ptr ss:[rbp-4]
shl eax,2
mov dword ptr ss:[rbp-C],eax
mov eax,dword ptr ss:[rbp-4]
sar eax,1
mov dword ptr ss:[rbp-C],eax
mov eax,0
add rsp,30
pop rbp
ret 

哇哦,感觉和前面的没啥区别,下班下班

条件判断在反汇编

先来个简单的if-else

c 复制代码
#include <stdio.h>
int main()
{
    printf("yulabu");
    int judge = 10;
    int ans;
    if(judge == 10){
        ans = 1;
    }else{
        ans = 0;
    }
    return 0;
}

编译,然后反汇编一下,得到以下代码:这次有跳转地址,不得不截图

image-20260913145918352

这还是很简单的:10压入局部变量空间,然后用这个局部变量和10对比(好唐的代码)

jne:如果比较结果为不相等,就跳转到我们这个模块的40157F位置,就相当于跳转到:

asm 复制代码
mov dword ptr ss:[rbp-8],0

就是我们的ans=0

实际上是相等的,所以这个跳转不会执行,而是继续往下执行:

asm 复制代码
mov dword ptr ss:[rbp-8],1

就是我们的ans=1

然后无条件跳转到401586:实际上就是跳过赋值为0的那段指令,然后开始回收栈帧

那我们来接着看看其他条件判断是怎么搞的

c 复制代码
#include <stdio.h>

int main()
{
    printf("yulabu");
    int a = 10;
    int b = 20;
    int ans;
    if (a == b)ans = 0;
    if (a != b)ans = 1;
    if (a > b)ans = 2;
    if (a < b)ans = 3;
    if (a >= b)ans = 4;
    if (a <= b)ans = 5;
    return 0;
}

得到反汇编;

image-20260913151117980

我发现一个规律!汇编里面的条件判断命令和c代码是反过来的!

在c语言中,我们判断的思路是:是否满足条件,满足就执行下面的指令,否则就跳过

在汇编中,判断思路却是:是否不满足条件,不满足就跳过下面的指令,否则继续执行

所以c语言的中==到了汇编就变成jne了!嗯,就这样理解

神秘的test

至今不清楚test有什么用,反汇编我也搞不出来,先留着以后遇到了再记录

循环在反汇编

我们使用以下c语言代码作为示例

c 复制代码
#include <stdio.h>

int main()
{
    int sum = 0;

    for (int i = 0; i < 5; i++)
    {
        sum += i;
    }

    printf("%d\n", sum);

    return 0;
}

这个循环长这样:

image-20260913153748375

前面的两个初始化应该分别是sum和i,都被赋值为0,然后先跳转到条件判断,看看循环能不能进行:

asm 复制代码
jle test_main.401579

依旧和我c语言写的是反过来的,显然现在的i是小于等于4的,所以被跳转去执行循环了(

asm 复制代码
mov eax,dword ptr ss:[rbp-8]
add dword ptr ss:[rbp-4],eax
add dword ptr ss:[rbp-8],1

这个循环就是一个简单的加法,运行完再判断,直到i>4,跳转不再执行,就往下走结束函数了

肥肠简单!

switch在反汇编

这个应该跟if差不多吧(大概

c 复制代码
#include <stdio.h>

int main()
{
    int x = 2;
    int result = 0;

    switch (x)
    {
        case 1:
            result = 100;
            break;

        case 2:
            result = 200;
            break;

        case 3:
            result = 300;
            break;

        default:
            result = -1;
            break;
    }

    printf("%d\n", result);

    return 0;
}

然后看看反汇编

image-20260913155151689

我们会发现这个switch和之前的if不一样,这里排了一堆条件判断和cmp的组合

asm 复制代码
cmp dword ptr ss:[rbp-8],2
je test_main.401592
cmp dword ptr ss:[rbp-8],3
je test_main.40159B
cmp dword ptr ss:[rbp-8],1
jne test_main.4015A4

条件成立就跳转到对应的位置执行指令,不然就继续判断

前面两个还挺正常的,就是比较是不是2或3,是的话就跳转执行下一步指令,不然继续判断

但是最后的1判断有些不一样,可能switch开头第一个条件编译器都会这样特殊处理吧,用的是jne判断,如果是1的话就继续往下执行

asm 复制代码
mov dword ptr ss:[rbp-4],64
jmp test_main.4015AC

然后跳转到4015AC(跟其他两个条件一样的跳转位置2)

如果不等于1,就跳转到5015A4执行:

asm 复制代码
mov dword ptr ss:[rbp-4],FFFFFFFF

最后result变成一个神圣的-1,后续就是打印输出的功能了

**如果没有break,或许这个jmp的位置会有所不同?**不管了先这样吧

下一波搞指针吧

评论

正在加载评论区…