CMU 15213:machine-level programming课程笔记和bomb实验

文章目录

    • 1. 笔记
      • 1.1 basics
      • 1.2 control
      • 1.3 Procedures
    • 2. bomb lab
      • 2.1 phase 1
      • 2.2 phase 2
      • 2.3 phase 3
      • 1.4 phase 4
      • 1.5 phase 5
      • 1.6 phase 6

1. 笔记

本章的主要讲了编程语法,包括:汇编和C语言。

1.1 basics

1.介绍了intel x86 processors的发展历史(AMD和ARM);

2.指令集大的分类:cisc和risc;

3.c代码到可执行代码的过程:

  • c代码;
  • 汇编代码(gcc -og -S);
  • 二进制文件(gcc);
  • 二进制文件(链接了其他的静态库)(gcc 或者 ld);

4.Objdump(将二进制代码转换成汇编代码);

5.gdb调试时使用disassemble命令,也可以获取汇编代码;

6.x86-64寄存器:rax、rbx、rcx、rdx、rsi、rdi、rsp、rb和 r8-r15;

  • rax表示是64bit;
  • eax表示32bit;
  • ax表示16bit;
  • ah表示高8bit;
  • al表示低8bit;

7.Operation:

CMU 15213:machine-level programming课程笔记和bomb实验_第1张图片

8.寻址方式(三种模式):

  • movq (%rcx),%rax:将rcx寄存器指向的地址数据移动到rax
  • movq 8(%rbp),%rdx:将[rbp]+8地址的数据移动到rdx
  • Mem[Reg[Rb]+S*Reg[Ri]+ D](D只能是1、2、4和8)

假设rdx=0xf000 rcx=0x0100,则计算地址的结果如下:

CMU 15213:machine-level programming课程笔记和bomb实验_第2张图片
9. 地址计算指令leaq,它主要有两个用途:

  • 计算地址(而不会将该地址的数据存放到dest);
  • 计算算术表达式,形如x+k*y(k是1、2、4或者8)(和取址指令一样)(要比普通的加法和乘法快一些)

1.2 control

一般采用linux类型的汇编模式,而不是Intel模式的;它们之间差别还是很大的

1.X86-64单bit的寄存器:CF、SF、ZF、OF

  • cf:下图是cf的置1的情况,分别是:进位(unsigned溢出)和借位;
    CMU 15213:machine-level programming课程笔记和bomb实验_第3张图片
  • sf:下图是sf置1的情况,根据符号位是否为1来判断;
    CMU 15213:machine-level programming课程笔记和bomb实验_第4张图片
  • zf:当数据全为0时,zf置1;
    在这里插入图片描述
  • of:当两个数(符号位一致)相加,得到结果的符号位同原数据的符号位不一致;
    CMU 15213:machine-level programming课程笔记和bomb实验_第5张图片

2.Comq src2,src1指令:类似于src1-src2,影响PSW;

3.Testq src2,src1指令:类似于计算src1&src2;(经常用于判断一个寄存器里面得值是否为0)

  • Zf set:src1&src2 == 0;
  • Sf set:src1&src2 < 0;

4.Set dest指令:用于判断PSW的值,当条件成立时将1写入dest;

CMU 15213:machine-level programming课程笔记和bomb实验_第6张图片

  • 下图时setl成立的示意图:
    CMU 15213:machine-level programming课程笔记和bomb实验_第7张图片

5.Movzbl:高位填充为0;

6.jx指令:
CMU 15213:machine-level programming课程笔记和bomb实验_第8张图片
7.c语言goto;

8.c语言条件语句:if、a?b:c(注意b和c如果是表达式都会被计算,所以会增加复杂度);

9.循环语句:do-while、while、for;

10.Switch:编译成汇编后,会结合jump table和rdi(index)找到对应的跳转入口;

1.3 Procedures

1.ABI:机器程序级别的接口(windows linux ios都有自己的abi,大致相同,细节不同)。它包括:passing control、passing data、memory management等机制;

  • 比如一个编译好的二进制程序,如果放在ABI修改后的机器上运行会导致程序出现无法预计的错误;

2.Stack:向低地址增长

  • Pushq src:修改(减少8bytes)%rsp;将src写入stack
  • Popq dest:将栈顶的数据放入dest;修改(增加8bytes)%rsp

