当前位置:首页 » 《随便一记》 » 正文

CSAPP:BombLab 详细解析

18 人参与  2023年05月03日 11:05  分类 : 《随便一记》  评论

点击全文阅读


Bomb Lab 来自《深入理解计算机系统》(CSAPP)一书的第三章的配套实验,该实验的目的是通过反汇编可执行程序,来反推出程序执行内容,进而能够正确破解”密码“,解除“炸弹”。

本实验共有6个 phase,对于每个 phase,你需要输入一段字符串,然后让代码中 explode_bomb函数不执行,这样就不会 boom !

准备工作

在拆炸弹之前我们先复习一下第三章的知识

1.关于跳转指令

指令解释
jz如果ZF=1,跳转至指定位置
jnz如果ZF=0,跳转至制定位置
je与jz类似,但通常在一条cmp指令后使用。如果源操作数与目的操作数相等,则跳转
jne与jnz类似,但通常在一条cmp指令后使用。如果源操作数与目的操作数不相等,则跳转
jgcmp指令做有符号比较之后,如果目的操作数大于原操作数,跳转
jgecmp指令做有符号比较之后,如果目的操作数大于或等于原操作数,跳转
ja与jg类似,但使用无符号比较
jae与jge类似,但使用无符号比较
jlcmp指令做有符号比较之后,如果目的操作数小于原操作数,则跳转
jlecmp指令做有符号比较之后,如果目的操作数小于或等于原操作数,则跳转
jb与jl类似,但使用无符号比较
jbe与jle类似,但使用无符号比较
jo如果前一条指令置位了溢出标志位(OF=1),则跳转
js如果符号标志位被置位(SF=1),则跳转
jecxz如果ECX=0,则跳转

2. 关于栈

当某个函数运行时,机器需要分配一定的内存去进行函数内的各种操作,这个过程中分配的那部分栈称为栈帧。下图描述了栈帧的通用结构。

栈帧是一段有界限的内存区间,由最顶端的两个指针界定,寄存器 %ebp 为帧指针(栈底),而寄存器 %esp 为栈指针(也就是说寄存器%ebp保存了所分配内存的最高地址,寄存器%esp保存了所分配内存的最低地址)。

当程序执行时,栈指针(栈顶)可以移动,因此大多数信息的访问都是相对于桢指针的。

 在函数被调用之前,调用者会为调用函数做准备,具体来说就是传参。(传参对应的寄存器分别为:第一个参数%rdi,第二个参数%rsi......)

(c 程序使用栈存放局部变量、函数参数和返回地址)

被调用函数运行时,我们在函数里面是要执行各种操作的,所以我们需要给新栈帧分配一定的内存。也就是:

sub $0x10,%esp

(举个例子,具体分配内存大小按照所需空间分配),将%esp低地址移动16个字节。有了这么多的储存空间,才能支持函数里面的各种操作。

被调用函数运行结束时,恢复原来栈顶的状态。

⚠️这里要注意:函数的返回值一般储存在%eax寄存器中

请把下面这张图刻进 DNA 

3.关于sscanf

sscanf(char *string, char *format, arg1,arg2, arg3....)

其中是%rdi寄存器作为指向string的头指针

同理,%rsi作为format

arg1, arg2, arg3, arg4分别是由寄存器%rdx, %rcx, %r8, %r9存储

如果上面的知识你已经掌握了,那我们来开始拆炸弹吧!

Bomb Lab

首先拿到代码我们先看 README 文件,好吧,什么都没有,我们继续看其他文件。

发现 bomb.c 文件,但是没有头文件,所以不能进行运行和编译。但可以看出该程序要求从命令行或者文件以“行”为单位读入字符串,每行字符串对应一个phase的输入。如果phase执行完毕,会调用phase_defused 函数表明该 phase 成功搞定。

最后剩一个可执行文件,我们通过gdb调试,反汇编bomb文件,尝试得到该可执行文件的汇编代码。

具体操作如下:

objdump -d bomb > bomb.asm

这样就把反汇编得到的汇编代码存在一个文件 bomb.asm 中,便于查看。

然后通过打 breakpoint 的方法,以及查看寄存器和内存里存的值的情况,结合汇编语句,推算出应该输入的语句。

phase_1

在汇编代码中找到phase_1函数

