CSAPP实验2 : bomblab

观察之后还可以发现有所谓的secret_phasefunc7之类的东西,但是感觉差不太多了也就不太想做...就这样吧

前置姿势/技能


这里记一下做lab需要的前置姿势/技能,尽量不和理论方面的东西重叠吧...
编译实际上就是把源代码变成一堆CPU指令的过程(在这里到这个程度就够了)

GCC使用的是叫ATT格式的汇编,和Intel格式的汇编有很多不一样,最显著的就是操作元素的顺序...看文档的时候要小心

编译产生.s文件:
gcc -Og -S file.c

编译产生.o文件:
gcc -Og -c file.c

以上-Og是为了避免过度优化使得程序的执行顺序和结构发生变化

反汇编得到一堆指令:
objdump -d file

gdb打印寄存器内容:
i registers

gdb打印内存内容:
x /<n/f/u> <addr>

n、f、u 为可选参数,其中n 表示显示内存的长度,也就是说从当前地址向后显示几个地址的内容;f 表示显示的格式(b);u 表示将多少个字节作为一个值取出来,如果不指定的话,GDB默认是4个bytes,如果不指定的话,默认是4个bytes。当我们指定了字节长度后,GDB会从指内存定的内存地址开始,读写指定字节,并把其当作一个值取出来。为起始内存地址。

参数 f 的可选值:
x 按十六进制格式显示变量。
d 按十进制格式显示变量。
u 按十六进制格式显示无符号整型。
o 按八进制格式显示变量。
t 按二进制格式显示变量。
a 按十六进制格式显示变量。
c 按字符格式显示变量。
f 按浮点数格式显示变量。

参数 u 的可选值:
b 表示单字节
h 表示双字节
w 表示四字节
g 表示八字节

上面的内容摘自GDB查看内存

这个非常有用,事实上知道了这个我才做出了Phase_1,用了这么久gdb还是只会fileqlsrb真是惭愧...

打印PC寄存器往后5条指令,在做lab3的时候比较有用
x /5i $pc

有些程序运行需要参数,就可以用这个命令来设置
set args xxx

Phase_1


终于做完了第一个QUQ

读代码

0000000000400ee0 <phase_1>:
  400ee0:	48 83 ec 08          	sub    $0x8,%rsp
  400ee4:	be 00 24 40 00       	mov    $0x402400,%esi ; 这里给出了第二个参数,实际上就是要和input比较的字符串常量
  400ee9:	e8 4a 04 00 00       	callq  401338 <strings_not_equal>
  400eee:	85 c0                	test   %eax,%eax
  400ef0:	74 05                	je     400ef7 <phase_1+0x17> ; 如果返回值是0则安全
  400ef2:	e8 43 05 00 00       	callq  40143a <explode_bomb>
  400ef7:	48 83 c4 08          	add    $0x8,%rsp
  400efb:	c3                   	retq   

重点在于函数名strings_not_equal和400ee4的位置给出了%esi
我们知道%rdi和%rsi表示传入的前两个参数,而%rdi是输入的串的起始字符的地址,那么%rsi就是目标串的起始字符的地址了
知道了这个地址就可以进GDB扫内存,这里扫出来结果是

(gdb) x /60db 0x402400
0x402400:	66	111	114	100	101	114	32	114
0x402408:	101	108	97	116	105	111	110	115
0x402410:	32	119	105	116	104	32	67	97
0x402418:	110	97	100	97	32	104	97	118
0x402420:	101	32	110	101	118	101	114	32
0x402428:	98	101	101	110	32	98	101	116
0x402430:	116	101	114	46	0	0	0	0
0x402438:	87	111	119	33

很显然到0就停止了,剩下的就是把这个字符序列由ascii码变成字符就完了,现在看看不是太难的...难点在于不要被绕晕

Phase_2


有了第一个铺垫第二个就比较简单了(bushi)

还是先贴代码,这里把关键的循环抠出来了

  400f0a:	83 3c 24 01          	cmpl   $0x1,(%rsp)
  400f0e:	74 20                	je     400f30 <phase_2+0x34> ; if (%rsp)==1 safe, a[1]=1

  400f10:	e8 25 05 00 00       	callq  40143a <explode_bomb>
  400f15:	eb 19                	jmp    400f30 <phase_2+0x34>
  
  400f17:	8b 43 fc             	mov    -0x4(%rbx),%eax
  400f1a:	01 c0                	add    %eax,%eax
  400f1c:	39 03                	cmp    %eax,(%rbx)
  400f1e:	74 05                	je     400f25 <phase_2+0x29> ; if 2*a[i]!=a[i+1] bomb
  400f20:	e8 15 05 00 00       	callq  40143a <explode_bomb>

  400f25:	48 83 c3 04          	add    $0x4,%rbx
  400f29:	48 39 eb             	cmp    %rbp,%rbx
  400f2c:	75 e9                	jne    400f17 <phase_2+0x1b>
  400f2e:	eb 0c                	jmp    400f3c <phase_2+0x40>
  
  400f30:	48 8d 5c 24 04       	lea    0x4(%rsp),%rbx
  400f35:	48 8d 6c 24 18       	lea    0x18(%rsp),%rbp
  400f3a:	eb db                	jmp    400f17 <phase_2+0x1b>

