Как найти точку входа основной функции исполняемого файла elf без какой-либо символической информации? - PullRequest
11 голосов
/ 27 марта 2012

Я разработал небольшую программу cpp на платформе Ubuntu-Linux 11.10. Теперь я хочу перепроектировать это. Я новичок Я использую такие инструменты: GDB 7.0, редактор, hexeditor .

Впервые я сделал это довольно легко. С помощью символической информации я нашел адрес основной функции и сделал все необходимое. Затем я чередовал (--strip-all) исполняемый elf-файл и у меня возникли некоторые проблемы. Я знаю, что функция main начинается с 0x8960 в этой программе. Но я понятия не имею, как мне найти эту точку без этого знания. Я попытался отладить мою программу шаг за шагом с помощью GDB, но она переходит в __libc_start_main затем в ld-linux.so.3 (таким образом, он находит и загружает разделяемые библиотеки, необходимые программе). Я отладил это около 10 минут. Конечно, может быть через 20 минут я смогу добраться до точки входа основной функции, но, похоже, должен существовать более легкий путь.

Что я должен сделать, чтобы найти точку входа функции main без какой-либо символической информации? Не могли бы вы посоветовать мне несколько хороших книг / sites / other_sources от обратного инжиниринга elf-файлов с помощью gdb? Любая помощь будет оценена.

Ответы [ 3 ]

9 голосов
/ 23 января 2017

Найти main() в разобранном двоичном файле ELF для Linux просто. Информация о символах не требуется.

Прототип для __libc_start_main равен

int __libc_start_main(int (*main) (int, char**, char**), 
                      int argc, 
                      char *__unbounded *__unbounded ubp_av, 
                      void (*init) (void), 
                      void (*fini) (void), 
                      void (*rtld_fini) (void), 
                      void (*__unbounded stack_end));

Адрес памяти времени выполнения main() - это аргумент, соответствующий первому параметру int (*main) (int, char**, char**). Это означает, что последний адрес памяти, сохраненный в стеке выполнения перед вызовом __libc_start_main, является адресом памяти main(), поскольку аргументы помещаются в стек времени выполнения в порядке, обратном их соответствующим параметрам в определении функции.

Можно ввести main() в gdb в 4 этапа:

  1. Найти точку входа в программу
  2. Найдите, где __libc_start_main называется
  3. Установить точку останова для адреса, последнего сохраненного в стеке до вызова _libc_start_main
  4. Пусть выполнение программы continue, пока точка останова для main() не будет достигнута

Процесс одинаков как для 32-битных, так и для 64-битных двоичных файлов ELF.

Ввод main() в примере раздельного 32-разрядного двоичного файла ELF с именем "test_32":

$ gdb -q -nh test_32
Reading symbols from test_32...(no debugging symbols found)...done.
(gdb) info file                                  #step 1
Symbols from "/home/c/test_32".
Local exec file:
    `/home/c/test_32', file type elf32-i386.
    Entry point: 0x8048310
    < output snipped >
(gdb) break *0x8048310
Breakpoint 1 at 0x8048310
(gdb) run
Starting program: /home/c/test_32 

Breakpoint 1, 0x08048310 in ?? ()
(gdb) x/13i $eip                                 #step 2
=> 0x8048310:   xor    %ebp,%ebp
   0x8048312:   pop    %esi
   0x8048313:   mov    %esp,%ecx
   0x8048315:   and    $0xfffffff0,%esp
   0x8048318:   push   %eax
   0x8048319:   push   %esp
   0x804831a:   push   %edx
   0x804831b:   push   $0x80484a0
   0x8048320:   push   $0x8048440
   0x8048325:   push   %ecx
   0x8048326:   push   %esi
   0x8048327:   push   $0x804840b                # address of main()
   0x804832c:   call   0x80482f0 <__libc_start_main@plt>
(gdb) break *0x804840b                           # step 3
Breakpoint 2 at 0x804840b
(gdb) continue                                   # step 4 
Continuing.

Breakpoint 2, 0x0804840b in ?? ()                # now in main()
(gdb) x/x $esp+4
0xffffd110: 0x00000001                           # argc = 1
(gdb) x/s **(char ***) ($esp+8)
0xffffd35c: "/home/c/test_32"                    # argv[0]
(gdb)

Ввод main() в примере раздельного 64-битного двоичного файла ELF с именем "test_64":

$ gdb -q -nh test_64
Reading symbols from test_64...(no debugging symbols found)...done.
(gdb) info file                                  # step 1
Symbols from "/home/c/test_64".
Local exec file:
    `/home/c/test_64', file type elf64-x86-64.
    Entry point: 0x400430
    < output snipped >