0000000000400ee0 <phase_1>:  400ee0:48 83 ec 08          sub    $0x8,%rsp  400ee4:be 00 24 40 00       mov    $0x402400,%esi  400ee9:e8 4a 04 00 00       callq  401338 <strings_not_equal>  400eee:85 c0                test   %eax,%eax  400ef0:74 05                je     400ef7 <phase_1+0x17>  400ef2:e8 43 05 00 00       callq  40143a <explode_bomb>  400ef7:48 83 c4 08          add    $0x8,%rsp  400efb:c3                   retq   

阅读代码我们先找到了 explode_bomb,在继续往上看

由于我们不能执行 explode_bomb 函数,所以需要在该函数之前执行跳转指令跳过explode_bomb 函数,那么什么条件可以执行该跳转指令呢?我们在继续往上看

发现 %eax 作为上一个函数的返回值,若 %eax 为0,才可以执行跳转

继续往上推发现了函数 strings_not_equal ,通过阅读代码可以发现这个函数是判断输入的两个字符串是否相等,根据上面我们提到的知识可以知道函数传进去的参数分别在寄存器 %edi 和 %es i中,其中 %edi 是我们输入的字符串

答案出来了,寄存器 %esi 里的值就是本题答案,寄存器 %esi 是被地址在 0x402400 的内容赋值,我们可以通过gdb查看一下该地址内容

先进入bomb(注意此时路径一定要在bomb文件下)

gdb bomb

查看该地址内容

(gdb) x/s 0x402400

出现本题答案

Border relations with Canada have never been better. 

测试结果如下: 

phase_2  (循环)

还是阅读代码找到 explode_bomb 函数,发现有两个

我们先看一下如何避免第一个引爆函数的执行

好与上题同理,这里需让 %rsp 的值为1,后执行跳转指令 je

目标地址 400f30 处是 lea 指令,功能为加载有效地址

两条指令执行后运行时栈的状态为

 设置完栈地址后又执行了跳转指令

  400f17:8b 43 fc             mov    -0x4(%rbx),%eax  400f1a:01 c0                add    %eax,%eax  400f1c:39 03                cmp    %eax,(%rbx)  400f1e:74 05                je     400f25 <phase_2+0x29>  400f20:e8 15 05 00 00       callq  40143a <explode_bomb>  400f25:48 83 c3 04          add    $0x4,%rbx  400f29:48 39 eb             cmp    %rbp,%rbx  400f2c:75 e9                jne    400f17 <phase_2+0x1b>  400f2e:eb 0c                jmp    400f3c <phase_2+0x40>

我们来仔细看一下这几条指令

首先 %eax 为 %rsp 指针指向的位置(值为1),%eax = %eax + %eax(此时值为2)

比较 %eax 与% rbx 指针所指向的值,这里为了不引爆炸弹,所以 %rbx 指针指向的值必须等于 %eax(值为2),那么我们可以知道 %rbx 指针指向的位置的值为 2

下一步将 %rbx + 4(其实也就是%rbx指针向上移动4个字节),比较 %rbp 与 %rbx ,相等则跳到结束语句,不相等则继续执行上几条指令

由此我们可以看出这是几条语句是循环指令,当 %rbp 与 %rbx 的值相等,则结束循环,若不相等则仍然执行上述指令

同理可得整个栈的内容为

由此phase_2的功能大概可以清楚了,首先将字符串解析成6个数字的正数数组,其中第一个整数必须为1,后面的每一个元素都是前一个元素的两倍。

即可得到本题答案

1 2 4 8 16 32

phase_3 (switch-case)

这次我们从上往下开始看

进入 phase_3 之后,首先是通过 sscanf 解析字符串,通过 gdb 调试可看出 sscanf 的 format 是%d %d  因此推断输出两个数字

(gdb) x/s 0x4025cf0x4025cf:"%d %d"

好下面我们开始找 explode_bomb 函数,发现有四个( 400f6f 行也算进去了)

400f60:83 f8 01             cmp    $0x1,%eax400f63:7f 05                jg     400f6a <phase_3+0x27>400f65:e8 d0 04 00 00       callq  40143a <explode_bomb>

在找到第一个 explode_bomb 函数后往上看,发现在地址 400f63 处必须跳转

也就等同于输入的第一个值要大于等于 1

下面我们找到第二个函数

  400f6a:83 7c 24 08 07       cmpl   $0x7,0x8(%rsp)  400f6f:77 3c                ja     400fad <phase_3+0x6a> #bomb

