皮蛋厂的学习日记 | 2022.03.23 exit_hook(源码分析) & IO_FREAD

admin 2022年3月23日12:18:06评论6 views字数 19128阅读63分45秒阅读模式

皮蛋厂的学习日记系列为山东警察学院网安社成员日常学习分享,希望能与大家共同学习、共同进步~


  • 2020级 大能猫 | exit_hook(源码分析)

    • 前言

    • 原理分析

  • 2020级 shsuhushu | IO_FREAD

    • 0.1 struct _IO_FILE

    • 0.2 _IO_FILE_plus & *vtable

    • 0.3 fread


PWN

2020级 大能猫 | exit_hook(源码分析)

前言

在进行堆漏洞的利用的时候,受到程序保护的影响会限制掉我们修改got,plt等一下表。为了绕过这样的保护我们只能够利用一些调用链上的修改劫持程序的执行流来达到我们想要的效果。hook就是一种我们可以利用的调用链上的东西,在平时我们利用malloc_hook、free_hook居多,本篇文章讲的是另一种hook的利用——exit_hook。总的来说就是更改exit某一结构体可以事项exit()函数的劫持。

原理分析

先进行exit函数源码的分析

首先我们需要先写一个测试程序来调试,结合源码来查看exit函数调用的流程

#include<stdio.h>
#include<stdlib.h>
int main()
{
    puts('hahahahahahaha,xswl!');
    exit(0);
    return 0;
}//gcc demo.c -o dem

用gdb调试一下

皮蛋厂的学习日记 | 2022.03.23  exit_hook(源码分析) & IO_FREAD

单步调试,从一开始下断点的puts函数开始往下走

到了执行exit(0)的地方,我们si步入看看exit究竟是如何调用的。

首先我么先静态审一下exit函数的源码:我们看到执行exit()函数首先调用的是__run_exit_handlers,同样在源码中可以看到参数的类型也对应了在gdb中我们调试中显示的参数。

皮蛋厂的学习日记 | 2022.03.23  exit_hook(源码分析) & IO_FREAD
皮蛋厂的学习日记 | 2022.03.23  exit_hook(源码分析) & IO_FREAD

在源码中,我们可以找到函数__run_exit_handlers的相关定义,通过与上面调试来比对发现是status为0,run_list_atexit,run_dtors为ture。

皮蛋厂的学习日记 | 2022.03.23  exit_hook(源码分析) & IO_FREAD

由于run_dtors为ture,所以还会继续调用 __call_tls_dtors ()函数

皮蛋厂的学习日记 | 2022.03.23  exit_hook(源码分析) & IO_FREAD

在gdb调试的过程中也有所体现,si进入函数__run_exit_handlers后

皮蛋厂的学习日记 | 2022.03.23  exit_hook(源码分析) & IO_FREAD

找到__call_tls_dtors源码:

struct dtor_list
{

  dtor_func func;
  void *obj;
  struct link_map *map;
  struct dtor_list *next;
};
static __thread struct dtor_list *tls_dtor_list;
static __thread void *dso_symbol_cache;
static __thread struct link_map *lm_cache;
___

、、、、
___
void
__call_tls_dtors (void)
{
  while (tls_dtor_list)
    {
      struct dtor_list *cur = tls_dtor_list;
      dtor_func func = cur->func;
#ifdef PTR_DEMANGLE
      PTR_DEMANGLE (func);
#endif
      tls_dtor_list = tls_dtor_list->next;
      func (cur->obj);
      /* Ensure that the MAP dereference happens before
     l_tls_dtor_count decrement.  That way, we protect this access from a
     potential DSO unload in _dl_close_worker, which happens when
     l_tls_dtor_count is 0.  See CONCURRENCY NOTES for more detail.  */

      atomic_fetch_add_release (&cur->map->l_tls_dtor_count, -1);
      free (cur);
    }
}

查看__call_tls_dtors源码,同时gdb跟进__call_tls_dtors。里面有一个dtor_list结构体定义的tls_dtor_list指针,__call_tls_dtors这个函数的作用就是遍历tls_dtor_list结构体链表,每次遍历都会用到tls_dtor_list里的func,将tls_dtor_list里的obj作为第一个参数,这里其实也可以进行利用,只要将tls_dtor_list覆盖成我们的堆地址,便可以控制调用函数和其参数。