3.passing control

  • 使用stack;
  • 执行call label语句时:将return address压入栈;跳转到label执行;
  • Return address:当前call label语句的下一条语句的地址;
  • 执行ret语句时:pop出栈的return address;跳转到该处运行;

4.Passing data

  • 参数传递:前6个参数通过寄存器:%rdi、%rsi、%rdx、%rcx、%r8、%r9;剩余的参数通过stack传递;(浮点数参数有单独的寄存器);
  • 返回值:%rax

5.Stack frames:

  • Contents:返回地址;局部变量空间;临时空间
  • Management:stack分配空间(call)和释放空间的时机(return);

CMU 15213:machine-level programming课程笔记和bomb实验_第9张图片

call的时候是否压入%rbp是可选的

6.Linux stack frame

CMU 15213:machine-level programming课程笔记和bomb实验_第10张图片

7.Register saving conventions

  • “Caller Saved”:Caller saves temporary values in its frame before the call;
  • “Callee Saved”:Callee saves temporary values in its frame before using;Callee restores them before returning to caller;

Caller-saved registers (AKA volatile registers, or call-clobbered) are used to hold temporary quantities that need not be preserved across calls. caller如果在callee返回之后需要使用这些寄存器,则应该在callee之前保存它们。
CMU 15213:machine-level programming课程笔记和bomb实验_第11张图片

Callee-saved registers (AKA non-volatile registers, or call-preserved) are used to hold long-lived values that should be preserved across calls. 说明这些寄存器在callee返回时会被恢复,所以caller可以不用保存这些寄存器,即使后面需要使用也不需要保存(因为Callee返回时会恢复)。
CMU 15213:machine-level programming课程笔记和bomb实验_第12张图片

2. bomb lab

本实验主要考察了汇编、程序ABI的了解、gdb调试等基础知识。实验提供了一个二进制文件:bomb和一个c语言文件:bomb.c。

2.1 phase 1

Phase 1的函数汇编代码如下:

0000000000400ee0 <phase_1>:
  400ee0:	48 83 ec 08          	sub    $0x8,%rsp
  400ee4:	be 00 24 40 00       	mov    $0x402400,%esi
  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>
  400ef2:	e8 43 05 00 00       	callq  40143a <explode_bomb>
  400ef7:	48 83 c4 08          	add    $0x8,%rsp
  400efb:	c3                   	retq   

很显然phase 1函数将我们的输入同bomb程序预先存储的字符串进行匹配,如果不一致的话则会引爆炸弹。

我们知道%rdi和%rsi可以用来传递参数,%rdi当前指向我们输入的字符串的地址,那么%esi就指向密钥。从汇编mov $0x402400,%esi可知,该密钥存储在0x402400内存地址上。

用gdb查看该地址存放的字符串的命令是:x/s 0x402400。得到输出结果:Border relations with Canada have never been better.

2.2 phase 2

Phase 2的汇编代码如下:

0000000000400efc <phase_2>:
  400efc:	55                   	push   %rbp
  400efd:	53                   	push   %rbx
  400efe:	48 83 ec 28          	sub    $0x28,%rsp
  400f02:	48 89 e6             	mov    %rsp,%rsi
  400f05:	e8 52 05 00 00       	callq  40145c <read_six_numbers>
  400f0a:	83 3c 24 01          	cmpl   $0x1,(%rsp)
  400f0e:	74 20                	je     400f30 <phase_2+0x34>
  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>
  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>
  400f3c:	48 83 c4 28          	add    $0x28,%rsp
  400f40:	5b                   	pop    %rbx
  400f41:	5d                   	pop    %rbp
  400f42:	c3                   	retq   

关键代码是:

400efe:	48 83 ec 28          	sub    $0x28,%rsp
400f02:	48 89 e6             	mov    %rsp,%rsi
400f05:	e8 52 05 00 00       	callq  40145c <read_six_numbers>

函数read_six_numbers接收了两个参数,一个是我们的输入字符串的地址,它存放在%rdi;另一个%rsi,它保存了我们当前栈顶的地址。该函数的原型是:read_six_numbers(char *c_str, int *a);那么可以猜测read_six_numbers函数把我们的输入字符串转成六个整数,并存放在以%rsp为首地址的连续内存区域,将该区域以数组A表示。

400f0a: 83 3c 24 01 cmpl $0x1,(%rsp) 说明A[0]是1。

跳转到400f25:

  • 指向数组的下一个元素;
  • 取数组A[1]到%rbx,取数组end位置赋值给%rbp(用于遍历数组);