此时 7 必须大于 0x8(%rsp) --就是输入的第一个值,才不会发生爆炸

我们继续往下看

将输入的第一个值存到 %eax 中,然后执行一个间接跳转,地址为内存地址 0x402470(,%rax,8)处的值(这里%rax是我们输入的第一个值)

下面我们用 gdb 调试一下看看内存地址为 0x402470(,%rax,8) 的值

这里我们能看出,当输入值(%rax)为 0 的时候,跳转到 0x400f7c

同理为 1 的时候,跳到 0x400fb9 ...... 可以得出一个跳转表

index01234567
address0x400f7c0x400fb90x400f830x400f8a0x400f910x400f980x400f9f0x400fa6
%eax207311707256389206682327

为了跳过 400fad 行的炸弹,我们需要完成跳转表跳到  400fbe

这里为跳过 400fc4 行的 explode_bomb 函数,我们输入的第二个值要对应上面的跳转表

所以答案出来了,可以是以下答案的任意一种

1 311 / 2 707 / 3 256 / 4 389 / 5 206 / 6 682 / 7 327

phase_4 (递归)

这回我们还是从上往下看,发现很多代码与上一个相同

这里的 sscanf 也是输入两个数字,分别在地址0x8(%rsp)、0xc(%rsp) 上

  401029:83 f8 02             cmp    $0x2,%eax  40102c:75 07                jne    401035 <phase_4+0x29> #bomb

在 40102c 行我们遇到了第一个炸弹,此时限制条件是输入的第一个值不能为 2

  40102e:83 7c 24 08 0e       cmpl   $0xe,0x8(%rsp)  401033:76 05                jbe    40103a <phase_4+0x2e>  401035:e8 00 04 00 00       callq  40143a <explode_bomb>

在这里的为了跳过这个炸弹,限制条件是0x8(%rsp) --输入的第一个值要小于 14(0xe) 

  40103a:ba 0e 00 00 00       mov    $0xe,%edx  40103f:be 00 00 00 00       mov    $0x0,%esi  401044:8b 7c 24 08          mov    0x8(%rsp),%edi  401048:e8 81 ff ff ff       callq  400fce <func4>

再解决这两个限制条件之后,将 edx、esi、edi 赋值,将参数传进 func4函数

当执行完 func4 函数之后返回的值在寄存器 %eax 中

  40104d:85 c0                test   %eax,%eax  40104f:75 07                jne    401058 <phase_4+0x4c>  401051:83 7c 24 0c 00       cmpl   $0x0,0xc(%rsp)  401056:74 05                je     40105d <phase_4+0x51>  401058:e8 dd 03 00 00       callq  40143a <explode_bomb>

此时为了防止调用 explode_bomb 函数,返回值 %eax 要为 0 

并且,0xc(%rsp) --输入的第二个值 也必须为 0

至此我们知道:

解析字符串为两个数字 x、y

x 必须小于等于14

调用 fun​​​​​​​c4,x、0、14 分别为其参数

func4 返回值必须为0,y 必须为0

那下面看一下 func4 函数

这个函数最初看着可能有点麻烦,需要整理一下

