linux core文件解析

Linux ELF core files

A core file is essentially a snapshot of the process and its state right before it cored
(crashed or dumped). A core file is a type of ELF file that is primarily made up of program
headers and memory segments. They also contain a fair amount of notes in the PT_NOTE
segment that describe file mappings, shared library paths, and other information.

Core file program headers

In a core file, there are many program headers. All of them except one are of the PT_LOAD
type. There is a PT_LOAD program header for every single memory segment in the process,
with the exception of special devices (that is /dev/mem). Everything from shared libraries
and anonymous mappings to the stack, the heap, text, and data segments is represented by
a program header.
Then, there is one program header of the PT_NOTE type; it contains the most useful and
descriptive information in the entire core file.

The PT_NOTE segment

The eu-readelf -n output that is shown next shows the parsing of the core file notes
segment.

$ eu-readelf -n core
Note segment of 4200 bytes at offset 0x900:
Owner Data size Type
CORE 336 PRSTATUS
info.si_signo: 11, info.si_code: 0, info.si_errno: 0, cursig: 11
sigpend: <>
sighold: <>
pid: 9875, ppid: 7669, pgrp: 9875, sid: 5781
utime: 5.292000, stime: 0.004000, cutime: 0.000000, cstime: 0.000000
orig_rax: -1, fpvalid: 1
r15: 0 r14: 0
r13: 140736185205120 r12: 4195616
rbp: 0x00007fffb25380a0 rbx: 0
r11: 582 r10: 140736185204304
r9: 15699984 r8: 1886848000
rax: -1 rcx: -160
rdx: 140674792738928 rsi: 4294967295
rdi: 4196093 rip: 0x000000000040064f
rflags: 0x0000000000000286 rsp: 0x00007fffb2538090
CORE 1812 FILE
30 files:
00400000-00401000 00000000 4096 /home/user/git/azazel/host
00600000-00601000 00000000 4096 /home/user/git/azazel/host
00601000-00602000 00001000 4096 /home/user/git/azazel/host
3001000000-3001019000 00000000 102400 /lib/x86_64-linux-
gnu/libaudit.so.1.0.0
3001019000-3001218000 00019000 2093056 /lib/x86_64-linux-
gnu/libaudit.so.1.0.0
3001218000-3001219000 00018000 4096 /lib/x86_64-linux-
gnu/libaudit.so.1.0.0
3001219000-300121a000 00019000 4096 /lib/x86_64-linux-
gnu/libaudit.so.1.0.0
3003400000-300340d000 00000000 53248 /lib/x86_64-linux-
gnu/libpam.so.0.83.1
300340d000-300360c000 0000d000 2093056 /lib/x86_64-linux-
gnu/libpam.so.0.83.1
300360c000-300360d000 0000c000 4096 /lib/x86_64-linux-
gnu/libpam.so.0.83.1
300360d000-300360e000 0000d000 4096 /lib/x86_64-linux-
gnu/libpam.so.0.83.1
7ff166bd9000-7ff166bdb000 00000000 8192 /lib/x86_64-linux-gnu/libutil-
2.19.so
7ff166bdb000-7ff166dda000 00002000 2093056 /lib/x86_64-linux-gnu/libutil-
2.19.so
7ff166dda000-7ff166ddb000 00001000 4096 /lib/x86_64-linux-gnu/libutil-
2.19.so

Being able to view the register state, auxiliary vector, signal information, and file
mappings is not bad news at all, but they are not enough by themselves to analyze a
process for malware infection.

PT_LOAD segments and the downfalls of core files for forensics purposes

Each memory segment contains a program header that describes the offset, address, and
size of the segment it represents. This would almost suggest that you can access every part
of a process image through the program segments, but this is only partially true. The text
image of the executable and every shared library that is mapped to the process get only the
first 4,096 bytes of themselves dumped into a segment.

This is for saving space and because the Linux kernel developers figured that the text
segment will not be modified in memory. So, it suffices to reference the original
executable file and shared libraries when accessing the text areas from a debugger. If a
core file were to dump the complete text segment for every shared library, then for a large
program such as Wireshark or Firefox, the output core dump files would be enormous.

