boom lab分析

phase1:

单步调试:

(gdb) bt
#1  0x0000000000401347 in strings_not_equal ()
#2  0x0000000000400eee in phase_1 ()
#3  0x0000000000400e3f in main (argc=<optimized out>, argv=<optimized out>) at bomb.c:74

这里的执行流程为:

    /* 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");

可以发现只有phase_1正常返回的时候,才会执行下面的phase_defused()函数将阶段1的炸弹拆除。现在就想让phase_1正常执行完,那就需要让此函数中所有不正常的跳转都不执行。

首先查看phase_1函数的汇编代码:

(gdb) disassemble phase_1
Dump of assembler code for function phase_1:
   0x0000000000400ee0 <+0>:     sub    $0x8,%rsp
   0x0000000000400ee4 <+4>:     mov    $0x402400,%esi               #0x402400 --> %esi
   0x0000000000400ee9 <+9>:     call   0x401338 <strings_not_equal> #调用strings_not_equal函数
   0x0000000000400eee <+14>:    test   %eax,%eax                    
   0x0000000000400ef0 <+16>:    je     0x400ef7 <phase_1+23>		#如果%eax值为0,则正常返回
   0x0000000000400ef2 <+18>:    call   0x40143a <explode_bomb>		#如果%eax值不为0,则触发炸弹
   0x0000000000400ef7 <+23>:    add    $0x8,%rsp
   0x0000000000400efb <+27>:    ret

test 指令将 %eax 寄存器的值与自身进行逻辑与操作,并设置标志位,但不修改 %eax 的值。如果 %eax 的值为零,则标志位会被置为零;如果 %eax 的值不为零,则标志位会被置为非零。

如果想让程序正常执行,那么就需要让获得%esi寄存器值的函数strings_not_equal返回值为0。

下面查看函数strings_not_equal的代码,并且分析下主要的控制代码,看看如何让返回值为0:

(gdb) disassemble strings_not_equal 
Dump of assembler code for function strings_not_equal:
   0x0000000000401338 <+0>:     push   %r12
   0x000000000040133a <+2>:     push   %rbp
   0x000000000040133b <+3>:     push   %rbx
   0x000000000040133c <+4>:     mov    %rdi,%rbx						
   0x000000000040133f <+7>:     mov    %rsi,%rbp
   0x0000000000401342 <+10>:    call   0x40131b <string_length>			# 将%rbx和 %rbp传入函数string_length
   0x0000000000401347 <+15>:    mov    %eax,%r12d						# 第一次返回值%eax赋给 %r12d
   0x000000000040134a <+18>:    mov    %rbp,%rdi						# %rbp —> %rdi
   0x000000000040134d <+21>:    call   0x40131b <string_length>			# %rdi的值作为入参	
   0x0000000000401352 <+26>:    mov    $0x1,%edx
   0x0000000000401357 <+31>:    cmp    %eax,%r12d 						# 对比两次函数长度调用的结果
   0x000000000040135a <+34>:    jne    0x40139b <strings_not_equal+99>  # 若长度不同,返回1
   
   # 以上代码均为判断两个字符串的长度是否相同,现在的%r12d、%eax分别保存了第一次和第二次的函数调用结果,即两个字符串的长度
   # 寄存器rbx 和 rbp 分别保存着函数的两个参数
   
   # 35c-41:若函数第一个参数低8位为0,则返回0
   0x000000000040135c <+36>:    movzbl (%rbx),%eax						# 参数1的低1位字节赋值给eax
   0x000000000040135f <+39>:    test   %al,%al							# %al & %al
   0x0000000000401361 <+41>:    je     0x401388 <strings_not_equal+80>  # Jump condition:ZF---(Equal / zero)
   # 35c-37f:对比参数1和参数2的每个字节,均相同的话遍
   0x0000000000401363 <+43>:    cmp    0x0(%rbp),%al					# 参数2的低1位字节与数1的低1位字节对比
   0x0000000000401366 <+46>:    je     0x401372 <strings_not_equal+58>	# 如果相等,则对比更高1位的字节
   0x0000000000401368 <+48>:    jmp    0x40138f <strings_not_equal+87>  # 不相等,返回错误1
   0x000000000040136a <+50>:    cmp    0x0(%rbp),%al					
   0x000000000040136d <+53>:    nopl   (%rax)
   0x0000000000401370 <+56>:    jne    0x401396 <strings_not_equal+94> #若参数1、2的第2个字节不同,则返回错误1
   0x0000000000401372 <+58>:    add    $0x1,%rbx
   0x0000000000401376 <+62>:    add    $0x1,%rbp
   0x000000000040137a <+66>:    movzbl (%rbx),%eax
   0x000000000040137d <+69>:    test   %al,%al
   0x000000000040137f <+71>:    jne    0x40136a <strings_not_equal+50>
   
   
   0x0000000000401381 <+73>:    mov    $0x0,%edx
   0x0000000000401386 <+78>:    jmp    0x40139b <strings_not_equal+99>
   0x0000000000401388 <+80>:    mov    $0x0,%edx
   0x000000000040138d <+85>:    jmp    0x40139b <strings_not_equal+99>
   0x000000000040138f <+87>:    mov    $0x1,%edx
   0x0000000000401394 <+92>:    jmp    0x40139b <strings_not_equal+99>
   0x0000000000401396 <+94>:    mov    $0x1,%edx
   0x000000000040139b <+99>:    mov    %edx,%eax
   0x000000000040139d <+101>:   pop    %rbx
   0x000000000040139e <+102>:   pop    %rbp
   0x000000000040139f <+103>:   pop    %r12
   0x00000000004013a1 <+105>:   ret    
End of assembler dump.

这里的几个比较重要的跳转代码:

0x40139b <strings_not_equal+99>:函数将以%edx作为返回值返回。

0x401388 <strings_not_equal+80>:返回0。

0x40136a <strings_not_equal+50>:比较 %al 寄存器中的值与 %rbp 寄存器偏移 0 的内存地址处的值,如果不相等,则跳转到地址 0x401396 处执行相应的代码(函数会返回1),否则继续执行后续指令。

重点理解的指令有:

  • test S1, S2:相当于S1&S2,若结果为0,则设置ZF为1
  • jmp的所有指令。重点有je,jump condition为ZF = 1
  • movzbl:0拓展数据。Move zero-extended byte to double word(movz S,R: R ← ZeroExtend(S))

综上,可以得知如果想拆除阶段1的炸弹,则需要输入合理的字符串,而这个字符串可以通过以下分析得出:

  1. 查看phase_1函数的汇编代码,发现入参%rdi需要跟另一个参数0x402400 --> %rsi进入函数strings_not_equal 进行计算,若结果为0,则可以拆除炸弹,否则爆炸。
  2. 查看strings_not_equal 函数的汇编代码,可以分析出此函数是将入参寄存器(%rdi%rsi)所指地址处的字符串进行比对,如果长度相同并且每个字都相同(按照字节对比),则返回0

通过上面两个函数的分析,就可以得知我们所输入的字符串,需要跟地址为0x402400处的字符串进行比对,比对成功则炸弹拆除。

那这样,通过gdb的命令x/30s 0x402400,以 ASCII 格式打印出从地址 0x402400 开始的 10 个字节的数据(假设这些数据是以 0 结尾的字符串),并且找出第一个字符串即可,这里打印的值为:

(gdb) x/30s 0x402400                                                                                                                                           
0x402400:       "Border relations with Canada have never been better."
				#translation:边境与加拿大的关系从未如此良好。

phase2

开始下断点b phase_2,单步执行,反汇编当前函数,汇编代码如下:

(gdb) disassemble 
Dump of assembler code for function phase_2:
=> 0x0000000000400efc <+0>:     push   %rbp
   0x0000000000400efd <+1>:     push   %rbx
   0x0000000000400efe <+2>:     sub    $0x28,%rsp
   0x0000000000400f02 <+6>:     mov    %rsp,%rsi		#将栈指针保存到%rsi中
   0x0000000000400f05 <+9>:     call   0x40145c <read_six_numbers>
   0x0000000000400f0a <+14>:    cmpl   $0x1,(%rsp)
   0x0000000000400f0e <+18>:    je     0x400f30 <phase_2+52>
   0x0000000000400f10 <+20>:    call   0x40143a <explode_bomb>
   0x0000000000400f15 <+25>:    jmp    0x400f30 <phase_2+52>
   0x0000000000400f17 <+27>:    mov    -0x4(%rbx),%eax
   0x0000000000400f1a <+30>:    add    %eax,%eax
   0x0000000000400f1c <+32>:    cmp    %eax,(%rbx)
   0x0000000000400f1e <+34>:    je     0x400f25 <phase_2+41>
   0x0000000000400f20 <+36>:    call   0x40143a <explode_bomb>
   0x0000000000400f25 <+41>:    add    $0x4,%rbx
   0x0000000000400f29 <+45>:    cmp    %rbp,%rbx
   0x0000000000400f2c <+48>:    jne    0x400f17 <phase_2+27>
   0x0000000000400f2e <+50>:    jmp    0x400f3c <phase_2+64>
   0x0000000000400f30 <+52>:    lea    0x4(%rsp),%rbx
   0x0000000000400f35 <+57>:    lea    0x18(%rsp),%rbp
   0x0000000000400f3a <+62>:    jmp    0x400f17 <phase_2+27>
   0x0000000000400f3c <+64>:    add    $0x28,%rsp
   0x0000000000400f40 <+68>:    pop    %rbx
   0x0000000000400f41 <+69>:    pop    %rbp
   0x0000000000400f42 <+70>:    ret    
End of assembler dump.

分析这个函数的功能:

这里<+6>行的mov %rsp,%rsi的目的是保存caller中栈顶的位置,方便在read_six_numbers中进行改值。

函数只有在寄存器%rsp所指的内存数据为0x1的时候,才不会引爆炸弹,而栈指针 %rsp 就是用来指示当前栈顶位置的寄存器。等效于栈顶数据是否为1。

假设现在栈顶数据为1((%rsp) == 0x1),则不会引爆炸弹,而是会调用+52行的代码,即:

lea 0x4(%rsp),%rbx:将%rsp的值加4(dec),然后赋值给寄存器%rbx。

lea 0x18(%rsp),%rbp:将%rsp的值加24(dec),然后赋值给寄存器%rbp。

给%rbx和%rbp重新赋值,此时%rbx = %rsp + 4, %rbp = %rsp + 18。

这里可以通过函数read_six_numbers推测出,栈顶保存的数据是第一个int类型数据,所占空间为4bytes,然后从栈顶开始向高位地址处连续6个int数据,就是此函数的6个返回值。

随后跳转调用<phase_2+27>处的代码:

   0x0000000000400f17 <+27>:    mov    -0x4(%rbx),%eax		#(%rsp + 4 - 4)-->%eax
   0x0000000000400f1a <+30>:    add    %eax,%eax			#%eax = 2 %eax
   0x0000000000400f1c <+32>:    cmp    %eax,(%rbx)			#(%rsp + 4) - %eax
   0x0000000000400f1e <+34>:    je     0x400f25 <phase_2+41>	#如果相等,则不爆炸
   0x0000000000400f20 <+36>:    call   0x40143a <explode_bomb>

此时不爆炸的条件是:栈(%rsp + 4)处的数据(第二个参数)等于两倍的栈顶(%rsp)的数据。

假若此时不爆炸,则程序又会跳转到<phase_2+41>处执行:

   0x0000000000400f25 <+41>:    add    $0x4,%rbx			# %rbx = %rsp + 8
   0x0000000000400f29 <+45>:    cmp    %rbp,%rbx			#todo:为什么要对比%rbp,%rbx?
   0x0000000000400f2c <+48>:    jne    0x400f17 <phase_2+27>
   0x0000000000400f2e <+50>:    jmp    0x400f3c <phase_2+64>

分析下为什么cmp %rbp,%rbx:这里%rbx的作用是作为6个参数的指针,而%rbp保存的是第6个参数的位置。比较两者的大小就是确认是否指针索引到了最后一个参数。

此时%rbx = %rsp + 8。如果还没到最后一个参数,那么重新跳转到<phase_2+27>处的代码:

   0x0000000000400f17 <+27>:    mov    -0x4(%rbx),%eax		#(%rsp + 4)-->%eax
   0x0000000000400f1a <+30>:    add    %eax,%eax			#%eax = 2 * %eax = 2 * (%rsp + 4)
   0x0000000000400f1c <+32>:    cmp    %eax,(%rbx)			#(%rsp + 8) == 2 * (%rsp + 4)
   0x0000000000400f1e <+34>:    je     0x400f25 <phase_2+41>	#如果相等,则不爆炸
   0x0000000000400f20 <+36>:    call   0x40143a <explode_bomb>

这里 cmp %eax,(%rbx) #(%rsp + 8) == 2 * (%rsp + 4)可以得知:第三个参数需要为第二个参数的两倍。

如此循环往复,直到最后一个参数。若栈顶数据为a,则其余数据分别为2a,4a,8a,16a,32a。这时候程序会执行jmp 0x400f3c <phase_2+64>,到phase_2+64位置继续执行指令:

   0x0000000000400f3c <+64>:    add    $0x28,%rsp
   0x0000000000400f40 <+68>:    pop    %rbx
   0x0000000000400f41 <+69>:    pop    %rbp
   0x0000000000400f42 <+70>:    ret   

这里就是回收栈空间,函数正常返回了。

我们可以通过调用函数 call 0x40145c <read_six_numbers>完毕后的比较语句,以及上述分析的后续语句推测函数read_six_numbers的功能就是返回一个从1开始的等比序列。
看下这个函数read_six_numbers的具体执行:

(gdb) disassemble 
Dump of assembler code for function read_six_numbers:
=> 0x000000000040145c <+0>:     sub    $0x18,%rsp
   0x0000000000401460 <+4>:     mov    %rsi,%rdx
   0x0000000000401463 <+7>:     lea    0x4(%rsi),%rcx
   0x0000000000401467 <+11>:    lea    0x14(%rsi),%rax
   0x000000000040146b <+15>:    mov    %rax,0x8(%rsp)
   0x0000000000401470 <+20>:    lea    0x10(%rsi),%rax
   0x0000000000401474 <+24>:    mov    %rax,(%rsp)
   0x0000000000401478 <+28>:    lea    0xc(%rsi),%r9
   0x000000000040147c <+32>:    lea    0x8(%rsi),%r8
   0x0000000000401480 <+36>:    mov    $0x4025c3,%esi
   0x0000000000401485 <+41>:    mov    $0x0,%eax
   0x000000000040148a <+46>:    call   0x400bf0 <__isoc99_sscanf@plt>
   0x000000000040148f <+51>:    cmp    $0x5,%eax
   0x0000000000401492 <+54>:    jg     0x401499 <read_six_numbers+61>
   0x0000000000401494 <+56>:    call   0x40143a <explode_bomb>
   0x0000000000401499 <+61>:    add    $0x18,%rsp
   0x000000000040149d <+65>:    ret    
End of assembler dump.

这里补充下gdb如何将内存数据以int类型输出:

# 以 int 类型输出内存地址为0x12345678处的数据
x/dw 0x12345678

在这个示例中,x 是 GDB 的查看内存命令,/dw 指定了打印格式,其中 /d 表示十进制,w 表示以字(4 字节)为单位进行打印。你可以根据需要调整打印的数据大小和内存地址。

逐一分析指令:

  • sub $0x18,%rsp:开辟24bytes的栈空间(6个double word类型数据)
  • mov %rsi,%rdx: %rdx = %rsi。(问:此时%rsi保存的是什么?)此时%rsi保存的是rsi 0x7fffffffe310
  • lea 0x4(%rsi),%rcx:%rcx = %rsi + 4
  • lea 0x14(%rsi),%rax:%rax = %rsi + 20
  • mov %rax,0x8(%rsp):栈顶下第二个int数据(0x7fffffffe2f8)= 0x7fffffffe324
  • lea 0x10(%rsi),%rax:%rax = %rsi + 16,%rax = 0x7fffffffe320
  • mov %rax,(%rsp):(%rsp)= %rsi + 16

额,好像不需要分析了。

首先通过phase_2代码可知:

Dump of assembler code for function phase_2:
=> 0x0000000000400efc <+0>:     push   %rbp
   0x0000000000400efd <+1>:     push   %rbx
   0x0000000000400efe <+2>:     sub    $0x28,%rsp
   0x0000000000400f02 <+6>:     mov    %rsp,%rsi		#将栈指针保存到%rsi中
   0x0000000000400f05 <+9>:     call   0x40145c <read_six_numbers>

进入函数read_six_numbers之前,栈指针已经保存到%rsi中了。而phase_2代码中对于返回栈空间的值,需要从栈顶开始保存数据大小为word(4 bytes)的数据,数据格式是从1开始的比例为2的等比序列。

进入函数read_six_numbers之后,发现很多操作就是将phase_2的栈空间地址分配给各个寄存器,不难推断出函数read_six_numbers就是将输入的数据分别分配到phase_2的栈空间中,预测一波答案1 2 4 8 16 32,输入后果然正确。

补充sscanf函数:

sscanf 是 C 标准库中的一个函数,用于按照指定的格式从字符串中读取数据并进行解析。它的函数原型如下:

int sscanf(const char *str, const char *format, ...);
  • str 是要解析的字符串。
  • format 是格式控制字符串,指定了 sscanf 如何解析字符串中的数据。格式控制字符串中可以包含转换说明符,用来指定要解析的数据类型和格式。
  • ... 是可变参数列表,用来接收解析后的数据。

sscanf 函数的作用是根据指定的格式从字符串中读取数据并存储到指定的变量中。例如:

#include <stdio.h>

int main() {
    char str[] = "Hello 123 456";
    char word[10];
    int num1, num2;

    sscanf(str, "%s %d %d", word, &num1, &num2);
    printf("Word: %s\n", word);
    printf("Number 1: %d\n", num1);
    printf("Number 2: %d\n", num2);

    return 0;
}

在这个例子中,sscanf(str, "%s %d %d", word, &num1, &num2) 从字符串 str 中解析出一个字符串、一个整数和另一个整数,并分别存储到变量 wordnum1num2 中。然后通过 printf 函数输出这些数据。

sscanf 的返回值 result 将会是解析成功的参数个数,如果返回值等于格式字符串中的参数个数,则表示解析成功。如果解析失败或者没有匹配到任何数据,sscanf 的返回值将会是 0。

这里阶段2重点是函数sscanf的作用,挖个坑,等日后整理笔记的时候,重新回顾下这个问题。

phase3:

现在看阶段3,phase3代码:

(gdb) disassemble 
Dump of assembler code for function phase_3:
   0x0000000000400f43 <+0>:     sub    $0x18,%rsp
   0x0000000000400f47 <+4>:     lea    0xc(%rsp),%rcx			#第二个参数的指针是 0xc(%rsp)
   0x0000000000400f4c <+9>:     lea    0x8(%rsp),%rdx			#第一个参数的指针是 0x8(%rsp)
   0x0000000000400f51 <+14>:    mov    $0x4025cf,%esi
   0x0000000000400f56 <+19>:    mov    $0x0,%eax
=> 0x0000000000400f5b <+24>:    call   0x400bf0 <__isoc99_sscanf@plt>
   0x0000000000400f60 <+29>:    cmp    $0x1,%eax
   0x0000000000400f63 <+32>:    jg     0x400f6a <phase_3+39>
   0x0000000000400f65 <+34>:    call   0x40143a <explode_bomb>
   0x0000000000400f6a <+39>:    cmpl   $0x7,0x8(%rsp)			#第一个参数要小于7
   0x0000000000400f6f <+44>:    ja     0x400fad <phase_3+106>
   0x0000000000400f71 <+46>:    mov    0x8(%rsp),%eax			#第一个参数的指针赋给eax
   0x0000000000400f75 <+50>:    jmp    *0x402470(,%rax,8)
   0x0000000000400f7c <+57>:    mov    $0xcf,%eax
   0x0000000000400f81 <+62>:    jmp    0x400fbe <phase_3+123>
   0x0000000000400f83 <+64>:    mov    $0x2c3,%eax
   0x0000000000400f88 <+69>:    jmp    0x400fbe <phase_3+123>
   0x0000000000400f8a <+71>:    mov    $0x100,%eax
   0x0000000000400f8f <+76>:    jmp    0x400fbe <phase_3+123>
   0x0000000000400f91 <+78>:    mov    $0x185,%eax
   0x0000000000400f96 <+83>:    jmp    0x400fbe <phase_3+123>
   0x0000000000400f98 <+85>:    mov    $0xce,%eax
   0x0000000000400f9d <+90>:    jmp    0x400fbe <phase_3+123>
   0x0000000000400f9f <+92>:    mov    $0x2aa,%eax
   0x0000000000400fa4 <+97>:    jmp    0x400fbe <phase_3+123>
   0x0000000000400fa6 <+99>:    mov    $0x147,%eax
   0x0000000000400fab <+104>:   jmp    0x400fbe <phase_3+123>
   0x0000000000400fad <+106>:   call   0x40143a <explode_bomb>
   0x0000000000400fb2 <+111>:   mov    $0x0,%eax
   0x0000000000400fb7 <+116>:   jmp    0x400fbe <phase_3+123>
   0x0000000000400fb9 <+118>:   mov    $0x137,%eax
   0x0000000000400fbe <+123>:   cmp    0xc(%rsp),%eax	# 第二个参数需要符合eax(+57到+104mov传递的参数)上面的取值
   0x0000000000400fc2 <+127>:   je     0x400fc9 <phase_3+134>
   0x0000000000400fc4 <+129>:   call   0x40143a <explode_bomb>
   0x0000000000400fc9 <+134>:   add    $0x18,%rsp
   0x0000000000400fcd <+138>:   ret    
End of assembler dump.

在执行call 0x400bf0 <__isoc99_sscanf@plt>之前,分析sscanf函数的入参:

  • 第一个参数是一个字符串地址,由%rdi保存。

  • 第二个参数(%rsi)是格式控制字符串,指定了 sscanf 如何解析字符串中的数据。打印%rsi的数据为:

    (gdb) x/s $rsi
    0x4025cf:       "%d %d"
    

    这里可以得知sccanf函数会将输入的字符串分割为两个整数。

  • 那么各自保存两个整数的地址就是%rdx和%rcx了。

这里的问题就在于jmp *0x402470(,%rax,8)这个间接跳转指令。这个跳转指令的意思就是从内存中读出一个地址进行跳转。

第一个参数输入2,那么寻址就是0x402470(hex) + 2*8(dec) = 0x402480,打印这个地址处的数据:

(gdb) x/4x 0x402480
0x402480:       0x83    0x0f    0x40    0x00

那就是跳转到地址为0x400f83的位置上去执行代码:

(gdb) x/i 0x400f83
   0x400f83 <phase_3+64>:       mov    $0x2c3,%eax

从上面反汇编的代码可以看出,将0x2c3赋值给eax后,函数就会将输入的第二个参数与eax做比较,如果相等,则不会引爆炸弹。所以如果第一个参数输入2,那么第二个参数输入必须是0x2c3 == 707

phase4:

下面开始分析phase_4:

Dump of assembler code for function phase_4:
   0x000000000040100c <+0>:     sub    $0x18,%rsp
   0x0000000000401010 <+4>:     lea    0xc(%rsp),%rcx
   0x0000000000401015 <+9>:     lea    0x8(%rsp),%rdx
   0x000000000040101a <+14>:    mov    $0x4025cf,%esi
   0x000000000040101f <+19>:    mov    $0x0,%eax
=> 0x0000000000401024 <+24>:    call   0x400bf0 <__isoc99_sscanf@plt>
   0x0000000000401029 <+29>:    cmp    $0x2,%eax
   0x000000000040102c <+32>:    jne    0x401035 <phase_4+41>
   0x000000000040102e <+34>:    cmpl   $0xe,0x8(%rsp)
   0x0000000000401033 <+39>:    jbe    0x40103a <phase_4+46>
   0x0000000000401035 <+41>:    call   0x40143a <explode_bomb>
   0x000000000040103a <+46>:    mov    $0xe,%edx
   0x000000000040103f <+51>:    mov    $0x0,%esi
   0x0000000000401044 <+56>:    mov    0x8(%rsp),%edi
   0x0000000000401048 <+60>:    call   0x400fce <func4>
   0x000000000040104d <+65>:    test   %eax,%eax
   0x000000000040104f <+67>:    jne    0x401058 <phase_4+76>
   0x0000000000401051 <+69>:    cmpl   $0x0,0xc(%rsp)
   0x0000000000401056 <+74>:    je     0x40105d <phase_4+81>
   0x0000000000401058 <+76>:    call   0x40143a <explode_bomb>
   0x000000000040105d <+81>:    add    $0x18,%rsp
   0x0000000000401061 <+85>:    ret

依旧是函数sscanf,查看第二个参数查看如何解析字符串中的数据:

(gdb) x/s $esi
0x4025cf:       "%d %d"

可以得出需要输入两个数字,而第一个数字保存在0x8(%rsp),第二个数字存放在0xc(%rsp)

然后下面代码就是判断第一个参数是否小于14(cmpl $0xe,0x8(%rsp)),不是的话直接爆炸。

假若第一个入参小于14,则将第一个参数和0传给<func4>,并且校验返回值是否为0,如果不为0,则爆炸。

下面分析下<func4>的代码,看看如何才能让函数的返回值为0:

(gdb) disassemble
Dump of assembler code for function func4:
=> 0x0000000000400fce <+0>:     sub    $0x8,%rsp
   0x0000000000400fd2 <+4>:     mov    %edx,%eax
   0x0000000000400fd4 <+6>:     sub    %esi,%eax
   0x0000000000400fd6 <+8>:     mov    %eax,%ecx
   0x0000000000400fd8 <+10>:    shr    $0x1f,%ecx	#shr k,D D ← D>>L k Logical right shift
   0x0000000000400fdb <+13>:    add    %ecx,%eax
   0x0000000000400fdd <+15>:    sar    %eax			#sar k,D D ← D>>A k Arithmetic right shift
   0x0000000000400fdf <+17>:    lea    (%rax,%rsi,1),%ecx
   0x0000000000400fe2 <+20>:    cmp    %edi,%ecx
   0x0000000000400fe4 <+22>:    jle    0x400ff2 <func4+36>
   0x0000000000400fe6 <+24>:    lea    -0x1(%rcx),%edx
   0x0000000000400fe9 <+27>:    call   0x400fce <func4>
   0x0000000000400fee <+32>:    add    %eax,%eax
   0x0000000000400ff0 <+34>:    jmp    0x401007 <func4+57>
   0x0000000000400ff2 <+36>:    mov    $0x0,%eax
   0x0000000000400ff7 <+41>:    cmp    %edi,%ecx
   0x0000000000400ff9 <+43>:    jge    0x401007 <func4+57>
   0x0000000000400ffb <+45>:    lea    0x1(%rcx),%esi 		#Imm(rb) M[Imm + R[rb]] Base + displacement
   0x0000000000400ffe <+48>:    call   0x400fce <func4>
   0x0000000000401003 <+53>:    lea    0x1(%rax,%rax,1),%eax #Imm(rb,ri,s) M[Imm + R[rb]+ R[ri] . s] Scaled indexed
   0x0000000000401007 <+57>:    add    $0x8,%rsp
   0x000000000040100b <+61>:    ret

此时查看寄存器状态:

  • mov %edx,%eax: eax = 14

  • sub %esi,%eax:eax = eax - 0 = 14 - 0= e

  • mov %eax,%ecx:ecx = eax = e

  • shr $0x1f,%ecx:ecx = ecx >>L 31(保留符号位)= 0

  • add %ecx,%eax:eax = eax + sign_bit(eax)(1 or 0) = e

  • sar %eax :表示将 %eax 寄存器中的值向右移动一位,并用最高位的值填充左侧空出的位。这个操作通常用于实现整数的除法运算,因为算术右移相当于将数值除以 2 的幂次方。eax = eax / 2 = 7

  • lea (%rax,%rsi,1),%ecx:ecx = rax + rsi = 7 + 0 = 7

  • cmp %edi,%ecx:7 - 2 = 5,因为此时结果大于0,则不会执行jle 0x400ff2 <func4+36>

  • lea -0x1(%rcx),%edx:edx = rcx - 1 = 6

  • call 0x400fce <func4>(调用func4):这里涉及到了递归调用的逻辑:

    可以得知,我需要用C语言写一下这个代码逻辑:

int func4(int rdi, int rsi, int rdx){
	//rdi = param1, rsi =0, rdx = 14,
    //return value = rax

	rax = rdx - rsi;
	rcx = (unsigned) rax >> 31; //sign_bit
	
	rax = (rax + (unsigned) rax >> 31) /2;
	
	rcx = rax + rsi;
	
	if(rcx > rdi){
		
		return 2 * func4(rdi, rsi, rcx - 1);
		
	}else if(rcx < rdi)
		
		return 2 * func4(rdi, rcx + 1, rdx) + 1;
		
	}else{
		
		return 0;
	}
}

这个问题卡了我一个小时,其实并不需要写下每一个指令的作用,而是需要组块起来,拼装函数整体逻辑。

这里的知识就是递归函数的汇编指令辨别:call func4后,返回值为eax,然后对eax操作,这样就隐藏了一个递归调用本函数的流程。

终于发现了问题所在:复习操作数的来源:

Imm(rb) 	 M[Imm + R[rb]] 				Base + displacement
Imm(rb,ri,s) M[Imm + R[rb]+ R[ri] . s] 		Scaled indexed

这样反汇编到C语言后,分析下如何得到0返回值:

就是让rcx == rdi。根据

	rax = rdx - rsi;			#rax = 14  
	rcx = (unsigned) rax >> 31; #rcx = 0
	rax = (rax + (unsigned) rax >> 31) /2; //rax = 7
	rcx = rax + rsi;			#rcx = 7

如果想第一轮的时候就让条件rcx == rdi成立,那么parm1必须是7。然后第二个参数根据phase_4代码:cmpl $0x0,0xc(%rsp)可以判断出是0。

输入7 0,阶段4成功通过。

阶段4总结:

  • 如果分析时间较长,直接看答案就行了,因为有自己没绕过去的弯或者不具备的前置知识阻碍了自己的思考。这里的例子就是递归调用不了解和操作数的取值不熟悉导致的代码反汇编异常困难。

phase5:

分析phase_5:

(gdb) disassemble phase_5
Dump of assembler code for function phase_5:
   0x0000000000401062 <+0>:     push   %rbx
   0x0000000000401063 <+1>:     sub    $0x20,%rsp
   0x0000000000401067 <+5>:     mov    %rdi,%rbx				# char* rbx = rdi
   0x000000000040106a <+8>:     mov    %fs:0x28,%rax
   0x0000000000401073 <+17>:    mov    %rax,0x18(%rsp)
   0x0000000000401078 <+22>:    xor    %eax,%eax
   0x000000000040107a <+24>:    call   0x40131b <string_length>
   0x000000000040107f <+29>:    cmp    $0x6,%eax				# length(input_string) == 6
   0x0000000000401082 <+32>:    je     0x4010d2 <phase_5+112>
   0x0000000000401084 <+34>:    call   0x40143a <explode_bomb>
   0x0000000000401089 <+39>:    jmp    0x4010d2 <phase_5+112>
   0x000000000040108b <+41>:    movzbl (%rbx,%rax,1),%ecx		# ecx = mem(input_string)&0xFF(初始) --将字符串的第一个字符赋值给ecx
   																# 	 => mem(input_string + rax)&0xFF
   0x000000000040108f <+45>:    mov    %cl,(%rsp)				# *rsp = ecx & 0xFF 	-- 第一个字符值写入到栈顶
   0x0000000000401092 <+48>:    mov    (%rsp),%rdx				# rdx = *rsp			
   0x0000000000401096 <+52>:    and    $0xf,%edx				# rdx = rdx & 0xF		-- 第一个字符值的低4位赋值给rdx
   0x0000000000401099 <+55>:    movzbl 0x4024b0(%rdx),%edx		# rdx = mem(0x4024b0 + rdx) & 0xFF -- 获取内存某处的一个字节数据
   0x00000000004010a0 <+62>:    mov    %dl,0x10(%rsp,%rax,1)	# mem(0x10 + rsp + rax) = rdx & 0xF(初始)
   0x00000000004010a4 <+66>:    add    $0x1,%rax				# rax = 0 + 1 + 1 + ...
   0x00000000004010a8 <+70>:    cmp    $0x6,%rax				# if rax != 6
   0x00000000004010ac <+74>:    jne    0x40108b <phase_5+41>
   0x00000000004010ae <+76>:    movb   $0x0,0x16(%rsp)			# mem(rsp + 0x16) = 0		
   0x00000000004010b3 <+81>:    mov    $0x40245e,%esi			# *esi = flyers
   0x00000000004010b8 <+86>:    lea    0x10(%rsp),%rdi			
   0x00000000004010bd <+91>:    call   0x401338 <strings_not_equal> #if(*(rsp + 0x10) == "flyers")
   0x00000000004010c2 <+96>:    test   %eax,%eax
   0x00000000004010c4 <+98>:    je     0x4010d9 <phase_5+119>
   0x00000000004010c6 <+100>:   call   0x40143a <explode_bomb>
   0x00000000004010cb <+105>:   nopl   0x0(%rax,%rax,1)
   0x00000000004010d0 <+110>:   jmp    0x4010d9 <phase_5+119>
   0x00000000004010d2 <+112>:   mov    $0x0,%eax
   0x00000000004010d7 <+117>:   jmp    0x40108b <phase_5+41>
   0x00000000004010d9 <+119>:   mov    0x18(%rsp),%rax
   0x00000000004010de <+124>:   xor    %fs:0x28,%rax
   0x00000000004010e7 <+133>:   je     0x4010ee <phase_5+140>
   0x00000000004010e9 <+135>:   call   0x400b30 <__stack_chk_fail@plt>
   0x00000000004010ee <+140>:   add    $0x20,%rsp
   0x00000000004010f2 <+144>:   pop    %rbx
   0x00000000004010f3 <+145>:   ret
End of assembler dump.

看下函数string_length的代码实现:

(gdb) disassemble string_length
Dump of assembler code for function string_length:
   0x000000000040131b <+0>:     cmpb   $0x0,(%rdi)
   0x000000000040131e <+3>:     je     0x401332 <string_length+23>
   0x0000000000401320 <+5>:     mov    %rdi,%rdx	# char* rdx = rdi 
   0x0000000000401323 <+8>:     add    $0x1,%rdx	# rdx = rdx + 1
   0x0000000000401327 <+12>:    mov    %edx,%eax	# char* rax = rdx
   0x0000000000401329 <+14>:    sub    %edi,%eax	# rax = rdi + 1 - rdi = 1
   0x000000000040132b <+16>:    cmpb   $0x0,(%rdx)	# if rdi[1] != Null
   0x000000000040132e <+19>:    jne    0x401323 <string_length+8> # rax + 1
   0x0000000000401330 <+21>:    repz ret
   0x0000000000401332 <+23>:    mov    $0x0,%eax
   0x0000000000401337 <+28>:    ret
End of assembler dump.

入参只有一个%rdi,保存的是一个字符串指针。如果入参为空字符串,则返回0。如果不为0,则记录字符串长度。

其中rdx相当于字符串指针,每次都指向字符串的下一个字符,直到遇到空字符(ascii为NUL)截止,每次移动都会使得eax+1。

所以这里输入的字符串长度需要为6。分析到指令movzbl 0x4024b0(%rdx),%edx的时候,发现这个地址的数据的确不清楚是什么,先输入字符串123456看看结果吧。

函数+39-- +74的代码反汇编为C语言代码为:

int rax = 0;
char* rbx = rdi;		 // rdi = input_string
while (rax != 6){		 // cmp    $0x6,%rax
    char rcx = rbx[rax]; // movzbl (%rbx,%rax,1),%ecx
    char *rsp = rcx;     // mov    %cl,(%rsp)
    //char rdx = rcx;		// mov    (%rsp),%rdx
    //rdx = rdx & 0xF;	 	// and    $0xf,%edx
    //rdx = mem(0x4024b0 + rdx) & 0xFF 		// movzbl 0x4024b0(%rdx),%edx
    //mem(0x10 + rsp + rax) = rdx & 0xFF;	// mov    %dl,0x10(%rsp,%rax,1)0
    //相当于将rsp + 0x10、11、12、13、14、15处的空间,被mem(0x4024b0 + input[i] & 0xF)赋值
    mem(rsp + 0x10 +rax) = mem(0x4024b0 + rbx[rax] & 0xF) & 0xFF //上面4行代码的抽象
    rax++;				 // add    $0x1,%rax
}

打印0x4024b0处的数据:

(gdb) x/s 0x4024b0
0x4024b0 <array.3449>:  "maduiersnfotvbylSo you think you can stop the bomb with ctrl-c, do you?"

然后分析+76后的代码,并将其转换为C伪代码:

//+76
/* mem(0x4024b0) :
	(gdb) x/16x 0x4024b0
	0x4024b0 <array.3449>:  		0x6d    0x61    0x64    0x75    0x69    0x65    0x72    0x73
	0x4024b8 <array.3449+8>:        0x6e    0x66    0x6f    0x74    0x76    0x62    0x79    0x6c
*/
if(*(rsp + 0x10) == (char)*esi){
    *(rsp + 0x10) = "flyers";
    *(rsp + 0x10) = 0x66; //if mem(0x4024b0 + rbx[0] & 0xF) == 0x66 -->rbx[0]& 0xF = 9 -->rbx[0] = 0xn9  
    *(rsp + 0x11) = 0x6c; //if mem(0x4024b0 + rbx[1] & 0xF) == 0x6c -->rbx[1]& 0xF = F -->rbx[0] = 0xnF
    *(rsp + 0x12) = 0x79; //if mem(0x4024b0 + rbx[2] & 0xF) == 0x79 -->rbx[1]& 0xF = E -->rbx[0] = 0xnE
    *(rsp + 0x13) = 0x65; //if mem(0x4024b0 + rbx[3] & 0xF) == 0x65 -->rbx[1]& 0xF = 5 -->rbx[0] = 0xn5
    *(rsp + 0x14) = 0x72; //if mem(0x4024b0 + rbx[4] & 0xF) == 0x72 -->rbx[1]& 0xF = 6 -->rbx[0] = 0xn6
    *(rsp + 0x15) = 0x73; //if mem(0x4024b0 + rbx[5] & 0xF) == 0x73 -->rbx[1]& 0xF = 7 -->rbx[0] = 0xn7
}

