我的问题很笼统,但是要举个例子,让我们从“旋风教程”中获取一个例子。

; tiny.asm
BITS 32
          org     0x00010000
          db      0x7F, "ELF"             ; e_ident
          dd      1                                       ; p_type
          dd      0                                       ; p_offset
          dd      $$                                      ; p_vaddr 
          dw      2                       ; e_type        ; p_paddr
          dw      3                       ; e_machine
          dd      _start                  ; e_version     ; p_filesz
          dd      _start                  ; e_entry       ; p_memsz
          dd      4                       ; e_phoff       ; p_flags
_start:
          mov     bl, 42                  ; e_shoff       ; p_align
          xor     eax, eax
          inc     eax                     ; e_flags
          int     0x80
          db      0
          dw      0x34                    ; e_ehsize
          dw      0x20                    ; e_phentsize
          db      1                       ; e_phnum
                                          ; e_shentsize
                                          ; e_shnum
                                          ; e_shstrndx

filesize      equ     $ - $$  ; tiny.asm


要使用nasm -f bin -o tiny nasm.asm;chmod +x tiny进行二进制编译。它本身可以执行,是一个微小的怪物。它比ELF标头小,但包含ELF标头,程序标头和程序代码-但Linux(至少在我的64 Debian上)运行它。

我希望能够调试具有(有意或无意)损坏/不正确的elf标头的此类文件。有修复精灵标题的工具吗?

我尝试过的调试程序是获取入口点:readelf -h tiny,但readelf甚至拒绝查看文件:readelf: Error: tiny: Failed to read file headerobjdump也不是更好。

运行rabin2 -e tiny,我们获得了入口地址(带有一些警告):

[Entrypoints]
vaddr=0x00010020 paddr=0x00010020 baddr=0x00000000 laddr=0x00000000 haddr=0x00000018 type=program


我设法使用radare2 tinypd命令获得了一些不适: br />
 [0x00010020]> pd
        ;-- entry0:
        0x00010020      b32a           mov bl, 0x2a                ; '*' ; 42
        0x00010022      31c0           xor eax, eax
        0x00010024      40             inc eax
        0x00010025      cd80           int 0x80
        0x00010027      003400         add byte [eax + eax], dh
        0x0001002a      2000           and byte [eax], al
        ;-- section_end.uphdr:
        0x0001002c  ~   01ff           add edi, edi


接下来,我尝试了gdb tinylldb tiny,但均无济于事。 IDA的免费版本5.0在无限循环时停止。

那么有没有办法自动/半自动地修理精灵?还是其他一些技巧可以调试此(或类似的)二进制文件?我想到的一个主意是用循环的指令修补入口点并附加gdb

如果不存在用于修复elf的工具,内核源文件中的哪些文件包含负责加载二进制文件的代码?

评论

尝试用零填充

#1 楼

有几种选项可用于分析头文件已损坏或损坏的ELF二进制文件。这些包括但不限于:使用基于ptrace的调试器,例如Radare2(但绝对不是gdb)。通过Unicorn仿真框架

修复标头,这可能涉及重建二进制文件

由于某些原因,这种特殊的二进制文件是标准工具的挑战:


程序头表与ELF头重叠,而不是位于其外部。
没有节,并且与节有关的字段被程序头表覆盖等等-从分析节信息的工具的角度来看-包含废话值。基于BFD的工具(例如objdump和GDB)依赖于部分信息的存在和正确性,因此,即使所有其他字段都包含正确的信息,它们也将失败。
入口点位于ELF标头中,这意味着可执行文件标头中的代码

使用基于ptrace的调试器

Radare2可以附加到该进程:

$ r2 -d tiny-i386 
Process with PID 6756 started...
= attach 6756 6756
bin.baddr 0x00010000
Using 0x10000
Warning: Cannot initialize program headers
Warning: Cannot initialize section headers
Warning: Cannot initialize strings table
Warning: Cannot initialize dynamic strings
Warning: Cannot initialize dynamic section
Warning: read (init_offset)
asm.bits 32
[0x00010020]> pd 5
            ;-- eip:
            0x00010020      b32a           mov bl, 0x2a                ; '*' ; 42
            0x00010022      31c0           xor eax, eax
            0x00010024      40             inc eax
            0x00010025      cd80           int 0x80
            0x00010027      003400         add byte [eax + eax], dh
[0x00010020]>


对于这么小的程序,r2之类的东西似乎很重。指令只有7个字节。

还可以滚动自己的基于ptrace的调试器。在调试器的工作原理:第1部分-基础知识中可以找到一个很好的指南。

仿真

在这种情况下,仿真很容易,因为程序是如此简单。对于这种挑战,仿真是一个很好的解决方案,因为除了第一个和最后一个指令的偏移量之外,不需要任何信息。可以从十六进制转储中手动检索此信息,而根本不需要解析标头。

这里是一个脚本,用于模拟问题中的二进制文件:

#!/usr/bin/python3

from unicorn import *
from unicorn.x86_const import *
from capstone import *
import struct


BASE = 0x100000
STACK_ADDR = 0x0
STACK_SIZE = 1024 * 1024

def read(name):
   with open(name, 'rb') as f:
      return f.read()

#https://github.com/unicorn-engine/unicorn/blob/master/bindings/python/shellcode.py
# callback for tracing instructions
def hook_code(uc, address, size, user_data):
    instruction = uc.mem_read(address, size)    # read this instruction code from memory
    md = user_data
    for i in md.disasm(instruction, address):
        print(">>> Tracing instruction at 0x%x, instruction size = 0x%x, disassembly:\t%s\t%s" %(i.address, i.size, i.mnemonic, i.op_str))