跳转到400f17

  • 取数组当前位置的前一个数据,即A[0];
  • 判断A[1] == A[0] + A[0],则可知A[1] = 2

以此类推。

最终结果是:1 2 4 8 16 32

2.3 phase 3

phase 3汇编代码如下:

0000000000400f43 <phase_3>:
  400f43:	48 83 ec 18          	sub    $0x18,%rsp
  400f47:	48 8d 4c 24 0c       	lea    0xc(%rsp),%rcx
  400f4c:	48 8d 54 24 08       	lea    0x8(%rsp),%rdx
  400f51:	be cf 25 40 00       	mov    $0x4025cf,%esi
  400f56:	b8 00 00 00 00       	mov    $0x0,%eax
  400f5b:	e8 90 fc ff ff       	callq  400bf0 <__isoc99_sscanf@plt>
  400f60:	83 f8 01             	cmp    $0x1,%eax
  400f63:	7f 05                	jg     400f6a <phase_3+0x27>
  400f65:	e8 d0 04 00 00       	callq  40143a <explode_bomb>
  400f6a:	83 7c 24 08 07       	cmpl   $0x7,0x8(%rsp)
  400f6f:	77 3c                	ja     400fad <phase_3+0x6a>
  400f71:	8b 44 24 08          	mov    0x8(%rsp),%eax
  400f75:	ff 24 c5 70 24 40 00 	jmpq   *0x402470(,%rax,8)  400f7c
  400f7c:	b8 cf 00 00 00       	mov    $0xcf,%eax    
  400f81:	eb 3b                	jmp    400fbe <phase_3+0x7b>
  400f83:	b8 c3 02 00 00       	mov    $0x2c3,%eax
  400f88:	eb 34                	jmp    400fbe <phase_3+0x7b>
  400f8a:	b8 00 01 00 00       	mov    $0x100,%eax
  400f8f:	eb 2d                	jmp    400fbe <phase_3+0x7b>
  400f91:	b8 85 01 00 00       	mov    $0x185,%eax
  400f96:	eb 26                	jmp    400fbe <phase_3+0x7b>
  400f98:	b8 ce 00 00 00       	mov    $0xce,%eax
  400f9d:	eb 1f                	jmp    400fbe <phase_3+0x7b>
  400f9f:	b8 aa 02 00 00       	mov    $0x2aa,%eax
  400fa4:	eb 18                	jmp    400fbe <phase_3+0x7b>
  400fa6:	b8 47 01 00 00       	mov    $0x147,%eax
  400fab:	eb 11                	jmp    400fbe <phase_3+0x7b>
  400fad:	e8 88 04 00 00       	callq  40143a <explode_bomb>
  400fb2:	b8 00 00 00 00       	mov    $0x0,%eax
  400fb7:	eb 05                	jmp    400fbe <phase_3+0x7b>
  400fb9:	b8 37 01 00 00       	mov    $0x137,%eax
  400fbe:	3b 44 24 0c          	cmp    0xc(%rsp),%eax
  400fc2:	74 05                	je     400fc9 <phase_3+0x86>
  400fc4:	e8 71 04 00 00       	callq  40143a <explode_bomb>
  400fc9:	48 83 c4 18          	add    $0x18,%rsp
  400fcd:	c3                   	retq   

关键代码是:

  400f47:	48 8d 4c 24 0c       	lea    0xc(%rsp),%rcx
  400f4c:	48 8d 54 24 08       	lea    0x8(%rsp),%rdx
  400f51:	be cf 25 40 00       	mov    $0x4025cf,%esi
  400f56:	b8 00 00 00 00       	mov    $0x0,%eax
  400f5b:	e8 90 fc ff ff       	callq  400bf0 <__isoc99_sscanf@plt>

我们知道%rdi、%rsi、%rdx、%rcx、%r8、%r9用来传递参数,这里将phase_3函数的栈地址写入%rcx和%rdx,说明接下来被调用的函数将把关键数据写入这两个地址。可以得到phase_4调用的函数是__isoc99_sscanf(char *c,"%d %d",&a, &b)。所以,我们应该两个整数。

跳转到400f6a:

  • 检查我们输入的第一个整数是否大于7;
  • 将输入的第一个整数保存到%rax;

400f75: ff 24 c5 70 24 40 00 jmpq *0x402470(,%rax,8)根据我们的输入的第一个整数来进行跳转,很明显是一个switch case。

