gdb使用指南拆弹

这里是为了熟悉gdb使用,默认gdb和gef环境已经配置好以及有一定的linux的环境下操作基础

这里作者贴心的设计一个存储密码的txt的文件(这里密码本要自己创建一个txt文件在文件目录下面),以减小打到后面以后重复输入密码的繁琐操作

1.前卫知识

1.gdb的基本指令

运行

  1. gdb bomb :使用 gdb 调试可执行文件 bomb
  2. r :run运行程序,遇到断点处停止,等待用户输入下一步指令
  3. c :continue,继续执行,到下一个断点(或程序结束)
  4. q :quit, 退出 gdb 调试
  5. si :单指令执行,即每次只执行一条指令,结合”交互模式下直接回车的作用是重复上一指令,对于单步调试非常方便
  6. n :next,单步跟踪程序,遇到函数调用时,不会进入函数体内部
  7. s :step,单步调试,遇到函数调用时,会进入函数体内部
  8. until :当你厌倦了在一个循环体内部单步跟踪时,这个命令可以运行程序直到退出循环体
  9. until + 行号 :运行至行号处

设置断点

  1. b n :break n,在第 n 行设置断点
  2. b func :在函数 func() 的入口处设置断点
  3. b *地址值 在地址值处设置断点,例如,`b *0x401460
  4. i b :info b, 显示当前程序的断点设置情况,会给出各个断点的序号,类型等信息
  5. delete 断点号n :删除第 n 个断点(从 info b 中得出断点序号)
  6. disable 断点号n :暂停第 n 个断点
  7. enable 断点号n :开启第 n 个断点
  8. delete breakpoints :删除所有断点

分割窗口

  1. layout regs 显示寄存器和反汇编窗口
  2. layout asm 显示反汇编窗口

显示内容

  1. x/[count][format] [address] 打印内存值,从所给地址(address)处开始,以指定格式(format)显示 count 个值,例如 x/100i foo 反汇编并打印出从 foo 处开始的 100 条指令常见 format 有 d decimal, x hex, t binary, f floating point, i instruction, c character, s string(以 8bit 字符串形式显示数据)
  2. i r 寄存器名 , info registers 寄存器名,查看当前某个寄存器的内容,寄存器名前不需要 % 例如,i r rsi
  3. Ctrl + L 清屏,由于 gdb 没有专门的清屏命令,所以用 Linux 自带的。(有的时候不退出 gdb 重新 r 会导致显示内容覆盖,使用这个命令清屏,有可能不好使)

2.常见寄存器名称

image-20240321175027177

为了保证64位对32位文件的兼容性:rsi和esi的关系

image-20240321175631573

2.在kali装载bomb

1.下载bomb

使用 wget 下载,记得先 cd 到你想存放炸弹的文件夹

wget csapp.cs.cmu.edu/3e/bomb.tar

2.解压bomb

要在你选择的bomb的文件夹打开终端

tar xvf bomb.tar

会生成三个文件夹

bomb, bomb.c, README

3.bomb的源码

将源文件取出方便查看

image-20240321163746603
/***************************************************************************
* Dr. Evil's Insidious Bomb, Version 1.1
* Copyright 2011, Dr. Evil Incorporated. All rights reserved.
*
* LICENSE:
*
* Dr. Evil Incorporated (the PERPETRATOR) hereby grants you (the
* VICTIM) explicit permission to use this bomb (the BOMB). This is a
* time limited license, which expires on the death of the VICTIM.
* The PERPETRATOR takes no responsibility for damage, frustration,
* insanity, bug-eyes, carpal-tunnel syndrome, loss of sleep, or other
* harm to the VICTIM. Unless the PERPETRATOR wants to take credit,
* that is. The VICTIM may not distribute this bomb source code to
* any enemies of the PERPETRATOR. No VICTIM may debug,
* reverse-engineer, run "strings" on, decompile, decrypt, or use any
* other technique to gain knowledge of and defuse the BOMB. BOMB
* proof clothing may not be worn when handling this program. The
* PERPETRATOR will not apologize for the PERPETRATOR's poor sense of
* humor. This license is null and void where the BOMB is prohibited
* by law.
***************************************************************************/

#include <stdio.h>
#include <stdlib.h>
#include "support.h"
#include "phases.h"

/*
* Note to self: Remember to erase this file so my victims will have no
* idea what is going on, and so they will all blow up in a
* spectaculary fiendish explosion. -- Dr. Evil
*/

FILE *infile;

int main(int argc, char *argv[])
{
  char *input;

  /* Note to self: remember to port this bomb to Windows and put a
    * fantastic GUI on it. */

  /* When run with no arguments, the bomb reads its input lines
    * from standard input. */
  if (argc == 1) {  
infile = stdin;
  }

  /* When run with one argument <file>, the bomb reads from <file>
    * until EOF, and then switches to standard input. Thus, as you
    * defuse each phase, you can add its defusing string to <file> and
    * avoid having to retype it. */
  else if (argc == 2) {
if (!(infile = fopen(argv[1], "r"))) {
  printf("%s: Error: Couldn't open %s\n", argv[0], argv[1]);
  exit(8);
}
  }

  /* You can't call the bomb with more than 1 command line argument. */
  else {
printf("Usage: %s [<input_file>]\n", argv[0]);
exit(8);
  }

  /* Do all sorts of secret stuff that makes the bomb harder to defuse. */
  initialize_bomb();

  printf("Welcome to my fiendish little bomb. You have 6 phases with\n");
  printf("which to blow yourself up. Have a nice day!\n");

  /* Hmm... Six phases must be more secure than one phase! */
  input = read_line();             /* Get input                   */
  phase_1(input);                 /* Run the phase               */
  phase_defused();                 /* Drat! They figured it out!
    * Let me know how they did it. */
  printf("Phase 1 defused. How about the next one?\n");

  /* The second phase is harder. No one will ever figure out
    * how to defuse this... */
  input = read_line();
  phase_2(input);
  phase_defused();
  printf("That's number 2. Keep going!\n");

  /* I guess this is too easy so far. Some more complex code will
    * confuse people. */
  input = read_line();
  phase_3(input);
  phase_defused();
  printf("Halfway there!\n");

  /* Oh yeah? Well, how good is your math? Try on this saucy problem! */
  input = read_line();
  phase_4(input);
  phase_defused();
  printf("So you got that one. Try this one.\n");
   
  /* Round and 'round in memory we go, where we stop, the bomb blows! */
  input = read_line();
  phase_5(input);
  phase_defused();
  printf("Good work! On to the next...\n");

  /* This phase will never be used, since no one will get past the
    * earlier ones. But just in case, make this one extra hard. */
  input = read_line();
  phase_6(input);
  phase_defused();

  /* Wow, they got it! But isn't something... missing? Perhaps
    * something they overlooked? Mua ha ha ha ha! */
   
  return 0;
}

3.拆弹

1.拆弹第一关(内存取串)

1.在bomb文件夹下面打开终端输入指令

gdb bomb
image-20240321162932923

2.设置b_explode断点,技术输入错误也能组织爆炸

b explode_bomb
image-20240321163202566

3.查看bomb.c源文件发现,phase_1是文件的传参函数(input传力输入字符串),所以设置b phase_1断点

b phase_1
image-20240321163913939

4.执行该程序到断点停止

r
image-20240321164015169

5.随便输入字符串进行占位,为了进行下一步

image-20240321164151210

6.进行反汇编查看

layout asm
image-20240321164323350

7.发现这里调用了string_not_equal函数, 在函数入口的地方设置断点

b strings_not_equal
image-20240321164549844

8.继续执行函数

c

9.发现这里又调用string_length函数(这里估计是处理字符串长度的函数)

b string_length
image-20240321170543055

10.继续执行程序

c

11.单步执行追踪rdi寄存器推测石村饭字符串的地方

si
image-20240321170854307
image-20240321170906490

12.这里发现string_length函数就是将输入的字符串长度进行统计并放到rax寄存器里面

image-20240321171108804

13.这里回到strings_not_equal,查看处理字符串的函数汇编程序逻辑

image-20240321172920114

它首先得到我们输入字符串长度,然后得到正确答案字符串长度

进行比较,二者不相等则在 %eax 中存 1 返回;若二者长度相等,则逐个比较二者字符串内容,若全部相等则在 %rax 中存 0 返回

若有任意一个不相等则在 %eax 中存1并返回

14.这里直接知道rdi寄存器应该是存储了密文字符串,调用info查看字符串的地址

info register
image-20240321181053931

15.进入rdi的地址进行查看字符串的数值

x /s 0x402400
image-20240321181211737

得到字符串

Border relations with Canada have never been better.

16.重新运行这程序检验是否成功拆除第一层炸弹

先q退出

q
image-20240321182218293

好的成功了

2.拆弹第二关(栈中遨游)

1.前面操作基本一致(不下phase_1断点,下一个phase_2断点)

传入上面的第一关得到的字符串

image-20240321183548477

2.进入phase_2断点处,反编译

image-20240321183649939

3.发现这里体术传入6个数字,在0x40145c<read six numbers>函数的位置下断点,c继续进行调试

b read_six_numbers
image-20240321183901476

4.查看six number函数,然后反编译该函数

image-20240321184258642

4.这里为了理解这个地方栈帧的变化我们选择通过画图将栈帧具体化

先将这里对栈的操作的每一步进行解析(纯自绘很费人)

image-20240321184425192

注意:

LEA 指令实际上不执行内存访问,而是用于计算有效地址,并将结果存储在目标寄存器

1.在调用read six numbers函数前,栈顶减去0x28 分配了40个字节的空间
image-20240321185108157
2.进入函数
屏幕截图 2024-03-21 184406
3.画出这里栈帧的示意图,先理解这里的栈,不用管具体的用处

纯理解手绘,累了

关联图

屏幕截图 2024-03-21 202755

4.这里我们观察到在0x401480处理了一个esi,0x4025c3的指令可能与数据的处理有关(即将0x4025c3 数据放到esi寄存器中,打印该数据查看

image-20240321204148614

发现这里的传参是将6 个数字进行空格间隔传参

5.结合前面发现的指令cmp eax,0x5判断是否为6 个字符,否则爆炸退出

由这里推断

image-20240321204824060

6.这里我们可以退出后传入6个字符进行调试观察机构

这里我们将前面的密码存到密码本里面,后面调试方便一点(密码本要自己创建)

运行的时候将参数带上就行

r code.txt
image-20240321205300775
1.运行程序这里发现这里直接就跳过了第一步
image-20240321205727257
2.传入6个数字加空格
image-20240321205817402
3.layout asm进行反编译
image-20240321210210162

7.分析前面栈的结构理解数据的处理

0x400f0a <phase 2+14> Cmp DWORD PTR [rsp],0x1
0x400f0e <phase 2+18> je 0x400f30 <phase 2+52>
0x400f10 <phase 2+20> call 0x40143a<explode_bomb>
0x400f15 <phase 2+25> jmp 0x400f30 <phase 2+52>
0x400f17 <phase 2+27> moV eax,DWORD PTR [rbx-0x4]
0x400f1a <phase 2+30> add eax eax
0x400f1c <phase 2+32> Cmp DWORD PTR [rbx],eax
0x400f1e <phase 2+34> je 0x400f25 <phase 2+41>
0x400f20 <phase 2+36> call 0x40143a <explode_bomb>
0x400f25 <phase 2+41> add rbx,0x4
0x400f29 <phase 2+45> cmp rbx,rbp
0x400f2c <phase 2+48> jne 0x400f17 <phase 2+27>
0x400f2e <phase 2+50> jmp 0x400f3c <phase 2+64>
0x400f30 <phase 2+52> lea rbx,[rsp+0x4]
0x400f35 <phase_2+57> lea rbp,[rsp+0x18]
0x400f3a <phase 2+62> jmp 0x400f17 <phase_2+27>
0x400f3c <phase 2+64> addrsp,0x28
0x400f40 <phase 2+68> pop rbx
0x400f41 <phase 2+69> pop rbp

0x400f0a <phase 2+14> cmp DWORD PTR [rsp],0x1 // 比较栈顶的 DWORD(4字节)值是否等于1 0x400f0e <phase 2+18> je 0x400f30 <phase 2+52> // 如果相等(即上一条指令的比较结果是相等的),则跳转到地址0x400f30 0x400f10 <phase 2+20> call 0x40143a<explode_bomb> // 如果上一次比较不相等,则执行这条指令,调用 explode_bomb 函数 0x400f15 <phase 2+25> jmp 0x400f30 <phase 2+52> // 跳转到0x400f30,无条件跳转,这通常是避免无条件执行 explode_bomb 0x400f17 <phase 2+27> mov eax,DWORD PTR [rbx-0x4] // 从rbx指针减去4个字节的位置取出一个 DWORD(4字节)的值到eax寄存器中 0x400f1a <phase 2+30> add eax, eax // 将eax寄存器中的值与自身相加,结果存回eax 0x400f1c <phase 2+32> cmp DWORD PTR [rbx], eax // 将rbx指向的位置上的 DWORD 与 eax 中的值进行比较 0x400f1e <phase 2+34> je 0x400f25 <phase 2+41> // 如果比较结果相等,则跳转到0x400f25 0x400f20 <phase 2+36> call 0x40143a <explode_bomb> // 如果上一次比较不相等,则执行这条指令,调用 explode_bomb 函数 0x400f25 <phase 2+41> add rbx, 0x4 // 将rbx寄存器的值增加4,通常是为了指向下一个元素(假设操作DWORD) 0x400f29 <phase 2+45> cmp rbx, rbp // 比较 rbx 与 rbp,可能是在检查是否已经到达了某个边界或列表的末尾 0x400f2c <phase 2+48> jne 0x400f17 <phase 2+27> // 如果rbx和rbp的值不相等,则跳转回0x400f17继续循环,否则继续向下执行 0x400f2e <phase 2+50> jmp 0x400f3c <phase 2+64> // 无条件跳转到0x400f3c 0x400f30 <phase 2+52> lea rbx,[rsp+0x4] // 将栈指针rsp加上4个字节的地址加载到rbx寄存器中 0x400f35 <phase_2+57> lea rbp,[rsp+0x18] // 将栈指针rsp加上24个字节(0x18)的地址加载到rbp寄存器中 0x400f3a <phase 2+62> jmp 0x400f17 <phase_2+27> // 无条件跳转到0x400f17,开始循环处理 0x400f3c <phase 2+64> add rsp,0x28 // 将栈指向的地址下移40字节(0x28),通常用于清理栈空间 0x400f40 <phase 2+68> pop rbx // 将栈顶部的值弹出到rbx寄存器,同时栈指针上移 0x400f41 <phase 2+69> pop rbp // 将栈顶部的值弹出到rbp寄存器,同时栈指针上移,这两个pop通常是在结束函数时恢复现场使用

8.整理栈的运行思路

1.栈顶的值为1

2.后面依次从栈顶的数据1 ,循环累计加到栈的高位,每次循环的数据是前一次数据自加(及前一位x2)

3.钱买你的参数是通过栈及逆行传递参数(所以是逆序入栈)所以传入的参数从1开始到64依次压入栈中

所以得到掺入的参数为

1 2 4 8 16 32 64

9.将密码存入密码本中运行检验是否通过

传入参数得到第二关过了😊

image-20240321213142302

3.拆弹第三关

1.与前面操作类似,随便传入几个字符进入phase_3断点

image-20240321213730836

2.layout asm进入反编译

image-20240321214539209

3.打开双终端(右键垂直布局)

image-20240324140535484

4.发现这里有一个0x4025cf的存储地址,根据上面的拆弹猜测,这了可能是传参的分配的地址,进入地址进行查询

x/s 0x4025cf

果然是传入两个空格隔开的数字

image-20240324140752591

5.查看这个可疑的jg操作

image-20240324141818435
image-20240324142057305

好吧啥也没有

但是根据教程说的是这里必须传入两个参数以上才行,否则爆炸

(但是我没看出来)

没关系的这里如果手动传入参数后回车对应的(\n)也是参数,如果文件输出再末尾多一个换行也不影响

cmp eax,0x1
jg 0x400f6a<phase_3+39>
call 0x40143a<explode_bomb>

6.继徐审计下面的数据

image-20240324165230532

ja:jump if above

cmp DWORD PTR [rep+0x8],0x7//从内存[rep+0x8]读取32位的数据,与0x7比较
ja 0x400fad<phase_3+106>//之前的比较结果表明操作数1大于操作数2,则跳转到指定地址,在这里即比较操作结果为真(即 [rep+0x8] 大于 0x7),则会跳转到地址 0x400fad<phase_3+106> 处的代码
mov eax,DWORD PTR [rsp+0x8]//这条指令将从地址 [rsp+0x8] 处读取的双字数据(32位)移动到 eax 寄存器中
jmp QWORD PTR [rax *8+0x40270]//rax 寄存器中的值,计算跳转目的地址,并跳转到该地址处执行。这里根据 rax 寄存器的值,计算出一个地址

自己理解了这部分的汇编(字有点抽象)

7.打印0x402770后面56位的数据

x/56x 0x402470

得到如下数据

image-20240324170624652

去除无效数据

gef➤  x/16x 0x402470
0x402470: 0x7c
0x402478: 0xb9
0x402480: 0x83
0x402488: 0x8a
0x402490: 0x91
0x402498: 0x98
0x4024a0: 0x9f

这里的gef显示容易引起误会

(这里实际对应的是地址,这里的可以注意到,实际上提取的数据是衔接了前半部分地址的)

原因的话因为前面检查第一位是否小于0x7,所以这里传入参数只能在0-7之间

这里我们动调一下发现如果传入的是0那么根据跳转就是检查是不是0xcf,以此类推

8.调试推理验证

1.我重新调试传入1 3
image-20240324214743931

查看gef发现前面的推测都得验证

image-20240324215844239
2.这个时候ni单步执行第二个参数验证的关键步骤查看一下rax寄存器
image-20240324215301419

rax是我传入的1,没有问题

所以这里跳转的就是(402470加上1*8)的最后一个地址(前面应该还有一个0乘8对应的地址)

3.跳转执行检查跟进一下
image-20240324220410262

发现这里的mov 0x137数据是对的上的

4.验证不正确直接ret了
image-20240324215551056
对应的第一位对应的第二位对应的10进制
0cf207
1137311
22c3707
3100256
4185389
5ce206
62aa682
7147327

这里将

0 207

9.放到密码本里面,成功跳过

image-20240324220630393

4.拆弹第四关(递归函数)

递归就是一个函数在它的函数体内调用它自身。执行递归函数将反复调用其自身,每调用一次就进入新的一层。递归函数必须有结束条件 当函数在一直递推,直到遇到墙后返回,这个墙就是结束条件 所以递归要有两个要素,结束条件与递推关系

1.前面的操作省略了一样的

2.发现和phase_3的结构很像,单步进行发现一样的传参

image-20240325094024708

3.退出重新传一下参数,再调试

1.查看反编译
image-20240325095501541

0x401029处指令cmp $0x2,%eax及其后指令jne 0x401035` 表明这次输入参数要正好 3 个