eax = edxeax = eax - esiecx = eax ecx = 0eax = eax + ecxeax = eax / 2ecx = esi + eaxif(edi >= ecx){    return    eax = 0 //正好是我们想要的结果    edi <= ecx }else{    edx = rcx - 1    func4()    eax = eax + eax    return}

我们将之前对参数带入发现:

        寄存器 %eax 和 %ecx 存了7

        %ecx 寄存器的值与 %edi 里的值比较 (%edi里是我们输入的第一个数)

这里比较的时候有两种情况

发生跳转时

  400ff2:b8 00 00 00 00       mov    $0x0,%eax  400ff7:39 f9                cmp    %edi,%ecx  400ff9:7d 0c                jge    401007 <func4+0x39>

        此时 %edi 大于等于 7

        地址400ff2处把%eax置为0,正好是我们想要的结果

        比较要求 7 大于等于 %edi,后跳转可以顺利退出

我们发现当 %edi = 7 的时候可以满足这些条件顺利退出(这是一个答案,也就是说第一个输入的值可以为 7 )

不发生跳转时

  400fe6:8d 51 ff             lea    -0x1(%rcx),%edx  400fe9:e8 e0 ff ff ff       callq  400fce <func4>  400fee:01 c0                add    %eax,%eax  400ff0:eb 15                jmp    401007 <func4+0x39>

这里用到递归,但也不要怕,我们只需要按照上面的指令,对寄存器 %eax 和 %ecx 一顿操作就可以了

至此答案就出来了:我们第一个输入的值可以是 0、1、3、7,第二个输入的值为 0

0 0 / 1 0 / 3 0 / 7 0

phase_5 (循环 字符串 ascii 码)

从上往下看,首先将输入值赋给寄存器 rbx ,第40106a行是金丝雀值,这里不用管,我们可以简单的看成从内存中读取一个数到寄存器 rax 中(忘记的可以回头看看书,这里是为了验证是否发生缓冲区溢出)

  401062:53                   push   %rbx  401063:48 83 ec 20          sub    $0x20,%rsp  401067:48 89 fb             mov    %rdi,%rbx  40106a:64 48 8b 04 25 28 00 mov    %fs:0x28,%rax  401071:00 00   401073:48 89 44 24 18       mov    %rax,0x18(%rsp)  401078:31 c0                xor    %eax,%eax  40107a:e8 9c 02 00 00       callq  40131b <string_length>  40107f:83 f8 06             cmp    $0x6,%eax  401082:74 4e                je     4010d2 <phase_5+0x70>  401084:e8 b1 03 00 00       callq  40143a <explode_bomb>

其次将寄存器 eax 清零,调用 string_length 函数(之前我们看过,这里不再赘述,就是比较字符串长度的函数)

此时为了防止炸弹爆炸,我们知道了输入的字符串长度应该为 6

 4010d2:b8 00 00 00 00       mov    $0x0,%eax 4010d7:eb b2                jmp    40108b <phase_5+0x29>

寄存器 eax 清零,继续执行跳转

  40108b:0f b6 0c 03          movzbl (%rbx,%rax,1),%ecx  40108f:88 0c 24             mov    %cl,(%rsp)  401092:48 8b 14 24          mov    (%rsp),%rdx  401096:83 e2 0f             and    $0xf,%edx  401099:0f b6 92 b0 24 40 00 movzbl 0x4024b0(%rdx),%edx  4010a0:88 54 04 10          mov    %dl,0x10(%rsp,%rax,1)  4010a4:48 83 c0 01          add    $0x1,%rax  4010a8:48 83 f8 06          cmp    $0x6,%rax  4010ac:75 dd                jne    40108b <phase_5+0x29>

这里我们将汇编代码做一下调整

for(rax = 0,rax != 6,rax ++){    ecx = rbx + rax*(rsp) = clrdx = *(rsp)edx = edx & 0xfedx = 0x4024b0 + rdxrsp + rax + 0x10 = dl}

取输入字符串的字符,然后逐次将每个字符与 0xf “与”操作,得到的值作为 0x4024b0 处字符串的下标。

与 0xf “与运算” 操作意味着能取到 0x4024b0 处字符串的范围是 0-15,通过 gdb 查看 0x4024b0 处字符串:

得到了前16为字符串:maduiersnfotvbyl

通过 for 循环能够生成一个字符串,该字符串由输入的每个字符和 0xf “与运算” 得到的值,作为 maduiersnfotvbyl 的下标,来选择字符

那么生成的字符串是什么呢?我们继续往下看

  4010ae:c6 44 24 16 00       movb   $0x0,0x16(%rsp)  4010b3:be 5e 24 40 00       mov    $0x40245e,%esi  4010b8:48 8d 7c 24 10       lea    0x10(%rsp),%rdi  4010bd:e8 76 02 00 00       callq  401338 <strings_not_equal>  4010c2:85 c0                test   %eax,%eax  4010c4:74 13                je     4010d9 <phase_5+0x77>  4010c6:e8 6f 03 00 00       callq  40143a <explode_bomb>

4010ae 行是字符串的结束符,将生成的字符串与 0x40245e 处的字符串比较,为了防止炸弹爆炸,我们生成的字符串就要和 0x40245e 处的字符串相等,那我们查看一下

 所以结果出来了:

1.输入的字符串长度为6

2.输入字符串的每个字符与 0xf “与运算”得到的数值在“maduiersnfotvbyl”中寻找字符,最后得到一段字符串

3.循环得到的字符串应该为“flyers”

我们可以发现“flyers”六个字母对应 “maduiersnfotvbyl” 的下标分别为 9、15、14、5、6、7

查一下 ascii 表,可以找到六个 & 0xf 分别为 9、15、14、5、6、7 的字符

答案为:

ionefg

phase_6 (链表、循环)

代码看着很长,我们拆成六个模块一点一点看

第一个模块就是调用 read_six_numbers 函数

  4010f4:41 56                push   %r14  4010f6:41 55                push   %r13  4010f8:41 54                push   %r12  4010fa:55                   push   %rbp  4010fb:53                   push   %rbx  4010fc:48 83 ec 50          sub    $0x50,%rsp  401100:49 89 e5             mov    %rsp,%r13  401103:48 89 e6             mov    %rsp,%rsi  401106:e8 51 03 00 00       callq  40145c <read_six_numbers>  40110b:49 89 e6             mov    %rsp,%r14  40110e:41 bc 00 00 00 00    mov    $0x0,%r12d

不停的赋值,然后读入六个数,得到的栈如下图

 第二个模块就是一个嵌套循环,利用上图的栈,对输入的数进行一些操作,这里就是看指针的变化

  401114:4c 89 ed             mov    %r13,%rbp  401117:41 8b 45 00          mov    0x0(%r13),%eax  40111b:83 e8 01             sub    $0x1,%eax  40111e:83 f8 05             cmp    $0x5,%eax  401121:76 05                jbe    401128 <phase_6+0x34>  401123:e8 12 03 00 00       callq  40143a <explode_bomb>  401128:41 83 c4 01          add    $0x1,%r12d  40112c:41 83 fc 06          cmp    $0x6,%r12d  401130:74 21                je     401153 <phase_6+0x5f>  401132:44 89 e3             mov    %r12d,%ebx  401135:48 63 c3             movslq %ebx,%rax  401138:8b 04 84             mov    (%rsp,%rax,4),%eax  40113b:39 45 00             cmp    %eax,0x0(%rbp)  40113e:75 05                jne    401145 <phase_6+0x51>  401140:e8 f5 02 00 00       callq  40143a <explode_bomb>  401145:83 c3 01             add    $0x1,%ebx  401148:83 fb 05             cmp    $0x5,%ebx  40114b:7e e8                jle    401135 <phase_6+0x41>  40114d:49 83 c5 04          add    $0x4,%r13  401151:eb c1                jmp    401114 <phase_6+0x20>//双层循环

这里大家可能要回去复习一下几种寻址方式

比如(%r13)表示的是 r13 寄存器指向的值而不是 r13 寄存器的值

好,那我们看一下这个嵌套循环是怎么实现的呢?

40111e 行中 eax 寄存器其实是外层循环,循环六次,每次 r12 寄存器分别指向 num1、num2、num3......

内层循环就是拿 r12 寄存器的值和剩下的五个对比,这里为了跳过炸弹,这里告诉我们六个值都是不一样的且都小于等于6

用 c 代码给大家清楚的看一下

for (int r12 = 0; r12 != 6; r12++) {    rbp = r13;    eax = *r13;    eax -= 1;    if ((eax > 5)// 最大为6        explode_bomb();    for (ebx = r12+1; ebx <= 5; ebx++) {        rax = ebx;        eax = *(rsp+rax*4);        if (*rbp == eax)    //所有值不能相等            explode_bomb();        }    r13 += 4;}

第三个模块就是一个循环,让每个栈中的 num 的值修改为 “7 - num”

  401153:48 8d 74 24 18       lea    0x18(%rsp),%rsi  401158:4c 89 f0             mov    %r14,%rax  40115b:b9 07 00 00 00       mov    $0x7,%ecx  401160:89 ca                mov    %ecx,%edx  401162:2b 10                sub    (%rax),%edx  401164:89 10                mov    %edx,(%rax)  401166:48 83 c0 04          add    $0x4,%rax  40116a:48 39 f0             cmp    %rsi,%rax  40116d:75 f1                jne    401160 <phase_6+0x6c>//num = 7 - num

这里还是要注意一个寻址方式的问题哦~

第四个模块是本题组重要的环节,真的很复杂,可以多看几遍

  40116f:be 00 00 00 00       mov    $0x0,%esi  401174:eb 21                jmp    401197 <phase_6+0xa3>  401176:48 8b 52 08          mov    0x8(%rdx),%rdx  40117a:83 c0 01             add    $0x1,%eax  40117d:39 c8                cmp    %ecx,%eax  40117f:75 f5                jne    401176 <phase_6+0x82>  401181:eb 05                jmp    401188 <phase_6+0x94>    401183:ba d0 32 60 00       mov    $0x6032d0,%edx  401188:48 89 54 74 20       mov    %rdx,0x20(%rsp,%rsi,2)  40118d:48 83 c6 04          add    $0x4,%rsi  401191:48 83 fe 18          cmp    $0x18,%rsi  401195:74 14                je     4011ab <phase_6+0xb7>  401197:8b 0c 34             mov    (%rsp,%rsi,1),%ecx  40119a:83 f9 01             cmp    $0x1,%ecx  40119d:7e e4                jle    401183 <phase_6+0x8f>  40119f:b8 01 00 00 00       mov    $0x1,%eax  4011a4:ba d0 32 60 00       mov    $0x6032d0,%edx  4011a9:eb cb                jmp    401176 <phase_6+0x82>//将六个节点的起始地址按照某顺序放到栈上

对栈里的6个数字,先判断他们是否为1

如果为1,就把0x6032d0放在栈里;如果不为1,就循环操作

对这个地址进行计算之后的地址里的值放在寄存器里,可以得到5个地址

分别是 0x6032e0、0x6032f0、 0x603300,、0x603310、0x603320

分别对应值2、3、4、5、6,我们用 gdb 查看对应的值

然后把他们按照某种顺序放在栈的 %rsp + 0x20 到 %rsp + 0x48 处

第五个模块对每个地址里存的值做了操作,其实就是将链表重新连接

  4011ab:48 8b 5c 24 20       mov    0x20(%rsp),%rbx  4011b0:48 8d 44 24 28       lea    0x28(%rsp),%rax  4011b5:48 8d 74 24 50       lea    0x50(%rsp),%rsi  4011ba:48 89 d9             mov    %rbx,%rcx  4011bd:48 8b 10             mov    (%rax),%rdx  4011c0:48 89 51 08          mov    %rdx,0x8(%rcx)  4011c4:48 83 c0 08          add    $0x8,%rax  4011c8:48 39 f0             cmp    %rsi,%rax  4011cb:74 05                je     4011d2 <phase_6+0xde>  4011cd:48 89 d1             mov    %rdx,%rcx  4011d0:eb eb                jmp    4011bd <phase_6+0xc9>  4011d2:48 c7 42 08 00 00 00 movq   $0x0,0x8(%rdx)  4011d9:00 //重新连接链表

第六个模块为了防止炸弹爆炸,我们只能将链表降序连接

  4011da:bd 05 00 00 00       mov    $0x5,%ebp  4011df:48 8b 43 08          mov    0x8(%rbx),%rax  4011e3:8b 00                mov    (%rax),%eax  4011e5:39 03                cmp    %eax,(%rbx)  4011e7:7d 05                jge    4011ee <phase_6+0xfa>  4011e9:e8 4c 02 00 00       callq  40143a <explode_bomb>  4011ee:48 8b 5b 08          mov    0x8(%rbx),%rbx  4011f2:83 ed 01             sub    $0x1,%ebp  4011f5:75 e8                jne    4011df <phase_6+0xeb>

好啦答案就出来啦

链表排序,输入的数字为原链表位置,输入的次序为新链表的位置

又因为第二个循环对array每个数字 7 - array[i],因此输入的数字需要将链表重排成为升序

地址对应值输入值
0x6032d014c16
0x6032e0a825
0x6032f039c34
0x6033002b343
0x6033101dd52
0x6033201bb61

原链表的从小到大顺序是 5 6 1 2 3 4, 重排后为 4 3 2 1 6 5

即可得到答案

4 3 2 1 6 5

总结

其实这篇博客算是我写的最久的一篇了(可能下一篇会更久),上学期写项目有压力的时候开始写(结果写完更有压力了bushi)一直到所有比赛结束,我才开始收心写这篇博客。

不过也正是因为如此我做了很多遍这个lab,同时让我对汇编、逆向有了更深层次的了解,希望能给你们带来帮助❤️


点击全文阅读


本文链接:http://m.zhangshiyu.com/post/60382.html

<< 上一篇 下一篇 >>

  • 评论(0)
  • 赞助本站

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

关于我们 | 我要投稿 | 免责申明

Copyright © 2020-2022 ZhangShiYu.com Rights Reserved.豫ICP备2022013469号-1