House of botcake与IOFILE任意读写
利用条件与效果及使用范围应该是在libc2.31这个手法要的条件就是两个UAF与申请足够数量的堆块。而效果就是任意地址写配合对IOFILE结构体的利用可以实现任意读结合起来就可以任意读写。主要思路就是绕过tcache的double free检测即tcache key字段来完成攻击。虽然自有tcache机制以来到现在都可以使用不过因为之前tcache检测很弱直接double free也没有影响所以主要还是glibc2.29-至今用的比较多。但因为在libc2.32加入了safelinking机制所以在2.32及之后这个手法就还是回归正常tcache attack了没有任意读写这么强了得靠其他漏洞的提前泄露。攻击思路及流程主要流程就是先申请7个同一大小的堆块再申请两个相同大小的堆块前申请的我们不妨叫堆块a后申请的叫堆块b再申请一个堆块防止合并。释放申请的9个堆块其中7个堆块会填满tcache对应大小的链表此时因为tcache已满我们再释放相同大小的堆块就会进入unsortedbin。同时因为unsortedbin的堆块如果物理相邻就会合并我们需要让堆块a和b合并。合并后我们再申请一个相同大小的堆块由于系统会首先遍历tcache链表如果能找到就会在tcache里取堆块。所以会从tcache里取一个堆块出来此时我们利用UAF漏洞再free一次堆块b这样堆块b就又在tcache又在unsortedbin了。为什么能free实际上free一个指针应该只会检测这个指针*(这个存放这个指针的地址-8)的地址的p标志位是不是1以及这个指针*(这个存放这个指针的地址-8)的地址的下一个地址的p标志位是不是1。文字描述太抽象了比如有UAF我们直接在链表里找出来那个地址。这个地址一般就是我们写入数据的地址我们用这个地址减0x10再用heap 这个地址这个命令看一下堆块的完整性即可。此时如果我们申请比堆块a大小大一点的堆块由于在tcache里找不到系统就会在unsortedbin里切割此时就会切割我们ab合并后的堆块了。此时我们就完成了overlapping可以改在tcache里的堆块b的fd指针实现任意地址写了。如果我们不这样申请而是多申请几次申请的堆块的总大小等于堆块a的大小就可以在堆块b的fd指针里留下main_arena相关的地址而这个地址又和libc相关。只要我们再继续切割unsortedbin不就可以改堆块b的fd指针吗只要爆破两位就可以构造出指向IOFILE结构体的指针进而完成任意读写。IOFILE任意读写目前我感觉我还是不太有能力结合源码分析各位如果想看结合源码分析可以看raycp师傅的文章IO FILE 之任意读写写的非常好。本文大部分也借鉴了其中的内容不过我是以比较宏观的视角来分析的可能比较好理解一点。不过源码肯定要看之后我也会结合源码讲讲。先回顾一下IOFILE结构体0x0 _flags0x8 _IO_read_ptr0x10 _IO_read_end0x18 _IO_read_base0x20 _IO_write_base0x28 _IO_write_ptr0x30 _IO_write_end0x38 _IO_buf_base0x40 _IO_buf_end0x48 _IO_save_base0x50 _IO_backup_base0x58 _IO_save_end0x60 _markers0x68 _chain0x70 _fileno0x74 _flags20x78 _old_offset0x80 _cur_column0x82 _vtable_offset0x83 _shortbuf0x88 _lock0x90 _offset0x98 _codecvt0xa0 _wide_data0xa8 _freeres_list0xb0 _freeres_buf0xb8 __pad50xc0 _mode0xc4 _unused20xd8 vtable我们知道除了进行系统调用的输入/输出readwrite这些其他的输入/输出都会用到IOFILE结构体即三个输入/输出流中即stdin(标准输入)stdout(标准输出)stderr(标准错误)。而我们输入/输出一般都是缓冲输入/输出。我们可以这些流中的结构体成员起到任意读写的效果。比如stdin我们就是利用了改变输入缓冲区的地址来把我们本应该输入到输入缓冲区的数据先输入到了我们要输入的目标地址即控制stdin的_IO_buf_base与 _IO_buf_end。Stdin利用流程当然我们利用肯定是有条件的:条件第一个就是输入缓冲区要没有数据即_IO_read_end —— _ IO_read_ptr 0条件第二个就是flags字段表明这地方可写即flag里没有不可写的位标志其中c语言定义了#define _IO_NO_READS 4也就是我们的flags字段0x4要为0即设置flags~0x4条件第三个我们输入的文件描述符如果想用键盘输入就要把_fileno设为0条件第四个就是 _IO_buf_base设置成我们写入的地址 _IO_buf_end设置成我们写入结束的地址。且输入缓冲区的大小即 _IO_buf_end- _IO_buf_base要大于我们输入的数据的大小。这样我们就可以利用stdin进行任意写了。当然其实实际上一般我们很多条件本来就会满足我们改的时候注意一下这些条件即可一般就改 _IO_buf_base与 _IO_buf_end就可以了。Stdout利用流程任意写由于stdout即有把输出缓冲区的数据输出出来又有把数据写入输出缓冲区所以利用stdout可以进行任意读写。但是这个任意写需要我们能控制输出的数据并不能直接从键盘上读取数据。实际利用的话把_IO_write_ptr(输出缓冲区的开始)改成我们想写的地址的开始把 _IO_write_end(输出缓冲区的结束)改成我们想写地址的结束即可。任意读跟stdin类似我们攻击的是输出缓冲区利用输出时如果系统检测到输出缓冲区有数据就会刷新输出缓冲区的机制把 _IO_write_base改成我们想泄露数据的开始把 _IO_write_end改成我们想泄露数据的结束即可。条件就是flags标志不能有_IO_NO_WRITES标志即flags0x8必须为0。可以设置flags~0x8flags标志最好有_IO_CURRENTLY_PUTTING标志因为不然会多一些操作让流程不可控可以设置flag | 0x800设置_fileno为1设置_IO_read_end等于 _IO_write_base 或设置 _flag _IO_IS_APPENDING即 _flag | 0x1000。把 _IO_write_base改成我们想泄露数据的开始把 _IO_write_ptr改成我们想泄露数据的结束即可。下面我们看例题2026ISCC线上挑战赛决赛——note这个比赛如何暂且不骂了这个远程靶机也先不骂了。单单看这个题还是出的可以的。题开了pie开了沙箱只允许ORWglibc给的版本是2.31我们当2.31打吧。远程好像不是这个版本(我***)逻辑就是普通的堆菜单题有edit功能但是没有show功能不能输出数据。有UAF索引可以申请0-0x15但索引可以复用。所以我们可以打House of botcake然后攻击stdout泄露出libc此时就有两种思路了。第一种是我们之前在打stdout的时候已经有了攻击stdout的伪造的堆块所以我们可以用edit功能一直打stdout。这样我们就可以利用stdout读环境变量泄露栈地址打栈上rop。当然没有edit也影响不大因为索引可以复用再打一遍House of botcake也一样。第二种是因为版本是2.31我们可以打__free_hook把freehook改成magic gadget然后利用setcontext调用实现栈迁移把栈迁移到freehook来并且利用setcontext调用read往freehook上写rop链实现ORW。这两种都可以这里要泄露堆地址个人感觉不是很方便所以就没想过写shellcode的。用mprotect把freehook附近的地址改成可执行然后在上面填shellcode感觉应该也可以。第一种的exp如下#!/usr/bin/env python3from pwn import *import sysfrom ctypes import *#from pwncli import *import socks# cli_script()#from ae64 import AE64#from pymao import *context.log_leveldebugcontext.archamd64elfELF(./pwn)libc ELF(./libc.so.6)# libc1cdll.LoadLibrary(./libc.so.6)li./libc.so.6socks.set_default_proxy(socks.SOCKS5,81.dart.ccsssc.com,25790,username1nkvap1o,passwordcl330rd,rdnsTrue)socket.socket socks.socksocketflag 0if flag:p remote(39.96.193.120,10011)else:p process(./pwn)sa lambda s,n : p.sendafter(s,n)sla lambda s,n : p.sendlineafter(s,n)sl lambda s : p.sendline(s)slr lambda s : p.sendline(str(s))sd lambda s : p.send(s)sdr lambda s : p.send(str(s).encode())rc lambda n : p.recv(n)ru lambda s : p.recvuntil(s)ti lambda : p.interactive()rcl lambda : p.recvline()leak lambda name,addr :log.success(name---hex(addr))u6 lambda a : u64(rc(a).ljust(8,b\x00).strip())i6 lambda a : int(a,16)def csu():payp64(0)p64(0)p64(1)return paydef ph(s):print(hex(s))def dbg():# context.terminal [tmux, splitw, -h]gdb.attach(p)#maybe gdbscriptset debug-file-directory ./starpause()def add(s,a,d):ru(bChoice: )sdr(1)ru(bIndex: )sdr(s)ru(bSize: )sdr(a)ru(bContent: )sd(d)def free(s):ru(bChoice: )sdr(2)ru(bIndex: )sdr(s)def edit(s,a):