2012-02-03 15 views
12

Sto scrivendo uno strumento che utilizza libbfd e libopcodes in x86-32 e x86-64 Linux per eseguire il disassemblaggio. Il problema è che mentre sono in grado di far smontare i libopcode, non sono in grado di ottenere alcuna informazione di istruzioni. Ai fini della dimostrazione, ho fatto un esempio minimale che riproduce il mio problema. Il programma dovrebbe smontarsi dal punto di ingresso al primo RET/RETQ.Come ottenere informazioni di istruzioni da libopcodes?

Il codice è un po 'compromesso con i globali e il controllo degli errori è stato omesso per brevità, ecc., Ma dovrebbe illustrare chiaramente il problema.

#include <bfd.h> 
#include <dis-asm.h> 
#include <stdbool.h> 
#include <stdio.h> 
#include <stdlib.h> 
#include <unistd.h> 
#include <sys/types.h> 
#include <string.h> 
#include <ctype.h> 
#include <limits.h> 
#include <libiberty.h> 

/* 
* Holds state for BFD and libopcodes. 
*/ 
bfd *  abfd = NULL; 
disassemble_info dinfo = {0}; 

/* 
* Temporary hack to signal when disassembling should stop. 
*/ 
static bool stop_disassembling = FALSE; 

/* 
* Gets path to currently running executable. 
*/ 
bool get_target_path(char * target_path, size_t size) 
{ 
    char * path; 
    ssize_t len; 

    pid_t pid = getpid(); 
    sprintf(target_path, "/proc/%d/exe", (int)pid); 

    path = strdup(target_path); 
    len = readlink(path, target_path, size); 

    target_path[len] = '\0'; 
    free(path); 
    return TRUE; 
} 

/* 
* libopcodes appends spaces on the end of some instructions so for 
* comparisons, we want to strip those first. 
*/ 
void strip_tail(char * str, unsigned int size) 
{ 
    int i; 
    for(i = 0; i < size; i++) { 
     if(!isgraph(str[i])) { 
      str[i] = '\0'; 
      break; 
     } 
    } 
} 

/* 
* Checks whether the current instruction will cause the control flow to not 
* proceed to the linearly subsequent instruction (e.g. ret, jmp, etc.) 
*/ 
bool breaks_control_flow(char * str) 
{ 
    if(abfd->arch_info->bits_per_address == 64) { 
     if(strcmp(str, "retq") == 0) { 
      return TRUE; 
     } 
    } else { 
     if(strcmp(str, "ret") == 0) { 
      return TRUE; 
     } 
    } 

    return FALSE; 
} 

/* 
* Used as a callback for libopcodes so we can do something useful with the 
* disassembly. Currently this just outputs to stdout. 
*/ 
int custom_fprintf(void * stream, const char * format, ...) 
{ 
    /* silly amount */ 
    char str[128] = {0}; 
    int rv; 
    va_list args; 

    va_start(args, format); 
    rv = vsnprintf(str, ARRAY_SIZE(str) - 1, format, args); 
    va_end(args); 

    puts(str); 
    strip_tail(str, ARRAY_SIZE(str)); 

    if(breaks_control_flow(str)) { 
     puts("Stopped disassembly"); 
     stop_disassembling = TRUE; 
    } 

    if(dinfo.insn_info_valid) { 
     switch(dinfo.insn_type) { 
      case dis_noninsn: 
       printf("not an instruction\n"); 
       break; 
      case dis_nonbranch: 
       printf("not a branch\n"); 
       break; 
      case dis_branch: 
       printf("is a branch\n"); 
       break; 
      case dis_condbranch: 
       printf("is a conditional branch\n"); 
       break; 
      case dis_jsr: 
       printf("jump to subroutine\n"); 
       break; 
      case dis_condjsr: 
       printf("conditional jump to subroutine\n"); 
       break; 
      case dis_dref: 
       printf("data reference in instruction\n"); 
       break; 
      case dis_dref2: 
       printf("two data references in instruction\n"); 
       break; 
      default: 
       printf("not enumerated\n"); 
       break; 
     } 
    } else { 
     printf("insn_info not valid\n"); 
    } 

    return rv; 
} 

/* 
* Initialises libopcodes disassembler and returns an instance of it. 
*/ 
disassembler_ftype init_disasm(bfd * abfd, disassemble_info * dinfo) 
{ 
    /* Override the stream the disassembler outputs to */ 
    init_disassemble_info(dinfo, NULL, custom_fprintf); 
    dinfo->flavour = bfd_get_flavour(abfd); 
    dinfo->arch = bfd_get_arch(abfd); 
    dinfo->mach = bfd_get_mach(abfd); 
    dinfo->endian = abfd->xvec->byteorder; 
    disassemble_init_for_target(dinfo); 

    return disassembler(abfd); 
} 

/* 
* Method of locating section from VMA taken from opdis. 
*/ 
typedef struct { 
    bfd_vma vma; 
    asection * sec; 
} BFD_VMA_SECTION; 

/* 
* Loads section and fills in dinfo accordingly. Since this function allocates 
* memory in dinfo->buffer, callers need to call free once they are finished. 
*/ 
bool load_section(bfd * abfd, disassemble_info * dinfo, asection * s) 
{ 
    int  size = bfd_section_size(s->owner, s); 
    unsigned char * buf = xmalloc(size); 

    if(!bfd_get_section_contents(s->owner, s, buf, 0, size)) { 
     free(buf); 
     return FALSE; 
    } 

    dinfo->section  = s; 
    dinfo->buffer  = buf; 
    dinfo->buffer_length = size; 
    dinfo->buffer_vma = bfd_section_vma(s->owner, s); 

    printf("Allocated %d bytes for %s section\n: 0x%lX", size, s->name, 
      dinfo->buffer_vma); 
    return TRUE; 
} 

/* 
* Used to locate section for a vma. 
*/ 
void vma_in_section(bfd * abfd, asection * s, void * data) 
{ 
    BFD_VMA_SECTION * req = data; 

    if(req && req->vma >= s->vma && 
    req->vma < (s->vma + bfd_section_size(abfd, s))) { 
     req->sec = s; 
    } 
} 

/* 
* Locate and load section containing vma. 
*/ 
bool load_section_for_vma(bfd * abfd, disassemble_info * dinfo, 
     bfd_vma vma) 
{ 
    BFD_VMA_SECTION req = {vma, NULL}; 
    bfd_map_over_sections(abfd, vma_in_section, &req); 

    if(!req.sec) { 
     return FALSE; 
    } else { 
     return load_section(abfd, dinfo, req.sec); 
    } 
} 

/* 
* Start disassembling from entry point. 
*/ 
bool disassemble_entry(bfd * abfd, disassemble_info * dinfo, 
     disassembler_ftype disassembler) 
{ 
    bfd_vma vma = bfd_get_start_address(abfd); 

    /* First locate and load the section containing the vma */ 
    if(load_section_for_vma(abfd, dinfo, vma)) { 
     int size; 

     /* Keep disassembling until signalled otherwise or error */ 
     while(true) { 
      dinfo->insn_info_valid = 0; 
      size = disassembler(vma, dinfo); 
      printf("Disassembled %d bytes at 0x%lX\n", size, vma); 

      if(size == 0 || size == -1 || stop_disassembling) { 
       break; 
      } 

      vma += size; 
     } 

     free(dinfo->buffer); 
     return TRUE; 
    } 

    return FALSE; 
} 

int main(void) 
{ 
    char target_path[PATH_MAX] = {0}; 

    bfd_init(); 

    /* Get path for the running instance of this program */ 
    get_target_path(target_path, ARRAY_SIZE(target_path)); 

    abfd = bfd_openr(target_path, NULL); 

    if(abfd != NULL && bfd_check_format(abfd, bfd_object)) { 
     disassembler_ftype disassembler = init_disasm(abfd, &dinfo); 

     disassemble_entry(abfd, &dinfo, disassembler); 

     bfd_close(abfd); 
    } 

    return EXIT_SUCCESS; 
} 

Questa fonte può essere creata con il seguente makefile. Per eseguire un collegamento con successo, il pacchetto binutils-dev deve essere installato sulla macchina locale:

all: 
    gcc -Wall disasm.c -o disasm -lbfd -lopcodes 

clean: 
    rm -f disasm 

Quando eseguito, l'output è questo:

Allocated 2216 bytes for .text section 
: 0x400BF0xor  
insn_info not valid 
%ebp 
insn_info not valid 
, 
insn_info not valid 
%ebp 
insn_info not valid 
Disassembled 2 bytes at 0x400BF0 
mov  
insn_info not valid 
%rdx 
insn_info not valid 
, 
insn_info not valid 
%r9 
insn_info not valid 
Disassembled 3 bytes at 0x400BF2 
pop  
insn_info not valid 
%rsi 
insn_info not valid 
Disassembled 1 bytes at 0x400BF5 
mov  
insn_info not valid 
%rsp 
insn_info not valid 
, 
insn_info not valid 
%rdx 
insn_info not valid 
Disassembled 3 bytes at 0x400BF6 
and  
insn_info not valid 
$0xfffffffffffffff0 
insn_info not valid 
, 
insn_info not valid 
%rsp 
insn_info not valid 
Disassembled 4 bytes at 0x400BF9 
push 
insn_info not valid 
%rax 
insn_info not valid 
Disassembled 1 bytes at 0x400BFD 
push 
insn_info not valid 
%rsp 
insn_info not valid 
Disassembled 1 bytes at 0x400BFE 
mov  
insn_info not valid 
$0x401450 
insn_info not valid 
, 
insn_info not valid 
%r8 
insn_info not valid 
Disassembled 7 bytes at 0x400BFF 
mov  
insn_info not valid 
$0x4013c0 
insn_info not valid 
, 
insn_info not valid 
%rcx 
insn_info not valid 
Disassembled 7 bytes at 0x400C06 
mov  
insn_info not valid 
$0x4012ce 
insn_info not valid 
, 
insn_info not valid 
%rdi 
insn_info not valid 
Disassembled 7 bytes at 0x400C0D 
callq 
insn_info not valid 
0x0000000000400ad8 
insn_info not valid 
Disassembled 5 bytes at 0x400C14 
hlt  
insn_info not valid 
Disassembled 1 bytes at 0x400C19 
nop 
insn_info not valid 
Disassembled 1 bytes at 0x400C1A 
nop 
insn_info not valid 
Disassembled 1 bytes at 0x400C1B 
sub  
insn_info not valid 
$0x8 
insn_info not valid 
, 
insn_info not valid 
%rsp 
insn_info not valid 
Disassembled 4 bytes at 0x400C1C 
mov  
insn_info not valid 
0x2013b9(%rip) 
insn_info not valid 
, 
insn_info not valid 
%rax 
insn_info not valid 
     # 
insn_info not valid 
0x0000000000601fe0 
insn_info not valid 
Disassembled 7 bytes at 0x400C20 
test 
insn_info not valid 
%rax 
insn_info not valid 
, 
insn_info not valid 
%rax 
insn_info not valid 
Disassembled 3 bytes at 0x400C27 
je  
insn_info not valid 
0x0000000000400c2e 
insn_info not valid 
Disassembled 2 bytes at 0x400C2A 
callq 
insn_info not valid 
*%rax 
insn_info not valid 
Disassembled 2 bytes at 0x400C2C 
add  
insn_info not valid 
$0x8 
insn_info not valid 
, 
insn_info not valid 
%rsp 
insn_info not valid 
Disassembled 4 bytes at 0x400C2E 
retq 
Stopped disassembly 
insn_info not valid 
Disassembled 1 bytes at 0x400C32 

Quello che mi aspetto è quello di essere in grado di leggere informazioni di istruzioni per ogni istruzione tramite dinfo->insn_type, target, ecc. Il comportamento è esposto sia su x86-32 che su x86-64. Se posso almeno avere conferma che questo non è implementato su queste due architetture, allora posso andare a compilare queste informazioni da solo.

+1

si potrebbe trovare solo più facile da usare un disassembler multi-piattaforma come beaengine e saltare tutto il mal di testa: http://www.beaengine.org/ – Necrolis

+0

Sfortunatamente, questi sono i requisiti per il progetto su cui sto lavorando. Qualcosa di interessante è che opdis usa le informazioni dell'istruzione, o almeno lo copia in un buffer, suggerendo che l'informazione è accessibile. Ho difficoltà a vedere che cosa sta facendo opdis che io non sono però. –

+1

a proposito, c'è un problema con il tuo codice sorgente: 'readlink' non aggiunge un trailing' \ 0' alla stringa. –

risposta

9

Purtroppo, come di binutils libopcodes 2.22, insn_type non è compilato su entrambi i386 o x86_64. Le uniche architetture supportate diffuse sono MIPS, Sparc e SPU di Cell. Questo è ancora vero a partire dall'attuale CVS HEAD.

E 'difficile dimostrare che qualcosa non esiste, ma per esempio, in the Sparc disassembler source si possono vedere diverse occorrenze di insn_type essere impostati, per esempio info->insn_type = dis_branch, mentre in the i386 disassembler source non ci sono le occorrenze di insn_type né uno dei valori sarebbe dovrebbe avere (dis_branch, dis_nonbranch ecc.).

Controllo per tutti i file che supportano libopcodes insn_type si ottiene:

  • opcodes/mips-dis.c
  • opcodes/spu-dis.c
  • opcodes/microblaze-dis.c
  • opcodes/cris-dis.c
  • opcodes/sparc-dis.c
  • opcodes/mmix-dis.c
+0

Questa è esattamente la risposta che stavo cercando! C'è comunque qualche citazione o documentazione per queste informazioni? –

+0

@ MikeKwan: ​​Ho aggiunto più informazioni alla risposta che potevo raccogliere; non sembra esserci documentazione ufficiale su ciò che è supportato o meno. Ma l'intestazione 'dis-asm.h' dice esplicitamente _Non tutti i decoder supportano ancora queste informazioni_. –

+0

Grazie per avermi contattato. Ho anche guardato i386-dis.c, che supporta quello che stai dicendo. Ho ora assegnato la taglia. –

3

Fare questo con solo quelle librerie sarà un processo estremamente doloroso e difficile. I pensa a dovresti ascoltare Necrolis e usare una libreria che già lo fa. Ho utilizzato lo Dyninst in passato (ovvero l'InstructionAPI + ParseAPI). Sono molto ben documentati e faranno esattamente quello che stai cercando di fare.Per lo meno, passare un'ora con questa libreria e compilare i loro esempi nei manuali ti darà un'applicazione che ti permetterà di esaminare cose come gli opcode di ciascuna istruzione, la lunghezza di ogni istruzione, il numero di argomenti per ogni istruzione, ecc. Queste sono cose che libopcodes non ti dice né gestisce (decodifica gli indirizzi alla volta, che non sono garantiti come istruzioni).

Ecco un frammento da parte degli sviluppatori di Opdis che ho preso da loro manual (che vorrei suggerire di leggere se non lo avete, un sacco di roba buona in là circa libopcodes):

La biblioteca libopcodes è un disassemblatore molto utile, ma ha tre difetti:

  1. è sotto-documentato, rendendo difficile per i nuovi utenti a capire
  2. suo set di funzionalità è limitata allo smontaggio di un singolo indirizzo
  3. è stato progettato soprattutto per le istruzioni print smontato per un flusso

Tra le altre cose, penso che si potrebbe essere pungersi dal secondo elemento in tale elenco. Vale a dire, il fatto che la maggior parte degli opcode (tutti?) Si inseriscano in un unico indirizzo e siano d'accordo con l'output osservato (ad es. Si ottengono mov e pop e alcuni argomenti di registro). Ma che dire di cose complicate come istruzioni o istruzioni di lunghezza variabile che non si allineano esattamente ai confini di 4 byte? Non stai facendo nulla per gestirli.

Lo smontaggio generato da libopcodes è una sequenza di stringhe destinata alla scrittura su un flusso. Non ci sono metadati, quindi le stringhe devono essere esaminate per determinare quali sono mnemonici e quali sono gli operandi e quali di questi sono istruzioni di salto/salto/ritorno e quali sono i loro obiettivi.

Immagino che Opdis sia più intelligente del tuo programma: sa come e cosa cercare nello stream. Forse a volte sa che ha bisogno di leggere due indirizzi invece di uno solo prima di smontare. Dal tuo codice e dalla descrizione di libopcodes, nessuno dei due sta facendo questo.

Buona fortuna! Ricordati di leggere quel manuale, e magari prendere in considerazione l'utilizzo di libopdis!

+0

Concordo sul fatto che 'libopcodes' sia un problema da usare. La ragione principale per cui la sto usando è a causa dell'obbligo di lavorare sull'astrazione del BFD. In effetti, lo smontaggio di un obiettivo è solo una piccola parte del progetto. La visione finale è di essere in grado di fornire editing eseguibile arbitrario. BFD offre funzionalità utili per la strumentazione di codice aggiuntivo. –

+0

Riguardo ai problemi che descrivi con 'libopcodes', quelli sono certamente delle vere preoccupazioni ma possono essere risolti. Nello specifico, è possibile dire la lunghezza di un'istruzione vedendo quanti byte sono smontati. Naturalmente, un prerequisito è che si stia iniziando da un confine di istruzioni. Lo assicuro avviando l'analisi del flusso di controllo dello smontaggio dal punto di ingresso del target. –

+0

I problemi elencati da 'Opdis' sono anche preoccupazioni legittime e possono essere risolti come segue. 1) sì ... questo è un problema e questa domanda lo dimostra: p 2) con l'approccio dell'analisi del flusso di controllo che sono preso, non dobbiamo preoccuparci di questo, smontiamo semplicemente l'istruzione successiva e smettiamo di ramificarci adeguatamente jmps/calls/rets, ecc. 3) è possibile reindirizzare e sovrascrivere il flusso di stampa dal built-in fprintf a una funzione personalizzata (che è ciò che fa opdis stesso). –

0

Libopcodes stampa istruzioni disassemblate nello stream che sono intercettate dalla funzione custom_printf. Il tuo errore è che tu presumi che custom_printf venga chiamato una volta ogni volta che una singola istruzione viene smontata, tuttavia, viene chiamata più spesso, in particolare, per stampare ogni mnemonico, operando, indirizzo o separatore.

Così, con conseguente smontaggio del tuo binario è

xor %ebp, %ebp 

mov %rdx, %r9 

pop %rsi 

mov %rsp, %rdx 

and $0xfffffffffffffff0, %rsp 

push %rax 

push %rsp 

mov $0x401450,%r8 

... 
+0

Ciao Alexandra. Grazie per la risposta. Penso che tu abbia frainteso la mia domanda. Sono consapevole che è come funziona libopcodes. Saluti! –

Problemi correlati