Contoh reproduksi minimal dengan analisis pembongkaran
main.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 hulu .
Kompilasi dan jalankan:
gcc -fstack-protector -g -O0 -std=c99 main.c
ulimit -c unlimited && rm -f core
./a.out
gagal seperti yang diinginkan:
*** stack smashing detected ***: ./a.out terminated
Aborted (core dumped)
Diuji pada Ubuntu 16.04, GCC 6.4.0.
Membongkar
Sekarang kita melihat pembongkaran:
objdump -D a.out
yang mengandung:
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
Perhatikan komentar berguna secara otomatis ditambahkan oleh objdump
's modul kecerdasan buatan .
Jika Anda menjalankan program ini beberapa kali melalui GDB, Anda akan melihat bahwa:
- kenari mendapat nilai acak yang berbeda setiap kali
- loop terakhir
myfunc
adalah apa yang memodifikasi alamat kenari
Kenari diacak dengan menyetelnya %fs:0x28
, yang berisi nilai acak seperti yang dijelaskan di:
Upaya debug
Mulai sekarang, kami memodifikasi kode:
myfunc(arr, len + 1);
sebagai gantinya:
myfunc(arr, len);
myfunc(arr, len + 1); /* line 12 */
myfunc(arr, len);
menjadi lebih menarik.
Kami kemudian akan mencoba untuk melihat apakah kami dapat menentukan + 1
panggilan pelakunya dengan metode yang lebih otomatis daripada hanya membaca dan memahami seluruh kode sumber.
gcc -fsanitize=address
untuk mengaktifkan Sanitizer Alamat Google (ASan)
Jika Anda mengkompilasi ulang dengan flag ini dan menjalankan program, itu output:
#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
diikuti oleh beberapa keluaran yang lebih berwarna.
Ini dengan jelas menunjukkan garis bermasalah 12.
Kode sumber untuk ini adalah di: https://github.com/google/sanitizers tetapi seperti yang kita lihat dari contoh, kode itu sudah di-upstream ke GCC.
ASan juga dapat mendeteksi masalah memori lain seperti kebocoran memori: Bagaimana menemukan kebocoran memori dalam kode / proyek C ++?
Valgrind SGCheck
Seperti yang disebutkan oleh orang lain , Valgrind tidak pandai memecahkan masalah seperti ini.
Itu memang memiliki alat percobaan yang disebut SGCheck :
SGCheck adalah alat untuk menemukan kelebihan tumpukan dan susunan global. Ini bekerja dengan menggunakan pendekatan heuristik yang berasal dari pengamatan tentang kemungkinan bentuk stack dan akses array global.
Jadi saya tidak terlalu terkejut ketika tidak menemukan kesalahan:
valgrind --tool=exp-sgcheck ./a.out
Pesan kesalahan akan terlihat seperti ini rupanya: Valgrind missing error
GDB
Pengamatan penting adalah bahwa jika Anda menjalankan program melalui GDB, atau memeriksa core
file setelah fakta:
gdb -nh -q a.out core
kemudian, seperti yang kita lihat di majelis, GDB akan mengarahkan Anda ke akhir fungsi yang melakukan pemeriksaan kenari:
(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)
Dan karena itu masalahnya kemungkinan ada di salah satu panggilan yang dibuat fungsi ini.
Selanjutnya kita mencoba untuk menentukan panggilan gagal yang tepat dengan langkah pertama setelah kenari ditetapkan:
400581: 64 48 8b 04 25 28 00 mov %fs:0x28,%rax
400588: 00 00
40058a: 48 89 45 f8 mov %rax,-0x8(%rbp)
dan memperhatikan alamat:
(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
Sekarang, ini benar-benar membuat kita berada pada instruksi yang menyinggung: len = 5
dan i = 4
, dalam kasus khusus ini, memang mengarahkan kita ke jalur pelakunya 12.
Namun, jejak balik rusak, dan berisi beberapa sampah. Backtrace yang benar akan terlihat seperti:
#0 myfunc (src=0x7fffffffcf14 "abcd", len=4) at main.c:3
#1 0x00000000004005b8 in main () at main.c:11
jadi mungkin ini bisa merusak tumpukan dan mencegah Anda melihat jejak.
Juga, metode ini membutuhkan mengetahui apa panggilan terakhir dari fungsi pemeriksaan kenari jika tidak Anda akan memiliki false positive, yang tidak akan selalu layak, kecuali jika Anda menggunakan debugging terbalik .