本文是对计算机系统基础第二次实验的复现,涉及gdb
使用和汇编语言学习两个知识点。
一些说明 1.本文图片托管在Onedrive
上,请自备梯子,否则图片无法正常显示
2.感谢二进制炸弹拆弹实验 对本文的引用
3.推荐使用工具IDA
可以更方便地解决问题,本文更偏向汇编分析,建议读者自行了解IDA
的使用
一些准备 一、下载 我们默认把peda、pwngdb、pwndbg都安装用户的根目录下,可以减少一些文件的改动。
root下,下载 前先进入用户根目录:
下载Pwngdb
1 git clone https://github.com/scwuaptx/Pwngdb.git
下载pwndbg
1 git clone https://github.com/pwndbg/pwndbg
下载peda
1 git clone https://github.com/longld/peda.git
下载gef
1 git clone https://github.com/hugsy/gef.git
下载完成后目录如下所示:
1 2 # ls peda pwndbg Pwngdb gef
二、配置
先安装pwndbg
插件配置
打开文件后文件内容修改如下,这里要注意source /root/pwndbg/gdbinit.py
一定要在source /root/Pwngdb/angelheap/gdbinit.py
前面,要不然会使用默认配置。
gdb
的全局用户配置目录在/etc/gdb/gdbinit
,为使得pwndbg
汇编各式为AT&T
,在该文件中添加set disassembly-flavor att
set debuginfod enabled on
是/root/pwndbg/.gdbinit
中自带的,我给迁移过来了,还有中间的define ......
也是自带的,使用时通过对source /root/peda/peda.py
和source /root/pwndbg/gdbinit.py
和source /root/gef/gef.py
加解注释实现不同插件的使用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 source /root/pwndbg/gdbinit.py #source /root/peda/peda.py #source /root/gef/gef.py source /root/Pwngdb/pwngdb.py source /root/Pwngdb/angelheap/gdbinit.py define hook-run python import angelheap angelheap.init_angelheap() end end set debuginfod enabled on set disassembly-flavor att
若想在普通用户下使用原版gdb
,需要将第7-12行注释掉。
phase_1 answer
1 2 3 4 5 6 7 Public speaking is very easy. 1 2 6 24 120 720 7 b 524 9 austinpowers /0%+-! 4 2 6 3 1 5 1001
反汇编代码如下
这里的关键函数显然是,在执行时,两个参数入栈,经过实际测试(这里使用了gdb的一个插件pwndbg),一个参数是输入,一个参数是目标字符串,测试过程如下:
我们在输入时尝试输入字符串”11111111”时
查看下栈信息,可以看到ebx+0x8的内存地址存的数据为0x804b680,根据反汇编代码,这个数据要传给eax,然后作为一个参数入栈,另一个参数是立即数0x80497c0
接下来打印传入的两个参数作为内存地址储存了什么字符串:
可以看到传参确实符合猜测,eax存放输入字符串的内存地址,立即数0x80497c0存放目标字符串的内存地址。
动态调试同时发现strings_not_equal函数通过比较传入字符串和目标字符串,改变eax的数值,相等eax为0,不等为1,也符合<+28>
,<+30>
处的判断;
我们已经知道了目标字符串是“Public speaking is very easy.”,尝试传入结果,通过检测。
phase_2 反汇编代码
注意这里<+19>
处要读入六个数字,我们确定了字符类型为六个数字,我们这里不妨输入”1 2 3 4 5 6”,执行<+19>
处
<read_six_numbers>
后,栈变成了以下模样
很明显<+27>
处cmpl $1, -0x18(%ebp)
是将立即数1与栈上%ebp-0x18
地址存放的地址0xffffcc00
指向内容(第一个数字1
)比较,我们这里满足,后面的关键就是要过下面这一段的循环
这里关键的地方在与<+46>
处的相乘操作,这一步实际上实现了v[i] = v[i-1] * i
的效果,这里eax原本是下标i
(因为<+46>
处),而-4(%esi,%ebx,4)
实际上对应了上一个数字v[i-1]
。两个数相乘结果放在eax里,再比较参数v[i]
是否等于eax。根据参数1为1。我们可以构造1 2 6 24 120 720
,尝试输入,满足题意。
phase_3 反汇编代码:
首先看下sscanf@plt
的调用,了解到该函数的第一个参数是字符串,第二个参数是格式,同时,该函数返回匹配的参数个数。
1 int sscanf (const char *str, const char *format, ...)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 #include <stdio.h> #include <stdlib.h> #include <string.h> int main () { int day, year; char weekday[20 ], month[20 ], dtm[100 ]; strcpy ( dtm, "Saturday March 25 1989" ); sscanf ( dtm, "%s %s %d %d" , weekday, month, &day, &year ); printf ("%s %d, %d = %s\n" , month, day, year, weekday ); return (0 ); }
我们关心格式,打印下格式
这里可以看到edx保存了输入字符串保存的地址。输入格式是“%d %c %d”,可以看到后面eax需要大于2,也就是说输入需要完全匹配这个格式。再往后会把栈中数据和0x7比较,看下栈上数据即可,动态调试发现这个数据就是输入‘’%d %c %d‘’中的第一个%d,至于为什么这个数据被放栈上了,实际上是在sscanf执行前的参数入栈决定的,这是入栈的参数
执行sscanf以后
可以看到,符合预期,后面的重点放在0xffffcbfc 0xffffcc03 0xffffcc04处即可,容易发现,他们也在栈上,并且,0xffffcbfc处恰好就是要与0x7比较的栈上数据,ja是无符号大于,+240处是炸弹,也就是这里参数一小于等于7,且无符号数。后面,参数1作为偏移量,跳转到0x80497e8加偏移量乘4内存处地址存放的数据指向的指令,看下这个指令在哪个地址(这里以0作为偏移量)。
很容易可以看到,这里参数1为0时,要跳转到0x8048be0,这里观察发现其实就是跳转到<+72>的位置
继续往下走
这里0x309和第二个%d比较,0x71ffcb80的低位和0x71比较,这里都是正确的,直接结束程序,进入下一阶段,当然根据偏移量的不同,该题答案不同,其他偏移量的情况大多也与该次情况类似,不多赘述。
经过实验,该题八种不同的答案为
1 2 3 4 5 6 7 8 0 q 777 1 b 214 2 b 755 3 k 251 4 o 160 5 t 458 6 v 780 7 b 524
实验结果
phase_4 反汇编代码如下
sscanf函数重合了,看下格式要求输入数字,大致看了一眼汇编,发现这个数字需要大于0,该数字同时作为参数传入fun4,fun4的返回值要是55(0x37),所以关键就落在了fun4上
这里0x8(%ebp)位置为上一个栈帧,保存了传入参数9,也就是,当传参小于1时,直接给eax置1,返回。
否则,执行fun(n-1)+fun(n-2),明显是递归,c代码尝试逆向如下
1 2 3 4 5 6 int func4 (int n) {if (n <= 1 ) return 1 ; else return func4(n-1 )+func(n-2 ); }
解密程序
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 #include <stdio.h> int func4 (int n) { if (n <= 1 ) return 1 ; else return func4(n-1 )+func4(n-2 ); } int main () { int result = 55 ; int n = 0 ; while (func4(n) != result){ n++; } printf ("该函数的传入值为:%d\n" , n); return 0 ; }
编译运行,结果为9,尝试输入9,结果正确。实验结果:
phase_5 反汇编代码如下:
汇编信息:前面一段保证字符串需要有六个字符,到<+38>处看一眼,打印下0x804b220
容易知道,这段数组的存放地址被放入了esi寄存器,经历五轮循环,依次取出输入字符,取出字符后仅要低四位,作为数组该数组的数组下标取出字符,放入al寄存器以后转移到0xffffcc00,可以看到第一次取出字符‘O’(0x4f),截取出0xf(15),作为下标,取出arry[15],即字符‘g’,放入0xffffcc00的位置,如此经历循环,直到0xffcc00处有六个字符。
后面执行strings_not_equal函数,入栈两个参数,一个是0xffffcc00,即-2(%ebp),也即%ecx,一个是0x804980b,看下0x804980b,发现是字符串“giants”。
该字符串中字符在开头数组中的下标依次是
根据ASCII码表,可知
1 2 3 4 5 6 0xf {'/' '?' 'O' '_' 'o' }0x0 {'0' '@' 'P' '`' 'p' }0x5 {'%' '5' 'E' 'U' 'e' 'u' }0xb {'+' ';' 'K' '[' 'k' '{' }0xd {'-' '=' 'M' ']' 'm' '}' }0x2 {'!' '1' 'A' 'Q' 'a' 'q' }
从上到下依次在大括号里随机选择一个字符组成字符串即可。
试验结果
phase_6 反汇编后代码如下
前面代码逻辑比较简单,read_six_numbers函数将尝试输入的字符“1 2 3 4 5 6”放在栈上的特定位置,以便后续使用;并将链表的第一个元素node1(0x804b26c)放栈上;同时,我们打印链表的结构,方便后续使用。
下一阶段是和一个循环一起使用来保证输入的每一个数均小于等于6
继续向下读,结合上面这段,发现这不但是个循环,还是个双层循环,这段需要仔细读。我会给出C风格的伪代码来方便理解。
这一段的伪代码如下
1 2 3 4 5 6 7 8 for (int i = 0 ;i <= 5 ;i++){ if (v[i] > 6 ) explode_bomb(); for (int j = i+1 ;j <=5 ;j++){ if (v[i] == v[j]) explode_bomb(); } }
这段保证了输入的六个数字均小于等于6,且互不相等。
应该注意到,这块代码大概分成两个部分,在<+120>前,主要进行了一些栈初始化的操作,以方便接下来的使用。<+120>到<+170>是一个大循环。主要是将链表以输入数字的顺序依次放在栈空间上,核心操作步骤是<+163>,循环结束后形成的栈如下图所示。
后面这段根据栈上链表的顺序,对原链表进行了重新指向设定。
这段循环执行后链表的结构如图所示
来看最后一部分
可将其分成3部分。第一部分是<+216>之前的栈/寄存器初始化,第二部分是<+216>至<+237>之间的循环体,第三部分是之后的销毁栈指令。重点在循环体处,在循环中,要求每一个链表后的元素必须小于或等于当前链表中的元素。因此,我们将初始链表进行排序,作为输入参数传入,即可拆弹成功。
secret_phase 入口寻找比较简单,注意到每次通过一个phase,都会经过一个phase_defused函数,反汇编看下,这个函数是干嘛的。
<+7>处的比较要求0x804b480处的值与0x6相等。下面是通过第一阶段以后的0x804b480,经过尝试,每通过一个阶段,储存在该位置的数据加一,此处相当于记录了通过的关数,因此,只有走第六关以后的那个phase_defused函数才能进入secret_phase。实际上,incl num_input_strings在每个阶段执行前的处执行,因此,每到一关,0x804b480处的数值加一。
再往下走发现熟悉的sscanf函数,看下传入参数
应该注意到,这里的“9 ”是第四关输入数据,检测的格式是“%d %s”,这是否提示我们:第四关的输入不止有“9”,还有一个字符串?继续向下看,要求sscanf返回值为2,也就是我们必须再输入一个字符串。注意到下面还有strings_not_equal函数,我们查看该函数的入栈参数,判断需要输入字符串的具体情况。
所以,我们在第四行“9”后面输入“austinpowers”,开启secret_phase函数。看下secret_phase函数的反汇编代码
汇编的逻辑并不复杂,把输入字符串转成长整型之后,该长整型数据大小必须小于或等于1001,并与n1一起传入函数fun7,要求函数fun7的返回值为7。我们重点关注fun7实现的功能。
fun7的反汇编如下
可知道,该函数实现了对二叉树的操作,先打印下传入的二叉树。我们从传入fun7的n1参数,即根节点0x804b320开始打印该二叉树。
我们可以把这棵树画出来
1 2 3 4 36 8 50 6 22 45 107 1 7 20 35 40 47 99 1001
这个阶段的汇编并不复杂,主要是根据传入数据和当前节点递归处理来寻找目标二叉树节点,并对返回值进行处理。传入数据如果大于该节点储存的值,向左寻找,向左寻找会将被递归的fun7的返回值n进行(n*
2+1)的处理;传入数据如果小于储存的值,向右寻找,向右边寻找会将被递归的fun7的返回值n进行(n*
2)的处理;如果传入数据等于当前节点,直接返回0。因此只能一直向右左边边寻找得到1001,倘若在107-1001之间,<+14>处不会跳转,eax会变0xffffff,也不满足题目意思,因此,只能找到1001节点本身。自然,答案是1001。我们最后来验证一下成果:
可以看到所有阶段,包括secret stage阶段,都已经被攻克。
phase_6_调试记录 静态分析 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 (gdb) disas phase_6 Dump of assembler code for function phase_6: ;第一部分 0x08048d98 <+0>: push %ebp ;基地指针入栈 0x08048d99 <+1>: mov %esp,%ebp ;栈顶栈底指针统一 0x08048d9b <+3>: sub $0x4c,%esp ;开辟栈空间 0x08048d9e <+6>: push %edi ;参数入栈 0x08048d9f <+7>: push %esi ;参数入栈 0x08048da0 <+8>: push %ebx ;参数入栈 0x08048da1 <+9>: mov 0x8(%ebp),%edx ;把%epd加0x8指向内容给%edx 0x08048da4 <+12>: movl $0x804b26c,-0x34(%ebp) ;立即数放栈上 0x08048dab <+19>: add $0xfffffff8,%esp ;由于模运算的性质,多开辟了(f-8)空间 0x08048dae <+22>: lea -0x18(%ebp),%eax ;把%ebp-0x18值送入%eax(栈上地址入寄存器) 0x08048db1 <+25>: push %eax ;参数入栈 0x08048db2 <+26>: push %edx ;参数入栈 0x08048db3 <+27>: call 0x8048fd8 <read_six_numbers> ;函数调用 ;第二部分 0x08048db8 <+32>: xor %edi,%edi ;edi归零 0x08048dba <+34>: add $0x10,%esp ;栈空间减少 0x08048dbd <+37>: lea 0x0(%esi),%esi ;空指令 ;第五部分 0x08048dc0 <+40>: lea -0x18(%ebp),%eax ;把%ebp-0x18值送入%eax 0x08048dc3 <+43>: mov (%eax,%edi,4),%eax ;把%eax+%edi*4值指向内存地址(在栈上)送入%eax 0x08048dc6 <+46>: dec %eax ;%eax减一送入%eax 0x08048dc7 <+47>: cmp $0x5,%eax ;计算%eax-0x5,改变标志位 0x08048dca <+50>: jbe 0x8048dd1 <phase_6+57> ;`below or equal`跳转(%eax-0x5<=0跳) 0x08048dcc <+52>: call 0x80494fc <explode_bomb> ;上一步不跳转就炸了 ;第三部分 0x08048dd1 <+57>: lea 0x1(%edi),%ebx ;%edi+1放入%ebx 0x08048dd4 <+60>: cmp $0x5,%ebx ;计算%ebx-0x5,改变标志位 0x08048dd7 <+63>: jg 0x8048dfc <phase_6+100> ;`greater`跳转(%ebx-0x5>0跳) 0x08048dd9 <+65>: lea 0x0(,%edi,4),%eax ;%edi*4值送入%eax 0x08048de0 <+72>: mov %eax,-0x38(%ebp) ;%eax指向的值送入栈上(%ebp-0x38) 0x08048de3 <+75>: lea -0x18(%ebp),%esi ;%ebp-0x18送入%esi 0x08048de6 <+78>: mov -0x38(%ebp),%edx ;%ebp-0x38指向的值送入%edx --Type <RET> for more, q to quit, c to continue without paging--c 0x08048de9 <+81>: mov (%edx,%esi,1),%eax ;%edx+%esi*1指向的值送入%eax 0x08048dec <+84>: cmp (%esi,%ebx,4),%eax ;%eax值-(%esi+%ebx*4)指向的值改变标志位 0x08048def <+87>: jne 0x8048df6 <phase_6+94> ;`not equal`不相等跳转(...!=0跳) 0x08048df1 <+89>: call 0x80494fc <explode_bomb> ;上一步必须跳,不然炸弹炸了 0x08048df6 <+94>: inc %ebx ;`increment`,相当于%ebx++ 0x08048df7 <+95>: cmp $0x5,%ebx ;%ebx-0x5,改变标志位 0x08048dfa <+98>: jle 0x8048de6 <phase_6+78> ;`jump if less or equal`(....<=跳) ;第四部分 0x08048dfc <+100>: inc %edi ;%edi++ 0x08048dfd <+101>: cmp $0x5,%edi ;计算%edi-0x5,改变标志位 0x08048e00 <+104>: jle 0x8048dc0 <phase_6+40> ;%edi-0x5<=0跳转 ;第五部分 0x08048e02 <+106>: xor %edi,%edi ;%edi清零 0x08048e04 <+108>: lea -0x18(%ebp),%ecx ;%ebp-0x18值送入%ecx 0x08048e07 <+111>: lea -0x30(%ebp),%eax ;%ebp-0x30值送入%eax 0x08048e0a <+114>: mov %eax,-0x3c(%ebp) ;%eax的值送入栈上(%ebp-0x3c的地址上) 0x08048e0d <+117>: lea 0x0(%esi),%esi ;空指令 0x08048e10 <+120>: mov -0x34(%ebp),%esi ;%ebp-0x34指向的值送入%esi 0x08048e13 <+123>: mov $0x1,%ebx ;把%ebx置1 0x08048e18 <+128>: lea 0x0(,%edi,4),%eax ;%edi*4的值送入%eax 0x08048e1f <+135>: mov %eax,%edx ;%eax值送入%edx 0x08048e21 <+137>: cmp (%eax,%ecx,1),%ebx ;%ebx-(%eax+%ecx*1)指向值改变标志位 0x08048e24 <+140>: jge 0x8048e38 <phase_6+160> ;(gewater or equal)大于等于跳转 0x08048e26 <+142>: mov (%edx,%ecx,1),%eax ;(%edx+%ecx*1)指向的值赋给%eax 0x08048e29 <+145>: lea 0x0(%esi,%eiz,1),%esi ;空指令,%eiz并不存在 0x08048e30 <+152>: mov 0x8(%esi),%esi ;%esi+0x8指向的值赋给%esi 0x08048e33 <+155>: inc %ebx ;%ebx++ 0x08048e34 <+156>: cmp %eax,%ebx ;%ebx-%eax 0x08048e36 <+158>: jl 0x8048e30 <phase_6+152> ;%ebx-%eax<0跳转 ;第六部分 0x08048e38 <+160>: mov -0x3c(%ebp),%edx ;(%ebp-0x3x)指向的值赋给%edx 0x08048e3b <+163>: mov %esi,(%edx,%edi,4) ;%esi值赋给(%edx+%edi*4) 0x08048e3e <+166>: inc %edi ;%edi++ 0x08048e3f <+167>: cmp $0x5,%edi ;%edi-0x5 0x08048e42 <+170>: jle 0x8048e10 <phase_6+120> ;`less or equal`小于或等于跳转 ;第七部分 0x08048e44 <+172>: mov -0x30(%ebp),%esi ;(%ebp-0x30)指向的值赋给%esi 0x08048e47 <+175>: mov %esi,-0x34(%ebp) ;%esi值放栈上(%ebp-0x34)位置 0x08048e4a <+178>: mov $0x1,%edi ;0x1赋给%edi 0x08048e4f <+183>: lea -0x30(%ebp),%edx ;%ebp-0x30赋给%edx 0x08048e52 <+186>: mov (%edx,%edi,4),%eax ;%edx+%edi*4指向地址的值赋给%eax 0x08048e55 <+189>: mov %eax,0x8(%esi) ;%eax值放在0x8+%esi位置 0x08048e58 <+192>: mov %eax,%esi ;%eax赋给%esi 0x08048e5a <+194>: inc %edi ;%edi++ 0x08048e5b <+195>: cmp $0x5,%edi ;%edi-0x5改变标志位 0x08048e5e <+198>: jle 0x8048e52 <phase_6+186> ;`less or equal`小于等于跳 ;第八部分 0x08048e60 <+200>: movl $0x0,0x8(%esi) ;0放%esi+0x8位置 0x08048e67 <+207>: mov -0x34(%ebp),%esi ;%ebp-0x34位置的值赋给%esi 0x08048e6a <+210>: xor %edi,%edi ;edi置零 0x08048e6c <+212>: lea 0x0(%esi,%eiz,1),%esi ;空指令 0x08048e70 <+216>: mov 0x8(%esi),%edx ;%esi+0x8位置的值赋给%edx 0x08048e73 <+219>: mov (%esi),%eax ;esi指向值赋给%eax 0x08048e75 <+221>: cmp (%edx),%eax ;%eax-%edx值改变标志位 0x08048e77 <+223>: jge 0x8048e7e <phase_6+230> ;`greater or equal`大于等于跳转 0x08048e79 <+225>: call 0x80494fc <explode_bomb> ;不跳就炸了 ;第九部分 0x08048e7e <+230>: mov 0x8(%esi),%esi ;%esi+0x8指向地址的值赋给%esi 0x08048e81 <+233>: inc %edi ;%edi++ 0x08048e82 <+234>: cmp $0x4,%edi ;%edi-0x4值改变标志位 0x08048e85 <+237>: jle 0x8048e70 <phase_6+216> ;`less or equal`小于等于就跳转 0x08048e87 <+239>: lea -0x58(%ebp),%esp ;%ebp-0x58值赋给%esp 0x08048e8a <+242>: pop %ebx ;栈顶放%ebx里 0x08048e8b <+243>: pop %esi ;栈顶放%esi里 0x08048e8c <+244>: pop %edi ;栈顶放%edi里 0x08048e8d <+245>: mov %ebp,%esp ;销毁整个0x58大小的栈帧 0x08048e8f <+247>: pop %ebp ;空指令 0x08048e90 <+248>: ret ;返回到原函数 End of assembler dump.
动态调试 通过静态分析不难得知(read_six_numbers
)输入需要六个数字,不妨
第六行输入
调试时
第一部分 附一个静态的汇编代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 ;第一部分 0x08048d98 <+0>: push %ebp ;基地指针入栈 0x08048d99 <+1>: mov %esp,%ebp ;栈顶栈底指针统一 0x08048d9b <+3>: sub $0x4c,%esp ;开辟栈空间 0x08048d9e <+6>: push %edi ;参数入栈 0x08048d9f <+7>: push %esi ;参数入栈 0x08048da0 <+8>: push %ebx ;参数入栈 0x08048da1 <+9>: mov 0x8(%ebp),%edx ;把%epd加0x8指向内容给%edx 0x08048da4 <+12>: movl $0x804b26c,-0x34(%ebp) ;立即数放栈上 0x08048dab <+19>: add $0xfffffff8,%esp ;由于模运算的性质,多开辟了(f-8)空间 0x08048dae <+22>: lea -0x18(%ebp),%eax ;把%ebp-0x18值送入%eax(栈上地址入寄存器) 0x08048db1 <+25>: push %eax ;参数入栈 0x08048db2 <+26>: push %edx ;参数入栈 0x08048db3 <+27>: call 0x8048fd8 <read_six_numbers> ;函数调用
在read_six_numbers
前寄存器
1 2 3 4 5 6 7 8 9 EAX 0xffffcbf0 —▸ 0xffffccf4 —▸ 0xffffcf20 ◂— '/home/P4yl04d/Documents/lab_bomb/bomb' EBX 0xffffccf4 —▸ 0xffffcf20 ◂— '/home/P4yl04d/Documents/lab_bomb/bomb' ECX 0xfffffff1 EDX 0x804b810 (input_strings+400) ◂— xorl %esp, (%eax) /* 0x20322031; '1 2 3 4 5 6 ' */ EDI 0xf7ffcb80 (_rtld_global_ro) ◂— addb %al, (%eax) ESI 0x80486e0 (_init) ◂— pushl %ebp EBP 0xffffcc08 —▸ 0xffffcc38 ◂— 0x0 *ESP 0xffffcba0 —▸ 0x804b810 (input_strings+400) ◂— xorl %esp, (%eax) /* 0x20322031; '1 2 3 4 5 6 ' */ *EIP 0x8048db3 (phase_6+27) ◂— calll 0x8048fd8
在read_six_numbers
后寄存器
1 2 3 4 5 6 7 8 9 *EAX 0x6 EBX 0xffffccf4 —▸ 0xffffcf20 ◂— '/home/P4yl04d/Documents/lab_bomb/bomb' *ECX 0xffffca90 —▸ 0xffffcaac ◂— 0xfbad8001 *EDX 0x0 EDI 0xf7ffcb80 (_rtld_global_ro) ◂— addb %al, (%eax) ESI 0x80486e0 (_init) ◂— pushl %ebp EBP 0xffffcc08 —▸ 0xffffcc38 ◂— 0x0 ESP 0xffffcba0 —▸ 0x804b810 (input_strings+400) ◂— xorl %esp, (%eax) /* 0x20322031; '1 2 3 4 5 6 ' */ *EIP 0x8048db8 (phase_6+32) ◂— xorl %edi, %edi
在read_six_numbers
前栈帧
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 '这段内容是执行后被删除的内容' 00:0000│ esp 0xffffcba0 —▸ 0x804b810 (input_strings+400) ◂— xorl %esp, (%eax) /* 0x20322031; '1 2 3 4 5 6 ' */ 01:0004│-064 0xffffcba4 —▸ 0xffffcbf0 —▸ 0xffffccf4 —▸ 0xffffcf20 ◂— '/home/P4yl04d/Documents/lab_bomb/bomb' 02:0008│-060 0xffffcba8 ◂— 0x0 03:000c│-05c 0xffffcbac —▸ 0xf7e20e34 (_GLOBAL_OFFSET_TABLE_) ◂— decl %esp /* 'L\r"' */ '这段内容是执行前后不变的内容' 04:0010│-058 0xffffcbb0 —▸ 0xffffccf4 —▸ 0xffffcf20 ◂— '/home/P4yl04d/Documents/lab_bomb/bomb' 05:0014│-054 0xffffcbb4 —▸ 0x80486e0 (_init) ◂— pushl %ebp 06:0018│-050 0xffffcbb8 —▸ 0xf7ffcb80 (_rtld_global_ro) ◂— addb %al, (%eax) 07:001c│-04c 0xffffcbbc —▸ 0xffffccf4 —▸ 0xffffcf20 ◂— '/home/P4yl04d/Documents/lab_bomb/bomb' 08:0020│-048 0xffffcbc0 —▸ 0x80486e0 (_init) ◂— pushl %ebp 09:0024│-044 0xffffcbc4 —▸ 0xf7ffcb80 (_rtld_global_ro) ◂— addb %al, (%eax) 0a:0028│-040 0xffffcbc8 —▸ 0xffffcbf8 —▸ 0xffffcc18 —▸ 0xffffcc38 ◂— 0x0 0b:002c│-03c 0xffffcbcc —▸ 0x80491ea (skip+58) ◂— addl $0x10, %esp 0c:0030│-038 0xffffcbd0 —▸ 0x804b810 (input_strings+400) ◂— xorl %esp, (%eax) /* 0x20322031; '1 2 3 4 5 6 ' */ 0d:0034│-034 0xffffcbd4 —▸ 0x804b26c (node1) ◂— 0xfd 0e:0038│-030 0xffffcbd8 —▸ 0x804c1a0 ◂— 0xfbad2488 0f:003c│-02c 0xffffcbdc —▸ 0xf7c56fd9 (printf+41) ◂— addl $0x1c, %esp 10:0040│-028 0xffffcbe0 —▸ 0xffffccf4 —▸ 0xffffcf20 ◂— '/home/P4yl04d/Documents/lab_bomb/bomb' 11:0044│-024 0xffffcbe4 —▸ 0x80497a0 ◂— incl %edi /* 'Good work! On to the next...\n' */ '这段内容执行后被替换了' 12:0048│-020 0xffffcbe8 —▸ 0xffffcc04 ◂— 0x7374 /* 'ts' */ 13:004c│-01c 0xffffcbec ◂— 0x0 14:0050│ eax 0xffffcbf0 —▸ 0xffffccf4 —▸ 0xffffcf20 ◂— '/home/P4yl04d/Documents/lab_bomb/bomb' 15:0054│-014 0xffffcbf4 —▸ 0x80486e0 (_init) ◂— pushl %ebp 16:0058│-010 0xffffcbf8 —▸ 0xffffcc18 —▸ 0xffffcc38 ◂— 0x0 17:005c│-00c 0xffffcbfc —▸ 0x8049208 (read_line+12) ◂— testl %eax, %eax 18:0060│-008 0xffffcc00 —▸ 0xf7ffcb80 (_rtld_global_ro) ◂— addb %al, (%eax) 19:0064│-004 0xffffcc04 ◂— 0x7374 /* 'ts' */ 1a:0068│ ebp 0xffffcc08 —▸ 0xffffcc38 ◂— 0x0
在read_six_numbers
后栈帧
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 '这段内容被删除' '这段内容是执行前后不变的内容' 00:0000│ esp 0xffffcbb0 —▸ 0xffffccf4 —▸ 0xffffcf20 ◂— '/home/P4yl04d/Documents/lab_bomb/bomb' 01:0004│-054 0xffffcbb4 —▸ 0x80486e0 (_init) ◂— pushl %ebp 02:0008│-050 0xffffcbb8 —▸ 0xf7ffcb80 (_rtld_global_ro) ◂— addb %al, (%eax) 03:000c│-04c 0xffffcbbc —▸ 0xffffccf4 —▸ 0xffffcf20 ◂— '/home/P4yl04d/Documents/lab_bomb/bomb' 04:0010│-048 0xffffcbc0 —▸ 0x80486e0 (_init) ◂— pushl %ebp 05:0014│-044 0xffffcbc4 —▸ 0xf7ffcb80 (_rtld_global_ro) ◂— addb %al, (%eax) 06:0018│-040 0xffffcbc8 —▸ 0xffffcbf8 ◂— 0x3 07:001c│-03c 0xffffcbcc —▸ 0x80491ea (skip+58) ◂— addl $0x10, %esp 08:0020│-038 0xffffcbd0 —▸ 0x804b810 (input_strings+400) ◂— xorl %esp, (%eax) /* 0x20322031; '1 2 3 4 5 6 ' */ 09:0024│-034 0xffffcbd4 —▸ 0x804b26c (node1) ◂— 0xfd 0a:0028│-030 0xffffcbd8 —▸ 0x804c1a0 ◂— 0xfbad2488 0b:002c│-02c 0xffffcbdc —▸ 0xf7c56fd9 (printf+41) ◂— addl $0x1c, %esp 0c:0030│-028 0xffffcbe0 —▸ 0xffffccf4 —▸ 0xffffcf20 ◂— '/home/P4yl04d/Documents/lab_bomb/bomb' 0d:0034│-024 0xffffcbe4 —▸ 0x80497a0 ◂— incl %edi /* 'Good work! On to the next...\n' */ '这段内容是执行后被替换的内容' 0e:0038│-020 0xffffcbe8 —▸ 0xffffcc04 ◂— 0x6 0f:003c│-01c 0xffffcbec ◂— 0x0 10:0040│-018 0xffffcbf0 ◂— 0x1 11:0044│-014 0xffffcbf4 ◂— 0x2 12:0048│-010 0xffffcbf8 ◂— 0x3 13:004c│-00c 0xffffcbfc ◂— 0x4 14:0050│-008 0xffffcc00 ◂— 0x5 15:0054│-004 0xffffcc04 ◂— 0x6 16:0058│ ebp 0xffffcc08 —▸ 0xffffcc38 ◂— 0x0
第二部分* 附一个静态分析得到的汇编代码
1 2 3 4 5 6 7 8 9 10 ;第二部分 0x08048db8 <+32>: xor %edi,%edi ;edi归零 0x08048dba <+34>: add $0x10,%esp ;栈空间减少 0x08048dbd <+37>: lea 0x0(%esi),%esi ;空指令 0x08048dc0 <+40>: lea -0x18(%ebp),%eax ;把%ebp-0x18值送入%eax 0x08048dc3 <+43>: mov (%eax,%edi,4),%eax ;把%eax+%edi*4值指向内存地址(在栈上)送入%eax 0x08048dc6 <+46>: dec %eax ;%eax减一送入%eax 0x08048dc7 <+47>: cmp $0x5,%eax ;计算%eax-0x5,改变标志位 0x08048dca <+50>: jbe 0x8048dd1 <phase_6+57> ;`below or equal`跳转(%eax-0x5<=0跳) 0x08048dcc <+52>: call 0x80494fc <explode_bomb> ;上一步不跳转就炸了
比较关键的是走到<+40>
位置,执行以后可以看到栈空间如下所示
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 00:0000│ esp 0xffffcbb0 —▸ 0xffffccf4 —▸ 0xffffcf20 ◂— '/home/P4yl04d/Documents/lab_bomb/bomb' 01:0004│-054 0xffffcbb4 —▸ 0x80486e0 (_init) ◂— pushl %ebp 02:0008│-050 0xffffcbb8 —▸ 0xf7ffcb80 (_rtld_global_ro) ◂— addb %al, (%eax) 03:000c│-04c 0xffffcbbc —▸ 0xffffccf4 —▸ 0xffffcf20 ◂— '/home/P4yl04d/Documents/lab_bomb/bomb' 04:0010│-048 0xffffcbc0 —▸ 0x80486e0 (_init) ◂— pushl %ebp 05:0014│-044 0xffffcbc4 —▸ 0xf7ffcb80 (_rtld_global_ro) ◂— addb %al, (%eax) 06:0018│-040 0xffffcbc8 —▸ 0xffffcbf8 ◂— 0x3 07:001c│-03c 0xffffcbcc —▸ 0x80491ea (skip+58) ◂— addl $0x10, %esp 08:0020│-038 0xffffcbd0 —▸ 0x804b810 (input_strings+400) ◂— xorl %esp, (%eax) /* 0x20322031; '1 2 3 4 5 6 ' */ 09:0024│-034 0xffffcbd4 —▸ 0x804b26c (node1) ◂— 0xfd 0a:0028│-030 0xffffcbd8 —▸ 0x804c1a0 ◂— 0xfbad2488 0b:002c│-02c 0xffffcbdc —▸ 0xf7c56fd9 (printf+41) ◂— addl $0x1c, %esp 0c:0030│-028 0xffffcbe0 —▸ 0xffffccf4 —▸ 0xffffcf20 ◂— '/home/P4yl04d/Documents/lab_bomb/bomb' 0d:0034│-024 0xffffcbe4 —▸ 0x80497a0 ◂— incl %edi /* 'Good work! On to the next...\n' */ 0e:0038│-020 0xffffcbe8 —▸ 0xffffcc04 ◂— 0x6 0f:003c│-01c 0xffffcbec ◂— 0x0 10:0040│ eax 0xffffcbf0 ◂— 0x1 /* '这里eax存的值为0xffffcbf0,也就是第一个参数的地址' */ 11:0044│-014 0xffffcbf4 ◂— 0x2 12:0048│-010 0xffffcbf8 ◂— 0x3 13:004c│-00c 0xffffcbfc ◂— 0x4 14:0050│-008 0xffffcc00 ◂— 0x5 15:0054│-004 0xffffcc04 ◂— 0x6 16:0058│ ebp 0xffffcc08 —▸ 0xffffcc38 ◂— 0x0
走到<+43>
位置,将数值取出放eax
中,因为edi = 0
,所以执行后,eax = 1
,通过下图也容易知道,猜想成立。
1 2 3 4 5 6 7 8 9 *EAX 0x1 EBX 0xffffccf4 —▸ 0xffffcf20 ◂— '/home/P4yl04d/Documents/lab_bomb/bomb' ECX 0xffffca90 —▸ 0xffffcaac ◂— 0xfbad8001 EDX 0x0 EDI 0x0 ESI 0x80486e0 (_init) ◂— pushl %ebp EBP 0xffffcc08 —▸ 0xffffcc38 ◂— 0x0 ESP 0xffffcbb0 —▸ 0xffffccf4 —▸ 0xffffcf20 ◂— '/home/P4yl04d/Documents/lab_bomb/bomb' *EIP 0x8048dc6 (phase_6+46) ◂— decl %eax
后面逻辑比较简单
1 2 3 4 0x08048dc6 <+46>: dec %eax ;%eax减一送入%eax 0x08048dc7 <+47>: cmp $0x5,%eax ;计算%eax-0x5,改变标志位 0x08048dca <+50>: jbe 0x8048dd1 <phase_6+57> ;`below or equal`跳转(%eax-0x5<=0跳) 0x08048dcc <+52>: call 0x80494fc <explode_bomb> ;上一步不跳转就炸了
根据以上推理,也就是要满足:第一个参数-1-5<=0
。也就是第一个参数要小于6。
第三部分* 老样子,附一个静态分析的汇编代码
1 2 3 0x08048dd1 <+57>: lea 0x1(%edi),%ebx ;%edi+1放入%ebx 0x08048dd4 <+60>: cmp $0x5,%ebx ;计算%ebx-0x5,改变标志位 0x08048dd7 <+63>: jg 0x8048dfc <phase_6+100> ;`greater`跳转(%ebx-0x5>0跳)
由于前面<+32>
代码
1 0x08048db8 <+32>: xor %edi,%edi ;edi归零
所以这段其实是必不会跳转的,如果跳转的话,跳转第四部分。
1 2 3 4 0x08048dd9 <+65>: lea 0x0(,%edi,4),%eax ;%edi*4值送入%eax 0x08048de0 <+72>: mov %eax,-0x38(%ebp) ;%eax指向的值送入栈上(%ebp-0x38) 0x08048de3 <+75>: lea -0x18(%ebp),%esi ;%ebp-0x18送入%esi 0x08048de6 <+78>: mov -0x38(%ebp),%edx ;%ebp-0x38指向的值送入%edx
执行前栈帧
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 00:0000│ esp 0xffffcbb0 —▸ 0xffffccf4 —▸ 0xffffcf20 ◂— '/home/P4yl04d/Documents/lab_bomb/bomb' 01:0004│-054 0xffffcbb4 —▸ 0x80486e0 (_init) ◂— pushl %ebp 02:0008│-050 0xffffcbb8 —▸ 0xf7ffcb80 (_rtld_global_ro) ◂— addb %al, (%eax) 03:000c│-04c 0xffffcbbc —▸ 0xffffccf4 —▸ 0xffffcf20 ◂— '/home/P4yl04d/Documents/lab_bomb/bomb' 04:0010│-048 0xffffcbc0 —▸ 0x80486e0 (_init) ◂— pushl %ebp 05:0014│-044 0xffffcbc4 —▸ 0xf7ffcb80 (_rtld_global_ro) ◂— addb %al, (%eax) 06:0018│-040 0xffffcbc8 —▸ 0xffffcbf8 ◂— 0x3 07:001c│-03c 0xffffcbcc —▸ 0x80491ea (skip+58) ◂— addl $0x10, %esp 08:0020│-038 0xffffcbd0 —▸ 0x804b810 (input_strings+400) ◂— xorl %esp, (%eax) /* 0x20322031; '1 2 3 4 5 6 ' */ 09:0024│-034 0xffffcbd4 —▸ 0x804b26c (node1) ◂— 0xfd 0a:0028│-030 0xffffcbd8 —▸ 0x804c1a0 ◂— 0xfbad2488 0b:002c│-02c 0xffffcbdc —▸ 0xf7c56fd9 (printf+41) ◂— addl $0x1c, %esp 0c:0030│-028 0xffffcbe0 —▸ 0xffffccf4 —▸ 0xffffcf20 ◂— '/home/P4yl04d/Documents/lab_bomb/bomb' 0d:0034│-024 0xffffcbe4 —▸ 0x80497a0 ◂— incl %edi /* 'Good work! On to the next...\n' */ 0e:0038│-020 0xffffcbe8 —▸ 0xffffcc04 ◂— 0x6 0f:003c│-01c 0xffffcbec ◂— 0x0 10:0040│-018 0xffffcbf0 ◂— 0x1 11:0044│-014 0xffffcbf4 ◂— 0x2 12:0048│-010 0xffffcbf8 ◂— 0x3 13:004c│-00c 0xffffcbfc ◂— 0x4 14:0050│-008 0xffffcc00 ◂— 0x5 15:0054│-004 0xffffcc04 ◂— 0x6 16:0058│ ebp 0xffffcc08 —▸ 0xffffcc38 ◂— 0x0
执行后栈帧
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 00:0000│ esp 0xffffcbb0 —▸ 0xffffccf4 —▸ 0xffffcf20 ◂— '/home/P4yl04d/Documents/lab_bomb/bomb' 01:0004│-054 0xffffcbb4 —▸ 0x80486e0 (_init) ◂— pushl %ebp 02:0008│-050 0xffffcbb8 —▸ 0xf7ffcb80 (_rtld_global_ro) ◂— addb %al, (%eax) 03:000c│-04c 0xffffcbbc —▸ 0xffffccf4 —▸ 0xffffcf20 ◂— '/home/P4yl04d/Documents/lab_bomb/bomb' 04:0010│-048 0xffffcbc0 —▸ 0x80486e0 (_init) ◂— pushl %ebp 05:0014│-044 0xffffcbc4 —▸ 0xf7ffcb80 (_rtld_global_ro) ◂— addb %al, (%eax) 06:0018│-040 0xffffcbc8 —▸ 0xffffcbf8 ◂— 0x3 07:001c│-03c 0xffffcbcc —▸ 0x80491ea (skip+58) ◂— addl $0x10, %esp 08:0020│-038 0xffffcbd0 ◂— 0x0 09:0024│-034 0xffffcbd4 —▸ 0x804b26c (node1) ◂— 0xfd 0a:0028│-030 0xffffcbd8 —▸ 0x804c1a0 ◂— 0xfbad2488 0b:002c│-02c 0xffffcbdc —▸ 0xf7c56fd9 (printf+41) ◂— addl $0x1c, %esp 0c:0030│-028 0xffffcbe0 —▸ 0xffffccf4 —▸ 0xffffcf20 ◂— '/home/P4yl04d/Documents/lab_bomb/bomb' 0d:0034│-024 0xffffcbe4 —▸ 0x80497a0 ◂— incl %edi /* 'Good work! On to the next...\n' */ 0e:0038│-020 0xffffcbe8 —▸ 0xffffcc04 ◂— 0x6 0f:003c│-01c 0xffffcbec ◂— 0x0 10:0040│ esi 0xffffcbf0 ◂— 0x1 11:0044│-014 0xffffcbf4 ◂— 0x2 12:0048│-010 0xffffcbf8 ◂— 0x3 13:004c│-00c 0xffffcbfc ◂— 0x4 14:0050│-008 0xffffcc00 ◂— 0x5 15:0054│-004 0xffffcc04 ◂— 0x6 16:0058│ ebp 0xffffcc08 —▸ 0xffffcc38 ◂— 0x0
继续向下执行
1 2 3 4 5 6 7 8 0x08048de9 <+81>: mov (%edx,%esi,1),%eax ;%edx+%esi*1指向的值送入%eax 0x08048dec <+84>: cmp (%esi,%ebx,4),%eax ;%eax值-(%esi+%ebx*4)指向的值改变标志位 0x08048def <+87>: jne 0x8048df6 <phase_6+94> ;`not equal`不相等跳转(...!=0跳) 0x08048df1 <+89>: call 0x80494fc <explode_bomb> ;上一步必须跳,不然炸弹炸了 0x08048df6 <+94>: inc %ebx ;`increment`,相当于%ebx++ 0x08048df7 <+95>: cmp $0x5,%ebx ;%ebx-0x5,改变标志位 0x08048dfa <+98>: jle 0x8048de6 <phase_6+78> ;`jump if less or equal`(....<=跳)
执行前寄存器
1 2 3 4 5 6 7 8 9 EAX 0x0 EBX 0x1 ECX 0xffffca90 —▸ 0xffffcaac ◂— 0xfbad8001 EDX 0x0 EDI 0x0 ESI 0xffffcbf0 ◂— 0x1 EBP 0xffffcc08 —▸ 0xffffcc38 ◂— 0x0 ESP 0xffffcbb0 —▸ 0xffffccf4 —▸ 0xffffcf20 ◂— '/home/P4yl04d/Documents/lab_bomb/bomb' *EIP 0x8048de9 (phase_6+81 ) ◂— movl (%edx, %esi), %eax
执行后寄存器
1 2 3 4 5 6 7 8 9 EAX 0x1 EBX 0x1 ECX 0xffffca90 —▸ 0xffffcaac ◂— 0xfbad8001 EDX 0x0 EDI 0x0 ESI 0xffffcbf0 ◂— 0x1 EBP 0xffffcc08 —▸ 0xffffcc38 ◂— 0x0 ESP 0xffffcbb0 —▸ 0xffffccf4 —▸ 0xffffcf20 ◂— '/home/P4yl04d/Documents/lab_bomb/bomb' *EIP 0x8048df6 (phase_6+94 ) ◂— incl %ebx
这里容易知道:实际上这一步是必须要跳转的,不然炸弹必炸,跳转条件是第一个参数和第二个参数不能相等,跳转以后执行
1 2 3 0x08048df6 <+94>: inc %ebx ;`increment`,相当于%ebx++ 0x08048df7 <+95>: cmp $0x5,%ebx ;%ebx-0x5,改变标志位 0x08048dfa <+98>: jle 0x8048de6 <phase_6+78> ;`jump if less or equal`(....<=跳)
1 2 3 4 for (int %ebx = 1 ;%ebx <= 5 ;%ebx++){ jmp <phase_6+78 > }
这里得到了炸弹不会爆炸的第二个条件:其余参数不能和第一个参数相等
第四部分* 这段代码如下,其实很简单,就是对%edi++
,然后将%edi
和5
比较,小于等于就跳转到第五部分
1 2 3 0x08048dfc <+100>: inc %edi ;%edi++ 0x08048dfd <+101>: cmp $0x5,%edi ;计算%edi-0x5,改变标志位 0x08048e00 <+104>: jle 0x8048dc0 <phase_6+40> ;%edi-0x5<=0跳转
1 2 3 4 for (int i = 0 ;i <= 5 ;i++){ jmp <phase_6+40 >; }
这一部分通过调试,又回到了最开始判断%eax-1
是否小于等于5的地方。但此时的%eax
变成了参数二,根据循环6次的条件,推断出炸弹不爆炸的第三个条件是:第一个条件和第二个条件在所有参数上均成立。
第五部分 当%eax
为第六个参数时,在重新执行第三个部分时,由于<phase_6+57>
处
1 2 3 0x8048dd1 <phase_6+57> leal 1(%edi), %ebx 0x8048dd4 <phase_6+60> cmpl $5, %ebx ► 0x8048dd7 <phase_6+63> ✔ jg phase_6+100 <phase_6+100>
这里由于ebx
的值大于5,,所以发生了跳转,直接跳到了phase_6+100
的位置,不过也好理解,毕竟如果执行到最后一个参数,它必然不可能和其他参数相等,所有无需比较,直接跳转即可。来看看phase_6+100
处汇编
1 2 3 0x08048dfc <+100>: inc %edi ;%edi++ 0x08048dfd <+101>: cmp $0x5,%edi ;计算%edi-0x5,改变标志位 0x08048e00 <+104>: jle 0x8048dc0 <phase_6+40> ;%edi-0x5<=0跳转
由于%edi
在inc
后为6,进行跳转,直接向下执行
1 2 3 4 5 6 7 8 9 10 11 0x08048e02 <+106>: xor %edi,%edi ;%edi清零 0x08048e04 <+108>: lea -0x18(%ebp),%ecx ;%ebp-0x18值送入%ecx 0x08048e07 <+111>: lea -0x30(%ebp),%eax ;%ebp-0x30值送入%eax 0x08048e0a <+114>: mov %eax,-0x3c(%ebp) ;%eax的值送入栈上(%ebp-0x3c的地址上) 0x08048e0d <+117>: lea 0x0(%esi),%esi ;空指令 0x08048e10 <+120>: mov -0x34(%ebp),%esi ;%ebp-0x34指向的值送入%esi 0x08048e13 <+123>: mov $0x1,%ebx ;把%ebx置1 0x08048e18 <+128>: lea 0x0(,%edi,4),%eax ;%edi*4的值送入%eax 0x08048e1f <+135>: mov %eax,%edx ;%eax值送入%edx 0x08048e21 <+137>: cmp (%eax,%ecx,1),%ebx ;%ebx-(%eax+%ecx*1)指向值改变标志位 0x08048e24 <+140>: jge 0x8048e38 <phase_6+160> ;(gewater or equal)大于等于跳转
动态调试,容易知道:如果1大于等于参数1,跳<phase_6+160>
,我们这里参数一为1
,显然符合条件跳<phase_6+160>
第六部分 这里定义跳转到的<phase_6+160>
为第六部分
1 2 3 4 5 0x08048e38 <+160>: mov -0x3c(%ebp),%edx ;(%ebp-0x3x)指向的值赋给%edx 0x08048e3b <+163>: mov %esi,(%edx,%edi,4) ;%esi值赋给(%edx+%edi*4) 0x08048e3e <+166>: inc %edi ;%edi++ 0x08048e3f <+167>: cmp $0x5,%edi ;%edi-0x5 0x08048e42 <+170>: jle 0x8048e10 <phase_6+120> ;`less or equal`小于或等于跳转
这里给%edi++
,然后和0x5
比较,显然直接跳<phase_6+120>
,相当于回到第五部分,执行第二个参数和1
比较,这里显然1
并不大于参数二,因此不执行跳转,继续执行,即下面这段会继续执行
1 2 3 4 5 0x8048e13 <phase_6+123> movl $1, %ebx 0x8048e18 <phase_6+128> leal (, %edi, 4), %eax 0x8048e1f <phase_6+135> movl %eax, %edx 0x8048e21 <phase_6+137> cmpl (%eax, %ecx), %ebx 0x8048e24 <phase_6+140> jge phase_6+160
下面这块属实没啥意义(%ebx
每次循环都要++
,总有不满足跳转条件的一次),不满足跳转条件直接回到<phase_6+160>
了
1 2 3 4 5 6 0x08048e26 <+142>: mov (%edx,%ecx,1),%eax ;(%edx+%ecx*1)指向的值赋给%eax 0x08048e29 <+145>: lea 0x0(%esi,%eiz,1),%esi ;空指令,%eiz并不存在 0x08048e30 <+152>: mov 0x8(%esi),%esi ;%esi+0x8指向的值赋给%esi 0x08048e33 <+155>: inc %ebx ;%ebx++ 0x08048e34 <+156>: cmp %eax,%ebx ;%ebx-%eax 0x08048e36 <+158>: jl 0x8048e30 <phase_6+152> ;%ebx-%eax<0跳转
联系上方的分析,可知在执行循环,先记录下这几个node
,不知道有啥用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 pwndbg> p $node1 $1 = void pwndbg> p *(int *)node1 'Cannot access memory at address 0xfd' pwndbg> p (int)node1 $2 = 253 pwndbg> p (int)node2 $3 = 725 pwndbg> p (int)node3 $4 = 301 pwndbg> p (int)node4 $5 = 997 pwndbg> p (int)node5 $6 = 212 pwndbg> p (int)node6 $7 = 432 pwndbg> p (int)node7 No symbol "node7" in current context.
执行完这个循环以后的栈结构如图
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 pwndbg> stack 23 00 :0000 │ esp 0xffffcbb0 —▸ 0xffffccf4 —▸ 0xffffcf21 ◂— '/home/P4yl04d/Documents/lab_bomb/bomb' 01 :0004 │-054 0xffffcbb4 —▸ 0x80486e0 (_init) ◂— pushl %ebp02 :0008 │-050 0xffffcbb8 —▸ 0xf7ffcb80 (_rtld_global_ro) ◂— addb %al, (%eax)03 :000 c│-04 c 0xffffcbbc —▸ 0xffffccf4 —▸ 0xffffcf21 ◂— '/home/P4yl04d/Documents/lab_bomb/bomb' 04 :0010 │-048 0xffffcbc0 —▸ 0x80486e0 (_init) ◂— pushl %ebp05 :0014 │-044 0xffffcbc4 —▸ 0xf7ffcb80 (_rtld_global_ro) ◂— addb %al, (%eax)06 :0018 │-040 0xffffcbc8 —▸ 0xffffcbf8 ◂— 0x3 07 :001 c│-03 c 0xffffcbcc —▸ 0xffffcbd8 —▸ 0x804b26c (node1) ◂— 0xfd 08 :0020 │-038 0xffffcbd0 ◂— 0x10 09 :0024 │-034 0xffffcbd4 —▸ 0x804b26c (node1) ◂— 0xfd 0 a:0028 │ edx 0xffffcbd8 —▸ 0x804b26c (node1) ◂— 0xfd 0b :002 c│-02 c 0xffffcbdc —▸ 0x804b260 (node2) ◂— 0x2d5 0 c:0030 │-028 0xffffcbe0 —▸ 0x804b254 (node3) ◂— 0x12d 0 d:0034 │-024 0xffffcbe4 —▸ 0x804b248 (node4) ◂— 0x3e5 0 e:0038 │-020 0xffffcbe8 —▸ 0x804b23c (node5) ◂— 0xd4 0f :003 c│-01 c 0xffffcbec —▸ 0x804b230 (node6) ◂— 0x1b0 10 :0040 │ ecx 0xffffcbf0 ◂— 0x1 11 :0044 │-014 0xffffcbf4 ◂— 0x2 12 :0048 │-010 0xffffcbf8 ◂— 0x3 13 :004 c│-00 c 0xffffcbfc ◂— 0x4 14 :0050 │-008 0xffffcc00 ◂— 0x5 15 :0054 │-004 0xffffcc04 ◂— 0x6 16 :0058 │ ebp 0xffffcc08 —▸ 0xffffcc38 ◂— 0x0
第七部分 继续向下执行
1 2 3 4 5 6 7 8 9 10 0x08048e44 <+172>: mov -0x30(%ebp),%esi ;(%ebp-0x30)指向的值赋给%esi 0x08048e47 <+175>: mov %esi,-0x34(%ebp) ;%esi值放栈上(%ebp-0x34)位置 0x08048e4a <+178>: mov $0x1,%edi ;0x1赋给%edi 0x08048e4f <+183>: lea -0x30(%ebp),%edx ;%ebp-0x30赋给%edx 0x08048e52 <+186>: mov (%edx,%edi,4),%eax ;%edx+%edi*4指向地址的值赋给%eax 0x08048e55 <+189>: mov %eax,0x8(%esi) ;%eax值放在0x8+%esi位置 0x08048e58 <+192>: mov %eax,%esi ;%eax赋给%esi 0x08048e5a <+194>: inc %edi ;%edi++ 0x08048e5b <+195>: cmp $0x5,%edi ;%edi-0x5改变标志位 0x08048e5e <+198>: jle 0x8048e52 <phase_6+186> ;`less or equal`小于等于跳
循环5次,循环后的栈结构不变,寄存器数值如下
1 2 3 4 5 6 7 8 9 EAX 0x804b230 (node6) ◂— 0x1b0 EBX 0x6 ECX 0xffffcbf0 ◂— 0x1 EDX 0xffffcbd8 —▸ 0x804b26c (node1) ◂— 0xfd EDI 0x6 ESI 0x804b230 (node6) ◂— 0x1b0 EBP 0xffffcc08 —▸ 0xffffcc38 ◂— 0x0 ESP 0xffffcbb0 —▸ 0xffffccf4 —▸ 0xffffcf21 ◂— '/home/P4yl04d/Documents/lab_bomb/bomb' *EIP 0x8048e5e (phase_6+198 ) ◂— jle 0x8048e52
第八部分* 1 2 3 4 5 6 7 8 9 0x08048e60 <+200>: movl $0x0,0x8(%esi) ;0放%esi+0x8位置 0x08048e67 <+207>: mov -0x34(%ebp),%esi ;%ebp-0x34位置的值赋给%esi 0x08048e6a <+210>: xor %edi,%edi ;edi置零 0x08048e6c <+212>: lea 0x0(%esi,%eiz,1),%esi ;空指令 0x08048e70 <+216>: mov 0x8(%esi),%edx ;%esi+0x8位置的值赋给%edx 0x08048e73 <+219>: mov (%esi),%eax ;esi指向值赋给%eax 0x08048e75 <+221>: cmp (%edx),%eax ;%eax-%edx值改变标志位 0x08048e77 <+223>: jge 0x8048e7e <phase_6+230> ;`greater or equal`大于等于跳转 0x08048e79 <+225>: call 0x80494fc <explode_bomb> ;不跳就炸了
保证node1
大于等于node2
,不然就炸了。这里不符合条件,强行改变寄存器%edx
数值,先往下走着
这里得到了要满足的第四个条件:<node2>
大于等于node3
。(貌似<node1>
是传入的立即数0xfd
,不确定是否可以改变<node2>
的值,或者改变%edx
为其他节点上的数)
第九部分* 这里的话,类似前面,相当于一个循环
1 2 3 4 0x08048e7e <+230>: mov 0x8(%esi),%esi ;%esi+0x8指向地址的值赋给%esi 0x08048e81 <+233>: inc %edi ;%edi++ 0x08048e82 <+234>: cmp $0x4,%edi ;%edi-0x4值改变标志位 0x08048e85 <+237>: jle 0x8048e70 <phase_6+216> ;`less or equal`小于等于就跳转
动态调试下来,发现这次要满足的条件是<node2>
大于等于<node3>
。
容易知道:该循环循五次,综合下来需要满足的条件就是
node1
> = node2
> = node3
> = node4
> = node5
> = node6
猜测输入数字顺序对应节点顺序,于是,根据上面已知的排序
node4
> node2
> node6
> node3
> node1
> node5
尝试输入4 2 6 3 1 5
,运行发现结果正确。