Как разобрать основную функцию зачищенного приложения? - PullRequest
32 голосов
/ 29 марта 2011

Допустим, я скомпилировал приложение ниже и удалил его символы.

#include <stdio.h>

int main()
{
    printf("Hello\n");
}

Процедура сборки:

gcc -o hello hello.c
strip --strip-unneeded hello

Если бы приложение не было удалено, разбирать основную функцию было бы легко. Однако я понятия не имею, как разобрать функцию main удаленного приложения.

(gdb) disas main
No symbol table is loaded.  Use the "file" command.

(gdb) info line main
Function "main" not defined.

Как я мог это сделать? Это вообще возможно?

Примечания : это должно быть сделано только с GDB. Забудьте objdump . Предположим, что у меня нет доступа к коду.

Пошаговый пример был бы очень признателен.

Ответы [ 4 ]

42 голосов
/ 02 апреля 2011

Хорошо, вот большое издание моего предыдущего ответа.Я думаю, что нашел способ сейчас.

У вас (все еще :) есть эта конкретная проблема:

(gdb) disas main
No symbol table is loaded.  Use the "file" command.

Теперь, если вы скомпилируете код (я добавил return 0 в конце), вы получите с gcc -S:

    pushq   %rbp
    movq    %rsp, %rbp
    movl    $.LC0, %edi
    call    puts
    movl    $0, %eax
    leave
    ret

Теперь вы можете видеть, что ваш двоичный файл дает вам некоторую информацию:

Полосатый:

(gdb) info files
Symbols from "/home/beco/Documents/fontes/cpp/teste/stackoverflow/distrip".
Local exec file:
    `/home/beco/Documents/fontes/cpp/teste/stackoverflow/distrip', file type elf64-x86-64.
    Entry point: 0x400440
    0x0000000000400238 - 0x0000000000400254 is .interp
    ...
    0x00000000004003a8 - 0x00000000004003c0 is .rela.dyn
    0x00000000004003c0 - 0x00000000004003f0 is .rela.plt
    0x00000000004003f0 - 0x0000000000400408 is .init
    0x0000000000400408 - 0x0000000000400438 is .plt
    0x0000000000400440 - 0x0000000000400618 is .text
    ...
    0x0000000000601010 - 0x0000000000601020 is .data
    0x0000000000601020 - 0x0000000000601030 is .bss

самая важная запись здесь .text.Это общее название для начала сборки кода, и из нашего объяснения main ниже, из его размера, вы можете видеть, что он включает main.Если вы разберете его, вы увидите вызов __libc_start_main.Самое главное, вы разбираете хорошую точку входа, которая является реальным кодом (вы не вводите в заблуждение, чтобы изменить DATA на CODE).

disas 0x0000000000400440,0x0000000000400618
Dump of assembler code from 0x400440 to 0x400618:
   0x0000000000400440:  xor    %ebp,%ebp
   0x0000000000400442:  mov    %rdx,%r9
   0x0000000000400445:  pop    %rsi
   0x0000000000400446:  mov    %rsp,%rdx
   0x0000000000400449:  and    $0xfffffffffffffff0,%rsp
   0x000000000040044d:  push   %rax
   0x000000000040044e:  push   %rsp
   0x000000000040044f:  mov    $0x400540,%r8
   0x0000000000400456:  mov    $0x400550,%rcx
   0x000000000040045d:  mov    $0x400524,%rdi
   0x0000000000400464:  callq  0x400428 <__libc_start_main@plt>
   0x0000000000400469:  hlt
   ...

   0x000000000040046c:  sub    $0x8,%rsp
   ...
   0x0000000000400482:  retq   
   0x0000000000400483:  nop
   ...
   0x0000000000400490:  push   %rbp
   ..
   0x00000000004004f2:  leaveq 
   0x00000000004004f3:  retq   
   0x00000000004004f4:  data32 data32 nopw %cs:0x0(%rax,%rax,1)
   ...
   0x000000000040051d:  leaveq 
   0x000000000040051e:  jmpq   *%rax
   ...
   0x0000000000400520:  leaveq 
   0x0000000000400521:  retq   
   0x0000000000400522:  nop
   0x0000000000400523:  nop
   0x0000000000400524:  push   %rbp
   0x0000000000400525:  mov    %rsp,%rbp
   0x0000000000400528:  mov    $0x40062c,%edi
   0x000000000040052d:  callq  0x400418 <puts@plt>
   0x0000000000400532:  mov    $0x0,%eax
   0x0000000000400537:  leaveq 
   0x0000000000400538:  retq   

Вызов __ libc_start_main получает в качестве первого аргументауказатель на main ().Итак, последний аргумент в стеке непосредственно перед вызовом - это ваш адрес main ().

   0x000000000040045d:  mov    $0x400524,%rdi
   0x0000000000400464:  callq  0x400428 <__libc_start_main@plt>

Здесь это 0x400524 (как мы уже знаем).Теперь вы устанавливаете точку останова и попробуйте это:

(gdb) break *0x400524
Breakpoint 1 at 0x400524
(gdb) run
Starting program: /home/beco/Documents/fontes/cpp/teste/stackoverflow/disassembly/d2 

Breakpoint 1, 0x0000000000400524 in main ()
(gdb) n
Single stepping until exit from function main, 
which has no line number information.
hello 1
__libc_start_main (main=<value optimized out>, argc=<value optimized out>, ubp_av=<value optimized out>, 
    init=<value optimized out>, fini=<value optimized out>, rtld_fini=<value optimized out>, 
    stack_end=0x7fffffffdc38) at libc-start.c:258
258 libc-start.c: No such file or directory.
    in libc-start.c
(gdb) n

Program exited normally.
(gdb) 

Теперь вы можете разобрать его, используя:

(gdb) disas 0x0000000000400524,0x0000000000400600
Dump of assembler code from 0x400524 to 0x400600:
   0x0000000000400524:  push   %rbp
   0x0000000000400525:  mov    %rsp,%rbp
   0x0000000000400528:  sub    $0x10,%rsp
   0x000000000040052c:  movl   $0x1,-0x4(%rbp)
   0x0000000000400533:  mov    $0x40064c,%eax
   0x0000000000400538:  mov    -0x4(%rbp),%edx
   0x000000000040053b:  mov    %edx,%esi
   0x000000000040053d:  mov    %rax,%rdi
   0x0000000000400540:  mov    $0x0,%eax
   0x0000000000400545:  callq  0x400418 <printf@plt>
   0x000000000040054a:  mov    $0x0,%eax
   0x000000000040054f:  leaveq 
   0x0000000000400550:  retq   
   0x0000000000400551:  nop
   0x0000000000400552:  nop
   0x0000000000400553:  nop
   0x0000000000400554:  nop
   0x0000000000400555:  nop
   ...

Это в первую очередь решение.

Кстати, это другой код, чтобы посмотреть, работает ли он.Вот почему сборка выше немного отличается.Код выше взят из этого файла c:

#include <stdio.h>

int main(void)
{
    int i=1;
    printf("hello %d\n", i);
    return 0;
}

Но!


, если это не работает, то у вас все еще есть некоторые подсказки:

Вы должныищите установить точки останова в начале всех функций с этого момента.Они как раз перед ret или leave.Первая точка входа - это .text.Это начало сборки, но не главное.

Проблема в том, что не всегда точка останова будет запускать вашу программу.Как этот в очень .text:

(gdb) break *0x0000000000400440
Breakpoint 2 at 0x400440
(gdb) run
Starting program: /home/beco/Documents/fontes/cpp/teste/stackoverflow/disassembly/d2 

Breakpoint 2, 0x0000000000400440 in _start ()
(gdb) n
Single stepping until exit from function _start, 
which has no line number information.
0x0000000000400428 in __libc_start_main@plt ()
(gdb) n
Single stepping until exit from function __libc_start_main@plt, 
which has no line number information.
0x0000000000400408 in ?? ()
(gdb) n
Cannot find bounds of current function

Так что вам нужно продолжать пытаться, пока вы не найдете свой путь, устанавливая точки останова в:

0x400440
0x40046c
0x400490
0x4004f4
0x40051e
0x400524

Из другогоответ, мы должны сохранить эту информацию:

В не полосатой версии файла мы видим:

(gdb) disas main
Dump of assembler code for function main:
   0x0000000000400524 <+0>: push   %rbp
   0x0000000000400525 <+1>: mov    %rsp,%rbp
   0x0000000000400528 <+4>: mov    $0x40062c,%edi
   0x000000000040052d <+9>: callq  0x400418 <puts@plt>
   0x0000000000400532 <+14>:    mov    $0x0,%eax
   0x0000000000400537 <+19>:    leaveq 
   0x0000000000400538 <+20>:    retq   
End of assembler dump.

Теперь мы знаем, что main находится в 0x0000000000400524,0x0000000000400539.Если мы используем одно и то же смещение для просмотра полосатого двоичного файла, мы получим те же результаты:

(gdb) disas 0x0000000000400524,0x0000000000400539
Dump of assembler code from 0x400524 to 0x400539:
   0x0000000000400524:  push   %rbp
   0x0000000000400525:  mov    %rsp,%rbp
   0x0000000000400528:  mov    $0x40062c,%edi
   0x000000000040052d:  callq  0x400418 <puts@plt>
   0x0000000000400532:  mov    $0x0,%eax
   0x0000000000400537:  leaveq 
   0x0000000000400538:  retq   
End of assembler dump.

Так что, если вы не можете получить какой-либо совет, где начинается основное (например, с использованием другого кода с символами), другим способомесли у вас есть некоторая информация о первых инструкциях по сборке, так что вы можете разобрать в определенных местах и ​​посмотреть, соответствует ли он.Если у вас вообще нет доступа к коду, вы все равно можете прочитать определение ELF , чтобы понять, сколько разделов должно появиться в коде, и попробовать вычисленный адрес.Тем не менее, вам нужна информация о разделах в коде!

Это тяжелая работа, мой друг!Удачи!

Beco

8 голосов
/ 29 марта 2011

Как насчет info files, чтобы получить список разделов (с адресами), и идти оттуда?

Пример:

gdb) info files

Symbols from "/home/bob/tmp/t".
Local exec file:
`/home/bob/tmp/t', file type elf64-x86-64.
Entry point: 0x400490
0x0000000000400270 - 0x000000000040028c is .interp
0x000000000040028c - 0x00000000004002ac is .note.ABI-tag
    ....

