2012-02-03 16 views
12

Estoy escribiendo una herramienta que usa libbfd y libopcodes en x86-32 y x86-64 Linux para realizar el desmontaje. El problema es que, si bien puedo descifrar códigos de barras, no puedo obtener ninguna información de instrucciones. A los efectos de la demostración, he hecho un ejemplo mínimo que reproduce mi problema. El programa debe desarmarse desde el punto de entrada al primer RET/RETQ.¿Cómo obtener información de instrucciones de códigos de barras?

El código está un poco hackeado con globales y se ha omitido la comprobación de errores por brevedad, etc., pero debe ilustrar el problema con claridad.

#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; 
} 

Esta fuente se puede construir con la siguiente makefile. Para realizar un enlace con éxito, el paquete binutils-dev necesita ser instalado en la máquina local:

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

clean: 
    rm -f disasm 

Cuando se ejecuta, la salida es la siguiente:

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 

Lo que estoy esperando es ser capaz de leer información de instrucción para cada instrucción a través del dinfo->insn_type, target, etc. El comportamiento se exhibe tanto en x86-32 como en x86-64. Si al menos puedo obtener la confirmación de que esto no se aplica en estas dos arquitecturas, entonces puedo completar esta información yo mismo.

+1

que sólo podría encontrar más fácil de usar un desensamblador multi-plataforma como beaengine y omitir todo el dolor de cabeza: http://www.beaengine.org/ – Necrolis

+0

Lamentablemente, estos son requisitos para el proyecto en el que estoy trabajando. Algo interesante es que opdis utiliza la información de la instrucción, o al menos la copia en un búfer, lo que sugiere que la información es accesible. Tengo problemas para ver qué está haciendo Opdis que no estoy pensando. –

+1

por cierto, hay un problema con su código fuente: 'readlink' no agrega un' \ 0' posterior a la cadena. –

Respuesta

9

Lamentablemente, desde binutils libopcodes 2.22, insn_type no se rellena ni en i386 ni en x86_64. Las únicas arquitecturas compatibles ampliamente difundidas son MIPS, Sparc y SPU de Cell. Esto sigue siendo cierto a partir de CVS HEAD actual.

Es difícil probar que algo no existe, pero por ejemplo, en the Sparc disassembler source se pueden ver varias ocurrencias de insn_type están estableciendo, por ejemplo info->insn_type = dis_branch, mientras que en the i386 disassembler source no hay ocurrencias de insn_type ni ninguno de los valores que sería se espera que tenga (dis_branch, dis_nonbranch etc.).

Comprobación de todos los archivos que soportan libopcodes insn_type que se obtiene:

  • 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

¡Esta es exactamente la respuesta que estaba buscando! Sin embargo, hay alguna cita o documentación para esta información? –

+0

@MikeKwan: ​​He agregado tanta información a la respuesta como pude reunir; no parece haber documentación oficial sobre lo que se admite o no. Pero el encabezado 'dis-asm.h' dice explícitamente _No todos los decodificadores aún admiten esta información_. –

+0

Gracias por buscarme eso. También he estado mirando i386-dis.c, que respalda lo que estás diciendo. Ahora he otorgado la recompensa. –

3

Hacer esto con sólo esas bibliotecas va a ser un proceso extremadamente doloroso y arduo. I piensa debe escuchar Necrolis y usar una biblioteca que ya lo haga. He utilizado el Dyninst en el pasado (es decir, la InstructionAPI + ParseAPI). Están muy bien documentados, y harán exactamente lo que estás tratando de hacer.Por lo menos, pasar una hora con esta biblioteca y compilar sus ejemplos en los manuales le dará una aplicación que le permitirá examinar cosas como los códigos de operación de cada instrucción, la duración de cada instrucción, el número de argumentos para cada instrucción, etc. Estas son cosas que libopcodes no le dice ni maneja (decodifica direcciones a la vez, que no se garantiza que sean instrucciones).

