3.4 Writing New Tools

 <  Day Day Up  >  

As seen in the previous section, the current tools based on binutils and ptrace leave a lot to be desired. While there are currently tools in development that compensate for these shortcomings, the general nature of this book and the volatile state of many of the projects precludes mentioning them here. Instead, what follows is a discussion of the facilities available for writing new tools to manipulate binary files.

The last half of this chapter contains a great deal of example source code. The reader is assumed to be familiar with C as well as with the general operation of binary tools such as linkers, debuggers , and disassemblers. This section begins with a discussion of parsing the ELF file header, followed by an introduction to writing programs using ptrace(2) and a brief look at the GNU BFD library. It ends with a discussion of using GNU libopcodes to create a disassembler.

3.4.1 The ELF File Format

The standard binary format for Linux and Unix executables is the Executable and Linkable Format (ELF). Documentation for the ELF format is easily obtainable; Intel provides PDF documentation at no charge as part of its Tool Interface Standards series (see Section 3.5 at the end of this chapter for more information).

Typical file types in ELF include binary executables, shared libraries, and the object or ".o" files produced during compilation. Static libraries, or ".a" files, consist of a collection of ELF object files linked by AR archive structures.

An ELF file is easily identified by examining the first four bytes of the file; they must be \177ELF, or 7F 45 4C 46 in hexdecimal. This four-byte signature is the start of the ELF file header, which is defined in /usr/include/elf.h :

 typedef struct {                        /* ELF File Header */     unsigned char   e_ident[16];        /* Magic number */     Elf32_Half      e_type;             /* Object file type */     Elf32_Half      e_machine;          /* Architecture */     Elf32_Word      e_version;          /* Object file version */     Elf32_Addr      e_entry;            /* Entry point virtual addr */     Elf32_Off       e_phoff;            /* Prog hdr tbl file offset */     Elf32_Off       e_shoff;            /* Sect hdr tbl file offset */     Elf32_Word      e_flags;            /* Processor-specific flags */     Elf32_Half      e_ehsize;           /* ELF header size in bytes */     Elf32_Half      e_phentsize;        /* Prog hdr tbl entry size */     Elf32_Half      e_phnum;            /* Prog hdr tbl entry count */     Elf32_Half      e_shentsize;        /* Sect hdr tbl entry size */     Elf32_Half      e_shnum;            /* Sect hdr tbl entry count */     Elf32_Half      e_shstrndx;         /* Sect hdr string tbl idx */ } Elf32_Ehdr; 

Following the ELF header are a table of section headers and a table of program headers; the section headers represent information of interest to a compiler tool suite, while program headers represent everything that is needed to link and load the program at runtime. The difference between the two header tables is the cause of much confusion, as both sets of headers refer to the same code or data in the program.

Program headers are required for the program to run; each header in the table refers to a segment of the program. A segment is a series of bytes with one of the following types associated with it:

 PT_LOAD         -- Bytes that are mapped as part of the process image     PT_DYNAMIC  -- Information passed to the dynamic linker     PT_INTERP   -- Path to interpreter, usually "/lib/ld-linux.so.2"     PT_NOTE     -- Vendor-specific information     PT_PHDR     -- This segment is the program header table 

Each program header has the following structure:

 typedef struct {                        /* ELF Program Segment Header */     Elf32_Word      p_type;             /* Segment type */     Elf32_Off       p_offset;           /* Segment file offset */     Elf32_Addr      p_vaddr;            /* Segment virtual address */     Elf32_Addr      p_paddr;            /* Segment physical address */     Elf32_Word      p_filesz;           /* Segment size in file */     Elf32_Word      p_memsz;            /* Segment size in memory */     Elf32_Word      p_flags;            /* Segment flags */     Elf32_Word      p_align;            /* Segment alignment */ } Elf32_Phdr; 

Note that each program segment has a file offset as well as a virtual address, which is the address that the segment expects to be loaded into at runtime. The segments also have both "in-file" and "in-memory" sizes: the "in-file" size specifies how many bytes to read from the file, and "in-memory" specifies how much memory to allocate for the segment.

In contrast, the section headers have the following structure:

 typedef struct {         Elf32_Word      sh_name;          /* Section name */         Elf32_Word      sh_type;          /* Section type */         Elf32_Word      sh_flags;         /* Section flags */         Elf32_Addr      sh_addr;          /* Section virtual addr */         Elf32_Off       sh_offset;        /* Section file offset */         Elf32_Word      sh_size;          /* Section size in bytes */         Elf32_Word      sh_link;          /* Link to another section */         Elf32_Word      sh_info;          /* Additional section info */         Elf32_Word      sh_addralign;     /* Section alignment */         Elf32_Word      sh_entsize;       /* Section table entry size */     } Elf32_Shdr; 

Sections have the following types:

 SHT_PROGBITS      -- Section is mapped into process image SHT_SYMTAB        -- Section is a Symbol Table SHT_STRTAB        -- Section is a String Table SHT_RELA          -- Section holds relocation info SHT_HASH          -- Section is a symbol hash table SHT_DYNAMIC       -- Section contains dynamic linking info SHT_NOTE          -- Section contains vendor-specific info SHT_NOBITS        -- Section is empty but is mapped, e.g., ".bss" SHT_REL           -- Section holds relocation info SHT_DYNSYM        -- Section contains Dynamic Symbol Table 

As noted, sections are redundant with program segments and often refer to the same bytes in the file. It is important to realize that sections are not mandatory and may be removed from a compiled program by utilities such as sstrip. One of the greatest failings of the GNU binutils tools is their inability to work with programs that have had their section headers removed.

For this reason, only program segment headers will be discussed; in fact, all that is needed to understand the file structure are the program headers, the dynamic string table, and the dynamic symbol table. The PT_DYNAMIC segment is used to find these last two tables; it consists of a table of dynamic info structures:

 typedef struct {                  /* ELF Dynamic Linking Info */     Elf32_Sword     d_tag;        /* Dynamic entry type */     union {         Elf32_Word d_val;         /* Integer value */         Elf32_Addr d_ptr;         /* Address value */     } d_un; } Elf32_Dyn; 

The dt_tag field specifies the type of information that is pointed to by the d_val or d_ptr fields; it has many possible values, with the following being those of greatest interest:

 DT_NEEDED    -- String naming a shared library needed by the program DT_STRTAB    -- Virtual Address of the Dynamic String Table DT_SYMTAB    -- Virtual Address of the Dynamic Symbol Table DT_STRSZ     -- Size of the Dynamic String Table DT_SYMENT    -- Size of a Dynamic Symbol Table element DT_INIT      -- Virtual Addr of an initialization (".init") function DT_FINI      -- Virtual Addr of a termination (".fini") function DT_RPATH     -- String giving a path to search for shared libraries 

It should be noted that any information that consists of a string actually contains an index in the dynamic string table, which itself is simply a table of NULL- terminated strings; referencing the dynamic string table plus the index provides a standard C-style string. The dynamic symbol table is a table of symbol structures:

 typedef struct {                        /* ELF Symbol */     Elf32_Word      st_name;            /* Symbol name (strtab index) */     Elf32_Addr      st_value;           /* Symbol value */     Elf32_Word      st_size;            /* Symbol size */     unsigned char   st_info;            /* Symbol type and binding */     unsigned char   st_other;           /* Symbol visibility */     Elf32_Section   st_shndx;           /* Section index */ } Elf32_Sym; 

Both the string and symbol tables are for the benefit of the dynamic linker and they contain no strings or symbols associated with the source code of the program.

By way of disclaimer, it should be noted that this description of the ELF format is minimal and is intended only for understanding the section that follows. For a complete description of the ELF format, including sections, the PLT and GOT, and issues such as relocation, see the Intel specification.

3.4.1.1 Sample ELF reader

The following source code demonstrates how to work with the ELF file format, since the process is not immediately obvious from the documentation. In this routine, "buf" is assumed to be a pointer to a memory-mapped image of the target, and "buf_len" is the length of the target.

 /*---------------------------------------------------------------------------*/     #include <elf.h>     unsigned long elf_header_read( unsigned char *buf, int buf_len ){         Elf32_Ehdr *ehdr = (Elf32_Ehdr *)buf;         Elf32_Phdr *ptbl = NULL, *phdr;         Elf32_Dyn  *dtbl = NULL, *dyn;         Elf32_Sym  *symtab = NULL, *sym;         char       *strtab = NULL, *str;         int         i, j, str_sz, sym_ent, size;         unsigned long offset, va;    /* file pos, virtual address */         unsigned long entry_offset;    /* file offset of entry point */         /* set the default entry point offset */         entry_offset =  ehdr->e_entry;         /* iterate over the program segment header table */         ptbl = (Elf32_Phdr *)(buf + ehdr->e_phoff);         for ( i = 0; i < ehdr->e_phnum; i++ ) {                     phdr = &ptbl[i];             if ( phdr->p_type == PT_LOAD ) {                 /* Loadable segment: program code or data */                 offset = phdr->p_offset;                 va = phdr->p_vaddr;                 size = phdr->p_filesz;                 if ( phdr->p_flags & PF_X ) {                     /* this is a code section */                 } else if ( phdr->p_flags & (PF_R  PF_W) ){                     /* this is read/write data */                 } else if (phdr->p_flags & PF_R ) {                     /* this is read-only data */                 }    /* ignore other sections */                 /* check if this contains the entry point */                 if ( va <= ehdr->e_entry &&                       (va + size) > ehdr->e_entry ) {                     entry_offset = offset + (entry - va);                 }             } else if ( phdr->p_type == PT_DYNAMIC ) {                 /* dynamic linking info: imported routines */                 dtbl = (Elf32_Dyn *) (buf + phdr->p_offset);                 for ( j = 0; j < (phdr->p_filesz /                          sizeof(Elf32_Dyn)); j++ ) {                     dyn = &dtbl[j];                     switch ( dyn->d_tag ) {                     case DT_STRTAB:                         strtab = (char *)                             dyn->d_un.d_ptr;                         break;                     case DT_STRSZ:                         str_sz = dyn->d_un.d_val;                         break;                     case DT_SYMTAB:                         symtab = (Elf32_Sym *)                             dyn->d_un.d_ptr;                         break;                     case DT_SYMENT:                         sym_ent = dyn->d_un.d_val;                         break;                     case DT_NEEDED:                         /* dyn->d_un.d_val is index of                             library name in strtab */                         break;                     }                 }                          }    /* ignore other program headers */         }         /* make second pass looking for symtab and strtab */         for ( i = 0; i < ehdr->e_phnum; i++ ) {             phdr = &ptbl[i];             if ( phdr->p_type == PT_LOAD ) {                 if ( strtab >= phdr->p_vaddr && strtab <                      phdr->p_vaddr + phdr->p_filesz ) {                     strtab = buf + phdr->p_offset +                          ((int) strtab - phdr->p_vaddr);                 }                 if ( symtab >= phdr->p_vaddr && symtab <                              phdr->p_vaddr +                              phdr->p_filesz ) {                     symtab = buf + phdr->p_offset +                          ((int) symtab - phdr->p_vaddr);                 }             }         }         if ( ! symtab )    {             fprintf(stderr, "no symtab!\n");             return(0);         }         if ( ! strtab )    {             fprintf(stderr, "no strtab!\n");             return(0);         }         /* handle symbols for functions and shared library routines */         size = strtab - (char *)symtab;    /* strtab follows symtab */         for ( i = 0; i < size / sym_ent; i++ ) {             sym = &symtab[i];             str = &strtab[sym->st_name];             if ( ELF32_ST_TYPE( sym->st_info ) == STT_FUNC ){                 /* this symbol is the name of a function */                 offset = sym->st_value;                 if ( sym->st_shndx ) {                 /* 'str' == subroutine at 'offset' in file */                     ;                 } else {                 /* 'str' == name of imported func at 'offset' */                     ;                 }             }    /* ignore other symbols */         }         /* return the entry point */         return( entry_offset );     } /*----------------------------------------------------------------------*/ 

A few notes are needed to clarify the source code. First, the locations of the string and symbol tables are not immediately obvious; the dynamic info structure provides their virtual addresses, but not their locations in the file. A second pass over the program headers is used to find the segment containing each so that their file offsets can be determined; in a real application, each segment will have been added to a list for future processing, so the second pass will be replaced with a list traversal.

The length of the symbol table is also not easy to determine; while it could be found by examining the section headers, in practice it is known that GNU linkers place the string table immediately after the symbol table. It goes without saying that a real application should use a more robust method.

Note that section headers can be handled in the same manner as the program headers, using code such as:

 Elf32_Shdr *stbl, *shdr; stbl = buf + ehdr->s_shoff;    /* section header table */ for ( i = 0; i < ehdr->e_shnum; i++ ) {     shdr = &stbl[i];     switch ( shdr->sh_type ) {         /* ... handle different section types here */     } } 

The symbol and string tables in the section headers use the same structure as those in the program headers.

Here is the code used for loading a target when implementing the above ELF routines:

 /*---------------------------------------------------------------------------*/     #include <errno.h>     #include <fcntl.h>     #include <stdio.h>     #include <sys/mman.h>     #include <sys/stat.h>     #include <sys/types.h>     #include <unistd.h>     int main( int argc, char **argv ) {         int fd;         unsigned char *image;         struct stat s;              if ( argc < 2 ) {             fprintf(stderr, "Usage: %s filename\n", argv[0]);             return(1);             }         if ( stat( argv[1], &s) ) {             fprintf(stderr, "Error: %s\n", strerror(errno) );             return(2);         }         fd = open( argv[1], O_RDONLY );         if ( fd < 0 )  {             fprintf(stderr, "Error: %s\n", strerror(errno) );             return(3);         }         image = mmap(0, s.st_size, PROT_READ, MAP_SHARED, fd, 0);         if ( (int) image < 0 ) {             fprintf(stderr, "Error: %s\n", strerror(errno) );             return(4);         }         /* at this point the file can be accessed via 'fd' or 'image' */         printf( "Offset of entry point: 0x%X\n",                      elf_header_read( image, s.st_size ) );         munmap( image, s.st_size );         close(fd);         return(0);     } /*--------------------------------------------------------------------*/ 

3.4.2 Debugging with ptrace

On Unix and Linux (or, to split a further hair, GNU/Linux) systems, process debugging is provided by the kernel ptrace(2) facility. The purpose of ptrace is to allow one process to access and control another; this means that ptrace provides routines to read and write to the memory of the target process, to view and set the registers of the target process, and to intercept signals sent to the target.

This last feature is perhaps the most important, though it is often left unstated. On the Intel architecture, debug traps (i.e., traps caused by breakpoints) and trace traps (caused by single-stepping through code) raise specific interrupts: interrupts 1 and 3 for debug traps, and interrupt 1 for trace traps. The interrupt handlers in the kernel create signals that are sent to the process in whose context the trap occurred. Debugging a process is therefore a matter of intercepting these signals before they reach the target process and analyzing or modifying the state of the target based on the cause of the trap.

The ptrace API is based around this model of intercepting signals sent to the target:

 /* attach to process # pid */         int pid, status, cont = 1;           if ( ptrace( PTRACE_ATTACH, pid, 0, 0) == -1 ) {             /* failed to attach: do something terrible */         }         /* if PTRACE_ATTACH succeeded, target is stopped */         while ( cont && err != -1 )              /* target is stopped -- do something */             /* PTRACE_?? is any of the ptrace routines */                 err = ptrace( PTRACE_CONT, pid, NULL, NULL);             /* deal with result of ptrace(  ) */             /* continue execution of the target */                 err = ptrace( PTRACE_CONT, pid, NULL, NULL);             wait(&status);             /* target has stopped after the CONT */                if ( WIFSIGNALED(status) ) {                 /* handle signal in WTERMSIG(status) */             }         } 

Here the debugger receives control of the target in two cases: when the target is initially attached to and when the target receives a signal. As can be seen, the target will only receive a signal while it is executing ”i.e., after being activated with the PTRACE_CONT function. When a signal has been received, the wait(2) returns and the debugger can examine the target. There is no need to send a SIGSTOP, as ptrace has taken care of this.

The following functions are provided by ptrace:

 PTRACE_ATTACH     -- attach to a process [SIGSTOP] PTRACE_DETACH     -- detach from a ptraced process [SIGCONT] PTRACE_TRACEME    -- allow parent to ptrace this process [SIGSTOP]  PTRACE_CONT       -- Continue a ptraced process [SIGCONT] PTRACE_KILL       -- Kill the process [sends SIGKILL] PTRACE_SINGLESTEP -- Execute one instruction of a ptraced process PTRACE_SYSCALL    -- Execute until entry/exit of syscall [SIGCONT, SIGSTOP] PTRACE_PEEKTEXT   -- get data from .text segmen of ptraced processt PTRACE_PEEKDATA   -- get data from .data segmen of ptraced processt PTRACE_PEEKUSER   -- get data from kernel user struct of traced process PTRACE_POKETEXT   -- write data to .text segment of ptraced process PTRACE_POKEDATA   -- write data to .data segment of ptraced process PTRACE_POKEUSER   -- write data from kernel user struct of ptraced process PTRACE_GETREGS    -- Get CPU registers of ptraced process PTRACE_SETREGS    -- Set CPU registers of ptraced process PTRACE_GETFPREGS  -- Get floating point registers of ptraced process PTRACE_SETFPREGS  -- Set floating point registers of ptraced process 

Implementing standard debugger features with these functions can be complex; ptrace is designed as a set of primitives upon which a debugging API can be built, but it is not itself a full-featured debugging API.

Consider the case of tracing or single-stepping a target. The debugger first sets the TF flag (0x100) in the eflags register of the target, then starts or continues the execution of the target. The INT1 generated by the trace flag sends a SIGTRAP to the target; the debugger intercepts it, verifies that the trap is caused by a trace and not by a breakpoint (usually by looking at the debug status register DR6 and examining the byte at eip to see if it contains an embedded INT3), and sends a SIGSTOP to the target. At this point, the debugger allows the user to examine the target and choose the next action; if the user chooses to single-step the target again, the TF flag is set again (the CPU resets TF after a single instruction has executed) and a SIGCONT is sent to the target; otherwise , if the user chooses to continue execution of the target, just the SIGCONT is sent.

The ptrace facility performs much of this work itself; it provides functions that single-step a target:

 err = ptrace( PTRACE_SINGLESTEP, pid, NULL, NULL); wait(&status); if ( WIFSIGNALED(status) && WTERMSIG(status) == SIGTRAP ) {     /* we can assume this is a single-step if we        have set no BPs, or we can examine DR6 to         be sure ... see coverage of debug registers */ } 

on return from the wait(2), the target executed a single instruction and was stopped; subsequent calls to ptrace(PTRACE_SINGLESTEP) will step additional instructions.

The case of a breakpoint is slightly different. Here, the debugger installs a breakpoint either by setting a CPU debug register or by embedding a debug trap instruction (INT3) at the desired code address. The debugger then starts or continues execution of the target and waits for a SIGTRAP. This signal is intercepted, the breakpoint disabled, and the instruction executed. Note that this process can be quite intricate when using embedded trap instructions; the debugger must replace the trap instruction with the original byte at that address, decrement the instruction pointer (the eip register) in order to re-execute the instruction that contained the embedded debug trap, single-step an instruction, and re-enable the breakpoint.

In ptrace, an embedded or hardware breakpoint is implemented as follows:

 unsigned long old_insn, new_insn; old_insn = ptrace( PTRACE_PEEKTEXT, pid, addr, NULL ); if ( old_insn != -1 ) {     new_insn = old_insn;     ((char *)&new_insn)[0] = 0xCC;    /* replace with int3 */     err = ptrace( PTRACE_POKETEXT, pid, addr, &new_insn );     err = ptrace( PTRACE_CONT, pid, NULL, NULL );     wait(&status);     if ( WIFSIGNALED(status) && WTERMSIG(status) == SIGTRAP ) {         /* check that this is our breakpoint */         err = ptrace( PTRACE_GETREGS, pid, NULL, &regs);         if ( regs.eip == addr ) {             /* -- give user control before continue -- */             /* disable breakpoint ... */             err = ptrace( PTRACE_POKETEXT, pid, addr,                      &old_insn );             /* execute the breakpointed insn ... */                  err = ptrace( PTRACE_SINGLESTEP, pid, NULL,                      NULL );             /* re-enable the breakpoint */             err = ptrace( PTRACE_POKETEXT, pid, addr,                      &new_insn );         }     } } 

As can be seen, ptrace does not provide any direct support for breakpoints; however, support for breakpoints can be written quite easily.

Despite the fact that widely used ptrace-based debuggers do not implement breakpoints using Intel debug registers, ptrace itself provides facilities for manipulating these registers. The support for this can be found in the sys_ptrace routine in the Linux kernel:

 /* defined in /usr/src/linux/include/linux/sched.h */     struct task_struct {         /* ... */         struct user_struct *user;         /* ... */     };     /* defined in /usr/include/sys/user.h */     struct user {         struct user_regs_struct regs;         /* ... */         int u_debugreg[8];     };         /* from /usr/src/linux/arch/i386/kernel/ptrace.c */     int sys_ptrace(long request, long pid, long addr, long data) {           struct task_struct *child;           struct user * dummy = NULL;         /* ... */           case PTRACE_PEEKUSR:          unsigned long tmp;         /* ... check that address is in struct user ...  */         /* ... hand off reading of normal regs to getreg(  ) ... */         /* if address is a valid debug register: */                 if(addr >= (long) &dummy->u_debugreg[0] &&                    addr <= (long) &dummy->u_debugreg[7]){                       addr -= (long) &dummy->u_debugreg[0];                       addr = addr >> 2;                       tmp = child->thread.debugreg[addr];                 }             /* write contents using put_user(  ) */                 break;         /* ... */     case PTRACE_POKEUSR:         /* ... check that address is in struct user ...  */         /* ... hand off writing of normal regs to putreg(  ) ... */         /* if address is a valid debug register: */                   if(addr >= (long) &dummy->u_debugreg[0] &&                 addr <= (long) &dummy->u_debugreg[7]){             /* skip DR4 and DR5  */             if(addr == (long) &dummy->u_debugreg[4]) break;             if(addr == (long) &dummy->u_debugreg[5]) break;             /* do not write invalid addresses */             if(addr < (long) &dummy->u_debugreg[4] &&                 ((unsigned long) data) >= TASK_SIZE-3) break;             /* write control register DR7 */                         if(addr == (long) &dummy->u_debugreg[7]) {                               data &= ~DR_CONTROL_RESERVED;                               for(i=0; i<4; i++)                                     if ((0x5f54 >>                          ((data >> (16 + 4*i)) & 0xf)) & 1)                         goto out_tsk;                         }             /* write breakpoint address to DR0 - DR3 */                         addr -= (long) &dummy->u_debugreg;                         addr = addr >> 2;                         child->thread.debugreg[addr] = data;                         ret = 0;               }               break; 

The debug registers exist in the user structure for each process; ptrace provides special routines for accessing data in this structure ”the PTRACE_PEEKUSER and PTRACE_POKEUSER commands. These commands take an offset into the user structure as the addr parameter; as the above kernel excerpt shows, if the offset and data pass the validation tests, the data is written directly to the debug registers for the process. This requires some understanding of how the debug registers work.

There are eight debug registers in an Intel CPU: DR0-DR7. Of these, only the first four can be used to hold breakpoint addresses; DR4 and DR5 are reserved, DR6 contains status information following a debug trap, and DR7 is used to control the four breakpoint registers.

The DR7 register contains a series of flags with the following structure:

 condition word (16-31)                  control word (0-15) 00  00  00  00  -  00  00  00  00    00  00  00  00  -  00  00  00  00 Len R/W Len R/W    Len R/W Len R/W    RR  GR  RR  GL     GL  GL  GL  GL   DR3     DR2        DR1     DR0          D       EE     33  22  11  00 

The control word contains fields for managing breakpoints: G0-G3, Global (all tasks ) Breakpoint Enable for DR0-3; L0-L3, Local (single task) Breakpoint Enable for DR0-3; GE, Global Exact breakpoint enable; LE, Local Exact breakpoint enable; and GD, General Detect of attempts to modify DR0-7.

The condition word contains a nibble for each debug register, with two bits dedicated to read/write access and two bits dedicated to data length:

 R/W Bit    Break on...     ------------------------------------------------------      00        Instruction execution only      01        Data writes only      10        I/O reads or writes      11        Data read/write [not instruction fetches]      Len Bit    Length of data at address     ------------------------------------------------------      00        1 byte      01        2 bytes      10        Undefined 4 bytes 

Note that data breakpoints are limited in size to the machine word size of the processor.

The following source demonstrates how to implement debug registers using ptrace. Note that no special compiler flags or libraries are needed to compile programs with ptrace support; the usual gcc -o program_name *.c works just fine.

 /*---------------------------------------------------------------------------*/     #include <errno.h>     #include <stdio.h>     #include <stdlib.h>     #include <sys/ptrace.h>     #include <asm/user.h>     /* for struct user */          #define MODE_ATTACH 1     #define MODE_LAUNCH 2     /* shorthand for accessing debug registers */     #define DR( u, num ) u.u_debugreg[num]     /* get offset of dr 'num' from start of user struct */     #define DR_OFF( u, num ) (long)(&u.u_debugreg[num]) - (long)&u     /* get DR number 'num' into struct user 'u' from procss 'pid' */     #define GET_DR( u, num, pid )                        \         DR(u, num) = ptrace( PTRACE_PEEKUSER, pid,         \                   DR_OFF(u, num), NULL );     /* set DR number 'num' to struct user 'u' from procss 'pid' */     /* NOTE: the ptrace(2) man page is incorrect: the last argument to          POKEUSER must be the word itself, not the address of the word        in the parent's memory space. See arch/i386/kernel/ptrace.c  */     #define SET_DR( u, num, pid )                        \         ptrace( PTRACE_POKEUSER, pid, DR_OFF(u, num), DR(u, num) );     /* return # of bytes to << in order to set/get local enable bit */     #define LOCAL_ENABLE( num ) ( 1 << num )     #define DR_LEN_MASK 0x3     #define DR_LEN( num ) (16 + (4*num))     #define DR_RWX_MASK 0x3     #define DR_RWX( num )  (18 + (4*num))     /* !=0 if trap is due to single step */     #define DR_STAT_STEP( dr6 ) ( dr6 & 0x2000 )     /* !=0 if trap is due to task switch */     #define DR_STAT_TASK( dr6 ) ( dr6 & 0x4000 )          /* !=0 if trap is due to DR register access detected */     #define DR_STAT_DRPERM( dr6 ) ( dr6 & 0x8000 )     /* returns the debug register that caused the trap */     #define DR_STAT_DR( dr6 ) ( (dr6 & 0x0F)  )     /* length is 1 byte, 2 bytes, undefined, or 4 bytes */     enum dr_len { len_byte = 0, len_hword, len_unk, len_word };     /* bp condition is exec, write, I/O read/write, or data read/write */     enum dr_rwx { bp_x = 0, bp_w, bp_iorw, bp_rw };     int set_bp(int pid, unsigned long rva, enum dr_len len, enum dr_rwx rwx){         struct user u = {0};         int x, err, dreg = -1;         err = errno;         GET_DR( u, 7, pid );         if ( err != errno ) {             fprintf(stderr, "BP_SET read dr7 error: %s\n",                      strerror(errno));             return(0);         }         /* find unused debug register */         for ( x = 0; x < 4; x++ ){             if ( ! DR(u, 7) & LOCAL_ENABLE( x ) ) {                 dreg = x;                 break;             }         }         if ( dreg != -1 ) {             /* set bp */             DR(u, dreg) = rva;             err = SET_DR( u, dreg, pid );             if ( err == -1 ) {                 fprintf(stderr, "BP_SET DR%d error: %s\n", dreg,                          strerror(errno));                 return;             }             /* enable bp and conditions in DR7 */             DR(u, 7) &= ~(DR_LEN_MASK << DR_LEN(dreg));              DR(u, 7) &= ~(DR_RWX_MASK << DR_RWX(dreg));                   DR(u, 7) = len << DR_LEN(dreg);              DR(u, 7) = rwx << DR_RWX(dreg);              DR(u, 7) = LOCAL_ENABLE(dreg);             err = SET_DR( u, 7, pid );             if ( err == -1 ) {                 fprintf(stderr, "BP_SET DR7 error: %s\n",                          strerror(errno));                 return;             }         }         return( dreg );    /* -1 means no free debug register */     }     int unset_bp( int pid, unsigned long rva ) {         struct user u = {0};         int x, err, dreg = -1;         for ( x = 0; x < 4; x++ ){             err = errno;             GET_DR(u, x, pid);             if ( err != errno ) {                 fprintf(stderr, "BP_UNSET get DR%d error: %s\n", x,                              strerror(errno));                 return(0);             }             if ( DR(u, x) == rva ) {                 dreg = x;                 break;             }         }         if ( dreg != -1 ) {             err = errno;             GET_DR( u, 7, pid );             if ( err != errno ) {                 fprintf(stderr, "BP_UNSET get DR7 error: %s\n",                          strerror(errno));                 return(0);             }             DR(u, 7) &= ~(LOCAL_ENABLE(dreg));             err = SET_DR( u, 7, pid ) ;             if ( err == -1 ) {                 fprintf(stderr, "BP_UNSET DR7 error: %s\n",                          strerror(errno));                 return;             }         }         return(dreg);    /* -1 means no debug register set to rva */     }     /* reason for bp trap */     enum bp_status = { bp_trace, bp_task, bp_perm, bp_0, bp_1, bp_2, bp_3,                   bp_unk };     enum bp_status get_bp_status( int pid ) {         int dreg;         struct user u = {0};         enum bp_status rv = bp_unk;              GET_DR( u, 6, pid );         printf("Child stopped for ");         if ( DR_STAT_STEP( DR(u, 6) ) ) {             rv = bp_trace;         } else if  ( DR_STAT_TASK(DR(u,6)) ){             rv = bp_task;         } else if ( DR_STAT_DRPERM(DR(u,6)) ) {             rv = bp_perm;         } else {             dreg = DR_STAT_DR(DR(u,6));             if ( dreg == 1 ) {                 rv = bp_0;             } else if ( dreg == 2 ) {                 rv = bp_1;             } else if ( dreg == 4 ) {                 rv = bp_2;             } else if ( dreg == 8 ) {                 rv = bp_3;             }         }         return( rv );     } /*---------------------------------------------------------------------------*/ 

These routines can then be incorporated into a standard ptrace-based debugger such as the following:

 /*---------------------------------------------------------------------------*/     #include <stdio.h>     #include <stdlib.h>     #include <sys/ptrace.h>     #include <sys/wait.h>     #include <errno.h>     #include <signal.h>     #include "hware_bp.h"    /* protos for set_bp(), unset_bp(  ), etc */     #define DEBUG_SYSCALL    0x01     #define DEBUG_TRACE    0x02     unsigned long get_rva( char *c ) {         unsigned long rva;         while ( *c && ! isalnum( *c ) )             c++;         if ( c && *c )             rva = strtoul( c, NULL, 16 );         return(rva);     }          void print_regs( int pid ) {         struct user_regs_struct regs;         if (ptrace( PTRACE_GETREGS, pid, NULL, &regs) != -1 ) {             printf("CS:IP %04X:%08X\t SS:SP %04X:%08X FLAGS %08X\n",                 regs.cs, regs.eip, regs.ss, regs.esp, regs.eflags);             printf("EAX %08X \tEBX %08X \tECX %08X \tEDX %08X\n",                 regs.eax, regs.ebx, regs.ecx, regs.edx );         }         return;     }     void handle_sig( int pid, int signal, int flags ) {         enum bp_status status;         if ( signal == SIGTRAP ) {             printf("Child stopped for ");             /* see if this was caused by debug registers */             status = get_bp_status( pid );             if ( status == bp_trace ) {                 printf("trace\n");             } else if  ( status == bp_task ){                 printf("task switch\n");             } else if ( status == bp_perm ) {                 printf("attempted debug register access\n");             } else if ( status != bp_unk ) {                 printf("hardware breakpoint\n");             } else {                 /* nope */                 if ( flags & DEBUG_SYSCALL ) {                     printf("syscall\n");                 } else if ( flags & DEBUG_TRACE ) {                     /* this should be caught by bp_trace */                     printf("trace\n");                 }             }         }         return;     }     int main( int argc, char **argv) {           int mode, pid, status, flags = 0, err = 0, cont = 1;         char *c, line[256];         /* check args */         if ( argc == 3 && argv[1][0] == '-' && argv[1][1] == 'p' ) {             pid = strtoul( argv[2], NULL, 10 );             mode = MODE_ATTACH;         } else if ( argc >= 2 ) {             mode = MODE_LAUNCH;         } else {             printf( "Usage: debug [-p pid] [filename] [args...]\n");             return(-1);         }         /* start/attach target based on mode */         if ( mode == MODE_ATTACH ) {               printf("Tracing PID: %x\n", pid);               err = ptrace( PTRACE_ATTACH, pid, 0, 0);         } else {             if ( (pid = fork(  )) < 0 ) {                 fprintf(stderr, "fork(  ) error: %s\n", strerror(errno));                 return(-2);             } else if ( pid ) {                   printf("Executing %s PID: %x\n", argv[1], pid);                 wait(&status);             } else {                 err = ptrace( PTRACE_TRACEME, 0, 0, 0);                 if ( err == -1 ) {                     fprintf(stderr, "TRACEME error: %s\n",                          strerror(errno));                     return(-3);                 }                 return( execv(argv[1], &argv[1]) );             }         }           while ( cont && err != -1 ) {               print_regs( pid );               printf("debug:");               fgets( line, 256, stdin );               for ( c = line; *c && !(isalnum(*c)) ; c++ )                   ;                 switch (*c) {                 case 'b':                     set_bp(pid, get_rva(++c), len_byte, bp_x);                     break;                 case 'r':                     unset_bp(pid, get_rva(++c));                     break;                 case 'c':                             err = ptrace( PTRACE_CONT, pid, NULL, NULL);                     wait(&status);                     break;                 case 's':                     flags = DEBUG_SYSCALL;                             err = ptrace( PTRACE_SYSCALL, pid, NULL, NULL);                     wait(&status);                     break;                 case 'q':                             err = ptrace( PTRACE_KILL, pid, NULL, NULL);                     wait(&status);                     cont = 0;                     break;                 case 't':                     flags = DEBUG_TRACE;                             err = ptrace(PTRACE_SINGLESTEP, pid, NULL, NULL);                     wait(&status);                     break;                 case '?':                 default:                     printf("b [addr] - set breakpoint\n"                          "r [addr] - remove breakpoint\n"                            "c        - continue\n"                            "s        - run to syscall entry/exit\n"                            "q        - kill target\n"                            "t        - trace/single step\n" );                     break;             }             if ( WIFEXITED(status) ) {                 printf("Child exited with %d\n", WEXITSTATUS(status));                 return(0);             } else if ( WIFSIGNALED(status) ) {                 printf("Child received signal %d\n", WTERMSIG(status));                 handle_sig( pid, WTERMSIG(status), flags );              }         }         if ( err == -1 )              printf("ERROR: %s\n", strerror(errno));           ptrace( PTRACE_DETACH, pid, 0, 0);         wait(&status);           return(0);     } /*-------------------------------------------------------------------*/ 

Naturally, for this to be a "real" debugger, it should incorporate a disassembler as well as allow the user to read and write memory addresses and registers.

The ptrace facility can also be used to monitor a running process and report on its usage of library calls, system calls, or files, or to report on its own internal state (such as signals it has received, which internal subroutines have been called, what the contents of the register were when a conditional branch was reached, and so on). Most such utilities use either PTRACE_SYSCALL or PTRACE_SINGLESTEP in order to halt the process temporarily and make a record of its activity.

The following code demonstrates the use of PTRACE_SYSCALL to record all system calls made by the target:

 /*----------------------------------------------------------------------*/     struct user_regs_struct regs;     int state = 0, err = 0, cont = 1;       while ( cont && err != -1 ) {         state = state ? 0 : 1;             err = ptrace( PTRACE_SYSCALL, pid, NULL, NULL);         wait(&status);         if ( WIFEXITED(status) ) {             fprintf(stderr, "Target exited.\n");             cont = 0;             continue;         }         if (ptrace( PTRACE_GETREGS, pid, NULL, &regs) == -1 ) {             fprintf(stderr, "Unable to read process registers\n");             continue;         }         if ( state ) {             /* system call trap */             printf("System Call %X (%X, %X, %X, %X, %X)\n",                     regs.orig_eax, regs.ebx, regs.ecx,                     regs.edx, regs.esi, regs.edi );         } else {             printf("Return: %X\n", regs.orig_eax);         }       } /*--------------------------------------------------------------------*/ 

Obviously, the output of this code would be tedious to use; a more sophisticated version would store a mapping of system call numbers (i.e., the index into the system call table of a particular entry) to their names , as well as a list of their parameters and return types.

3.4.3 The GNU BFD Library

GNU BFD is the GNU Binary File Descriptor library; it is shipped with the binutils package and is the basis for all of the included utilities, including objdump , objcopy, and ld. The reason sstripped binaries cannot be loaded by any of these utilities can be traced directly back to improper handling of the ELF headers by the BFD library. As a library for manipulating binaries, however, BFD is quite useful; it provides an abstraction of the object file, which allows file sections and symbols to be dealt with as distinct elements.

The BFD API could generously be described as unwieldy; hundreds of functions, inhumanly large structures, uncommented header files, and vague documentation ”provided in the info format that the FSF still insists is a good idea ”combine to drive away most programmers who might otherwise move on to write powerful binary manipulation tools.

To begin with, you must understand the BFD conception of a file. Every object file is in a specific format:

 typedef enum bfd_format {         bfd_unknown = 0,      /* file format is unknown */         bfd_object,           /* linker/assember/compiler output */         bfd_archive,          /* object archive file */         bfd_core,             /* core dump */         bfd_type_end          /* marks the end; don't use it! */ }; 

The format is determined when the file is opened for reading using bfd_openr( ) . The format can be checked using the bfd_check_format( ) routine. Once the file is loaded, details such as the specific file format, machine architecture, and endianness are all known and recorded in the bfd structure.

When a file is opened, the BFD library creates a bfd structure (defined in bfd.h ), which is a bit large and has the following format:

 struct bfd {          const char                  *filename;         const struct bfd_target     *xvec;         void                        *iostream;         boolean                      cacheable;         boolean                      target_defaulted;         struct _bfd                 *lru_prev, *lru_next;         file_ptr                     where;         boolean                      opened_once;         boolean                      mtime_set;         long                         mtime;         int                          ifd;         bfd_format                   format;         enum bfd_direction           direction;         flagword                     flags;         file_ptr                     origin;         boolean                      output_has_begun;         struct sec                  *sections;         unsigned int                 section_count;         bfd_vma                      start_address;         unsigned int                 symcount;         struct symbol_cache_entry  **outsymbols;         const struct bfd_arch_info  *arch_info;         void                        *arelt_data;         struct _bfd                 *my_archive;         struct _bfd                 *next;              struct _bfd                 *archive_head;         boolean                      has_armap;         struct _bfd                 *link_next;         int                          archive_pass;         union {             struct aout_data_struct *aout_data;             struct elf_obj_tdata    *elf_obj_data;             /* ... */         } tdata;         void                        *usrdata;         void                        *memory;     }; 

This is the core definition of a BFD target; aside from the various management variables ( xvec , iostream , cacheable , target_defaulted , etc.), the bfd structure contains the basic object file components , such as the entry point ( start_address ), sections, symbols, and relocations.

The first step when working with BFD is to be able to open and close a file reliably. This involves initializing BFD, calling an open function (one of the read-only functions bfd_openr , bfd_fdopenr , or bfd_openstreamr , or the write function bfd_openw ), and closing the file with bfd_close :

 /*---------------------------------------------------------------------------*/     #include <errno.h>     #include <stdio.h>     #include <stdlib.h>     #include <sys/stat.h>     #include <sys/types.h>     #include <bfd.h>     int main( int argc, char **argv ) {               struct stat s;         bfd *b;           if ( argc < 2 ) {                 fprintf(stderr, "Usage: %s filename\n", argv[0]);                 return(1);           }           if ( stat( argv[1], &s) ) {                 fprintf(stderr, "Error: %s\n", strerror(errno) );                 return(2);           }         bfd_init(  );         b = bfd_openr( argv[1], NULL );         if ( bfd_check_format(b, bfd_object ) ) {             printf("Loading object file %s\n", argv[1]);         } else if ( bfd_check_format(b, bfd_archive ) ) {             printf("Loading archive file %s\n", argv[1]);         }         bfd_close(b);                return(0);     } /*--------------------------------------------------------------------*/ 

How do you compile this monstrosity?

 bash#  gcc -I/usr/src/binutils/bfd -I/usr/src/binutils/include -o bfd \   >   -lbfd -liberty bfd.c  

where /usr/src/binutils is the location of the binutils source. While most distributions ship with a copy of binutils, the include files for those libraries are rarely present. If the standard include paths contain "dis-asm.h" and "bfd.h", compilation will work fine without the binutils source code.

To the BFD library, an object file is just a linked list of sections, with file headers provided to enable traversing the list. Each section contains data in the form of code instructions, symbols, comments, dynamic linking information, or plain binary data. Detailed information about the object file, such as symbols and relocations, is associated with the bfd descriptor in order to make possible global modifications to sections.

The section structure is too large to be described here. It can be found among the 3,500 lines of bfd.h . The following routine demonstrates how to read the more interesting fields of the section structure for all sections in an object file.

 /*---------------------------------------------------------------------------*/     static void sec_print(bfd *b, asection *section, PTR jnk){         unsigned char *buf;         int i, j, size;         printf( "%d %s\n",                 section->index, section->name );         printf( "\tFlags 0x%08X",          section->flags );         printf( "\tAlignment: 2^%d\n",     section->alignment_power );         printf( "\tVirtual Address: 0x%X", section->vma );         printf( "\tLoad Address: 0x%X\n",  section->lma );         printf( "\tOutput Size: %4d",      section->_cooked_size );         printf( "\tInput Size: %d\n",      section->_raw_size );         size = section->_cooked_size;         buf = calloc( size, 1 );         if ( bfd_get_section_contents( b, section, buf, 0, size ) ) {             printf("\n\tContents:\n");             for ( i = 0; i < size; i +=16 ) {                 printf( "\t" );                 for (j = 0; j < 16 && j+i < size; j++ ) /* hex loop */                     printf("%02X ", buf[i+j] );                              for ( ; j < 16; j++ )             /* pad loop */                      printf("   ");                 for (j = 0; j < 16 && j+i < size; j++) /* ASCII loop */                     printf("%c", isprint(buf[i+j])? buf[i+j] : '.');                                  printf("\n");             }             printf("\n\n");         }         return;     }     int main( int argc, char **argv ) {         /*     ... add this line before bfd_close(  )   */         bfd_map_over_sections( b, sec_print, NULL );         /*     ...     */     } /*-----------------------------------------------------------------------*/ 

The only thing to notice here is the use of bfd_map_over_sections( ) , which iterates over all sections in the file and invokes a callback for each section. Most of the section attributes can be accessed directly using the section structure or with BFD wrapper functions; the contents of a section, however, are not loaded until bfd_get_section_contents( ) is called to explicitly copy the contents of a section (i.e., the code or data) to an allocated buffer.

Printing the contents of a file is fairly simple; however, BFD starts to earn its reputation when used to create output files. The process itself does not appear to be so difficult.

 b1 = bfd_openr( input_file, NULL ); b2 = bfd_openw( output_file, NULL ); bfd_map_over_sections( b1, copy_section, b2 ); bfdclose( b2 ); bfdclose( b1 ); 

Seems simple, eh? Well, keep in mind this is GNU software.

To begin with, all sections in the output file must be defined before they can be filled with any data. This means two iterations through the sections already:

 bfd_map_over_sections( b1, define_section, b2 );      bfd_map_over_sections( b1, copy_section, b2 ); 

In addition, the symbol table must be copied from one bfd descriptor to the other, and all of the relocations in each section must be moved over manually. This can get a bit clunky , as seen in the code below.

 /*---------------------------------------------------------------------------*/     #include <errno.h>     #include <fcntl.h>     #include <stdio.h>     #include <stdlib.h>     #include <sys/stat.h>     #include <sys/types.h>     #include <unistd.h>     #include <bfd.h>     /* return true for sections that will not be copied to the output file */     static int skip_section( bfd *b, asection *s ) {         /* skip debugging info */         if ( (bfd_get_section_flags( b, s ) & SEC_DEBUGGING) )              return( 1 );         /* remove gcc cruft */         if ( ! strcmp( s->name, ".comment" ) )             return( 1 );         if ( ! strcmp( s->name, ".note" ) )             return( 1 );              return(0);     }     struct COPYSECTION_DATA {         bfd * output_bfd;         asymbol **syms;         int sz_syms, sym_count;     };     static void copy_section( bfd *infile, asection *section, PTR data ){         asection *s;         unsigned char *buf;         long size, count, sz_reloc;         struct COPYSECTION_DATA *d = data;         bfd *outfile = d->output_bfd;         asymbol **syms = d->syms;         if ( skip_section( infile, section ) )              return;              /* get output section from input section struct */         s = section->output_section;         /* get sizes for copy */         size = bfd_get_section_size_before_reloc (section );         sz_reloc = bfd_get_reloc_upper_bound( infile, section );         if ( ! sz_reloc ) {             /* no relocations */             bfd_set_reloc( outfile, s, (arelent **) NULL, 0);         } else if ( sz_reloc  > 0 ) {             /* build relocations */             buf = calloc( sz_reloc, 1 );             /* convert binary relocs to BFD internal representation */             /* From info: "The SYMS table is also needed for horrible                 internal magic reasons". I kid you not.                 Welcome to hack city. */             count = bfd_canonicalize_reloc(infile, section,                                   (arelent **)buf, syms );             /* at this point, undesired symbols can be stripped */             /* set the relocations for the output section */             bfd_set_reloc( outfile, s, (arelent **) ((count) ?                                      buf : NULL), count );             free( buf );         }              /* here we manipulate BFD's private data for no apparent reason */         section->_cooked_size = section->_raw_size;         section->reloc_done = true;              /* get input section contents, set output section contents */         if ( section->flags & SEC_HAS_CONTENTS ) {             buf = calloc( size, 1 );             bfd_get_section_contents( infile, section, buf, 0, size );             bfd_set_section_contents( outfile, s, buf, 0, size );             free( buf );         }         return;     }     static void define_section( bfd *infile, asection *section, PTR data ){         bfd *outfile = (bfd *) data;         asection *s;         if ( skip_section( infile, section ) )              return;         /* no idea why this is called "anyway"... */         s = bfd_make_section_anyway( outfile, section->name );         /* set size to same as infile section */         bfd_set_section_size( outfile, s, bfd_section_size(infile,                                             section) );         /* set virtual address */         s->vma =  section->vma;         /* set load address */         s->lma =  section->lma;         /* set alignment -- the power 2 will be raised to */         s->alignment_power = section->alignment_power;         bfd_set_section_flags(outfile, s,                      bfd_get_section_flags(infile, section));         /* link the output section to the input section -- don't ask why */         section->output_section = s;         section->output_offset = 0;         /* copy any private BFD data from input to output section */         bfd_copy_private_section_data( infile, section, outfile, s );         return;     }     int file_copy( bfd *infile, bfd *outfile ) {         struct COPYSECTION_DATA data = {0};              if ( ! infile  ! outfile )    return(0);              /* set output parameters to infile settings */         bfd_set_format( outfile, bfd_get_format(infile) );         bfd_set_arch_mach(outfile, bfd_get_arch(infile),                              bfd_get_mach(infile));         bfd_set_file_flags( outfile, bfd_get_file_flags(infile) &                           bfd_applicable_file_flags(outfile) );         /* set the entry point of the output file */         bfd_set_start_address( outfile, bfd_get_start_address(infile) );              /* define sections for output file */         bfd_map_over_sections( infile, define_section, outfile );              /* get input file symbol table */         data.sz_syms = bfd_get_symtab_upper_bound( infile );         data.syms = calloc( data.sz_syms, 1 );              /* convert binary symbol data to BFD internal format */         data.sym_count = bfd_canonicalize_symtab( infile, data.syms );              /* at this point the symbol table may be examined via                for ( i=0; i < data.sym_count; i++ )                     asymbol *sym = data.syms[i];            ...and so on, examining sym->name, sym->value, and sym->flags */              /* generate output file symbol table */         bfd_set_symtab( outfile, data.syms, data.sym_count );              /* copy section content from input to output */         data.output_bfd = outfile;         bfd_map_over_sections( infile, copy_section, &data );              /* copy whatever weird data BFD needs to make this a real file */         bfd_copy_private_bfd_data( infile, outfile );         return(1);     }          int main( int argc, char **argv ) {              struct stat s;         bfd *infile, *outfile;           if ( argc < 3 ) {                     fprintf(stderr, "Usage: %s infile outfile\n", argv[0]);                 return(1);           }           if ( stat( argv[1], &s) ) {                 fprintf(stderr, "Error: %s\n", strerror(errno) );                 return(2);           }         bfd_init(  );         /* open input file for reading */         infile = bfd_openr( argv[1], NULL );         if ( ! infile ) {             bfd_perror( "Error on infile" );             return(3);         }         /* open output file for writing */         outfile = bfd_openw( argv[2], NULL );         if ( ! outfile ) {             bfd_perror( "Error on outfile" );             return(4);         }         if ( bfd_check_format (infile, bfd_object ) ) {             /* routine that does all the work */             file_copy( infile, outfile );         } else if ( bfd_check_format(infile, bfd_archive ) ) {             fprintf( stderr, "Error: archive files not supported\n");             return(5);         }         bfd_close(outfile);         bfd_close(infile);                return(0);     } /*---------------------------------------------------------------------------*/ 

This utility will strip the .comment and .note sections from an ELF executable:

 bash# gcc  -I/usr/src/binutils/bfd -I/usr/src/binutils/include \   >    -o bfdtest -lbfd -liberty bfd.c  bash#  ./bfdtest a.out a.out.2  bash#  objdump -h a.out  grep .comment  23 .comment      00000178  00000000  00000000  00001ff0  2**0 bash#  objdump -h tst  grep .comment  bash# 

With some work, this could be improved to provide an advanced ELF stripper (now there's a name that leaps out of the manpage ) such as sstrip(1), or it could be rewritten to add code into an existing ELF executable in the manner of objcopy and ld.

3.4.4 Disassembling with libopcodes

The libopcodes library, like much of the GNU code intended only for internal use, requires hackish and inelegant means (e.g., global variables, replacement fprintf(3) routines) to get it working. The result is ugly to look at and may get a bit dodgy when threaded ”but it's free, and it's a disassembler.

In a nutshell , one uses libopcodes by including the file dis-asm.h from the binutils distribution, filling a disassemble_info structure, and calling either print_insn_i386_att( ) or print_insn_i386_intel( ) .

The disassemble_info structure is pretty large and somewhat haphazard in design; it has the following definition (cleaned up from the actual header):

 typedef int (*fprintf_ftype) (FILE *, const char*, ...); typedef int (*read_memory_func_t) (bfd_vma memaddr, bfd_byte *myaddr,              unsigned int length, struct disassemble_info *info); typedef void (*memory_error_func_t) (int status, bfd_vma memaddr,              struct disassemble_info *info); typedef void (*print_address_func_t) (bfd_vma addr,              struct disassemble_info *info); typedef int (*symbol_at_address_func_t) (bfd_vma addr,              struct disassemble_info * info); typedef struct disassemble_info {       fprintf_ftype              fprintf_func;       unsigned char             *stream;       void                      *application_data;       enum bfd_flavour           flavour;       enum bfd_architecture      arch;       unsigned long              mach;       enum bfd_endian            endian;       asection                  *section;       asymbol                  **symbols;       int                        num_symbols;       unsigned long              flags;       void                      *private_data;       read_memory_func_t         read_memory_func;     memory_error_func_t          memory_error_func;     print_address_func_t         print_address_func;     symbol_at_address_func_t     symbol_at_address_func;     bfd_byte                    *buffer;     bfd_vma                      buffer_vma;     unsigned int                 buffer_length;     int                          bytes_per_line;     int                          bytes_per_chunk;     enum bfd_endian              display_endian;     unsigned int                 octets_per_byte;     char                         insn_info_valid;     char                         branch_delay_insns;     char                         data_size;     enum dis_insn_type           insn_type;     bfd_vma                      target;     bfd_vma                      target2;     char *                       disassembler_options; } disassemble_info; 

Some of these fields (e.g., flavour , section , symbols ) duplicate the data managed by the BFD library and are in fact unused by the disassembler, some are internal to the disassembler (e.g., private_data , flags ), some are the necessarily pedantic information required to support disassembly of binary files from another platform (e.g., arch , mach , endian , display_endian , octets_per_byte ), and some are actually not used at all in the x86 disassembler (e.g., insn_info_valid , branch_delay_insns , data_size , insn_type , target , target2 , disassembler_options ).

The enumerations are defined in bfd.h , supplied with binutils; note that flavour refers to the file format and can get set to unknown . The endian and arch fields should be set to their correct values. The definitions are as follows:

 enum bfd_flavour {     bfd_target_unknown_flavour, bfd_target_aout_flavour,      bfd_target_coff_flavour, bfd_target_ecoff_flavour,      bfd_target_xcoff_flavour, bfd_target_elf_flavour,bfd_target_ieee_flavour,     bfd_target_nlm_flavour, bfd_target_oasys_flavour,      bfd_target_tekhex_flavour, bfd_target_srec_flavour,      bfd_target_ihex_flavour, bfd_target_som_flavour, bfd_target_os9k_flavour,     bfd_target_versados_flavour, bfd_target_msdos_flavour,      bfd_target_ovax_flavour, bfd_target_evax_flavour     };     enum bfd_endian { BFD_ENDIAN_BIG,BFD_ENDIAN_LITTLE, BFD_ENDIAN_UNKNOWN};     enum bfd_architecture {       bfd_arch_unknown, bfd_arch_obscure, bfd_arch_m68k, bfd_arch_vax,        bfd_arch_i960, bfd_arch_a29k, bfd_arch_sparc, bfd_arch_mips,        bfd_arch_i386, bfd_arch_we32k, bfd_arch_tahoe, bfd_arch_i860,        bfd_arch_i370, bfd_arch_romp, bfd_arch_alliant, bfd_arch_convex,        bfd_arch_m88k, bfd_arch_pyramid, bfd_arch_h8300, bfd_arch_powerpc,       bfd_arch_rs6000, bfd_arch_hppa, bfd_arch_d10v, bfd_arch_d30v,        bfd_arch_m68hc11, bfd_arch_m68hc12, bfd_arch_z8k, bfd_arch_h8500,        bfd_arch_sh, bfd_arch_alpha, bfd_arch_arm, bfd_arch_ns32k, bfd_arch_w65,        bfd_arch_tic30, bfd_arch_tic54x, bfd_arch_tic80, bfd_arch_v850,        bfd_arch_arc, bfd_arch_m32r, bfd_arch_mn10200, bfd_arch_mn10300,        bfd_arch_fr30, bfd_arch_mcore, bfd_arch_ia64,  bfd_arch_pj, bfd_arch_avr,       bfd_arch_cris, bfd_arch_last      }; 

The mach field is an extension to the arch field; constants are defined (in the definition of the bfd_architecture enum in bfd.h ) for various CPU architectures. The Intel ones are:

 #define bfd_mach_i386_i386 0     #define bfd_mach_i386_i8086 1     #define bfd_mach_i386_i386_intel_syntax 2     #define bfd_mach_x86_64 3     #define bfd_mach_x86_64_intel_syntax 4 

This is more than a little strange , since Intel IA64 has its own arch type. Note that setting the mach field to bfd_mach_i386_i386_intel_syntax has no effect on the output format; you must call the appropriate print_insn routine, which sets the output format strings to AT&T or Intel syntax before calling print_insn_i386( ) .

The disassemble_info structure should be initialized to zero, then manipulated either directly or with one of the provided macros:

 #define INIT_DISASSEMBLE_INFO(INFO, STREAM, FPRINTF_FUNC) 

where INFO is the static address of the struct (i.e., not a pointer ”the macro uses "INFO." to access struct fields, not "INFO->"), STREAM is the file pointer passed to fprintf( ) , and FPRINTF_FUNC is either fprintf( ) or a replacement with the same syntax.

Why is fprintf( ) needed? It is assumed by libopcodes that the disassembly is going to be immediately printed with no intervening storage or analysis. This means that to store the disassembly for further processing, you must replace fprintf( ) with a custom function that builds a data structure for the instruction.

This is not as simple as it sounds, however. The fprintf( ) function is called once for the mnemonic and once for each operand in the instruction; as a result, any fprintf( ) replacement is going to be messy:

 char mnemonic[32] = {0}, src[32] = {0}, dest[32] = {0}, arg[32] = {0};     int disprintf(FILE *stream, const char *format, ...){         va_list args;         char *str;         va_start (args, format);         str = va_arg(args, char*);         if ( ! mnemonic[0] ) {             strncpy(mnemonic, str, 31);         } else if ( ! src[0] ) {             strncpy(src, str, 31);         } else if ( ! dest[0] ) {             strncpy(dest, str, 31);         } else {             if ( ! strcmp( src, dest ) )                  strncpy(dest, str, 31);             else                  strncpy(arg, str, 31);         }         va_end (args);         return(0);     } 

Simple, graceful , elegant, right? No. The src argument occasionally gets passed twice, requiring the strcmp( ) in the else block. Note that the string buffers must be zeroed out after every successful disassembly in order for disprintf( ) to work at all.

Despite the size of the disassemble_info structure, not much needs to be set in order to use libopcodes. The following code properly initializes the structure:

 /* target settings */         info.arch              = bfd_arch_i386;     info.mach              = bfd_mach_i386_i386;     info.flavour           = bfd_target_unknown_flavour;     info.endian            = BFD_ENDIAN_LITTLE;     /* display/output settings */     info.display_endian    = BFD_ENDIAN_LITTLE;     info.fprintf_func      = fprintf;     info.stream            = stdout;     /* what to disassemble */     info.buffer            = buf;        /* buffer of bytes to disasm */     info.buffer_length     = buf_len;    /* size of buffer */     info.buffer_vma        = buf_vma;    /* base RVA of buffer */ 

The disassembler can now enter a loop, calling the appropriate print_insn routine until the end of the buffer to be disassembled is reached:

 unsigned int pos = 0; while ( pos < info.buffer_length ) {     printf("%8X : ", info.buffer_vma + pos);     pos += print_insn_i386_intel(info.buffer_vma + pos, &info);     printf("\n"); } 

The following program implements a libopcodes-based disassembler, using BFD to load the file and providing a replacement fprintf( ) routine based on the above disprintf( ) routine. The code can be compiled with:

 gcc -I/usr/src/binutils/bfd -I/usr/src/binutils/include -o bfd \        -lbfd -liberty -lopcodes bfd.c 

Note that it requires the BFD libraries as well as libopcodes; this is largely in order to tie the code in with the discussion of BFD in the previous section, as libopcodes can be used without BFD simply by filling the disassemble_info structure with NULL values instead of BFD type information.

 /*---------------------------------------------------------------------------*/     #include <errno.h>     #include <fcntl.h>     #include <stdarg.h>     #include <stdio.h>     #include <stdlib.h>     #include <sys/stat.h>     #include <sys/types.h>     #include <unistd.h>     #include <bfd.h>     #include <dis-asm.h>     struct ASM_INSN {         char mnemonic[16];         char src[32];         char dest[32];         char arg[32];     } curr_insn;     int disprintf(FILE *stream, const char *format, ...){         /* Replacement fprintf(  ) for libopcodes.          * NOTE: the following assumes src, dest order from disassembler */         va_list args;         char *str;         va_start (args, format);         str = va_arg(args, char*);         /* this sucks, libopcodes passes one mnem/operand per call --          * and passes src twice */         if ( ! curr_insn.mnemonic[0] ) {             strncpy(curr_insn.mnemonic, str, 15);         } else if ( ! curr_insn.src[0] ) {             strncpy(curr_insn.src, str, 31);         } else if ( ! curr_insn.dest[0] ) {             strncpy(curr_insn.dest, str, 31);             if (strncmp(curr_insn.dest, "DN", 2) == 0)                     curr_insn.dest[0] = 0;         } else {             if ( ! strcmp( curr_insn.src, curr_insn.dest ) ) {                 /* src was passed twice */                 strncpy(curr_insn.dest, str, 31);             } else {                 strncpy(curr_insn.arg, str, 31);             }         }         va_end (args);         return(0);     }     void print_insn( void ) {         printf("\t%s", curr_insn.mnemonic);         if ( curr_insn.src[0] ) {             printf("\t%s", curr_insn.src );             if ( curr_insn.dest[0] ) {                 printf(", %s", curr_insn.dest );                 if ( curr_insn.arg[0] ) {                     printf(", %s", curr_insn.arg );                 }             }         }         return;     }     int disassemble_forward( disassembler_ftype disassemble_fn,                     disassemble_info *info, unsigned long rva ) {         int bytes = 0;              while ( bytes < info->buffer_length ) {             /* call the libopcodes disassembler */             memset( &curr_insn, 0, sizeof( struct ASM_INSN ));             bytes += (*disassemble_fn)(info->buffer_vma + bytes, info);                   /* -- print any symbol names as labels here -- */             /* print address of instruction */             printf("%8X : ", info->buffer_vma + bytes);             /* -- analyze disassembled instruction here -- */             print_insn(  );             printf("\n");         }         return( bytes );     }     int disassemble_buffer( disassembler_ftype disassemble_fn,                      disassemble_info *info ) {         int i, size, bytes = 0;              while ( bytes < info->buffer_length ) {             /* call the libopcodes disassembler */             memset( &curr_insn, 0, sizeof( struct ASM_INSN ));             size = (*disassemble_fn)(info->buffer_vma + bytes, info);                   /* -- analyze disassembled instruction here -- */                  /* -- print any symbol names as labels here -- */             printf("%8X:   ", info->buffer_vma + bytes);             /* print hex bytes */             for ( i = 0; i < 8; i++ ) {                 if ( i < size )                          printf("%02X ", info->buffer[bytes + i]);                 else                      printf("   ");             }             print_insn(  );             printf("\n");             bytes += size;    /* advance position in buffer */         }         return( bytes );     }     static void disassemble( bfd *b, asection *s, unsigned char *buf,                          int size, unsigned long buf_vma ) {         disassembler_ftype disassemble_fn;         static disassemble_info info = {0};              if ( ! buf )     return;         if ( ! info.arch ) {             /* initialize everything */             INIT_DISASSEMBLE_INFO(info, stdout, disprintf);             info.arch = bfd_get_arch(b);             info.mach = bfd_mach_i386_i386;    /* BFD_guess no worka */             info.flavour = bfd_get_flavour(b);             info.endian = b->xvec->byteorder;             /* these can be replaced with custom routines                info.read_memory_func = buffer_read_memory;               info.memory_error_func = perror_memory;               info.print_address_func = generic_print_address;               info.symbol_at_address_func = generic_symbol_at_address;               info.fprintf_func = disprintf; //handled in macro above               info.stream = stdout;          // ditto               info.symbols = NULL;               info.num_symbols = 0;             */             info.display_endian = BFD_ENDIAN_LITTLE;              }              /* choose disassembler function */         disassemble_fn = print_insn_i386_att;         /* disassemble_fn = print_insn_i386_intel; */         /* these are section dependent */         info.section = s;            /* section to disassemble */         info.buffer = buf;        /* buffer of bytes to disassemble */         info.buffer_length = size;    /* size of buffer */         info.buffer_vma = buf_vma;    /* base RVA of buffer */                  disassemble_buffer( disassemble_fn, &info );              return;     }     static void print_section_header( asection *s, const char *mode ) {              printf("Disassembly of section %s as %s\n", s->name, mode );         printf("RVA: %08X LMA: %08X Flags: %08X Size: %X\n", s->vma,                 s->lma, s->flags, s->_cooked_size );         printf( "---------------------------------------------------------"               "-----------------------\n" );         return;     }     static void disasm_section_code( bfd *b, asection *section ) {         int size;         unsigned char *buf;         size = bfd_section_size( b, section );         buf = calloc( size, 1 );         if (! buf  ! bfd_get_section_contents(b, section, buf, 0, size ))             return;         print_section_header( section, "code" );         disassemble( b, section, buf, size, section->vma );         printf("\n\n");         free( buf );         return;     }     static void disasm_section_data( bfd *b, asection *section ) {         int i, j, size;         unsigned char *buf;         size = bfd_section_size( b, section );         buf = calloc( size, 1 );         if ( ! bfd_get_section_contents( b, section, buf, 0, size ) )             return;         print_section_header( section, "data" );         /* do hex dump of data */         for ( i = 0; i < size; i +=16 ) {             printf( "%08X:   ", section->vma + i );             for (j = 0; j < 16 && j+i < size; j++ )                   printf("%02X ", buf[i+j] );             for ( ; j < 16; j++ )                 printf("   ");             printf("  ");             for (j = 0; j < 16 && j+i < size; j++ )                  printf("%c", isprint(buf[i+j]) ? buf[i+j] : '.' );             printf("\n");         }         printf("\n\n");         free( buf );         return;     }     static void disasm_section( bfd *b, asection *section, PTR data ){         if ( ! section->flags & SEC_ALLOC )  return;         if ( ! section->flags & SEC_LOAD ) return;         if ( section->flags & SEC_LINKER_CREATED ) return;         if ( section->flags & SEC_CODE) {             if ( ! strncmp(".plt", section->name, 4)                   ! strncmp(".got", section->name, 4) ) {                 return;             }             disasm_section_code( b, section );         } else if ( (section->flags & SEC_DATA                   section->flags & SEC_READONLY) &&                 section->flags & SEC_HAS_CONTENTS  ) {             disasm_section_data( b, section );         }         return;     }     int main( int argc, char **argv ) {               struct stat s;         bfd *infile;           if ( argc < 2 ) {                 fprintf(stderr, "Usage: %s target\n", argv[0]);                 return(1);           }           if ( stat( argv[1], &s) ) {                 fprintf(stderr, "Error: %s\n", strerror(errno) );                 return(2);           }         bfd_init(  );         /* open input file for reading */         infile = bfd_openr( argv[1], NULL );         if ( ! infile ) {             bfd_perror( "Error on infile" );             return(3);         }         if ( bfd_check_format (infile, bfd_object )                 bfd_check_format(infile, bfd_archive ) ) {             bfd_map_over_sections( infile, disasm_section, NULL );         } else  {             fprintf( stderr, "Error: unknown file format\n");             return(5);         }         bfd_close(infile);                return(0);     } /*-----------------------------------------------------------------------*/ 

As disassemblers go, this is rather mundane ”and it's not an improvement on objdump. Being BFD-based, it does not perform proper loading of the ELF file header and is therefore still unable to handle sstriped binaries ”however, this could be fixed by removing the dependence on BFD and using a custom ELF file loader.

The disassembler could also be improved by adding the ability to disassemble based on the flow of execution, rather than on the sequence of addresses in the code section. The next program combines libopcodes with the instruction types presented earlier in "Intermediate Code Generation." The result is a disassembler that records operand type information and uses the mnemonic to determine if the instruction influences the flow of execution, and thus whether it should follow the target of the instruction.

 /*---------------------------------------------------------------------------*/     #include <errno.h>     #include <fcntl.h>     #include <stdarg.h>     #include <stdio.h>     #include <stdlib.h>     #include <sys/stat.h>     #include <sys/types.h>     #include <unistd.h>     #include <bfd.h>     #include <dis-asm.h>     /* operand types */     enum op_type { op_unk, op_reg, op_imm, op_expr, op_bptr, op_dptr,                 op_wptr };     struct ASM_INSN {         char mnemonic[16];         char src[32];         char dest[32];         char arg[32];         enum op_type src_type, dest_type, arg_type;     } curr_insn;     enum op_type optype( char *op){         if ( op[0] == '%' ) { return(op_reg); }         if ( op[0] == '$' ) { return(op_imm); }         if ( strchr(op, '(')) { return(op_expr); }         if ( strncmp( op, "BYTE PTR", 8)) { return(op_bptr); }         if ( strncmp( op, "DWORD PTR", 9)) { return(op_dptr); }         if ( strncmp( op, "WORD PTR", 8)) { return(op_wptr); }         return(op_unk);     }     /* we kind of cheat with these, since Intel has so few 'j' insns */     #define JCC_INSN 'j'     #define JMP_INSN "jmp"     #define LJMP_INSN "ljmp"     #define CALL_INSN "call"     #define RET_INSN "ret"          enum flow_type { flow_branch, flow_cond_branch, flow_call, flow_ret,                   flow_none };     enum flow_type insn_flow_type( char *insn ) {         if (! strncmp( JMP_INSN, insn, 3)               ! strncmp( LJMP_INSN, insn, 4) ) {             return( flow_branch );         } else if ( insn[0] == JCC_INSN ) {             return( flow_cond_branch ) ;         } else if ( ! strncmp( CALL_INSN, insn, 4) ) {             return( flow_call );         } else if ( ! strncmp( RET_INSN, insn, 3) ) {             return( flow_ret );         }         return( flow_none );     }     int disprintf(FILE *stream, const char *format, ...){         va_list args;         char *str;         va_start (args, format);         str = va_arg(args, char*);         /* this sucks, libopcodes passes one mnem/operand per call --          * and passes src twice */         if ( ! curr_insn.mnemonic[0] ) {             strncpy(curr_insn.mnemonic, str, 15);         } else if ( ! curr_insn.src[0] ) {             strncpy(curr_insn.src, str, 31);             curr_insn.src_type = optype(curr_insn.src);         } else if ( ! curr_insn.dest[0] ) {             strncpy(curr_insn.dest, str, 31);             curr_insn.dest_type = optype(curr_insn.dest);             if (strncmp(curr_insn.dest, "DN", 2) == 0)                     curr_insn.dest[0] = 0;         } else {             if ( ! strcmp( curr_insn.src, curr_insn.dest ) ) {                 /* src was passed twice */                 strncpy(curr_insn.dest, str, 31);                 curr_insn.dest_type = optype(curr_insn.dest);             } else {                 strncpy(curr_insn.arg, str, 31);                 curr_insn.arg_type = optype(curr_insn.arg);             }         }         va_end (args);              return(0);     }     void print_insn( void ) {         printf("\t%s", curr_insn.mnemonic);         if ( curr_insn.src[0] ) {             printf("\t%s", curr_insn.src );             if ( curr_insn.dest[0] ) {                 printf(", %s", curr_insn.dest );                 if ( curr_insn.arg[0] ) {                     printf(", %s", curr_insn.arg );                 }             }         }         return;     }     int rva_from_op( char *op, unsigned long *rva ) {         if ( *op == '*' )     return(0);    /* pointer */         if ( *op == '$' )     op++;         if ( isxdigit(*op) ) {             *rva = strtoul(curr_insn.src, NULL, 16);             return(1);         }         return(0);     }     static void disassemble( bfd *b, unsigned long rva, int nest );     int disassemble_forward( disassembler_ftype disassemble_fn,                  disassemble_info *info, unsigned long rva, int nest ) {         int i, good_rva, size, offset, bytes = 0;         unsigned long branch_rva;         enum flow_type flow;         if (! nest )                  return(0);              /* get offset of rva into section */         offset = rva - info->buffer_vma;                  /* prevent runaway loops */         nest--;         while ( bytes < info->buffer_length ) {             /* this has to be verified because of branch following */             if ( rva < info->buffer_vma                         rva >= info->buffer_vma + info->buffer_length ) {                 /* recurse via disassemble(  ) then exit */                 disassemble( NULL, rva + bytes, nest );                 return(0);             }             /* call the libopcodes disassembler */             memset( &curr_insn, 0, sizeof( struct ASM_INSN ));             size = (*disassemble_fn)(rva + bytes, info);              /* -- analyze disassembled instruction here -- */             /* -- print any symbol names as labels here -- */             printf("%8X:   ", rva + bytes);             /* print hex bytes */             for ( i = 0; i < 8; i++ ) {                 if ( i < size )                          printf("%02X ", info->buffer[offset+bytes+i]);                 else                      printf("   ");             }             print_insn(  );             printf("\n");             bytes += size;    /* advance position in buffer */             /* check insn type */             flow = insn_flow_type( curr_insn.mnemonic );              if ( flow == flow_branch  flow == flow_cond_branch                    flow == flow_call ) {                 /* follow branch branch */                 good_rva = 0;                 if ( curr_insn.src_type == op_bptr                       curr_insn.src_type == op_wptr                       curr_insn.src_type == op_dptr    ) {                     good_rva = rva_from_op( curr_insn.src,                                  &branch_rva );                 }                 if ( good_rva ) {                     printf(";------------------ FOLLOW BRANCH %X\n",                             branch_rva );                     disassemble_forward( disassemble_fn, info,                                  branch_rva, nest );                 }             }             if ( flow == flow_branch  flow == flow_ret ) {                 /* end of execution flow : exit loop */                 bytes = info->buffer_length;                 printf(";------------------------- END BRANCH\n");                 continue;             }         }         return( bytes );     }     struct RVA_SEC_INFO {         unsigned long rva;         asection *section;     };     static void find_rva( bfd *b, asection *section, PTR data ){         struct RVA_SEC_INFO *rva_info = data;         if ( rva_info->rva >= section->vma &&               rva_info->rva < section->vma + bfd_section_size( b, section ))             /* we have a winner */             rva_info->section = section;         return;     }               static void disassemble( bfd *b, unsigned long rva, int nest ) {         static disassembler_ftype disassemble_fn;         static disassemble_info info = {0};         static bfd *bfd = NULL;         struct RVA_SEC_INFO rva_info;         unsigned char *buf;         int size;         if ( ! bfd ) {             if ( ! b )     return;             bfd = b;             /* initialize everything */             INIT_DISASSEMBLE_INFO(info, stdout, disprintf);             info.arch = bfd_get_arch(b);             info.mach = bfd_mach_i386_i386;             info.flavour = bfd_get_flavour(b);             info.endian = b->xvec->byteorder;             info.display_endian = BFD_ENDIAN_LITTLE;             disassemble_fn = print_insn_i386_att;         }              /* find section containing rva */         rva_info.rva = rva;         rva_info.section = NULL;         bfd_map_over_sections( bfd, find_rva, &rva_info );         if ( ! rva_info.section )              return;         size = bfd_section_size( bfd, rva_info.section );         buf = calloc( size, 1 );         /* we're gonna be mean here and only free the calloc at exit(  ) */         if ( ! bfd_get_section_contents( bfd, rva_info.section, buf, 0,                                              size ) )             return;              info.section = rva_info.section;            /* section to disasm */         info.buffer = buf;                          /* buffer to disasm */         info.buffer_length = size;                  /* size of buffer */         info.buffer_vma = rva_info.section->vma;    /* base RVA of buffer */                  disassemble_forward( disassemble_fn, &info, rva, nest );              return;     }          static void print_section_header( asection *s, const char *mode ) {              printf("Disassembly of section %s as %s\n", s->name, mode );         printf("RVA: %08X LMA: %08X Flags: %08X Size: %X\n", s->vma,                 s->lma, s->flags, s->_cooked_size );         printf( "--------------------------------------------------------"               "------------------------\n" );         return;     }     static void disasm_section( bfd *b, asection *section, PTR data ){         int i, j, size;         unsigned char *buf;         /* we only care about data sections */         if ( ! section->flags & SEC_ALLOC )  return;         if ( ! section->flags & SEC_LOAD ) return;         if ( section->flags & SEC_LINKER_CREATED ) return;         if ( section->flags & SEC_CODE) {             return;         } else if ( (section->flags & SEC_DATA                   section->flags & SEC_READONLY) &&                 section->flags & SEC_HAS_CONTENTS  ) {             /* print dump of data section */             size = bfd_section_size( b, section );             buf = calloc( size, 1 );             if ( ! bfd_get_section_contents( b, section, buf, 0, size ) )                 return;             print_section_header( section, "data" );             for ( i = 0; i < size; i +=16 ) {                 printf( "%08X:   ", section->vma + i );                 for (j = 0; j < 16 && j+i < size; j++ )                       printf("%02X ", buf[i+j] );                 for ( ; j < 16; j++ )                     printf("   ");                 printf("  ");                 for (j = 0; j < 16 && j+i < size; j++ )                      printf("%c", isprint(buf[i+j]) ? buf[i+j] : '.');                 printf("\n");             }             printf("\n\n");             free( buf );         }         return;     }     int main( int argc, char **argv ) {           struct stat s;         bfd *infile;           if ( argc < 2 ) {                 fprintf(stderr, "Usage: %s target\n", argv[0]);                 return(1);           }               if ( stat( argv[1], &s) ) {                     fprintf(stderr, "Error: %s\n", strerror(errno) );                     return(2);               }         bfd_init(  );         /* open input file for reading */         infile = bfd_openr( argv[1], NULL );         if ( ! infile ) {             bfd_perror( "Error on infile" );             return(3);         }         if ( bfd_check_format (infile, bfd_object )                 bfd_check_format(infile, bfd_archive ) ) {             /* disassemble forward from entry point */             disassemble( infile, bfd_get_start_address(infile), 10 );             /* disassemble data sections */             bfd_map_over_sections( infile, disasm_section, NULL );         } else  {             fprintf( stderr, "Error: unknown file format\n");             return(5);         }         bfd_close(infile);                return(0);     } /*-----------------------------------------------------------------------*/ 

Granted, this has its problems. The fprintf-based nature of the output means that instructions are printed as they are disassembled, rather than in address order; a better implementation would be to add each disassembled instruction to a linked list or tree, then print once all disassembly and subsequent analysis has been performed. Furthermore, since previously disassembled addresses are not stored, the only way to prevent endless loops is by using an arbitrary value to limit recursion. The disassembler relies on a single shared disassemble_info structure rather than providing its own, making for some messy code where the branch following causes a recursion into a different section (thereby overwriting info->buffer , info->buffer_vma , info->buffer->size , and info->section ). Not an award-winning design to be sure; it cannot even follow function pointers!

As an example, however, it builds on the code of the previous disassembler to demonstrate how to implement branch following during disassembly. At this point, the program is no longer a trivial objdump-style disassembler; further development would require some intermediate storage of the disassembled instructions, as well as more intelligent instruction analysis. The instruction types can be expanded and used to track cross-references, monitor stack position, and perform algorithm analysis. A primitive virtual machine can be implemented by simulating reads and writes to addresses and registers, as indicated by the operand type.

Modifications such as these are beyond the scope of a simple introduction and are not illustrated here; hopefully, the interested reader has found enough information here to pursue such projects with confidence.

 <  Day Day Up  >  


Security Warrior
Security Warrior
ISBN: 0596005458
EAN: 2147483647
Year: 2004
Pages: 211

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net