2.分析逻辑
phase_4函数
0x000000000040100c <+0>:     sub    rsp,0x18               ; 栈顶指针向下移动24个字节(在栈上为局部变量预留空间)
0x0000000000401010 <+4>: lea rcx,[rsp+0xc] ; 将第二个输入参数的地址加载到 rcx 寄存器中
0x0000000000401015 <+9>: lea rdx,[rsp+0x8] ; 将第一个输入参数的地址加载到 rdx 寄存器中
0x000000000040101a <+14>: mov esi,0x4025cf ; 将字符串(格式字符串的地址)的地址载入 esi 寄存器,用于后续的 scanf 调用
0x000000000040101f <+19>: mov eax,0x0 ; 清零 eax 寄存器(因为 sscanf 函数会将匹配到的输入项数返回在 eax 中)
0x0000000000401024 <+24>: call 0x400bf0 <__isoc99_sscanf@plt> ; 调用 sscanf 函数,从输入中读取格式化的数据
0x0000000000401029 <+29>: cmp eax,0x2 ; 比较 sscanf 的返回值(匹配到的输入项数)是否为2
0x000000000040102c <+32>: jne 0x401035 <phase_4+41> ; 如果不等于2,跳转到 explode_bomb 函数,即不满足条件即爆炸
0x000000000040102e <+34>: cmp DWORD PTR [rsp+0x8],0xe; 比较第一个输入参数(存储在栈上)是否小于等于14
0x0000000000401033 <+39>: jbe 0x40103a <phase_4+46> ; 如果小于等于14(包括等于14),继续执行,否则调用 explode_bomb
0x0000000000401035 <+41>: call 0x40143a <explode_bomb>; 调用爆炸函数
0x000000000040103a <+46>: mov edx,0xe ; 将 14 载入 edx 寄存器,用于后续调用 func4 时作为参数
0x000000000040103f <+51>: mov esi,0x0 ; 将 0 载入 esi 寄存器,用于后续调用 func4 时作为参数
0x0000000000401044 <+56>: mov edi,DWORD PTR [rsp+0x8]; 将第一个输入参数(从栈上获取)加载到 edi 寄存器中,用于后续调用 func4 时作为参数
0x0000000000401048 <+60>: call 0x400fce <func4> ; 调用 func4 函数,其参数已通过 edi, esi, edx 传入
0x000000000040104d <+65>: test eax,eax ; 检测 func4 的返回值(存储在 eax 中)是否为0
0x000000000040104f <+67>: jne 0x401058 <phase_4+76> ; 如果 func4 的返回值不为0,跳转到 explode_bomb 函数
0x0000000000401051 <+69>: cmp DWORD PTR [rsp+0xc],0x0; 比较第二个输入参数(存储在栈上)是否为0
0x0000000000401056 <+74>: je 0x40105d <phase_4+81> ; 如果等于0,正常结束,否则调用 explode_bomb 函数
0x0000000000401058 <+76>: call 0x40143a <explode_bomb>; 调用爆炸函数
0x000000000040105d <+81>: add rsp,0x18 ; 回收之前在栈上预留的空间
0x0000000000401061 <+85>: ret ; 返回到调用者
func4函数(关键运算一个递归运算加密)
0x0000000000400fce <+0>:     sub    rsp,0x8                    ; 在栈上为局部变量预留8字节的空间
0x0000000000400fd2 <+4>: mov eax,edx ; 将 edx(函数的第三个参数)的值赋给 eax
0x0000000000400fd4 <+6>: sub eax,esi ; 用 eax(目前等于 edx 的值)减去 esi(函数的第二个参数)
0x0000000000400fd6 <+8>: mov ecx,eax ; 将 eax 的值赋给 ecx
0x0000000000400fd8 <+10>: shr ecx,0x1f ; 将 ecx 右移31位,将数值的符号位移到最低位
0x0000000000400fdb <+13>: add eax,ecx ; 将 ecx 的值加到 eax 上
0x0000000000400fdd <+15>: sar eax,1 ; 算术右移 eax 1位,实际上是执行 (edx - esi + sign(edx - esi)) / 2
0x0000000000400fdf <+17>: lea ecx,[rax+rsi*1] ; 算的是 (rax + rsi) 的结果,并将这个计算结果保存在 ecx
0x0000000000400fe2 <+20>: cmp ecx,edi ; 比较 ecx 与 edi(函数的第一个参数)
0x0000000000400fe4 <+22>: jle 0x400ff2 <func4+36> ; 如果 ecx <= edi,跳转到 <+36>
0x0000000000400fe6 <+24>: lea edx,[rcx-0x1] ; 将 ecx - 1 的结果赋给 edx,准备下一次递归调用
0x0000000000400fe9 <+27>: call 0x400fce <func4> ; 递归调用 func4,此时第三个参数是 ecx - 1
0x0000000000400fee <+32>: add eax,eax ; 将递归调用的返回值乘以 2
0x0000000000400ff0 <+34>: jmp 0x401007 <func4+57> ; 跳转到 <+57>, 结束函数
0x0000000000400ff2 <+36>: mov eax,0x0 ; 将 eax 设为 0,用于返回值
0x0000000000400ff7 <+41>: cmp ecx,edi ; 再次比较 ecx 与 edi
0x0000000000400ff9 <+43>: jge 0x401007 <func4+57> ; 如果 ecx >= edi, 跳转到 <+57>, 结束函数
0x0000000000400ffb <+45>: lea esi,[rcx+0x1] ; 为下一次递归调用准备第二个参数,即 ecx + 1
0x0000000000400ffe <+48>: call 0x400fce <func4> ; 递归调用 func4
0x0000000000401003 <+53>: lea eax,[rax+rax*1+0x1] ; 将递归调用的返回值乘以 2 并加 1
0x0000000000401007 <+57>: add rsp,0x8 ; 回收之前在栈上预留的空间
0x000000000040100b <+61>: ret ; 返回调用者