So for debugging reasons, it is usually okay to assume that the text segments have not
changed in memory, and to just reference the executable and shared library files
themselves to get the text. But what about runtime malware analysis and process memory
forensics? In many cases, the text segments have been marked as writeable and contain
polymorphic engines for code mutation, and in these instances, core files may be useless
for viewing the code segments.

Also, what if the core file is the only artifact available for analysis and the original
executable and shared libraries are no longer accessible? This further demonstrates why
core files are not particularly good for process memory forensics; nor were they ever
meant to be.

源码分析

源码位置:

//fs/binfmt_elf.c
  #ifdef CONFIG_ELF_CORE
  static int elf_core_dump(struct coredump_params *cprm);
  #else
  #define elf_core_dump   NULL
  #endif
  
  static struct linux_binfmt elf_format = {
      .module     = THIS_MODULE,
      .load_binary    = load_elf_binary,
      .load_shlib = load_elf_library,
      .core_dump  = elf_core_dump,                                                                                                                                       
      .min_coredump   = ELF_EXEC_PAGESIZE,
  };
  //fs/coredump.c
  void do_coredump(const siginfo_t *siginfo)
  {
  	  ...
       audit_core_dumps(siginfo->si_signo);
  
      binfmt = mm->binfmt;
      if (!binfmt || !binfmt->core_dump)
          goto fail;
      if (!__get_dumpable(cprm.mm_flags))
          goto fail;
     ...
           if (!dump_interrupted()) {
          file_start_write(cprm.file);
          core_dumped = binfmt->core_dump(&cprm);                                                                                                                        
          file_end_write(cprm.file);
      }  
   ...       
}
//kernel/signal.c
int get_signal(struct ksignal *ksig)
{
	...
          if (sig_kernel_coredump(signr)) {
              if (print_fatal_signals)
                  print_fatal_signal(ksig->info.si_signo);
              proc_coredump_connector(current);
              /*   
               * If it was able to dump core, this kills all
               * other threads in the group and synchronizes with
               * their demise.  If we lost the race with another
               * thread getting here, it set group_exit_code
               * first and our do_group_exit call below will use
               * that value and ignore the one we pass it.
               */
              do_coredump(&ksig->info);
          } 
	...
}

下面具体来分析写elf core dump的动作:

//fs/binfmt_elf.c
  /*
   * Actual dumper
   *
   * This is a two-pass process; first we find the offsets of the bits,
   * and then they are actually written out.  If we run out of core limit
   * we just truncate.
   */
  static int elf_core_dump(struct coredump_params *cprm)
{
	elf = kmalloc(sizeof(*elf), GFP_KERNEL);
      segs = current->mm->map_count;     //segment的个数从vma中得到
      /* for notes section */
      segs++;                     //另外一个segment是note
      e_phnum = segs > PN_XNUM ? PN_XNUM : segs;
      /*   
       * Collect all the non-memory information about the process for the
       * notes.  This also sets up the file header.
       */
      if (!fill_note_info(elf, e_phnum, &info, cprm->siginfo, cprm->regs))  //构造note segment的内容,放在info中,主要是寄存器的值和file map,感兴趣的可以自己去研究这个函数
          goto cleanup;
          
    offset += sizeof(*elf);             /* Elf header */ 
     offset += segs * sizeof(struct elf_phdr);   /* Program headers */
     
           /* Write notes phdr entry */
      {
          size_t sz = get_note_info_size(&info);
  
          sz += elf_coredump_extra_notes_size();
  
          phdr4note = kmalloc(sizeof(*phdr4note), GFP_KERNEL);
          if (!phdr4note)
              goto end_coredump;
  
          fill_elf_note_phdr(phdr4note, sz, offset);    //构造phdr4note, 也就是note segment的phdr
          offset += sz;
      } 
	
	  vma_filesz = kmalloc_array(segs - 1, sizeof(*vma_filesz), GFP_KERNEL);
      if (!vma_filesz)
          goto end_coredump;
  
      for (i = 0, vma = first_vma(current, gate_vma); vma != NULL;
              vma = next_vma(vma, gate_vma)) {
          unsigned long dump_size;
  
          dump_size = vma_dump_size(vma, cprm->mm_flags);
          vma_filesz[i++] = dump_size;
          vma_data_size += dump_size;
      }
      
      if (!dump_emit(cprm, elf, sizeof(*elf)))   //真正写elf头
          goto end_coredump;
  
      if (!dump_emit(cprm, phdr4note, sizeof(*phdr4note)))   //真正写note segment的phdr,从phdr4note中写
          goto end_coredump;
          
      /* Write program headers for segments dump */
      for (i = 0, vma = first_vma(current, gate_vma); vma != NULL;
              vma = next_vma(vma, gate_vma)) {
          struct elf_phdr phdr;
  
          phdr.p_type = PT_LOAD;
          phdr.p_offset = offset;
          phdr.p_vaddr = vma->vm_start;     //虚拟地址
          phdr.p_paddr = 0;                          //物理地址总是为0
          phdr.p_filesz = vma_filesz[i++];     //segment的文件大小
          phdr.p_memsz = vma->vm_end - vma->vm_start;   //segment的内存大小
          offset += phdr.p_filesz;
          phdr.p_flags = vma->vm_flags & VM_READ ? PF_R : 0;
          if (vma->vm_flags & VM_WRITE)
              phdr.p_flags |= PF_W;
          if (vma->vm_flags & VM_EXEC)
              phdr.p_flags |= PF_X;
          phdr.p_align = ELF_EXEC_PAGESIZE;
  
          if (!dump_emit(cprm, &phdr, sizeof(phdr)))    //真正写vma segment的phdr, 从phdr中写
              goto end_coredump;
      }
      
	 /* write out the notes section */
      if (!write_note_info(&info, cprm))
          goto end_coredump;

      for (i = 0, vma = first_vma(current, gate_vma); vma != NULL;
              vma = next_vma(vma, gate_vma)) {
          unsigned long addr;
          unsigned long end;
  
          end = vma->vm_start + vma_filesz[i++];
  
          for (addr = vma->vm_start; addr < end; addr += PAGE_SIZE) {
              struct page *page;
              int stop;
  
              page = get_dump_page(addr);
              if (page) {
                  void *kaddr = kmap(page);
                  stop = !dump_emit(cprm, kaddr, PAGE_SIZE);    //真正写vma segment,每次写一页的内容
                  kunmap(page);
                  put_page(page);
              } else
                  stop = !dump_skip(cprm, PAGE_SIZE);
              if (stop)
                  goto end_coredump;
          }
      }
      dump_truncate(cprm);     //根据core文件大小的限制进行截断
}
// note segment phdr的构造
  static void fill_elf_note_phdr(struct elf_phdr *phdr, int sz, loff_t offset)                                                                                           
  {
      phdr->p_type = PT_NOTE;
      phdr->p_offset = offset;
      phdr->p_vaddr = 0;
      phdr->p_paddr = 0;
      phdr->p_filesz = sz;
      phdr->p_memsz = 0;   //内存中不存在
      phdr->p_flags = 0;
      phdr->p_align = 0;
      return; 
  }
  
  /*
   * Decide what to dump of a segment, part, all or none.
   */
  static unsigned long vma_dump_size(struct vm_area_struct *vma,
                     unsigned long mm_flags) //决定vma是否dump,已经dump的大小
{
  #define FILTER(type)    (mm_flags & (1UL << MMF_DUMP_##type))
  
      /* always dump the vdso and vsyscall sections */
      if (always_dump_vma(vma))
          goto whole;
  
      if (vma->vm_flags & VM_DONTDUMP)
          return 0;
      ...
	 /* Do not dump I/O mapped devices or special mappings */
      if (vma->vm_flags & VM_IO)
          return 0;
      /* By default, dump shared memory if mapped from an anonymous file. */
      if (vma->vm_flags & VM_SHARED) {
          if (file_inode(vma->vm_file)->i_nlink == 0 ?
              FILTER(ANON_SHARED) : FILTER(MAPPED_SHARED))
              goto whole;
          return 0;
      }
  
      /* Dump segments that have been written to.  */
      if (vma->anon_vma && FILTER(ANON_PRIVATE))
          goto whole;
      if (vma->vm_file == NULL)
          return 0;
  
      if (FILTER(MAPPED_PRIVATE))
          goto whole;
      /*
       * If this looks like the beginning of a DSO or executable mapping,
       * check for an ELF header.  If we find one, dump the first page to
       * aid in determining what was mapped here.
       */
      if (FILTER(ELF_HEADERS) &&
          vma->vm_pgoff == 0 && (vma->vm_flags & VM_READ)) {          
          u32 __user *header = (u32 __user *) vma->vm_start;
          u32 word;
          mm_segment_t fs = get_fs();
          /*
           * Doing it this way gets the constant folded by GCC.
           */
          union {
              u32 cmp;
              char elfmag[SELFMAG];
          } magic;
          BUILD_BUG_ON(SELFMAG != sizeof word);
          magic.elfmag[EI_MAG0] = ELFMAG0;
          magic.elfmag[EI_MAG1] = ELFMAG1;
          magic.elfmag[EI_MAG2] = ELFMAG2;
          magic.elfmag[EI_MAG3] = ELFMAG3;
          /*
           * Switch to the user "segment" for get_user(),
           * then put back what elf_core_dump() had in place.
           */
          set_fs(USER_DS);
          if (unlikely(get_user(word, header)))
              word = 0;
          set_fs(fs);
          if (word == magic.cmp)
              return PAGE_SIZE;    //可执行segment, 只dump一个page的大小
  #undef  FILTER
  
      return 0;
  
  whole:
      return vma->vm_end - vma->vm_start;
}
  /*
   * Core dumping helper functions.  These are the only things you should
   * do on a core-file: use only these functions to write out all the
   * necessary info.
   */
  int dump_emit(struct coredump_params *cprm, const void *addr, int nr) 
  {
      struct file *file = cprm->file;
      loff_t pos = file->f_pos;
      ssize_t n;
      if (cprm->written + nr > cprm->limit)  //判断是否超出core文件的大小
          return 0;
      while (nr) {
          if (dump_interrupted())
              return 0;
          n = __kernel_write(file, addr, nr, &pos);
          if (n <= 0)
              return 0;
          file->f_pos = pos;
          cprm->written += n;
          cprm->pos += n;
          nr -= n;
      }   
      return 1;
  }

从上面可以看出,core文件也是一个elf文件,只包含program header(也就是segment header),没有section header,segment包括:
一个PT_NOTE,其他的都是PT_LOAD
PT_NOTE segment保存的主要是寄存器,file map等信息
PT_LOAD segment保存的是vma,由filter决定一个vma是否dump,对于text等可执行段,只保存一个page的内容,因为一般认为text是不可写的,从原始可执行文件中可以得到
至于filter,可以通过设置/proc//coredump_filter来完成,主要是设置哪些vma需要coredump,具体的可以man core 来查看:

           bit 0  Dump anonymous private mappings.
           bit 1  Dump anonymous shared mappings.
           bit 2  Dump file-backed private mappings.
           bit 3  Dump file-backed shared mappings.
           bit 4 (since Linux 2.6.24)
                  Dump ELF headers.
           bit 5 (since Linux 2.6.28)
                  Dump private huge pages.
           bit 6 (since Linux 2.6.28)
                  Dump shared huge pages.
           bit 7 (since Linux 4.4)
                  Dump private DAX pages.
           bit 8 (since Linux 4.4)
                  Dump shared DAX pages.

       By default, the following bits are set: 0, 1, 4 (if the CONFIG_CORE_DUMP_DEFAULT_ELF_HEADERS kernel configuration option is enabled), and  5.   This  default
       can be modified at boot time using the coredump_filter boot option.

       The value of this file is displayed in hexadecimal.  (The default value is thus displayed as 33.)

       Memory-mapped I/O pages such as frame buffer are never dumped, and virtual DSO pages are always dumped, regardless of the coredump_filter value.

       A child process created via fork(2) inherits its parent's coredump_filter value; the coredump_filter value is preserved across an execve(2).

参考文档:
linux内核源码
Learning Linux Binary Analysis

发布了85 篇原创文章 · 获赞 26 · 访问量 12万+

猜你喜欢

转载自blog.csdn.net/whuzm08/article/details/94716768