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为1jmp
的所有指令。重点有je
,jump condition为ZF = 1
movzbl
:0拓展数据。Move zero-extended byte to double word(movz S,R
: R ← ZeroExtend(S))
综上,可以得知如果想拆除阶段1的炸弹,则需要输入合理的字符串,而这个字符串可以通过以下分析得出:
- 查看
phase_1
函数的汇编代码,发现入参%rdi
需要跟另一个参数0x402400 --> %rsi
进入函数strings_not_equal
进行计算,若结果为0,则可以拆除炸弹,否则爆炸。 - 查看
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 + 4lea 0x14(%rsi),%rax
:%rax = %rsi + 20mov %rax,0x8(%rsp)
:栈顶下第二个int数据(0x7fffffffe2f8)= 0x7fffffffe324lea 0x10(%rsi),%rax
:%rax = %rsi + 16,%rax = 0x7fffffffe320mov %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
中解析出一个字符串、一个整数和另一个整数,并分别存储到变量 word
、num1
和 num2
中。然后通过 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的值。
经过分析:
- rsp[8]到rsp[18]保存的是以0x6032d0开头的链表地址
- 最后一步判断条件,保证修改完指针域后的链表,前一个节点的值要大于后一个节点的值
- 打印0x6032d0处的数据可以看出,若想值域从大到小排列,那就需要rsp[8]指向结构:node3 -> node4 -> node5 -> node6 -> node1 -> node2。
- 等日后再分析吧,今晚很累
最终答案为:
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
晚安。
本文作者:上山砍大树
本文链接:https://www.cnblogs.com/shangshankandashu/p/18156497
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步