同样函数名给的提示很多,看过课程视频/看过书都应该知道只有前6个函数参数存在寄存器里,多余的参数都会都按照逆序插入到栈中(why?这样弹出的第一个就是最前面的参数,以此类推)
难点在于搞清楚内存分部和内存里储存的值究竟是什么,以及sscanf这个函数的返回值....

因此只要画出内存的地址&内容表格来就可以很简单地做出来了,答案是一个长为6的满足某些常数项递推条件的数列

Phase_3


感觉这个比前两个都要简单了

观察实现可以发现是对参数%rdi作0~7的switch-case判断,不同的%rdi对应不同的值%eax
炸弹不爆当且仅当%rsi=%eax,因此正确的输入有8对.我试了两个都是可以的,剩下的就没试了(太困了)

关键在这一句

400f75:	ff 24 c5 70 24 40 00 	jmpq   *0x402470(,%rax,8) ; jump to mem[0x402470+x*8]

用GDB硬扫就可以了,注意用/8xg+地址来扫

Phase_4


func_4写出来类似这样子,看上去是个递归实际上观察phase_4的初始条件可以知道最开始带入的x=tx,y=0,z=14
这里tx,ty表示读入的两个数字,再观察到所谓的q<=x且q>=x就是解一个简单的方程的条件,算出答案就可以了

int func4(int x,int y,int z) {
	int t = z - y;
	int q = ( (unsigned) t) >> 31;
	t += q;
	t >>= 1;
	q = t + y;
	if (q <= x) {
		if (q >= x) return 0;
		return 2 * func4(x, q + 1, z) + 1;
	} else {
		return 2 * func4(x, y, q - 1);
	}
}

phase_4的部分关键在这些地方

  40102e:	83 7c 24 08 0e       	cmpl   $0xe,0x8(%rsp);if *tx<14 bomb
  401033:	76 05                	jbe    40103a <phase_4+0x2e>
  401035:	e8 00 04 00 00       	callq  40143a <explode_bomb>
  40103a:	ba 0e 00 00 00       	mov    $0xe,%edx;z=14
  40103f:	be 00 00 00 00       	mov    $0x0,%esi;y=0
  401044:	8b 7c 24 08          	mov    0x8(%rsp),%edi;x=*tx
  401048:	e8 81 ff ff ff       	callq  400fce <func4>
  40104d:	85 c0                	test   %eax,%eax;if func4(tx,0,14)!=0 bomb
  40104f:	75 07                	jne    401058 <phase_4+0x4c>

Phase_5


一开始被%fs这个寄存器吓到了,往后看书才知道这个就是所谓的canary机制,在这里可以不用管~
剩下的就不是太难。这个函数主要实现了通过一个循环来获取一个常字符串的某些位置得到一个新的串,并将这个串和串"flyers"进行比较
关键是这个循环部分,看懂就非常简单

  401089:	eb 47                	jmp    4010d2 <phase_5+0x70>
  40108b:	0f b6 0c 03          	movzbl (%rbx,%rax,1),%ecx;此时rax为0,rbx为str,故rcx=*str
  40108f:	88 0c 24             	mov    %cl,(%rsp);mem[rsp]=str[0]
  401092:	48 8b 14 24          	mov    (%rsp),%rdx;rdx=str[0]
  401096:	83 e2 0f             	and    $0xf,%edx;rdx&=15,此时rdx<=15
  401099:	0f b6 92 b0 24 40 00 	movzbl 0x4024b0(%rdx),%edx;
  4010a0:	88 54 04 10          	mov    %dl,0x10(%rsp,%rax,1)
                                       ;mem[rsp+0x10+rax]=mem[0x4024b0+rdx]
  4010a4:	48 83 c0 01          	add    $0x1,%rax;rax+=1
  4010a8:	48 83 f8 06          	cmp    $0x6,%rax;
  4010ac:	75 dd                	jne    40108b <phase_5+0x29>;while

Phase_6


最后一题有点难度

首先可以把代码分成四块。
第一部分程序读入了6个数字,并检查它们是否全都不一样(否则就炸)且都在1到6之间
第二部分程序把每个a[i]都变成7-a[i]
第三部分实现了令c[i]=b[a[i]],其中b是一个常量数组,c是一个结果
第四部分实现了判断c是否递减,否则就炸

1、2都很简单,4在搞清楚3之后也很简单

  ;begin loop;
    401176:	48 8b 52 08          	mov    0x8(%rdx),%rdx
    40117a:	83 c0 01             	add    $0x1,%eax
    40117d:	39 c8                	cmp    %ecx,%eax
  40117f:	75 f5                	jne    401176 <phase_6+0x82>
  ;end loop

观察这段代码,%ecx储存了a[i],它实际上实现了找到b中的第a[i]个数,并取出它的位置储存在%rdx中的功能
其中

  4011a4:	ba d0 32 60 00       	mov    $0x6032d0,%edx

给出了b数组的起始位置,那么我们在gdb中扫就可以了,出来的结果是这样的

第一列就是b中的值

这时候再看第四部分,可以发现这是在遍历c中的6个数字观察它们是否按照一定顺序(降序)排好了
现在就很好猜了,第三部分首先用a[i]找到第a[i]个b中的元素,再把它存储到c[i]中。中间有点绕是因为程序对第一个元素做了特殊处理,编译器对默认情况的优化有点诡异...
所以对着图里的数字排序就好了,这样就做完了Phase_6

posted @ 2021-01-24 00:41  jjppp  阅读(401)  评论(1编辑  收藏  举报