2012-02-03 4 views
12

Я пишу инструмент, который использует libbfd и libopcodes в x86-32 и x86-64 Linux для выполнения разборки. Проблема в том, что, хотя я могу получить libopcodes для демонтажа, я не могу получить информацию о инструкциях. В целях демонстрации я сделал минимальный пример, который воспроизводит мою проблему. Программа должна демонтировать себя от начальной точки до первого RET/RETQ.Как получить информацию о инструкциях из libopcodes?

Код немного взломан с использованием глобальных переменных, и проверка ошибок была опущена для краткости и т. Д., Но должна четко проиллюстрировать проблему.

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

Этот источник может быть построен следующим образом: makefile. Для успешной ссылки, то binutils-dev пакета должен быть установлен на локальной машине:

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

clean: 
    rm -f disasm 

При запуске, выход это:

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 

То, что я ожидал, чтобы быть в состоянии прочитать инструкции для каждой команды через dinfo->insn_type, target и т. д. Поведение проявляется как на x86-32, так и на x86-64. Если я смогу, по крайней мере, получить подтверждение того, что это не реализовано на этих двух архитектурах, я могу сам заполнить эту информацию.

+1

вы можете просто найти его проще использовать мультиплатформенный дизассемблер как beaengine и пропустить все головная боль: http://www.beaengine.org/ – Necrolis

+0

К сожалению, это требования к проекту, над которым я работаю. Что-то интересное в том, что opdis использует информацию о инструкциях или, по крайней мере, копирует его в буфер, предлагая доступную информацию. У меня возникли проблемы с тем, что делает opdis, что я не знаю. –

+1

, кстати, есть проблема с вашим исходным кодом: 'readlink' не добавляет конец строки' \ 0' в строку. –

ответ

9

К сожалению, из binutils libopcodes 2.22, insn_type не заполняется ни на i386, ни в x86_64. Единственными широко распространенными поддерживаемыми архитектурами являются MIPS, Sparc и SPU Cell. Это по-прежнему актуально с текущей CVS HEAD.

Это трудно доказать, что что-то не существует, но, например, в the Sparc disassembler source вы можете увидеть несколько вхождений insn_type быть установлены, например info->insn_type = dis_branch, в то время как в the i386 disassembler source нет вхождений insn_type, ни каких-либо из значений было бы (dis_branch, dis_nonbranch и т. д.).

Проверка всех libopcodes файлов, которые поддерживают insn_type вы получите:

  • 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

Это именно тот ответ, который я искал! Есть ли какая-то цитата или документация для этой информации? –

+0

@MikeKwan: ​​Я добавил столько информации к ответу, сколько мог собрать; похоже, нет официальной документации о том, что поддерживается или нет. Но заголовок 'dis-asm.h' явно говорит _Not все декодеры еще не поддерживают эту информацию. –

+0

Спасибо, что посмотрели, что для меня. Я также смотрел на i386-dis.c, который поддерживает то, что вы говорите. Я теперь наградил щедрость. –

3

Выполнение этого только с помощью этих библиотек будет чрезвычайно болезненным и трудным процессом. I думаю вы должны послушать Necrolis и использовать библиотеку, которая уже делает это. В прошлом я использовал Dyninst (а именно, InstructionAPI + ParseAPI). Они очень хорошо документированы и будут делать точно, что вы пытаетесь сделать.По крайней мере, потратив час на эту библиотеку и составив их примеры в руководствах, вы получите приложение, которое позволит вам исследовать такие вещи, как коды операций каждой команды, длину каждой команды, количество аргументов для каждой команды и т. Д. Это вещи, которые libopcodes не говорят вам и не обрабатывают (он декодирует адреса за раз, что не гарантируется инструкциями).

Вот отрывок из разработчиков Opdis, что я взял из их manual (который я предложил бы читать, если у вас нет, много хороших вещей там о libopcodes):

Библиотека libopcodes является очень исправен дизассемблер, но у него есть три недостатка:

  1. он под документированы, что затрудняет для новых пользователей, чтобы понять
  2. свой набор функций является ограниченным к разборке одного адреса
  3. он предназначен, главным образом, печатать разобранные инструкции к ручью

Среди других вещей, я думаю, вы могли бы получать ужалила вторым пунктом в этом списке. А именно, тот факт, что большинство (все?) Opcodes вписываются в один адрес и согласуются с наблюдаемым выходом (например, вы получаете mov и pop и некоторые аргументы регистра). Но как насчет сложных вещей, таких как инструкции переменной длины или инструкции, которые не выстраиваются точно на 4-байтных границах? Вы не делаете ничего, чтобы справиться с ними.

Разборка, созданная libopcodes, представляет собой последовательность строк , предназначенную для записи в поток. Метаданных нет, поэтому строки должны быть проверены, чтобы определить, какие мнемоники и какие операнды, а какие из них - инструкции ветвления/перехода/возврата и , каковы их цели.

Я предполагаю, что Opdis умнее вашей программы - он знает, как и что искать в потоке. Возможно, иногда он знает, что перед разборкой ему нужно прочитать два адреса вместо одного. Из вашего кода и описания libopcodes это не делается.

Удачи вам! Не забудьте прочитать это руководство и, возможно, подумайте об использовании libopdis!

+0

Я согласен с тем, что 'libopcodes' - это боль в использовании. Основная причина, по которой я его использую, - это требование работать над абстракцией BFD. Фактически, демонтаж цели - это лишь небольшая часть проекта. Окончательное видение состоит в том, чтобы обеспечить произвольное редактирование исполняемого файла. BFD обеспечивает удобные функции для инструментария дополнительного кода. –

+0

Что касается проблем, которые вы описываете с помощью 'libopcodes', это, безусловно, настоящие проблемы, но все можно обойти. В частности, можно указать длину инструкции, видя, сколько байтов разобрано. Естественно, предварительным условием является то, что вы начинаете с границы инструкции. Я гарантирую это, начав анализ потока управления разборкой с точки входа цели. –

+0

Проблемы, перечисленные в «Opdis», также являются законными проблемами и могут быть обработаны следующим образом. 1) да ... это проблема, и этот вопрос демонстрирует это: p 2) с использованием подхода анализа потока управления, который я принимаю, нам не нужно об этом беспокоиться, мы просто разбираем следующую инструкцию и останавливаем ветку соответствующим образом на jmps/calls/rets и т. д. 3) можно перенаправить и переопределить поток печати из встроенного fprintf в пользовательскую функцию (что и делает opdis). –

0

Libopcodes печатает дизассемблированные инструкции в поток, который перехватывается вашей функцией custom_printf. Ваша ошибка заключается в том, что вы предполагаете, что custom_printf вызывается один раз при разборке одной команды, однако чаще ее вызывают, в частности, для печати каждой мнемоники, операнда, адреса или разделителя.

Таким образом, в результате разборки бинарным является

xor %ebp, %ebp 

mov %rdx, %r9 

pop %rsi 

mov %rsp, %rdx 

and $0xfffffffffffffff0, %rsp 

push %rax 

push %rsp 

mov $0x401450,%r8 

... 
+0

Привет, Александра. Спасибо за ответ. Я думаю, вы неправильно поняли мой вопрос. Я знаю, что так работает libopcodes. Ура! –