皮蛋厂的学习日记 | 2022.03.23  exit_hook(源码分析) & IO_FREAD

我们在__run_exit_handlers中找到了,三个关键函数的调用,源码如下:

皮蛋厂的学习日记 | 2022.03.23  exit_hook(源码分析) & IO_FREAD

在gdb中发现关键跳转

皮蛋厂的学习日记 | 2022.03.23  exit_hook(源码分析) & IO_FREAD

是调用了__dl_init函数,那么我们就查看下此函数的源码。

#ifdef SHARED
  int do_audit = 0;
 again:
#endif
  for (Lmid_t ns = GL(dl_nns) - 1; ns >= 0; --ns)
    {
      /* Protect against concurrent loads and unloads.  */
      __rtld_lock_lock_recursive (GL(dl_load_lock));

      unsigned int nloaded = GL(dl_ns)[ns]._ns_nloaded;
      /* No need to do anything for empty namespaces or those used for
     auditing DSOs.  */

      if (nloaded == 0
#ifdef SHARED
      || GL(dl_ns)[ns]._ns_loaded->l_auditing != do_audit
#endif
      )
    __rtld_lock_unlock_recursive (GL(dl_load_lock));

有两个关键call

 __rtld_lock_lock_recursive (GL(dl_load_lock));
__rtld_lock_unlock_recursive (GL(dl_load_lock));

github上面查到了__rtld_lock_unlock_recursive的定义源码

皮蛋厂的学习日记 | 2022.03.23  exit_hook(源码分析) & IO_FREAD

再寻找GL定义

#  define GL(name) _rtld_local._##name
else
#  define GL(name) _rtld_global._##name

存在_rtld_local结构体,动调查看一下_rtld_local结构体

pwndbg> p _rtld_global
$1 = {
  _dl_ns = {{
      _ns_loaded = 0x7ffff7ffe190,
      _ns_nloaded = 4,
      _ns_main_searchlist = 0x7ffff7ffe450,
      _ns_global_scope_alloc = 0,
      _ns_global_scope_pending_adds = 0,
      _ns_unique_sym_table = {
        lock = {
          mutex = {
            __data = {
              __lock = 0,
              __count = 0,
              __owner = 0,
              __nusers = 0,
              __kind = 1,
              __spins = 0,
              __elision = 0,
              __list = {
                __prev = 0x0,
                __next = 0x0
              }
            },
            __size = '00' <repeats 16 times>, "01"'00' <repeats 22 times>,
            __align = 0
          }
        },
        entries = 0x0,
        size = 0,
        n_elements = 0,
        free = 0x0
      },
      _ns_debug = {
        r_version = 0,
        r_map = 0x0,
        r_brk = 0,
        r_state = RT_CONSISTENT,
        r_ldbase = 0
      }
    }, {
      _ns_loaded = 0x0,
      _ns_nloaded = 0,
      _ns_main_searchlist = 0x0,
      _ns_global_scope_alloc = 0,
      _ns_global_scope_pending_adds = 0,
      _ns_unique_sym_table = {
        lock = {
          mutex = {
            __data = {
              __lock = 0,
              __count = 0,
              __owner = 0,
              __nusers = 0,
              __kind = 0,
              __spins = 0,
              __elision = 0,
              __list = {
                __prev = 0x0,
                __next = 0x0
              }
            },
            __size = '00' <repeats 39 times>,
            __align = 0
          }
        },
        entries = 0x0,
        size = 0,
        n_elements = 0,
        free = 0x0
      },
      _ns_debug = {
        r_version = 0,
        r_map = 0x0,
        r_brk = 0,
        r_state = RT_CONSISTENT,
        r_ldbase = 0
      }
    } <repeats 15 times>},
  _dl_nns = 1,
  _dl_load_lock = {
    mutex = {
      __data = {
        __lock = 0,
        __count = 0,
        __owner = 0,
        __nusers = 0,
        __kind = 1,
        __spins = 0,
        __elision = 0,
        __list = {
          __prev = 0x0,
          __next = 0x0
        }
      },
      __size = '00' <repeats 16 times>, "01"'00' <repeats 22 times>,
      __align = 0
    }
  },
  _dl_load_write_lock = {
    mutex = {
      __data = {
        __lock = 0,
        __count = 0,
        __owner = 0,
        __nusers = 0,
        __kind = 1,
        __spins = 0,
        __elision = 0,
        __list = {
          __prev = 0x0,
          __next = 0x0
        }
      },
      __size = '00' <repeats 16 times>, "01"'00' <repeats 22 times>,
      __align = 0
    }
  },
  _dl_load_adds = 4,
  _dl_initfirst = 0x0,
  _dl_profile_map = 0x0,
  _dl_num_relocations = 93,
  _dl_num_cache_relocations = 3,
  _dl_all_dirs = 0x7ffff7ffecd0,
  _dl_rtld_map = {
    l_addr = 140737353936896,
    l_name = 0x555555554318 "/lib64/ld-linux-x86-64.so.2",
    l_ld = 0x7ffff7ffce68,
    l_next = 0x0,
    l_prev = 0x7ffff7fb3000,
    l_real = 0x7ffff7ffd9e8 <_rtld_global+2440>,
    l_ns = 0,
    l_libname = 0x7ffff7ffe050,
    l_info = {0x00x00x7ffff7ffcee80x7ffff7ffced80x7ffff7ffce780x7ffff7ffce980x7ffff7ffcea80x7ffff7ffcf180x7ffff7ffcf280x7ffff7ffcf380x7ffff7ffceb80x7ffff7ffcec80x00x00x7ffff7ffce680x00x00x00x00x00x7ffff7ffcef80x00x00x7ffff7ffcf080x0 <repeats 13 times>, 0x7ffff7ffcf580x7ffff7ffcf480x00x00x7ffff7ffcf780x00x00x00x00x00x00x00x00x7ffff7ffcf680x0 <repeats 25 times>, 0x7ffff7ffce88},
    l_phdr = 0x7ffff7fcf040,
    l_entry = 0,
    l_phnum = 11,
    l_ldnum = 0,
    l_searchlist = {
      r_list = 0x0,
      r_nlist = 0
    },
    l_symbolic_searchlist = {
      r_list = 0x0,
      r_nlist = 0
    },
    l_loader = 0x0,
    l_versions = 0x7ffff7fb3930,
    l_nversions = 6,
    l_nbuckets = 17,
    l_gnu_bitmask_idxbits = 3,
    l_gnu_shift = 8,
    l_gnu_bitmask = 0x7ffff7fcf3d8,
    {
      l_gnu_buckets = 0x7ffff7fcf3f8,
      l_chain = 0x7ffff7fcf3f8
    },
    {
      l_gnu_chain_zero = 0x7ffff7fcf438,
      l_buckets = 0x7ffff7fcf438
    },
    l_direct_opencount = 0,
    l_type = lt_library,
    l_relocated = 1,
    l_init_called = 1,
    l_global = 1,
    l_reserved = 0,
    l_phdr_allocated = 0,
    l_soname_added = 0,
    l_faked = 0,
    l_need_tls_init = 0,
    l_auditing = 0,
    l_audit_any_plt = 0,
    l_removed = 0,
    l_contiguous = 0,
    l_symbolic_in_local_scope = 0,
    l_free_initfini = 0,
    l_nodelete_active = false,
    l_nodelete_pending = false,
    l_cet = lc_unknown,
    l_rpath_dirs = {
      dirs = 0x0,
      malloced = 0
    },
    l_reloc_result = 0x0,
    l_versyms = 0x7ffff7fcfa14,
    l_origin = 0x0,
    l_map_start = 140737353936896,
    l_map_end = 140737354129808,
    l_text_end = 140737354081908,
    l_scope_mem = {0x00x00x00x0},
    l_scope_max = 0,
    l_scope = 0x0,
    l_local_scope = {0x00x0},
    l_file_id = {
      dev = 0,
      ino = 0
    },
    l_runpath_dirs = {
      dirs = 0x0,
      malloced = 0
    },
    l_initfini = 0x0,
    l_reldeps = 0x0,
    l_reldepsmax = 0,
    l_used = 1,
    l_feature_1 = 0,
    l_flags_1 = 0,
    l_flags = 0,
    l_idx = 0,
    l_mach = {
      plt = 0,
      gotplt = 0,
      tlsdesc_table = 0x0
    },
    l_lookup_cache = {
      sym = 0x7ffff7fcf580,
      type_class = 1,
      value = 0x7ffff7fb3000,
      ret = 0x7ffff7dc62d0
    },
    l_tls_initimage = 0x0,
    l_tls_initimage_size = 0,
    l_tls_blocksize = 0,
    l_tls_align = 0,
    l_tls_firstbyte_offset = 0,
    l_tls_offset = 0,
    l_tls_modid = 0,
    l_tls_dtor_count = 0,
    l_relro_addr = 185632,
    l_relro_size = 2784,
    l_serial = 0
  },
  _dl_rtld_auditstate = {{
      cookie = 0,
      bindflags = 0
    } <repeats 16 times>},
  _dl_rtld_lock_recursive = 0x7ffff7fd0150,
  _dl_rtld_unlock_recursive = 0x7ffff7fd0160,
  _dl_x86_feature_1 = {00},
  _dl_x86_legacy_bitmap = {00},
  _dl_make_stack_executable_hook = 0x7ffff7fe4130 <_dl_make_stack_executable>,
  _dl_stack_flags = 6,
  _dl_tls_dtv_gaps = false,
  _dl_tls_max_dtv_idx = 1,
  _dl_tls_dtv_slotinfo_list = 0x7ffff7fb39c0,
  _dl_tls_static_nelem = 1,
  _dl_tls_static_size = 4160,
  _dl_tls_static_used = 144,
  _dl_tls_static_align = 64,
  _dl_initial_dtv = 0x7ffff7fb4e70,
  _dl_tls_generation = 1,
  _dl_init_static_tls = 0x7ffff7fdc850,
  _dl_wait_lookup_done = 0x0,
  _dl_scope_free_list = 0x0
}
皮蛋厂的学习日记 | 2022.03.23  exit_hook(源码分析) & IO_FREAD

找到函数存放位置,所以__rtld_lock_unlock_recursive_rtld_global结构题的指针变量。在exit()中执行流程为

exit()->__run_exit_handlers->_dl_fini->__rtld_lock_unlock_recursive

由于__rtld_lock_unlock_recursive存放在结构体空间,为可读可写,那么如果可以修改__rtld_lock_unlock_recursive,就可以在调用exit()时劫持程序流。

_rtld_lock_lock_recursive也是一样的流程。

2020级 shsuhushu | IO_FREAD

0.1 struct _IO_FILE

struct _IO_FILE {
  int _flags;       /* High-order word is _IO_MAGIC; rest is flags. */
#define _IO_file_flags _flags

  /* The following pointers correspond to the C++ streambuf protocol. */
  /* Note:  Tk uses the _IO_read_ptr and _IO_read_end fields directly. */
  char* _IO_read_ptr;   /* Current read pointer */
  char* _IO_read_end;   /* End of get area. */
  char* _IO_read_base;  /* Start of putback+get area. */
  char* _IO_write_base; /* Start of put area. */
  char* _IO_write_ptr;  /* Current put pointer. */
  char* _IO_write_end;  /* End of put area. */
  char* _IO_buf_base;   /* Start of reserve area. */
  char* _IO_buf_end;    /* End of reserve area. */
  /* The following fields are used to support backing up and undo. */
  char *_IO_save_base; /* Pointer to start of non-current get area. */
  char *_IO_backup_base;  /* Pointer to first valid character of backup area */
  char *_IO_save_end; /* Pointer to end of non-current get area. */

  struct _IO_marker *_markers;

  struct _IO_FILE *_chain;

  int _fileno;
#if 0
  int _blksize;
#else
  int _flags2;
#endif
  _IO_off_t _old_offset; /* This used to be _offset but it's too small.  */

#define __HAVE_COLUMN /* temporary */
  /* 1+column number of pbase(); 0 is unknown. */
  unsigned short _cur_column;
  signed char _vtable_offset;
  char _shortbuf[1];

  /*  char* _save_gptr;  char* _save_egptr; */

  _IO_lock_t *_lock;
#ifdef _IO_USE_OLD_IO_FILE
};

/////////////////////////////////////////////////////////////////////////////////////////

struct _IO_FILE_complete
{

  struct _IO_FILE _file;
#endif
#if defined _G_IO_IO_FILE_VERSION && _G_IO_IO_FILE_VERSION == 0x20001
  _IO_off64_t _offset;
if defined _LIBC || defined _GLIBCPP_USE_WCHAR_T
  /* Wide character stream stuff.  */
  struct _IO_codecvt *_codecvt;
  struct _IO_wide_data *_wide_data;
  struct _IO_FILE *_freeres_list;
  void *_freeres_buf;
else
  void *__pad1;
  void *__pad2;
  void *__pad3;
  void *__pad4;

  size_t __pad5;
  int _mode;
  /* Make sure we don't get into trouble again.  */
  char _unused2[15 * sizeof (int) - 4 * sizeof (void *) - sizeof (size_t)];
#endif
};

进程中的 FILE 结构会通过 _chain 域彼此连接形成一个链表,链表头部用全局变量 _IO_list_all 表示,64位程序下其偏移为0x60。

在标准 I/O 库中,每个程序启动时有三个文件流是自动打开的:stdin、stdout、stderr。因此在初始状态下,_IO_list_all 指向了一个有这些文件流构成的链表

但是需要注意的是这三个文件流位于 libc.so 的数据段。而我们使用 fopen 创建的文件流是分配在堆内存上的。我们可以在 libc.so 中找到 stdinstdoutstderr 等符号,这些符号是指向 FILE 结构的指针

FILE 结构如下图所示。

皮蛋厂的学习日记 | 2022.03.23  exit_hook(源码分析) & IO_FREAD

0.2 _IO_FILE_plus & *vtable

struct _IO_jump_t
{

    JUMP_FIELD(_G_size_t, __dummy);
#ifdef _G_USING_THUNKS
    JUMP_FIELD(_G_size_t, __dummy2);
#endif
    JUMP_FIELD(_IO_finish_t, __finish);
    JUMP_FIELD(_IO_overflow_t, __overflow);
    JUMP_FIELD(_IO_underflow_t, __underflow);
    JUMP_FIELD(_IO_underflow_t, __uflow);
    JUMP_FIELD(_IO_pbackfail_t, __pbackfail);
    /* showmany */
    JUMP_FIELD(_IO_xsputn_t, __xsputn);
    JUMP_FIELD(_IO_xsgetn_t, __xsgetn);
    JUMP_FIELD(_IO_seekoff_t, __seekoff);
    JUMP_FIELD(_IO_seekpos_t, __seekpos);
    JUMP_FIELD(_IO_setbuf_t, __setbuf);
    JUMP_FIELD(_IO_sync_t, __sync);
    JUMP_FIELD(_IO_doallocate_t, __doallocate);
    JUMP_FIELD(_IO_read_t, __read);
    JUMP_FIELD(_IO_write_t, __write);
    JUMP_FIELD(_IO_seek_t, __seek);
    JUMP_FIELD(_IO_close_t, __close);
    JUMP_FIELD(_IO_stat_t, __stat);
    JUMP_FIELD(_IO_showmanyc_t, __showmanyc);
    JUMP_FIELD(_IO_imbue_t, __imbue);
#if 0
    get_column;
    set_column;
#endif
};

/* We always allocate an extra word following an _IO_FILE.
   This contains a pointer to the function jump table used.
   This is for compatibility with C++ streambuf; the word can
   be used to smash to a pointer to a virtual function table. */


struct _IO_FILE_plus
{

  _IO_FILE file;
  const struct _IO_jump_t *vtable;
};

...

extern struct _IO_FILE_plus *_IO_list_all;

...

IO_jump_t 中保存了许多函数指针,在后面我们会看到在一系列标准 I/O 函数中会调用这些函数指针,在glibc里可以搜到 _IO_jump_t 的实例,在 libio/fileops.c 里面

struct _IO_jump_t _IO_file_jumps =
{

  JUMP_INIT_DUMMY,
  JUMP_INIT(finish, _IO_new_file_finish),
  JUMP_INIT(overflow, _IO_new_file_overflow),
  JUMP_INIT(underflow, _IO_new_file_underflow),
  JUMP_INIT(uflow, _IO_default_uflow),
  JUMP_INIT(pbackfail, _IO_default_pbackfail),
  JUMP_INIT(xsputn, _IO_new_file_xsputn),
  JUMP_INIT(xsgetn, _IO_file_xsgetn),
  JUMP_INIT(seekoff, _IO_new_file_seekoff),
  JUMP_INIT(seekpos, _IO_default_seekpos),
  JUMP_INIT(setbuf, _IO_new_file_setbuf),
  JUMP_INIT(sync, _IO_new_file_sync),
  JUMP_INIT(doallocate, _IO_file_doallocate),
  JUMP_INIT(read, _IO_file_read),
  JUMP_INIT(write, _IO_new_file_write),
  JUMP_INIT(seek, _IO_file_seek),
  JUMP_INIT(close, _IO_file_close),
  JUMP_INIT(stat, _IO_file_stat),
  JUMP_INIT(showmanyc, _IO_default_showmanyc),
  JUMP_INIT(imbue, _IO_default_imbue)
};

然后又出现一个 _IO_FILE_plus 结构体,内部嵌入 _IO_FILE ,其中包含了一个重要的指针 *vtable,是 _IO_jump_t 类型的。

在 libc2.23 版本下,32 位的 vtable 偏移为 0x94,64 位偏移为 0xd8

所以 I/O流如下图所示

皮蛋厂的学习日记 | 2022.03.23  exit_hook(源码分析) & IO_FREAD

0.3 fread

fread 是标准 IO 库函数,作用是从文件流中读数据

_IO_size_t
_IO_fread (buf, size, count, fp)
     void *buf;
     _IO_size_t size;
     _IO_size_t count;
     _IO_FILE *fp;
{
  _IO_size_t bytes_requested = size * count;
  _IO_size_t bytes_read;
  CHECK_FILE (fp, 0);
  if (bytes_requested == 0)
    return 0;
  _IO_cleanup_region_start ((void (*) __P ((void *))) _IO_funlockfile, fp);
  _IO_flockfile (fp);
  bytes_read = _IO_sgetn (fp, (char *) buf, bytes_requested);
  _IO_funlockfile (fp);
  _IO_cleanup_region_end (0);
  return bytes_requested == bytes_read ? count : bytes_read / size;
}

我们不难发现真正的功能实现在子函数 _IO_sgetn 中,在 libio/genops.c 里

_IO_size_t
_IO_sgetn (fp, data, n)
     _IO_FILE *fp;
     void *data;
     _IO_size_t n;
{
  /* FIXME handle putback buffer here! */
  return _IO_XSGETN (fp, data, n);
}

而 _IO_XSGETN 是个宏,在 libioP.h 里

typedef _IO_size_t (*_IO_xsgetn_t) __PMT ((_IO_FILE *FP, void *DATA,
                       _IO_size_t N));
#define _IO_XSGETN(FP, DATA, N) JUMP2 (__xsgetn, FP, DATA, N)
#define _IO_WXSGETN(FP, DATA, N) WJUMP2 (__xsgetn, FP, DATA, N)

而 __xsgetn 出现在 _IO_jump_t 中,最终指向 _IO_file_xsgetn,在 fileops.c 中

_IO_size_t
_IO_file_xsgetn (fp, data, n)
     _IO_FILE *fp;
     void *data;
     _IO_size_t n;
{
  register _IO_size_t want, have;
  register _IO_ssize_t count;
  register char *s = data;

  want = n;

  if (fp->_IO_buf_base == NULL)
    {
      /* Maybe we already have a push back pointer.  */
      if (fp->_IO_save_base != NULL)
    {
      free (fp->_IO_save_base);
      fp->_flags &= ~_IO_IN_BACKUP;
    }
      _IO_doallocbuf (fp);
    }

  while (want > 0)
    {
      have = fp->_IO_read_end - fp->_IO_read_ptr;
      if (want <= have)
    {
      memcpy (s, fp->_IO_read_ptr, want);
      fp->_IO_read_ptr += want;
      want = 0;
    }
      else
    {
      if (have > 0)
        {
#ifdef _LIBC
          s = __mempcpy (s, fp->_IO_read_ptr, have);
#else
          memcpy (s, fp->_IO_read_ptr, have);
          s += have;
#endif
          want -= have;
          fp->_IO_read_ptr += have;
        }

      /* Check for backup and repeat */
      if (_IO_in_backup (fp))
        {
          _IO_switch_to_main_get_area (fp);
          continue;
        }

      /* If we now want less than a buffer, underflow and repeat
         the copy.  Otherwise, _IO_SYSREAD directly to
         the user buffer. */

      if (fp->_IO_buf_base && want < fp->_IO_buf_end - fp->_IO_buf_base)
        {
          if (__underflow (fp) == EOF)
        break;

          continue;
        }

      /* These must be set before the sysread as we might longjmp out
         waiting for input. */

      _IO_setg (fp, fp->_IO_buf_base, fp->_IO_buf_base, fp->_IO_buf_base);
      _IO_setp (fp, fp->_IO_buf_base, fp->_IO_buf_base);

      /* Try to maintain alignment: read a whole number of blocks.  */
      count = want;
      if (fp->_IO_buf_base)
        {
          _IO_size_t block_size = fp->_IO_buf_end - fp->_IO_buf_base;
          if (block_size >= 128)
        count -= want % block_size;
        }

      count = _IO_SYSREAD (fp, s, count);
      if (count <= 0)
        {
          if (count == 0)
        fp->_flags |= _IO_EOF_SEEN;
          else
        fp->_flags |= _IO_ERR_SEEN;

          break;
        }

      s += count;
      want -= count;
      if (fp->_offset != _IO_pos_BAD)
        _IO_pos_adjust (fp->_offset, count);
    }
    }

  return n - want;
}

_IO_file_xsgetn 是处理 fread 读入数据的核心函数,分为三个部分:

  • 第一部分是fp->_IO_buf_base为空的情况,表明此时的FILE结构体中的指针未被初始化,输入缓冲区未建立,则调用_IO_doallocbuf去初始化指针,建立输入缓冲区。
  • 第二部分是输入缓冲区里有输入,即fp->_IO_read_ptr小于fp->_IO_read_end,此时将缓冲区里的数据直接拷贝至目标buffer。
  • 第三部分是输入缓冲区里的数据为空或者是不能满足全部的需求,则调用__underflow调用系统调用读入数据。

所以我们可以先看一下 _IO_doallocbuf ,在 libio/genops.c 里

void
_IO_doallocbuf (fp)
     _IO_FILE *fp;
{
  if (fp->_IO_buf_base) //_IO_buf_base为空
    return;
  if (!(fp->_flags & _IO_UNBUFFERED) || fp->_mode > 0//检查标志位
    if (_IO_DOALLOCATE (fp) != EOF) // 调用 _IO_DOALLOCATE
      return;
  _IO_setb (fp, fp->_shortbuf, fp->_shortbuf+10);
}

先判断 _IO_buf_base 是否为空,如果不为空,说明已经使用过了,直接返回

再检查 fp->_flags 是不是 _IO_UNBUFFERED,或者 fp -> _mode > 0

如果满足条件调用 _IO_DOALLOCATE,它跟 _IO_XSGETN 一样也是个宏,最终调用 _IO_file_doallocate ,在 libio/filedoalloc.c 中

int
_IO_file_doallocate (fp)
     _IO_FILE *fp;
{
  _IO_size_t size;
  int couldbetty;
  char *p;
  struct _G_stat64 st;

#ifndef _LIBC
  /* If _IO_cleanup_registration_needed is non-zero, we should call the
     function it points to.  This is to make sure _IO_cleanup gets called
     on exit.  We call it from _IO_file_doallocate, since that is likely
     to get called by any program that does buffered I/O. */

  if (_IO_cleanup_registration_needed)
    (*_IO_cleanup_registration_needed) ();
#endif

  if (fp->_fileno < 0 || _IO_SYSSTAT (fp, &st) < 0)
    {
      couldbetty = 0;
      size = _IO_BUFSIZ;
#if 0
      /* do not try to optimise fseek() */
      fp->_flags |= __SNPT;
#endif
    }
  else
    {
      couldbetty = S_ISCHR (st.st_mode);
#if _IO_HAVE_ST_BLKSIZE
      size = st.st_blksize <= 0 ? _IO_BUFSIZ : st.st_blksize;
#else
      size = _IO_BUFSIZ;
#endif
    }
  ALLOC_BUF (p, size, EOF);
  _IO_setb (fp, p, p + size, 1);
  if (couldbetty && isatty (fp->_fileno))
    fp->_flags |= _IO_LINE_BUF;
  return 1;
}


原文始发于微信公众号(山警网络空间安全实验室):皮蛋厂的学习日记 | 2022.03.23 exit_hook(源码分析) & IO_FREAD

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2022年3月23日12:18:06
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   皮蛋厂的学习日记 | 2022.03.23 exit_hook(源码分析) & IO_FREADhttps://cn-sec.com/archives/838948.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息