注释:

test 指令的语法通常如下:

test operand1, operand2
其中 operand1 和 operand2 可以是寄存器、内存位置或立即数。指令执行时,会对这两个操作数进行按位与操作,不保存结果,仅改变标志寄存器的状态。具体来说,test 指令会跟下面两个标志位有关:

Zero 标志位(ZF):如果操作数按位与的结果为 0,则将 ZF 置为 1;否则置为 0。
源操作数的符号位(SF):将 SF 置为源操作数的最高位。
在汇编语言中,test 指令常常和条件跳转指令(如 je, jne, jz, jnz 等)一起使用,根据 test 指令设置的标志位来判断条件是否满足,从而执行相应的操作。
算术右移(Signed Arithmetic Right Shift):算术右移是一种在执行移位操作时保留符号位的移位方式。即在右移时将最高位(符号位)的值保持不变,然后其他位向右移动。如果最高位是 1,右移后在高位补 1;如果最高位是 0,右移后在高位补 0

(edx - esi + sign(edx - esi)) / 2:这部分表达式是在解释这个右移操作的含义,由于右移 1 位相当于进行除以 2 的操作,所以右移操作可以近似理解为对寄存器中的值进行除以 2 的操作

sign(edx - esi) 是表示对表达式 (edx - esi) 的符号进行提取的函数,可以理解为取符号函数。在数学和编程中,取符号函数通常指的是表示一个数的符号性质(正数、负数或零)的函数
整理递归的思路(这是我手绘的)

这里还有大佬的图片(引自手把手教你拆解 CSAPP 的 炸弹实验室 BombLab – 知乎 (zhihu.com)

img

3.提取绕过思路

在第一次 cmp %edi,%ecx%ecx <= %edi

在第二次 cmp %edi,%ecx%ecx >= %edi,即 %ecx = %edi 时函数才能避免递归

跟着逻辑走一遍得

第一个数字为7

第二个位6, 3, 1, 0 都行

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