0x0000000000400448 - 0x0000000000400460 is .init
    ....

Разборка .init:

(gdb) disas 0x0000000000400448,0x0000000000400460
Dump of assembler code from 0x400448 to 0x400460:
   0x0000000000400448:  sub    $0x8,%rsp
   0x000000000040044c:  callq  0x4004bc
   0x0000000000400451:  callq  0x400550
   0x0000000000400456:  callq  0x400650
   0x000000000040045b:  add    $0x8,%rsp
   0x000000000040045f:  retq   

Затем продолжите и разберите остальные.

Если бы я был вами, и у меня была бы та же версия GCC, с которой был создан ваш исполняемый файл, я бы изучил последовательность функций, вызываемых дляисполняемый исполняемый файлПоследовательность вызовов, вероятно, аналогична в большинстве обычных случаев, так что вы можете сравнить последовательность запуска вплоть до main для сравнения.Оптимизация, вероятно, будет мешать.

Если ваш двоичный файл удален и оптимизирован, main может не существовать как "сущность" в двоичном файле;Скорее всего, вы не можете получить намного лучше, чем этот тип процедуры.

1 голос
/ 14 апреля 2011

Существует замечательный новый бесплатный инструмент под названием unstrip из проекта paradyn (полное раскрытие: я работаю над этим проектом), который переписывает двоичный файл вашей программы, добавляет в него информацию о символах и восстанавливает все (или почти все) функции вразделили эльфийские бинарные файлы для вас, с большой точностью.Он не идентифицирует основную функцию как «главную», но найдет ее, и вы можете применить эвристику, которую вы уже упоминали выше, чтобы выяснить, какая функция является главной.

http://www.paradyn.org/html/tools/unstrip.html

Извините, это решение не только для GDB.

0 голосов
/ 29 марта 2011

IIRC, x/i <location> ваш друг.Конечно, вы должны выяснить, в каком месте вы хотите разобрать себя.

...