Beispiel für minimale Reproduktion mit Demontageanalyse
Haupt c
void myfunc(char *const src, int len) {
int i;
for (i = 0; i < len; ++i) {
src[i] = 42;
}
}
int main(void) {
char arr[] = {'a', 'b', 'c', 'd'};
int len = sizeof(arr);
myfunc(arr, len + 1);
return 0;
}
GitHub stromaufwärts .
Kompilieren und ausführen:
gcc -fstack-protector -g -O0 -std=c99 main.c
ulimit -c unlimited && rm -f core
./a.out
schlägt wie gewünscht fehl:
*** stack smashing detected ***: ./a.out terminated
Aborted (core dumped)
Getestet unter Ubuntu 16.04, GCC 6.4.0.
Demontage
Nun schauen wir uns die Demontage an:
objdump -D a.out
was beinhaltet:
int main (void){
400579: 55 push %rbp
40057a: 48 89 e5 mov %rsp,%rbp
# Allocate 0x10 of stack space.
40057d: 48 83 ec 10 sub $0x10,%rsp
# Put the 8 byte canary from %fs:0x28 to -0x8(%rbp),
# which is right at the bottom of the stack.
400581: 64 48 8b 04 25 28 00 mov %fs:0x28,%rax
400588: 00 00
40058a: 48 89 45 f8 mov %rax,-0x8(%rbp)
40058e: 31 c0 xor %eax,%eax
char arr[] = {'a', 'b', 'c', 'd'};
400590: c6 45 f4 61 movb $0x61,-0xc(%rbp)
400594: c6 45 f5 62 movb $0x62,-0xb(%rbp)
400598: c6 45 f6 63 movb $0x63,-0xa(%rbp)
40059c: c6 45 f7 64 movb $0x64,-0x9(%rbp)
int len = sizeof(arr);
4005a0: c7 45 f0 04 00 00 00 movl $0x4,-0x10(%rbp)
myfunc(arr, len + 1);
4005a7: 8b 45 f0 mov -0x10(%rbp),%eax
4005aa: 8d 50 01 lea 0x1(%rax),%edx
4005ad: 48 8d 45 f4 lea -0xc(%rbp),%rax
4005b1: 89 d6 mov %edx,%esi
4005b3: 48 89 c7 mov %rax,%rdi
4005b6: e8 8b ff ff ff callq 400546 <myfunc>
return 0;
4005bb: b8 00 00 00 00 mov $0x0,%eax
}
# Check that the canary at -0x8(%rbp) hasn't changed after calling myfunc.
# If it has, jump to the failure point __stack_chk_fail.
4005c0: 48 8b 4d f8 mov -0x8(%rbp),%rcx
4005c4: 64 48 33 0c 25 28 00 xor %fs:0x28,%rcx
4005cb: 00 00
4005cd: 74 05 je 4005d4 <main+0x5b>
4005cf: e8 4c fe ff ff callq 400420 <__stack_chk_fail@plt>
# Otherwise, exit normally.
4005d4: c9 leaveq
4005d5: c3 retq
4005d6: 66 2e 0f 1f 84 00 00 nopw %cs:0x0(%rax,%rax,1)
4005dd: 00 00 00
Beachten Sie die handlichen Kommentare automatisch hinzugefügt von objdump
‚s Modul künstlicher Intelligenz .
Wenn Sie dieses Programm mehrmals über GDB ausführen, sehen Sie Folgendes:
- Der Kanarienvogel erhält jedes Mal einen anderen Zufallswert
- Die letzte Schleife von
myfunc
ist genau das, was die Adresse des Kanarienvogels ändert
Der Kanarienvogel wird randomisiert, indem er mit gesetzt %fs:0x28
wird. Dieser enthält einen zufälligen Wert, wie unter:
Debug-Versuche
Von nun an ändern wir den Code:
myfunc(arr, len + 1);
stattdessen sein:
myfunc(arr, len);
myfunc(arr, len + 1); /* line 12 */
myfunc(arr, len);
interessanter sein.
Wir werden dann versuchen zu sehen, ob wir den Täteraufruf + 1
mit einer Methode lokalisieren können, die automatisierter ist als nur das Lesen und Verstehen des gesamten Quellcodes.
gcc -fsanitize=address
um den Address Sanitizer (ASan) von Google zu aktivieren
Wenn Sie mit diesem Flag neu kompilieren und das Programm ausführen, wird Folgendes ausgegeben:
#0 0x4008bf in myfunc /home/ciro/test/main.c:4
#1 0x40099b in main /home/ciro/test/main.c:12
#2 0x7fcd2e13d82f in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x2082f)
#3 0x400798 in _start (/home/ciro/test/a.out+0x40079
gefolgt von etwas mehr farbiger Ausgabe.
Dies zeigt deutlich die problematische Linie 12.
Der Quellcode hierfür lautet: https://github.com/google/sanitizers, aber wie wir aus dem Beispiel gesehen haben, wird er bereits in GCC übertragen.
ASan kann auch andere Speicherprobleme wie Speicherverluste erkennen: Wie finde ich einen Speicherverlust in einem C ++ - Code / Projekt?
Valgrind SGCheck
Wie von anderen erwähnt , ist Valgrind nicht gut darin, diese Art von Problem zu lösen.
Es gibt ein experimentelles Tool namens SGCheck :
SGCheck ist ein Tool zum Auffinden von Überläufen von Stack- und globalen Arrays. Es funktioniert mit einem heuristischen Ansatz, der aus einer Beobachtung der wahrscheinlichen Formen von Stapel- und globalen Array-Zugriffen abgeleitet wurde.
Ich war also nicht sehr überrascht, als der Fehler nicht gefunden wurde:
valgrind --tool=exp-sgcheck ./a.out
Die Fehlermeldung sollte anscheinend so aussehen: Valgrind fehlender Fehler
GDB
Eine wichtige Beobachtung ist, dass, wenn Sie das Programm über GDB ausführen oder die core
Datei nachträglich untersuchen :
gdb -nh -q a.out core
Dann sollte GDB, wie wir auf der Baugruppe gesehen haben, Sie auf das Ende der Funktion hinweisen, die die Kanarienvogelprüfung durchgeführt hat:
(gdb) bt
#0 0x00007f0f66e20428 in __GI_raise (sig=sig@entry=6) at ../sysdeps/unix/sysv/linux/raise.c:54
#1 0x00007f0f66e2202a in __GI_abort () at abort.c:89
#2 0x00007f0f66e627ea in __libc_message (do_abort=do_abort@entry=1, fmt=fmt@entry=0x7f0f66f7a49f "*** %s ***: %s terminated\n") at ../sysdeps/posix/libc_fatal.c:175
#3 0x00007f0f66f0415c in __GI___fortify_fail (msg=<optimized out>, msg@entry=0x7f0f66f7a481 "stack smashing detected") at fortify_fail.c:37
#4 0x00007f0f66f04100 in __stack_chk_fail () at stack_chk_fail.c:28
#5 0x00000000004005f6 in main () at main.c:15
(gdb) f 5
#5 0x00000000004005f6 in main () at main.c:15
15 }
(gdb)
Und deshalb ist das Problem wahrscheinlich bei einem der Aufrufe dieser Funktion.
Als nächstes versuchen wir, den genauen fehlgeschlagenen Anruf zu lokalisieren, indem wir den ersten einzelnen Schritt unmittelbar nach dem Setzen des Kanarienvogels ausführen:
400581: 64 48 8b 04 25 28 00 mov %fs:0x28,%rax
400588: 00 00
40058a: 48 89 45 f8 mov %rax,-0x8(%rbp)
und die Adresse beobachten:
(gdb) p $rbp - 0x8
$1 = (void *) 0x7fffffffcf18
(gdb) watch 0x7fffffffcf18
Hardware watchpoint 2: *0x7fffffffcf18
(gdb) c
Continuing.
Hardware watchpoint 2: *0x7fffffffcf18
Old value = 1800814336
New value = 1800814378
myfunc (src=0x7fffffffcf14 "*****?Vk\266", <incomplete sequence \355\216>, len=5) at main.c:3
3 for (i = 0; i < len; ++i) {
(gdb) p len
$2 = 5
(gdb) p i
$3 = 4
(gdb) bt
#0 myfunc (src=0x7fffffffcf14 "*****?Vk\266", <incomplete sequence \355\216>, len=5) at main.c:3
#1 0x00000000004005cc in main () at main.c:12
Dies lässt uns nun bei der richtigen beleidigenden Anweisung zurück: len = 5
und i = 4
hat uns in diesem speziellen Fall auf die Täterlinie 12 hingewiesen.
Die Rückverfolgung ist jedoch beschädigt und enthält Müll. Eine korrekte Rückverfolgung würde folgendermaßen aussehen:
#0 myfunc (src=0x7fffffffcf14 "abcd", len=4) at main.c:3
#1 0x00000000004005b8 in main () at main.c:11
Vielleicht könnte dies den Stapel beschädigen und Sie daran hindern, die Ablaufverfolgung zu sehen.
Bei dieser Methode muss auch bekannt sein, was der letzte Aufruf der Kanarienvogelprüffunktion ist. Andernfalls treten falsch positive Ergebnisse auf, die nur dann möglich sind, wenn Sie das Reverse-Debugging verwenden .