CSAPP Attack Lab
吐槽
首先不得不说说官网的WriteUp了,看了半天看不懂他在讲啥,好吧其实是我菜
我的输出与官网演示的不一样:
而我的是
查了一下,说是默认连接评分服务器,要用-q
关闭
我都没输入任何字符串呢,就直接让我Segmentation fault了
我用gdb调试进去,发现只要没有用-i file
指定输入文件,那么程序连test
函数都不会让我们进行就直接出现Segmentation fault了
之前没有明白HEX2RAW作用是啥,这是啥意思。后来才知道:
因为我们要修改在栈中的return address地址,又这些地址是通过十六进制表示的,我们可能要将原来的return address地址代替为,如0x401930
. 因为我们是在程序外通过输入字符串,40和30这些ASCII码对应的Char
是可以通过我们键盘输入的,但是19不能。
这个时候我们通过在文件exploit.txt
写:40 19 30,然后cat exploit.txt | hex2raw
,这个时候hex2raw就可以帮我们输出我们用键盘输入不了的19这个ASCII码的Char
知识点
gdb
x/xb 地址
在gdb中这条指令可以输出地址处的内容,以16进制输出,每次输出就输出1字节,然后用空格分隔继续输出
x/xw 地址
是默认输入,即与x/x
输出效果是一样的,每次输出一个word(8字节),下面是效果展示
大端小端
需要注意的是小端模式是我们一个完整数据之间的反向放置
这里一个完整数据指的是,如int
,short
,立即数
,地址
等
如一个地址十六进制表示为0x401930
,那么他在内存中存放的顺序就为30 19 40 00 00 00 00 00
再如一条指令在内存中的十六进制保存:
在灰线处的指令中0x202d0e
放置方式为0e 2d 20 00
让我们看看字符串在内存中的保存
上述在printf中的字符串Touch1!....\n
在内存的数据保存如下
其中0x54
的ascii就为T
,0x6f
的ascii就为o
...这里并没有整个字符串翻转过来保存,而是以字符char为单位的
可以感受到一个完整数据
的含义了
Phase_2
错误历程
我首先看了看touch2
函数的汇编代码,并对比下源代码
我应该要发现vlevel和cookie应该是个全局变量,所以我的目的是要将我的cookie放到%edi上
好,我开始注入代码了,其他人的正确注入方式是:
而我的是:
然后段错误了......
为啥缓冲区注入代码就可以执行,我放到其他栈空间就报错?
目前我也不知道,看书上P201,难道是限制了可执行代码区域?缓冲区管理比较松散就可以执行?
知识点
这里一定要写$
,否则不是个立即数,而是个地址了 (血泪)
Phase_3
错误历程
想一想,因为我们要传入字符串的地址,我们要把字符串地址放到哪里好?
这个红框中吗?如果你也和我一开始想的一样那么(这实在是太酷了)
好吧,那你和我一样粗心,没有考虑全面,想想如果我们成功到了touch
并调用了hexmatch
那么在hexmatch
函数阶段就会分配一个超大空间的栈给hexmatch
函数使用,从而覆盖掉了我们保存的字符串
那么我们将字符串存放到哪呢?
我应该利用指令来扩充栈,存放我的字符串
push $0x00 # 不要忘了字符串后会跟个0 push $0x6166373939623935 # 因为小端的原因,我需要反过来放
整体的汇编代码为:
Phase_4
感觉这题挺简单,可能是我眼神比较好😀
58 popq %rax 90 nop c3 ret
48 89 c7 movq %rax,%rdi c3 ret
我的栈结构是长这样的:
具体思考过程
Key Word
-
buffer overflows.
-
stack and parameter-passing mechanisms of x86-64 machine code.
-
how x86-64 instructions are encoded.
-
debugging tools such as GDB and OBJDUMP.
File
-
README.txt: A file describing the contents of the directory
-
ctarget: An executable program vulnerable to code-injection attacks -
-
rtarget: An executable program vulnerable to return-oriented-programming attacks
-
cookie.txt: An 8-digit hex code that you will use as a unique identifier in your attacks.
-
farm.c: The source code of your target’s “gadget farm,” which you will use in generating return-oriented programming attacks.
-
hex2raw: A utility to generate attack strings.
Theory
gets() have no way to determine whether their destination buffers are large enough to store the string they read. They simply copy sequences of bytes, possibly overrunning the bounds of the storage allocated at the destinations.
Code Injection
the stack positions will be consistent from one run to the next and so that data on the stack can be treated as executable code.
每次运行时栈的位置是不变的,在栈中的数据可以被当做可执行代码
x86-64 machine 上使用的是小端模式
Level 1
因为lab给的ctarget是已经经过汇编的二进制文件,所以我们要通过objdump -d 进行反汇编查看要跳转到的函数touch1的地址,以及getbuf请求到的空间。
同时需要知道运行时的栈 的结构, 同时要清楚栈一"行"就是一个字节(x86-64是按照字节进行编址的)
在Level 1中我们只需要做到让getbuf读到的内容溢出,覆盖掉栈上保存的返回地址即可
过程如下:
P函数调用test函数,test函数调用getbuf函数,getbuf中调用Gets函数。
test函数中没有保存寄存器,局部变量和参数的行为,只有sub $0x8, %rsp
和add $0x8, %rsp
同理getbuf函数中只有:sub $0x28, %rsp
和add $0x28, %rsp
所以栈中情况如下:
——|—————————————---|—— ——| P函数返回地址 |—— ——|—————————————---|—— ——|................|-- --|—————————————---|-- --| test函数返回地址 |-- --|(被覆盖成设置的地址)|-- --|—————————————---|-- ——|................|<--getbuf栈帧 --|—————————————---|--
getbuf在最后会执行ret
,跳转到设置的地址执行
Level 2
在Level 1的基础上,还需要利用代码注入为touch2传入正确的参数
x86-64中可以通过寄存器传递6个整型(整数和指针)参数,寄存器使用是有一定顺序的:%rdi, %rsi, %rdx, $rcx. %r8, %r9
超出6个的部分需要通过栈来传递,参数位置越小则越在栈顶(即参数7比参数8越靠近栈顶)
通过栈传递参数时,所有的数据大小都向8的倍数对齐
P函数调用Q函数,P函数需要给Q函数传参,那么是在P函数的栈帧的参数构造区保存参数
我这里的主要问题是:我如何给让传递参数给寄存器%rdi?(因为只有一个参数,而且参数是unsigned类型为64位)
看来只有代码注入了!
——|—————————————-----|-- --| P函数返回地址 |-- ——|—————————————-----|—— ——|..................|-- ——|—————————————-----|—— ——| test函数返回地址 |—— --| (将要被覆盖为%rsp)|-- ——|—————————————-----|—— ——|..................|-- --|—————————————-----|-- --| ret |-- ——|—————————————-----|—— --|pushq touch2函数地址|-- ——|—————————————-----|—— --| movq cookie %rdi |<--%rsp ——|——————----———————-|——
根据pc每次执行后会自动+“1”的特点,以及栈地址大小是反向增长的特点,这样注入代码就可以跳转到touch2函数并且让参数为cookie
需要注意的是我们肯定给getbuf输入的不是上述内容,而是将汇编代码给编译得到二进制代码后用objdump -d反汇编,得到代码16进制下的内容,然后交给hex2raw程序,将16进制的内容转换成字符输入给getbuf
总结下
code injection 的精髓在于通过getbuf,溢出缓存区覆盖返回地址,利用函数末尾的ret跳转到完成代码注入的地址执行
Level 3
在Level 2的基础上,需要注入数据(字符串等)并使用。方法为注入汇编代码push data
,在栈上开辟出不会被影响的空间
........................ ——|—————————————-----|—— ——| test函数返回地址 |—— --| (将要被覆盖为%rsp)|-- ——|—————————————-----|—— ——|..................|-- --|—————————————-----|-- --| ret |-- ——|—————————————-----|—— --|pushq touch3函数地址|-- --|—————————————-----|-- --| movq cookie %rdi |-- --|—————————————-----|-- --| push 字符串data |<--%rsp ——|—————————————-----|——
Level 4
code injection能够成功的原因是:
- 每次程序运行时,栈帧的布局和地址位置在程序的不同运行中保持一致。
- 栈上的数据可以包含并执行有效的指令,意味着栈上的内容可以是有效的机器码,能够被处理器直接执行。
但是现代系统中,为了防止上述利用,通常会有以下安全措施:
- 地址空间布局随机化(ASLR)
- ASLR:改变进程每次运行时栈、堆和共享库的加载地址,使得栈位置不一致,增加攻击难度。
- 效果:地址变得不可预测,使得固定位置的攻击失效。
- 数据执行保护(DEP)/ 不可执行栈
- DEP:标记某些内存区域(包括栈)为不可执行,使得数据段不可作为代码执行。
- 效果:即使攻击者注入了恶意代码,也无法执行这些代码。
- It uses randomization so that the stack positions differ from one run to another. This makes it impossible to determine where your injected code will be located.
- It marks the section of memory holding the stack as nonexecutable, so even if you could set the program counter to the start of your injected code, the program would fail with a segmentation fault.
解决方法:
The strategy with ROP is to identify byte sequences within an existing program that consist of one or more instructions followed by the instruction ret. Such a segment is referred to as a gadge
具体而言,我们主要面对的有两个问题:
- 找不到栈的具体位置。
- 不能在栈中执行代码了。
面对这个问题,对应的解决方法为:
- ret和push,pop指令会自动帮我们找到栈的位置,使得我们可以操纵栈。
- 我们不直接在栈中执行代码,而是使用名为返回导向编程(ROP),其利用程序中已有的代码片段(称为“gadget”)来实现攻击者所需的功能。这些片段通常以
ret
指令结束,并且存在于已加载的程序或库中。
具体操作为:
- 利用gadget:在程序的代码段中查找并利用短的代码片段,这些片段通常以
ret
结尾。每个gadget执行一个简单的操作(如寄存器操作、内存访问等)。 - 构造ROP链:通过在栈中精心排列这些gadget的地址,形成一个指令序列,依次执行每个gadget,从而实现复杂的行为。
- 控制返回地址:覆盖栈上的返回地址,使程序在执行
ret
指令时跳转到ROP链中的下一个gadget。
栈中保存的是gadget的地址,所以图中形象的地用指针指向了Gadget code
本题目栈的具体内容为:
........................... ——|—————————————--------|—— --| touch2的地址 |-- ——|—————————————--------|—— --| gadget2的地址 |-- ——|—————————————--------|—— --| cookie的数据 |-- ——|—————————————--------|—— ——| test函数返回地址 |—— --|(被覆盖为gadget1的地址) |-- ——|—————————————--------|—— ——|.....................|<--getbuf栈帧 --|—————————————--------|--
其中gadget1为
58 popq %rax 90 nop c3 ret
gadget2为
48 89 c7 movq %rax,%rdi c3 ret
每一个gadget后都跟有一个ret指令, 不断地从栈中返回下一条执行的地址或数据,
Code and Notebook
参考博客
本文作者:次林梦叶
本文链接:https://www.cnblogs.com/cilinmengye/p/18076380
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步