要想让strings_not_equal函数正常,就需要让rdi指针指向的字符串和rsi指针指向的字符串等长且对应位置字符相同。而此时rsi指向的字符串为:

(gdb) x/s $rsi
0x40245e:       "flyers"

那么就需要让*(rsp + 0x10)的数据也为flyers才可以。

flyers的ASCII码分别对应从mem(rsp + 0x10)开始的6个字节:

	*(rsp + 0x10) = 0x66;// f 
    *(rsp + 0x11) = 0x6c;// l
    *(rsp + 0x12) = 0x79;// y
    *(rsp + 0x13) = 0x65;// e
    *(rsp + 0x14) = 0x72;// r
    *(rsp + 0x15) = 0x73;// s

再回头分析mem(rsp + 0x10 +rax)的赋值语句:

 mem(rsp + 0x10 +rax) = mem(0x4024b0 + rbx[rax] & 0xF) & 0xFF 

举例:当rax = 0的时候,需要给mem(rsp + 0x10)赋值0x66,而赋的值是从0x4024b0 + rbx[0] & 0xF获取到的,而纵观mem(0x4024b0)的数据:

	(gdb) x/16x 0x4024b0
	0x4024b0 <array.3449>:  		0x6d    0x61    0x64    0x75    0x69    0x65    0x72    0x73
	0x4024b8 <array.3449+8>:        0x6e    0x66    0x6f    0x74    0x76    0x62    0x79    0x6c

