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