# callback for tracing Linux interrupt
def hook_intr(uc, intno, user_data):
    # only handle syscall
    if intno != 0x80:
        print("got interrupt %x ???" %intno);
        uc.emu_stop()
        return

    eax = uc.reg_read(UC_X86_REG_EAX)
    eip = uc.reg_read(UC_X86_REG_EIP)

    print(">>> 0x%x: INTERRUPT: 0x%x, EAX = 0x%x" %(eip, intno, eax))

    uc.emu_stop()



def main():

    mu = Uc(UC_ARCH_X86, UC_MODE_32)    # initialize emulation engine class
    mu.mem_map(BASE, STACK_SIZE)    # allocate space at base address
    mu.mem_map(STACK_ADDR, STACK_SIZE)  # allocate space for stack

    mu.mem_write(BASE, read("./tiny_binaries/tiny-i386"))   # write file to memory
    mu.reg_write(UC_X86_REG_ESP, STACK_ADDR + STACK_SIZE - 1)   # initialize stack

    md = Cs(CS_ARCH_X86, CS_MODE_32)    # initialize disassembler engine class

    # add hooks
    mu.hook_add(UC_HOOK_CODE, hook_code, md)    # pass disassembler engine to hook
    mu.hook_add(UC_HOOK_INTR, hook_intr)

    mu.emu_start(BASE + 0x20, BASE + 0x27)

    print(">>> Emulation Complete.")

if __name__ == "__main__":
    main()


以下输出是通过模拟二进制执行而产生的:

$ ./emulate_tiny-i386.py 
>>> Tracing instruction at 0x100020, instruction size = 0x2, disassembly:   mov bl, 0x2a
>>> Tracing instruction at 0x100022, instruction size = 0x2, disassembly:   xor eax, eax
>>> Tracing instruction at 0x100024, instruction size = 0x1, disassembly:   inc eax
>>> Tracing instruction at 0x100025, instruction size = 0x2, disassembly:   int 0x80
>>> 0x100025: INTERRUPT: 0x80, EAX = 0x1
>>> Emulation Complete.


完整的文章可以在这里找到:使用格式错误的标题分析ELF二进制文件第1部分-模拟微型程序。完全公开:我是本文的作者。

修复标头

由于程序的整体都包含在标头中,因此对其进行修复就意味着要重建二进制文件。程序头表必须与ELF头分开,然后必须将代码附加到程序头表的末尾,最后必须重新计算入口点以指向二进制中第一条指令的新偏移量。在这种特殊情况下,可以使用称为lepton的工具(我是开发人员)相对简单地完成此操作。这是完成二进制文件重建的脚本:

#!/usr/bin/python3

from lepton import *

def main():
    # create new headers
    with open("tiny-i386", "rb") as f:
        elf_file = ELFFile(f, new_header=True)

    # recompose binary
    with open("repaired_tiny-i386", "wb") as f:
        f.write(elf_file.recompose_binary())    # this moves the program header out of the file
                                                # header and recalculates the entry point
    print("\n\tRepaired header field values:\n")
    elf_file.ELF_header.print_fields()          # call once entry point has been recalculated


if __name__=="__main__":
    main()


重建后,readelf可以成功解析新的二进制文件:

$ readelf -h repaired_tiny-i386 
ELF Header:
  Magic:   7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00 
  Class:                             ELF32
  Data:                              2's complement, little endian
  Version:                           1 (current)
  OS/ABI:                            UNIX - System V
  ABI Version:                       0
  Type:                              EXEC (Executable file)
  Machine:                           Intel 80386
  Version:                           0x1
  Entry point address:               0x10054
  Start of program headers:          52 (bytes into file)
  Start of section headers:          0 (bytes into file)
  Flags:                             0x0
  Size of this header:               52 (bytes)
  Size of program headers:           32 (bytes)
  Number of program headers:         1
  Size of section headers:           0 (bytes)
  Number of section headers:         0
  Section header string table index: 0

$ readelf -l repaired_tiny-i386 

Elf file type is EXEC (Executable file)
Entry point 0x10054
There is 1 program header, starting at offset 52

Program Headers:
  Type           Offset   VirtAddr   PhysAddr   FileSiz MemSiz  Flg Align
  LOAD           0x000000 0x00010000 0x00030002 0x10020 0x10020 R   0xc0312ab3


新文件的运行时行为与原始文件相同:

$ strace ./repaired_tiny-i386 
execve("./repaired_tiny-i386", ["./repaired_tiny-i386"], 0x7ffd19a0f1b0 /* 52 vars */) = 0
strace: [ Process PID=5822 runs in 32 bit mode. ]
exit(42)                                = ?
+++ exited with 42 +++


更多详细信息,信息和示例,请参见lepton存储库。

结论

通常,如果执行二进制文件,则应该可以附加ptrace。但是,GDB非常脆弱,很容易变得无用。仿真似乎是最可靠的解决方案,因为解析ELF标头在很大程度上是不必要的,并且可以挂钩任何已执行的指令(本质上是完全控制)。

最后,在LWN文章“如何运行程序:ELF二进制文件”中可以找到有关内核如何加载ELF程序的详细介绍。讨论中包括指向内核中相关代码的链接。