可以看出在0x4024b9处才能获取到0x66,所以rbx[0] & 0xF = 0x9,这时候rbx[0] = 0xn9即可。(这里的n代表0-F的任意数据都可,因为这里的n会被后面的0XF与运算截掉,不参与运算)而字符9的ASCII码正好是0x39,符合0xn9的最低字节是9的条件。所以输入的字符串第一个可以是9

同理可以确认正确字符串的其他位的ASCII码:

if mem(0x4024b0 + rbx[0] & 0xF) == 0x66 -->rbx[0]& 0xF = 9 -->rbx[0] = 0xn9  
if mem(0x4024b0 + rbx[1] & 0xF) == 0x6c -->rbx[1]& 0xF = F -->rbx[0] = 0xnF
if mem(0x4024b0 + rbx[2] & 0xF) == 0x79 -->rbx[1]& 0xF = E -->rbx[0] = 0xnE
if mem(0x4024b0 + rbx[3] & 0xF) == 0x65 -->rbx[1]& 0xF = 5 -->rbx[0] = 0xn5
if mem(0x4024b0 + rbx[4] & 0xF) == 0x72 -->rbx[1]& 0xF = 6 -->rbx[0] = 0xn6
if mem(0x4024b0 + rbx[5] & 0xF) == 0x73 -->rbx[1]& 0xF = 7 -->rbx[0] = 0xn7