最后结果有多个,其中一个是:0 207

1.4 phase 4

phase 4的汇编代码如下:

000000000040100c <phase_4>:
  40100c:	48 83 ec 18          	sub    $0x18,%rsp
  401010:	48 8d 4c 24 0c       	lea    0xc(%rsp),%rcx
  401015:	48 8d 54 24 08       	lea    0x8(%rsp),%rdx
  40101a:	be cf 25 40 00       	mov    $0x4025cf,%esi
  40101f:	b8 00 00 00 00       	mov    $0x0,%eax
  401024:	e8 c7 fb ff ff       	callq  400bf0 <__isoc99_sscanf@plt>
  401029:	83 f8 02             	cmp    $0x2,%eax    
  40102c:	75 07                	jne    401035 <phase_4+0x29>
  40102e:	83 7c 24 08 0e       	cmpl   $0xe,0x8(%rsp) 
  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
  40103f:	be 00 00 00 00       	mov    $0x0,%esi
  401044:	8b 7c 24 08          	mov    0x8(%rsp),%edi
  401048:	e8 81 ff ff ff       	callq  400fce <func4>
  40104d:	85 c0                	test   %eax,%eax 
  40104f:	75 07                	jne    401058 <phase_4+0x4c>
  401051:	83 7c 24 0c 00       	cmpl   $0x0,0xc(%rsp)
  401056:	74 05                	je     40105d <phase_4+0x51>
  401058:	e8 dd 03 00 00       	callq  40143a <explode_bomb>
  40105d:	48 83 c4 18          	add    $0x18,%rsp
  401061:	c3                   	retq   

401029: 83 f8 02 cmp $0x2,%eax可知需要我们输入两个整数;
40102e: 83 7c 24 08 0e cmpl $0xe,0x8(%rsp)而且第一个整数<=14;
401051: 83 7c 24 0c 00 cmpl $0x0,0xc(%rsp)可知第二个整数是0;

第一个整数我们知道了范围,但准确的值不清楚(可以从0-14试一下)。可以从调用的func4推测出该值是多少,func4的汇编代码如下:

0000000000400fce <func4>:
  400fce:	48 83 ec 08          	sub    $0x8,%rsp
  400fd2:	89 d0                	mov    %edx,%eax	//%edx在函数调用前被赋值14
  400fd4:	29 f0                	sub    %esi,%eax	//%esi在函数调用前被赋值0
  400fd6:	89 c1                	mov    %eax,%ecx    //%eax是14
  400fd8:	c1 e9 1f             	shr    $0x1f,%ecx   //14>>16得到0
  400fdb:	01 c8                	add    %ecx,%eax    //%eax不变,还是14
  400fdd:	d1 f8                	sar    %eax       	//14>>1得到7
  400fdf:	8d 0c 30             	lea    (%rax,%rsi,1),%ecx 	//%rax+1*%rsi得到7
  400fe2:	39 f9                	cmp    %edi,%ecx 			//%edi也就是我们输入的第一个参数
  400fe4:	7e 0c                	jle    400ff2 <func4+0x24>	//%edi不能够大于7,跳转
  400fe6:	8d 51 ff             	lea    -0x1(%rcx),%edx
  400fe9:	e8 e0 ff ff ff       	callq  400fce <func4>
  400fee:	01 c0                	add    %eax,%eax
  400ff0:	eb 15                	jmp    401007 <func4+0x39>
  400ff2:	b8 00 00 00 00       	mov    $0x0,%eax			//
  400ff7:	39 f9                	cmp    %edi,%ecx			//
  400ff9:	7d 0c                	jge    401007 <func4+0x39>	//%edi不能够小于7
  400ffb:	8d 71 01             	lea    0x1(%rcx),%esi
  400ffe:	e8 cb ff ff ff       	callq  400fce <func4>
  401003:	8d 44 00 01          	lea    0x1(%rax,%rax,1),%eax
  401007:	48 83 c4 08          	add    $0x8,%rsp
  40100b:	c3                   	retq   

func4的输入参数保存在%rdi,它也是我们输入的第一个整数。该函数有多个mov、sub和移位操作,可以稍微模拟一下,具体看代码的注释。由注释中可知%rdi不能大于7,也不能小于7,所以%rdi是7。

最终结果是:7 0

1.5 phase 5

phase 5的汇编代码如下:

0000000000401062 <phase_5>:
  401062:	53                   	push   %rbx
  401063:	48 83 ec 20          	sub    $0x20,%rsp
  401067:	48 89 fb             	mov    %rdi,%rbx
  40106a:	64 48 8b 04 25 28 00 	mov    %fs:0x28,%rax 
  401071:	00 00 
  401073:	48 89 44 24 18       	mov    %rax,0x18(%rsp)
  401078:	31 c0                	xor    %eax,%eax      
  40107a:	e8 9c 02 00 00       	callq  40131b <string_length>
  40107f:	83 f8 06             	cmp    $0x6,%eax				//1. 可知我们输入的字符串长度为6
  401082:	74 4e                	je     4010d2 <phase_5+0x70>
  401084:	e8 b1 03 00 00       	callq  40143a <explode_bomb>
  401089:	eb 47                	jmp    4010d2 <phase_5+0x70>
  40108b:	0f b6 0c 03          	movzbl (%rbx,%rax,1),%ecx		//4. 取字符串第一个字符
  40108f:	88 0c 24             	mov    %cl,(%rsp)				//5. 将该字符放到栈中
  401092:	48 8b 14 24          	mov    (%rsp),%rdx        		//6. 取该字符到%rdx
  401096:	83 e2 0f             	and    $0xf,%edx          		//7. 
  401099:	0f b6 92 b0 24 40 00 	movzbl 0x4024b0(%rdx),%edx  	//8. 0x4024b0 + %rdx
  4010a0:	88 54 04 10          	mov    %dl,0x10(%rsp,%rax,1) 	//9. 新数据存到栈中
  4010a4:	48 83 c0 01          	add    $0x1,%rax				//10. 递增索引
  4010a8:	48 83 f8 06          	cmp    $0x6,%rax				//11. 判断是否结束
  4010ac:	75 dd                	jne    40108b <phase_5+0x29>	
  4010ae:	c6 44 24 16 00       	movb   $0x0,0x16(%rsp)			//12. 结束前面的转换
  4010b3:	be 5e 24 40 00       	mov    $0x40245e,%esi    		//13. 该地址存放的字符串时flyers
  4010b8:	48 8d 7c 24 10       	lea    0x10(%rsp),%rdi			//14. 将9新生成的数据和flyers作比较
  4010bd:	e8 76 02 00 00       	callq  401338 <strings_not_equal>
  4010c2:	85 c0                	test   %eax,%eax
  4010c4:	74 13                	je     4010d9 <phase_5+0x77>
  4010c6:	e8 6f 03 00 00       	callq  40143a <explode_bomb>
  4010cb:	0f 1f 44 00 00       	nopl   0x0(%rax,%rax,1)
  4010d0:	eb 07                	jmp    4010d9 <phase_5+0x77>
  4010d2:	b8 00 00 00 00       	mov    $0x0,%eax				//2. %eax
  4010d7:	eb b2                	jmp    40108b <phase_5+0x29>	//3. 回跳
  4010d9:	48 8b 44 24 18       	mov    0x18(%rsp),%rax
  4010de:	64 48 33 04 25 28 00 	xor    %fs:0x28,%rax
  4010e5:	00 00 
  4010e7:	74 05                	je     4010ee <phase_5+0x8c>
  4010e9:	e8 42 fa ff ff       	callq  400b30 <__stack_chk_fail@plt>
  4010ee:	48 83 c4 20          	add    $0x20,%rsp
  4010f2:	5b                   	pop    %rbx
  4010f3:	c3                   	retq   

该函数大致流程是:

  • 我们输入长度为6的字符串;
  • 将该字符串中的每一个字符的二进制的低4bit作为0x4024b0的偏移地址,将该偏移地址的数据存放到栈中,最终得到新的字符串;
  • 将新字符串同“flyers”比较;

所以我们输入的字符串应该根据地址0x4024b0对应的字符f、l、y、e、r、s的偏移来决定。

最后的结果是:ionefg

1.6 phase 6

太复杂,只能大概推测一下:

  • 输入6个整数;
  • 函数首先是二重循环判断是否有相同的和是否大于6;
  • 用7减将该数组中的每一个元素得到新的数组;
  • 后面的有点类似于phase 5,将内存的数据根据输入的数据的大小存放到栈中,最后进行比较;

你可能感兴趣的:(cmu15213,cmu15213,bomb,lab)