(gdb) break *0x400430
Breakpoint 1 at 0x400430
(gdb) run 
Starting program: /home/c/test_64 

Breakpoint 1, 0x0000000000400430 in ?? ()
(gdb) x/11i $rip                                 # step 2
=> 0x400430:    xor    %ebp,%ebp
   0x400432:    mov    %rdx,%r9
   0x400435:    pop    %rsi
   0x400436:    mov    %rsp,%rdx
   0x400439:    and    $0xfffffffffffffff0,%rsp
   0x40043d:    push   %rax
   0x40043e:    push   %rsp
   0x40043f:    mov    $0x4005c0,%r8
   0x400446:    mov    $0x400550,%rcx
   0x40044d:    mov    $0x400526,%rdi            # address of main()
   0x400454:    callq  0x400410 <__libc_start_main@plt>
(gdb) break *0x400526                            # step 3
Breakpoint 2 at 0x400526
(gdb) continue                                   # step 4
Continuing.

Breakpoint 2, 0x0000000000400526 in ?? ()        # now in main()
(gdb) print $rdi                                    
$3 = 1                                           # argc = 1
(gdb) x/s **(char ***) ($rsp+16)
0x7fffffffe35c: "/home/c/test_64"                # argv[0]
(gdb) 

Подробное описание инициализации программы и того, что происходит до вызова main() и как добраться до main(), можно найти в руководстве Патрика Хоргана "Запуск программы Linux x86". или - Как, черт возьми, мы добираемся до main ()? "

7 голосов
/ 27 марта 2012

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

Значение символа mainне требуется для запуска программы: в формате ELF запуск программы указывается в поле e_entry исполняемого заголовка ELF.Это поле обычно указывает на код инициализации библиотеки C, а не прямо на main.

Хотя код инициализации библиотеки C вызывает main() после того, как она настроила среду выполнения C, этот вызовнормальный вызов функции, который полностью разрешается во время соединения.

В некоторых случаях для определения местоположения * 1012 можно использовать эвристику, зависящую от реализации (т. е. специальные знания внутренних элементов среды выполнения C).* в разобранном исполняемом файле.Однако я не знаю, как это сделать.

4 голосов
/ 22 марта 2016

Если у вас очень урезанная версия или даже упакованный бинарный файл, как при использовании UPX, вы можете жестко gdb использовать его как:

$ readelf -h echo | grep Entry
Entry point address:               0x103120

И затем вы можете разбить егов GDB как:

$ gdb mybinary
(gdb) break * 0x103120
Breakpoint 1 at 0x103120gdb) 
(gdb) r
Starting program: mybinary 
Breakpoint 1, 0x0000000000103120 in ?? ()

, а затем вы можете увидеть инструкции по вводу:

(gdb) x/10i 0x0000000000103120
=> 0x103120:    bl      0x103394
  0x103124: dcbtst  0,r5
  0x103128: mflr    r13
  0x10312c: cmplwi  r7,2
  0x103130: bne     0x103214
  0x103134: stw     r5,0(r6)
  0x103138: add     r4,r4,r3
  0x10313c: lis     r0,-32768
  0x103140: lis     r9,-32768
  0x103144: addi    r3,r3,-1

Надеюсь, это поможет

...