这样就可以获取phase_5的flag:9on567,并且输入后成功拆解phase_5炸弹。

爽!😎

现在是凌晨2点16分,月明星稀,不知乌雀在哪,睡觉!

phase6:

phase6:

=> 0x00000000004010f4 <+0>:     push   %r14
   0x00000000004010f6 <+2>:     push   %r13
   0x00000000004010f8 <+4>:     push   %r12
   0x00000000004010fa <+6>:     push   %rbp
   0x00000000004010fb <+7>:     push   %rbx
   0x00000000004010fc <+8>:     sub    $0x50,%rsp
   0x0000000000401100 <+12>:    mov    %rsp,%r13					#int *r13 = rsp
   0x0000000000401103 <+15>:    mov    %rsp,%rsi
   0x0000000000401106 <+18>:    call   0x40145c <read_six_numbers> #num1 - num6
   0x000000000040110b <+23>:    mov    %rsp,%r14				   #int *r14 = num1 = *rsp
   0x000000000040110e <+26>:    mov    $0x0,%r12d					# int i(r12d) = 0
   0x0000000000401114 <+32>:    mov    %r13,%rbp					# int *rbp = rsp
   0x0000000000401117 <+35>:    mov    0x0(%r13),%eax				# int eax = num1
   0x000000000040111b <+39>:    sub    $0x1,%eax					# if (rsp[0] <= 6)
   0x000000000040111e <+42>:    cmp    $0x5,%eax
   0x0000000000401121 <+45>:    jbe    0x401128 <phase_6+52>
   0x0000000000401123 <+47>:    call   0x40143a <explode_bomb>
   
   #<+0>- <+47>:输入的6个值均小于等于6
   0x0000000000401128 <+52>:    add    $0x1,%r12d					#i ++  (i = 1)
   0x000000000040112c <+56>:    cmp    $0x6,%r12d					# cmp 6 and i
   0x0000000000401130 <+60>:    je     0x401153 <phase_6+95>
  					
  					# if(i < 6)
   0x0000000000401132 <+62>:    mov    %r12d,%ebx				# int ebx = i (ebx = 1)
   0x0000000000401135 <+65>:    movslq %ebx,%rax				# Move sign-extended double word to quad word	
   																# rax = 1
   0x0000000000401138 <+68>:    mov    (%rsp,%rax,4),%eax		# rax = rsp[1]
   0x000000000040113b <+71>:    cmp    %eax,0x0(%rbp)			# if(rsp[1] != rsp[0])		
   0x000000000040113e <+74>:    jne    0x401145 <phase_6+81>
   0x0000000000401140 <+76>:    call   0x40143a <explode_bomb>
   					# rsp[1] != rsp[0]
   0x0000000000401145 <+81>:    add    $0x1,%ebx				# ebx += 1(ebx = 2)
   0x0000000000401148 <+84>:    cmp    $0x5,%ebx				# ebx <= 5
   0x000000000040114b <+87>:    jle    0x401135 <phase_6+65>
   #<+62> - <+87>分析:遍历输入的6个数据,判断是否后五个均不等于num[0]
   
   
   0x000000000040114d <+89>:    add    $0x4,%r13				# *r13 = rsp + 1
   0x0000000000401151 <+93>:    jmp    0x401114 <phase_6+32>
   #<+32> - <+93>:判断是否输入的所有数字均小于等于6,且互不相等
   
   
   					# if(i == 6) 此时满足的条件是:输入的所有数字均小于等于6,且互不相等
   0x0000000000401153 <+95>:    lea    0x18(%rsp),%rsi		# int *rsi = rsp + 6	(24 / 4 = 6 words) = 0
   0x0000000000401158 <+100>:   mov    %r14,%rax			# int *rax = rsp
   0x000000000040115b <+103>:   mov    $0x7,%ecx			# int ecx = 7
   0x0000000000401160 <+108>:   mov    %ecx,%edx			# edx = 7
   0x0000000000401162 <+110>:   sub    (%rax),%edx			# edx = 7 - rsp[0]
   0x0000000000401164 <+112>:   mov    %edx,(%rax)			# *rsp = 7 - rsp[0]
   0x0000000000401166 <+114>:   add    $0x4,%rax			# rax = rsp + 1(*rax = rsp[1])
   0x000000000040116a <+118>:   cmp    %rsi,%rax			# 指针 rax 索引rsp到rsp+5的6个输入数字
   0x000000000040116d <+121>:   jne    0x401160 <phase_6+108>
   #<+95> - <+121>:指针rax索引输入的6个数字,并且重新赋值为num[i] = 7-num[i]
   
   
