Wie kann man die Hauptfunktion einer abgespeckten Anwendung zerlegen?

Lesezeit: 11 Minuten

Benutzeravatar von karlphillip
Karl Philipp

Nehmen wir an, ich habe die Anwendung unten kompiliert und ihre Symbole entfernt.

#include <stdio.h>

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

Build-Verfahren:

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

Wenn die Anwendung nicht entfernt wurde, wäre das Zerlegen der Hauptfunktion einfach. Allerdings habe ich keine Ahnung, wie ich das zerlegen soll hauptsächlich Funktion einer abgespeckten Anwendung.

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

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

Wie könnte ich es tun? Ist es überhaupt möglich?

Anmerkungen: Dies muss nur mit GDB erfolgen. Vergessen objdump. Angenommen, ich habe keinen Zugriff auf den Code.

Ein Schritt-für-Schritt-Beispiel wäre sehr dankbar.

  • Stellen Sie sich vor, ich habe die Quellen der Anwendung nicht und auch keinen Zugriff auf andere Tools außer GDB.

    – Karl Philipp

    29. März 2011 um 16:54 Uhr

  • Benutze IDA 😉 … sorry, konnte nicht widerstehen. Ich weiß, dass Sie es nur für GDB wollen.

    – 0xC0000022L

    29. März 2011 um 18:36 Uhr

  • Wer auch immer meine Frage abgelehnt hat, bitte erklären Sie warum.

    – Karl Philipp

    3. Juli 2011 um 17:13 Uhr

  • @STATUS_ACCESS_DENIED Ich habe dieses Wochenende ein paar Dutzend Antworten gemeldet und ich vermute, dass jemand seine Antwort nicht löschen wollte. Ich habe innerhalb von 3 Minuten Stimmen zu fast allen meinen Fragen und aktuellen Antworten erhalten. Deshalb habe ich nach dem Grund für die Ablehnung gefragt. Anscheinend entschied sich die Person, ihre Stimme rückgängig zu machen.

    – Karl Philipp

    4. Juli 2011 um 13:24 Uhr

  • äh, okay. Aber greifen die Heuristiken nicht, wenn Sie stark herabgestuft werden und Ihren Ruf neu berechnen? Ich dachte, ich hätte irgendwo etwas darüber gelesen.

    – 0xC0000022L

    4. Juli 2011 um 15:29 Uhr

Benutzeravatar von DrBeco
DrBeco

Ok, hier eine große Ausgabe meiner vorherigen Antwort. Ich glaube, ich habe jetzt einen Weg gefunden.