He aquí un fragmento de los desarrolladores de Opdis que tomé de su manual (lo que sugeriría leer si usted no tiene, un montón de cosas buenas en allí sobre libopcodes):

La biblioteca libopcodes es un desensamblador muy útil, pero tiene tres defectos:

  1. está poco documentada, por lo que es difícil para los nuevos usuarios a entender
  2. su conjunto de características es limitado para el desmontaje de una sola dirección
  3. que está diseñado principalmente para imprimir las instrucciones desmontadas de una corriente

Entre otras cosas, creo que podría estar ser picado por el segundo elemento de esa lista. A saber, el hecho de que la mayoría (¿todos?) De los códigos de operación encajarían en una sola dirección y estarían de acuerdo con el resultado observado (por ejemplo, obtendrá el mov y pop y algunos argumentos de registro). Pero, ¿qué hay de las cosas complicadas como las instrucciones de longitud variable o las instrucciones que no se alinean exactamente en los límites de 4 bytes? No estás haciendo nada para manejarlos.

El desensamblaje generado por libopcodes es una secuencia de cadenas destinada a escribir en una secuencia. No hay metadatos, por lo que se deben examinar las cadenas para determinar cuáles son los mnemónicos y cuáles son los operandos , y cuáles de estos son instrucciones de bifurcación/salto/retorno y cuáles son sus objetivos.

Supongo que Opdis es más inteligente que su programa: sabe cómo y qué buscar en la transmisión. Quizás a veces sabe que necesita leer dos direcciones en lugar de una sola antes de desmontarlas. Desde su código, y la descripción de códigos de libop, ninguno está haciendo esto.

¡Buena suerte! Recuerde leer ese manual, ¡y quizás considere usar libopdis en su lugar!

+0

Acepto que 'libopcodes' es un dolor de usar. La razón principal por la que lo uso es debido a un requisito para trabajar en la parte superior de la abstracción de BFD. De hecho, el desensamblaje de un objetivo es solo una pequeña parte del proyecto. La visión final es poder proporcionar edición ejecutable arbitraria. BFD proporciona funciones convenientes para la instrumentación de código adicional. –

+0

En lo que respecta a los problemas que describes con 'libopcodes', estos son sin duda preocupaciones genuinas, pero se pueden solucionar. Específicamente, es posible indicar la duración de una instrucción al ver cuántos bytes se desmontan. Naturalmente, un requisito previo es que comiences en un límite de instrucción. Lo aseguro iniciando el análisis del flujo de control del desmontaje desde el punto de entrada del objetivo. –

+0

Los problemas enumerados por 'Opdis' también son preocupaciones legítimas y se pueden solucionar de la siguiente manera. 1) sí ... esto es un problema y esta pregunta lo demuestra: p 2) con el enfoque de análisis de flujo de control que estoy tomando, no tenemos que preocuparnos por esto, simplemente desensamblamos la siguiente instrucción y paramos la bifurcación apropiadamente en jmps/calls/rets, etc. 3) es posible redirigir y anular la secuencia de impresión desde el fprintf incorporado a una función personalizada (que es lo que hace el opdis). –

0

Libopcodes imprime las instrucciones desmontadas en la secuencia interceptada por su función custom_printf. Su error es que supone que se llama a custom_printf una vez cada vez que se desensambla una instrucción, sin embargo, se invoca con más frecuencia, particularmente, para imprimir cada mnemotécnico, operando, dirección o separador.

Por lo tanto, lo que resulta desmontaje de su binario es

xor %ebp, %ebp 

mov %rdx, %r9 

pop %rsi 

mov %rsp, %rdx 

and $0xfffffffffffffff0, %rsp 

push %rax 

push %rsp 

mov $0x401450,%r8 

... 
+0

Hola Alexandra. Gracias por la respuesta. Creo que malinterpretaste mi pregunta sin embargo. Soy consciente de que así es como funcionan los códigos de barras. ¡Aclamaciones! –

Cuestiones relacionadas