# ------------------- 将rsp[i]重新赋值为7 - rsp[i]------------------

   0x000000000040116f <+123>:   mov    $0x0,%esi				# int rsi = 0
   0x0000000000401174 <+128>:   jmp    0x401197 <phase_6+163>
   																# eax = 1, *edx = 0x6032d0
   0x0000000000401176 <+130>:   mov    0x8(%rdx),%rdx			# rdx = mem(0x6032d0 + 8) = 6304480
   0x000000000040117a <+134>:   add    $0x1,%eax				# eax ++ (eax = 2)
   0x000000000040117d <+137>:   cmp    %ecx,%eax				# compare eax and ecx(rsp[0])
   0x000000000040117f <+139>:   jne    0x401176 <phase_6+130>	# if eax != rsp[0]: eax ++
   0x0000000000401181 <+141>:   jmp    0x401188 <phase_6+148>	#	此时一定满足rax == rsp[0]


						#if(rsp[i] <= 1)
   0x0000000000401183 <+143>:   mov    $0x6032d0,%edx			# int *edx = 0x6032d0(*edx = 332)
   0x0000000000401188 <+148>:   mov    %rdx,0x20(%rsp,%rsi,2)	# 8(32bytes = 8words--int) + rsp + 2 * rsi = 0x6032d0
   #在40118d打断点,调试可以确认每次将rsp[8+2i]赋值什么了
   
   0x000000000040118d <+153>:   add    $0x4,%rsi				# rsi + 1
   0x0000000000401191 <+157>:   cmp    $0x18,%rsi				# rsi and 6
   0x0000000000401195 <+161>:   je     0x4011ab <phase_6+183>
   
   0x0000000000401197 <+163>:   mov    (%rsp,%rsi,1),%ecx		# ecx = rsp[rsi] 
   0x000000000040119a <+166>:   cmp    $0x1,%ecx				# compare rsp[rsi] and 1 
   0x000000000040119d <+169>:   jle    0x401183 <phase_6+143>	
   # <+143> - <+169>:如果7-num[i]小于等于1,则将给rsp+8(32 = 0x20)、10(40 = 0x28)、12(48 = 0x30)、14(56 = 0x38)、16(64 = 0x40)、18赋值
   # 为0x6032d0
   
   						#if(rsp[i] > 1)
   0x000000000040119f <+171>:   mov    $0x1,%eax				# int eax = 1
   0x00000000004011a4 <+176>:   mov    $0x6032d0,%edx			# int *edx = 0x6032d0
   0x00000000004011a9 <+181>:   jmp    0x401176 <phase_6+130>
   
   
   0x00000000004011ab <+183>:   mov    0x20(%rsp),%rbx			# rbx = rsp[8]
   0x00000000004011b0 <+188>:   lea    0x28(%rsp),%rax			# rax = rsp + 10
   0x00000000004011b5 <+193>:   lea    0x50(%rsp),%rsi			# rsi = rsp + 20
   0x00000000004011ba <+198>:   mov    %rbx,%rcx				# rcx = rsp[8]
   0x00000000004011bd <+201>:   mov    (%rax),%rdx				# rdx = rsp[10]
   0x00000000004011c0 <+204>:   mov    %rdx,0x8(%rcx)			# *(rcx + 0x8) = rsp[10]
   0x00000000004011c4 <+208>:   add    $0x8,%rax				# rax = rsp + 10 + 2
   0x00000000004011c8 <+212>:   cmp    %rsi,%rax				# if rax != rsp+20
   
   0x00000000004011cb <+215>:   je     0x4011d2 <phase_6+222>
   0x00000000004011cd <+217>:   mov    %rdx,%rcx				# rcx = rsp[10]
   0x00000000004011d0 <+220>:   jmp    0x4011bd <phase_6+201>
   # <+183> - <+220>:不知道做了什么,应该是将rsp[10]、rsp[12]、...、rsp[18]的所有值,赋值给
   
   						# if rax == rsp+20
   						# 跳出循环时,rax=rsp+20,rdx = *(rsp+18),rbx = *(rsp + 8)
   0x00000000004011d2 <+222>:   movq   $0x0,0x8(%rdx)
   0x00000000004011da <+230>:   mov    $0x5,%ebp
   0x00000000004011df <+235>:   mov    0x8(%rbx),%rax	# rax = *(rsp[8] + 2);//链表指针域
   0x00000000004011e3 <+239>:   mov    (%rax),%eax		# rax = *(rax);//rax 为node->next
   0x00000000004011e5 <+241>:   cmp    %eax,(%rbx)	# 下一个节点 和 此节点 的低32位数据进行比对		
   0x00000000004011e7 <+243>:   jge    0x4011ee <phase_6+250>	# 保证当前节点的低32位数据要大于下一节点的低32位数据
   
   0x00000000004011e9 <+245>:   call   0x40143a <explode_bomb>
   0x00000000004011ee <+250>:   mov    0x8(%rbx),%rbx
   0x00000000004011f2 <+254>:   sub    $0x1,%ebp
   0x00000000004011f5 <+257>:   jne    0x4011df <phase_6+235>
   0x00000000004011f7 <+259>:   add    $0x50,%rsp
   0x00000000004011fb <+263>:   pop    %rbx
   0x00000000004011fc <+264>:   pop    %rbp
   0x00000000004011fd <+265>:   pop    %r12
   0x00000000004011ff <+267>:   pop    %r13
   0x0000000000401201 <+269>:   pop    %r14
   0x0000000000401203 <+271>:   ret