Sie haben (immer noch 🙂 dieses spezielle Problem:

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

Wenn Sie nun den Code kompilieren (ich habe eine return 0 am Ende), erhalten Sie mit gcc -S:

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

Jetzt können Sie sehen, dass Ihre Binärdatei Ihnen einige Informationen gibt:

Gestreift:

(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

Der wichtigste Eintrag hier ist .text. Es ist ein gebräuchlicher Name für einen Assembly-Start von Code, und aus unserer Erklärung von main unten können Sie anhand seiner Größe erkennen, dass es main enthält. Wenn Sie es zerlegen, sehen Sie einen Aufruf von __libc_start_main. Am wichtigsten ist, dass Sie einen guten Einstiegspunkt zerlegen, der echter Code ist (Sie führen nicht in die Irre, DATA in CODE zu ändern).

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   

Der Aufruf an __libc_start_main erhält als erstes Argument einen Zeiger auf main(). Das letzte Argument im Stack direkt vor dem Aufruf ist also Ihre main()-Adresse.

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

Hier ist es 0x400524 (wie wir bereits wissen). Jetzt setzen Sie einen Haltepunkt und versuchen Folgendes:

(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) 

Jetzt können Sie es zerlegen mit:

(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
   ...

Das ist in erster Linie die Lösung.

Übrigens, das ist ein anderer Code, um zu sehen, ob es funktioniert. Deshalb ist die obige Montage etwas anders. Der obige Code stammt aus dieser C-Datei:

#include <stdio.h>

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

Aber!


wenn das nicht klappt, dann hast du noch ein paar tipps:

Sie sollten von nun an versuchen, Haltepunkte am Anfang aller Funktionen zu setzen. Sie sind kurz vor a ret oder leave. Der erste Einstiegspunkt ist .text selbst. Dies ist der Montagestart, aber nicht der Hauptteil.

Das Problem ist, dass ein Haltepunkt Ihr Programm nicht immer laufen lässt. Wie dieser in der sehr .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

Sie müssen es also weiter versuchen, bis Sie Ihren Weg finden, indem Sie Haltepunkte setzen bei:

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

Aus der anderen Antwort sollten wir diese Informationen behalten:

In der nicht gestreiften Version der Datei sehen wir:

(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.

Jetzt wissen wir, dass main at ist 0x0000000000400524,0x0000000000400539. Wenn wir denselben Offset verwenden, um die gestreifte Binärdatei zu betrachten, erhalten wir dieselben Ergebnisse:

(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.

Wenn Sie also keinen Tipp erhalten, wo der Hauptcode beginnt (z. B. die Verwendung eines anderen Codes mit Symbolen), besteht eine andere Möglichkeit darin, Informationen über die ersten Montageanweisungen zu erhalten, damit Sie an bestimmten Stellen zerlegen und prüfen können, ob sie übereinstimmen. Wenn Sie überhaupt keinen Zugriff auf den Code haben, können Sie ihn trotzdem lesen ELF-Definition um zu verstehen, wie viele Abschnitte im Code erscheinen sollten, und versuchen Sie es mit einer berechneten Adresse. Trotzdem benötigen Sie Informationen zu Abschnitten im Code!

Das ist harte Arbeit, mein Freund! Viel Glück!

Beco

  • In der Tat, wenn ich Zugriff auf den Code hätte, wäre es ziemlich einfach. Alles, was ich tun müsste, ist eine Version mit Symbolen zu kompilieren und entweder diese Binärdatei zu laden oder gdb mitzuteilen, woher Symbole geladen werden sollen, wenn ich wirklich die abgespeckte Version debuggen müsste. Das Problem ist, dass ich wirklich weder die Quellen noch eine nicht gestrippte Version der Anwendung habe, die ich debuggen möchte. Danke für deine Bemühungen. +1

    – Karl Philipp

    2. April 2011 um 0:57 Uhr

  • Was Sie suchen, ist eine Möglichkeit, den Startpunkt bei einem gegebenen ELF zu berechnen. Sie müssen sich eingehend mit der ELF-Definition befassen und verstehen, wie stark sich jeder Abschnitt im Offset nach unten bewegen kann. Trotzdem müssen Sie die Anzahl und Größe der Abschnitte kennen. Dass info files kann dir ein bisschen helfen. Wenn ich in den nächsten Tagen ein Update finde, kommentiere ich hier. Viel Glück.

    – DrBeco

    2. April 2011 um 1:01 Uhr

  • Eine weitere nützliche Information ist, wie Sie eine Schritt-für-Schritt-Anleitung ausprobieren können, ohne zu wissen, wo Sie einen Haltepunkt setzen müssen. Sie können verwenden catch syscall write oder nur catch syscall, und dann versuchst du zu rennen. Es funktioniert nicht immer, weil der Kontext fehlt, wenn der Haltepunkt zu früh ist.

    – DrBeco

    2. April 2011 um 2:37 Uhr

  • Ich habe es gerade mit dem berüchtigten Befehl getestet gdb gdb, info files, disas 0x44f400,0x44f429, disas 0x44f4f0,0x44f500 und es hat funktioniert. 😉

    – DrBeco

    2. April 2011 um 3:15 Uhr

  • @DrBeco, ausgezeichnete Antwort! Übrigens, woher wusstest du in Aussage (gdb) disas 0x0000000000400524,0x0000000000400600 wo ist das ende von main?

    – Robert

    10. November 2015 um 8:00 Uhr

Benutzeravatar von Mat
Matte

Wie wäre es mit tun info files um die Abschnittsliste (mit Adressen) zu erhalten und von dort aus zu gehen?

Beispiel:

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
    ....

Das zerlegen .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   

Dann gehen Sie voran und demontieren Sie den Rest.

Wenn ich Sie wäre und dieselbe GCC-Version hätte, mit der Ihre ausführbare Datei erstellt wurde, würde ich die Sequenz von Funktionen untersuchen, die auf einer nicht gestrippten ausführbaren Dummy-Datei aufgerufen werden. Die Reihenfolge der Aufrufe ist in den meisten Fällen wahrscheinlich ähnlich, sodass Sie möglicherweise die Startsequenz bis zu Ihrer durcharbeiten können main im Vergleich. Optimierungen werden jedoch wahrscheinlich in den Weg kommen.

Wenn Ihre Binärdatei entfernt und optimiert ist, main existiert möglicherweise nicht als “Entität” in der Binärdatei; Die Chancen stehen gut, dass Sie nicht viel besser als diese Art von Verfahren bekommen können.

  • @karlphillip: es ist so weit wie du kommst. Die Kunst des Zerlegens ist um diese Dinge herauszufinden, auch wenn Sie keine symbolischen Namen haben. Die Dateistruktur ermöglicht es Ihnen auf allen Plattformen zu sehen, wo Sie anfangen müssen, aber dann liegt es ganz bei Ihnen, den CRT-Code zu durchsuchen und die zu finden main(). IDA verwendet beispielsweise Signaturen, um dies zu einem großen Teil zu automatisieren, wobei ein ähnlicher Ansatz wie der von Mat vorgeschlagene manuelle verwendet wird.

    – 0xC0000022L

    1. April 2011 um 21:45 Uhr

  • Wenn die Binärdatei dynamisch gelinkt ist, können Sie immer noch ltrace verwenden, um __libc_start_main zu finden (ruft main() auf, plus etwas Setup), was Ihnen nahe kommt.

    – Mudis

    1. April 2011 um 21:57 Uhr

Es gibt ein großartiges neues kostenloses Tool namens unstrip aus dem paradyn-Projekt (vollständige Offenlegung: Ich arbeite an diesem Projekt), das Ihre Programmbinärdatei umschreibt, Symbolinformationen hinzufügt und alle (oder fast alle) Funktionen in entfernten Elf-Binärdateien wiederherstellt für Sie, mit großer Genauigkeit. Es wird die main-Funktion nicht als “main” identifizieren, aber es wird sie finden, und Sie können die bereits oben erwähnte Heuristik anwenden, um herauszufinden, welche Funktion main ist.

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

Es tut mir leid, dass dies keine reine gdb-Lösung ist.

  • Hey, ich habe den Unstrip-Befehl in der Linux-Kernel-Binärdatei ausprobiert. Ich habe den Befehl “unstrip -f vmlinux” verwendet. Es hat jedoch nichts ausgegeben. Da es sich bei vmlinux um eine spezielle Binärdatei handelt, sollte eine Option zum Aufheben des Befehls bereitgestellt werden? Hier ist die betrachtete Binärdatei dl.dropboxusercontent.com/u/56211033/vmlinux

    – prathmesh.kallurkar

    27. Januar 2015 um 17:45 Uhr

IIRC, x/i <location> ist dein Freund. Natürlich muss man selbst herausfinden, an welcher Stelle man abbauen möchte.

1387230cookie-checkWie kann man die Hauptfunktion einer abgespeckten Anwendung zerlegen?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy