bomb lab 第六关详细分析
由于第六关的汇编代码太长且复杂,需要非常耐心地进行分析,故将整个汇编代码分为几个部分详细说明。
一、Part1
00000000004010f4 <phase_6>:
// arg1=input(input是从外部传入的字符串)
4010f4: 41 56 push %r14
4010f6: 41 55 push %r13
4010f8: 41 54 push %r12
4010fa: 55 push %rbp
4010fb: 53 push %rbx
4010fc: 48 83 ec 50 sub $0x50,%rsp
401100: 49 89 e5 mov %rsp,%r13
401103: 48 89 e6 mov %rsp,%rsi
401106: e8 51 03 00 00 callq 40145c <read_six_numbers>
// read_six_numbers
int read_six_numbers(input,a)
{//input是phase_6的参数1,a是在栈中分配的一个int*数组
return (sscanf(input,"%d %d %d %d %d %d",&a[0],&a[1],&a[2],&a[3],&a[4],&a[5]));
}
//
40110b: 49 89 e6 mov %rsp,%r14
40110e: 41 bc 00 00 00 00 mov $0x0,%r12d
>>>>>>>>>>>>>>>>>>>>>>>>>>> 循环开始<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
// 令 r12d<=>i , rbx<=>j (后面注释的i和j代表这两个寄存器的值)
401114: 4c 89 ed mov %r13,%rbp
401117: 41 8b 45 00 mov 0x0(%r13),%eax # eax=a[i]
40111b: 83 e8 01 sub $0x1,%eax # eax -= 1
40111e: 83 f8 05 cmp $0x5,%eax
401121: 76 05 jbe 401128 <phase_6+0x34>
// if (eax>=0 && eax<=5) -> jmp else -> bomb (所以eax必须在区间[0,5])
401123: e8 12 03 00 00 callq 40143a <explode_bomb>
401128: 41 83 c4 01 add $0x1,%r12d # i++
// r12d一开始为0,这边加1,感觉会是个用于循环的计数器
40112c: 41 83 fc 06 cmp $0x6,%r12d
401130: 74 21 je 401153 <phase_6+0x5f> # 等于6时跳出循环
401132: 44 89 e3 mov %r12d,%ebx # 否则继续,j初始化=i+1
>>>>>>>>>>>>>>>>>>>>>>>>>>> 内循环开始<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
401135: 48 63 c3 movslq %ebx,%rax
401138: 8b 04 84 mov (%rsp,%rax,4),%eax # 取下一个数到%eax
40113b: 39 45 00 cmp %eax,0x0(%rbp) #该数不能与a[i]相同,否则爆炸
40113e: 75 05 jne 401145 <phase_6+0x51>
401140: e8 f5 02 00 00 callq 40143a <explode_bomb>
401145: 83 c3 01 add $0x1,%ebx # ebx+=1
401148: 83 fb 05 cmp $0x5,%ebx
40114b: 7e e8 jle 401135 <phase_6+0x41>
// ebx开始时为i,if (ebx<=5) -> 跳出循环
>>>>>>>>>>>>>>>>>>>>>>>>>>> 内循环结束<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
40114d: 49 83 c5 04 add $0x4,%r13
401151: eb c1 jmp 401114 <phase_6+0x20>
>>>>>>>>>>>>>>>>>>>>>>>>>>> 循环结束<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
// 这后面还一大段代码,暂作省略,在后面部分再进行分析
上面是一个多重循环,详细注释已在上写出,分析清楚逻辑不难写成C代码:
for (int i=0; i<6; i++){
if (a[i]-1>5 || a[i]-1<0) bomb();
for (int j=i+1;j<=5;j++){
if (a[j] == a[i]) bomb();
}
}
这段代码逻辑很清晰,就是保证6个数都处于区间[1,6]中且不能重复,下面进入代码分析的第二部分。
二、Part2
401153: 48 8d 74 24 18 lea 0x18(%rsp),%rsi # 边界&a[6]
401158: 4c 89 f0 mov %r14,%rax # rax=&a[0]
40115b: b9 07 00 00 00 mov $0x7,%ecx
>>>>>>>>>>>>>>>>>>>>>>>>>>> 循环开始<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
401160: 89 ca mov %ecx,%edx
401162: 2b 10 sub (%rax),%edx # edx =7-a[0]
401164: 89 10 mov %edx,(%rax) # a[0]=7-a[0]
401166: 48 83 c0 04 add $0x4,%rax # rax指向下一个数
40116a: 48 39 f0 cmp %rsi,%rax
40116d: 75 f1 jne 401160 <phase_6+0x6c>
上面这个循环很清楚是将a[i]线性变换为7-a[i].
>>>>>>>>>>>>>>>>>>>>>>>>>>> 循环结束<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
这部分代码很简单,就是将a[i]变换为7-a[i],写成代码就是
for (int i=0;i<6;i++)
a[i] = 7-a[i];
继续进入第三部分。
三、Part3
40116f: be 00 00 00 00 mov $0x0,%esi
// esi = 0 感觉又是一个计数器 令:esi<=>i
401174: eb 21 jmp 401197 <phase_6+0xa3>
>>>>>>>>>>>>>>>>>>>>>>>>>>> 循环开始<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
#############小循环1开始:
401176: 48 8b 52 08 mov 0x8(%rdx),%rdx
// rdx=mem[rdx+0x8],这种写法很奇怪,猜想rdx=是一个结构体,且0x8偏移为该结构体指针
40117a: 83 c0 01 add $0x1,%eax # i++
40117d: 39 c8 cmp %ecx,%eax
// ecx=7,eax一开始=&a[6]
40117f: 75 f5 jne 401176 <phase_6+0x82>
#############小循环1结束。
401181: eb 05 jmp 401188 <phase_6+0x94>
#############小循环2开始:
401183: ba d0 32 60 00 mov $0x6032d0,%edx
401188: 48 89 54 74 20 mov %rdx,0x20(%rsp,%rsi,2)
// mem(a+0x20+2i)=0x4032d0
40118d: 48 83 c6 04 add $0x4,%rsi # 另外一个循环
401191: 48 83 fe 18 cmp $0x18,%rsi # 保证每个a[i]都运行过
401195: 74 14 je 4011ab <phase_6+0xb7> # 退出循环
401197: 8b 0c 34 mov (%rsp,%rsi,1),%ecx # 指针偏移,依次获取6个数
40119a: 83 f9 01 cmp $0x1,%ecx
40119d: 7e e4 jle 401183 <phase_6+0x8f> #<=1则跳回开头
#############小循环2结束。
40119f: b8 01 00 00 00 mov $0x1,%eax
4011a4: ba d0 32 60 00 mov $0x6032d0,%edx
4011a9: eb cb jmp 401176 <phase_6+0x82>
>>>>>>>>>>>>>>>>>>>>>>>>>>> 循环结束<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
这部分内容有点复杂且不知所云,那么先用gdb看看里面的 0x6032d0是个啥东西。这个名字叫node1,再结合上面的mov 0x8(%rdx),%rdx ,猜测这是一个结构体且8字节的位置是这个结构体的指针,我们尝试打印更多信息。
从上面的信息可以看出,该0~3字节是一个不知道是什么的数据,4~7字节是node的编号,8~15字节是应该是一个指针,0x6032d0结构体中的指针为0x006032e0,这个指针指向node2,同样node2中的指针指向node3 …最后一个node6中的指针指向地址0。该结构体是一个链表啊!
可以大致确定这个结构体为:
struct node{
int val;//这个暂不知道是什么数据
int id;//id是编号
node *next; //next是指向的node的地址
}
再看这段的汇编代码,将第三部分忠实地逆向得:
while(1){
cx=*(sp+si*1); // cx=sp+0,sp+0x4,sp+0x8,sp+0xc,....
if(cx<=1) dx=0x6032d0; //dx=表头地址
else {
ax=1;
dx=0x6032d0;
//遍历链表,使得dx=第sp[si/4]项的地址
do{
dx=*(dx+0x8); //dx=(dx.next)->val
ax+=1;
}while(ax!=cx);
}
*(sp+si*2+0x20)=dx;//从sp+0x20起,每8字节记录一个链表项地址
si+=4;
if(si==0x18) break; //sp+0x18,即&sp[6]
}
简化为C代码为:
node *n[6]; //n = sp+0x20
for (int i=0;i<6;i++)
{
addr = 0x6032d0;
for (int j=0;j<val[i];j++)
addr = *(addr+0x8);
n[i]=addr;
}
C代码就很清楚了,一开始的节点规定为0x6032d0
,即node1的地址,然后是不断地找当前节点的next节点(next的次数为数组的值),找到后放在新分配的sp+0x20
内存中。也就是相当于根据数组值,给6个node节点重新排列,并放于其实地址为sp+0x20
的指针数组n[6]
中。
第三部分真是费劲,但是基本搞清这个node是什么了,接下来进入Part4部分。
四、Part4
4011ab: 48 8b 5c 24 20 mov 0x20(%rsp),%rbx # rbx=n[0]
4011b0: 48 8d 44 24 28 lea 0x28(%rsp),%rax # rax=&n[1]
4011b5: 48 8d 74 24 50 lea 0x50(%rsp),%rsi # 边界 rsi=&n[6]
4011ba: 48 89 d9 mov %rbx,%rcx # rcx=rbx
>>>>>>>>>>>>>>>>>>>>>>>>>>> 循环开始<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
// rax是数组n的下一个值的地址,rcx是数组n前一个值,记:rax=&n[i+1] rcx=n[i] rdx=n[i+1]
4011bd: 48 8b 10 mov (%rax),%rdx # rdx=n[i+1]
4011c0: 48 89 51 08 mov %rdx,0x8(%rcx) #n[i]->next=n[i+1]
4011c4: 48 83 c0 08 add $0x8,%rax # rax+=8
4011c8: 48 39 f0 cmp %rsi,%rax
4011cb: 74 05 je 4011d2 <phase_6+0xde>
// if (rax==&n[6]),即 i->退出循环
4011cd: 48 89 d1 mov %rdx,%rcx
// else -> rcx=rdx , 回到循环开始循环
4011d0: eb eb jmp 4011bd <phase_6+0xc9>
>>>>>>>>>>>>>>>>>>>>>>>>>>> 循环结束<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
4011d2: 48 c7 42 08 00 00 00 movq $0x0,0x8(%rdx)
4011d9: 00
一开始i=0 rcx=n[0]
,rax=&n[1]
, rdx=n[1]
,使得rcx->next=rdx,即n[0]->next=n[1]
然后循环一次后变成 -> rcx=n[1]
,rax=&n[2]
,rdx=n[2]
,使得n[1]->next=n[2]
最后一次循环时,rcx=n[4]
,rax=&n[5]
,rdx=n[5]
,使得n[4]->next=n[5],再将当前的n[5]的next指向地址0,结束Part4的部分。
将其上述过程逆向得:
bx=n[0]
ax=&n[1]
si=&n[6]
cx=bx
while(1)
{
dx = *ax
cx->next = dx
ax+=0x8
if(ax==si) {
// n[0]~n[4] 都已经分配了next,再给n[5]分配0地址为next
dx->next=0;
break;
}
}
这一部分就是把按照数组n的顺序去修改链表的next,使得该链表按数组的顺序串成顺链。
下面进入最后一部分的分析,答案近在咫尺!
五、Part5
4011da: mov $0x5,%ebp
4011df: mov 0x8(%rbx),%rax # rax=rbx的下一个节点的指针
4011e3: mov (%rax),%eax # 结构体中val的值 保存在rax中
4011e5: cmp %eax,(%rbx) # 比较两个node的val值
4011e7: jge 4011ee <phase_6+0xfa> # 如果靠前结点的val < 靠后结点的val
4011e9: callq 40143a <explode_bomb> # 爆炸
4011ee: mov 0x8(%rbx),%rbx # 移动指针
4011f2: sub $0x1,%ebp
4011f5: jne 4011df <phase_6+0xeb> # 循环
4011f7: add $0x50,%rsp
4011fb: pop %rbx
4011fc: pop %rbp
4011fd: pop %r12
4011ff: pop %r13
401201: pop %r14
401203: retq
最后一段逻辑很清晰,即要求排序按val递减,否则爆炸。
上面分析了这么多,是时候全部整合起来并出结果了!
进入最后一部分,结果的呈现。
六、Part6
val:
node1=14c node2=0a8 node3=39c
node4=2b3 node5=1dd node6=1bb
根据val从大到小排序分别为:3 4 5 6 1 2
,但由于之前进行了一次a[i]=7-[i]的线性变换,所以现在的3 4 5 6 1 2
即为之前的4 3 2 1 6 5
。
结果:4 3 2 1 6 5
。
至此,lab1~lab6已全部完成,运行一下来看看结果吧这个实验花了好久,尤其是最后一个Phase,太难搞了不过也终于把bomb lab给完成了,看到弹出的 Congratulations!还是挺激动的。
下面附上六关全部的结果吧
Border relations with Canada have never been better.
1 2 4 8 16 32
0 207
7 0
YONUVW
4 3 2 1 6 5