将汇编语言反汇编为C伪代码后:

phase_6(char *str){
    int *rsp = read_six_numbers(str);//rsp[0] = input[0] ---> rsp[5] = input[5]
    for(int i = 0; i < 6; i++){
        //输入数字均小于等于6,且所有数字均不相同
        for(int j = i + 1; j < 6; j++){
            if(rsp[i] > 6 || rsp[j] == rsp[i]){
                return explode_bomb();
            }
        }		
    }
	
	//将所有数字置为7减去自身
	for(int i = 0; i < 6; i++){
		rsp[i] = 7 - rsp[i];
	}
	
	int i = 0;
		
	//rsp[8]、rsp[10]、rsp[12]、rsp[14]、rsp[16]、rsp[18]均会被赋值
	while(i < 6){
		if(rsp[i] <= 1){//7-rsp[i] and 1//	num[i] >= 6
			rsp[8 + 2*i] = 0x6032d0;
		}else{
			int eax = 1;
			int edx = 0x6032d0;
			do{
				//edx = 2:0x006032e0,3:0x006032f0,4:0x00603300,5:0x00603310,6:0x00603320
				edx = *(edx + 0x8);
				eax++;
			}while(eax != rsp[i]);
			//rax == rsp[i]
			rsp[8 + 2*i] = edx;
		}
		i++;
	}
	//修改node节点的指针域
	int rbx = rsp[8]; 	//存储的是链表节点位置/指针域
	int *rax = rsp + 10;
	int *rsi = rsp + 20;//边界
	do{
		// *(rsp[8] + 2) = rsp[10]
		// *(rsp[10] + 2) = rsp[12]
		/* ....
		   *(rsp[16] + 2) = rsp[18]
		*/
		int rcx = rbx;
		int rdx = *rax;
		*(rbx + 2) = *(rax);//修改链表的指针域
		rax = rax + 2;
		&rbx ++;
	}while(rax < rsi);
	
    //判断rsp[8]到rsp[18]所指的节点,值域是否均后一个大于前一个
	//跳出循环时,rax=rsp+20,rdx = *(rsp+18),rbx = *(rsp + 8)
    //rax            0x7fffffffe0b0 == rsp+20
    //rbx            0x603320       == *(rsp + 8)
	//rcx            0x6032e0            6304480
	//rdx            0x6032d0       == *(rsp+18)

	rdx = rsp[18];
	rbx = rsp[8]
	*(rsp[18] + 2) = 0;
	ebp = 5;
    while(ebp != 0){
        rax = *(rsp[8] + 2);//链表指针域
		rax = *(rax);		//将此节点给指针rax
		if(*(rbx) >= rax){//将前一节点(*(rbx))和后一个节点的值(猜测为值域:哪里为值域)进行比较
			rbx = *(rsp[8] + 2);//rbx指向链表的下一个节点
		//这里保证了修改完指针域后的链表,前一个节点的值要大于后一个节点的值
		}
        ebp--;
    }

	
}

补充0x6032d0处的数据:

(gdb) x/24xw 0x6032d0
0x6032d0 <node1>:       0x0000014c      0x00000001      0x006032e0      0x00000000
0x6032e0 <node2>:       0x000000a8      0x00000002      0x006032f0      0x00000000
0x6032f0 <node3>:       0x0000039c      0x00000003      0x00603300      0x00000000
0x603300 <node4>:       0x000002b3      0x00000004      0x00603310      0x00000000
0x603310 <node5>:       0x000001dd      0x00000005      0x00603320      0x00000000
0x603320 <node6>:       0x000001bb      0x00000006      0x00000000      0x00000000

可以看出这是一个链表结构,长度为6,依次存储1到6的值。

经过分析:

  1. rsp[8]到rsp[18]保存的是以0x6032d0开头的链表地址
  2. 最后一步判断条件,保证修改完指针域后的链表,前一个节点的值要大于后一个节点的值
  3. 打印0x6032d0处的数据可以看出,若想值域从大到小排列,那就需要rsp[8]指向结构:node3 -> node4 -> node5 -> node6 -> node1 -> node2。
  4. 等日后再分析吧,今晚很累

最终答案为:

Border relations with Canada have never been better.
1 2 4 8 16 32
2 707
7 0
9on567
4 3 2 1 6 5

晚安。

posted @ 2024-04-24 22:08  上山砍大树  阅读(25)  评论(0编辑  收藏  举报