Ví dụ sinh sản tối thiểu với phân tích tháo gỡ
C chính
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 ngược dòng .
Biên dịch và chạy:
gcc -fstack-protector -g -O0 -std=c99 main.c
ulimit -c unlimited && rm -f core
./a.out
thất bại như mong muốn:
*** stack smashing detected ***: ./a.out terminated
Aborted (core dumped)
Đã thử nghiệm trên Ubuntu 16.04, GCC 6.4.0.
Tháo gỡ
Bây giờ chúng tôi xem xét việc tháo gỡ:
objdump -D a.out
trong đó có:
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
Chú ý các ý kiến tiện tự động thêm bởi objdump
's mô-đun trí tuệ nhân tạo .
Nếu bạn chạy chương trình này nhiều lần thông qua GDB, bạn sẽ thấy rằng:
- chim hoàng yến nhận được một giá trị ngẫu nhiên khác nhau mỗi lần
- vòng lặp cuối cùng
myfunc
chính xác là những gì sửa đổi địa chỉ của chim hoàng yến
Chim hoàng yến ngẫu nhiên bằng cách đặt nó với %fs:0x28
, chứa một giá trị ngẫu nhiên như được giải thích tại:
Nỗ lực gỡ lỗi
Từ bây giờ, chúng tôi sửa đổi mã:
myfunc(arr, len + 1);
thay vào đó:
myfunc(arr, len);
myfunc(arr, len + 1); /* line 12 */
myfunc(arr, len);
để thú vị hơn
Sau đó, chúng tôi sẽ cố gắng xem liệu chúng tôi có thể xác định chính xác + 1
cuộc gọi thủ phạm bằng một phương thức tự động hơn là chỉ đọc và hiểu toàn bộ mã nguồn.
gcc -fsanitize=address
để bật Công cụ vệ sinh địa chỉ của Google (ASan)
Nếu bạn biên dịch lại với cờ này và chạy chương trình, nó sẽ xuất ra:
#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
tiếp theo là một số đầu ra màu hơn.
Điều này xác định rõ ràng dòng 12 có vấn đề.
Mã nguồn cho việc này là tại: https://github.com/google/sanitulators nhưng như chúng ta đã thấy từ ví dụ này, nó đã được đưa vào GCC.
ASan cũng có thể phát hiện các vấn đề bộ nhớ khác như rò rỉ bộ nhớ: Làm thế nào để tìm rò rỉ bộ nhớ trong mã / dự án C ++?
Valgrind SGCheck
Như những người khác đã đề cập , Valgrind không giỏi trong việc giải quyết loại vấn đề này.
Nó có một công cụ thử nghiệm gọi là SGCheck :
SGCheck là một công cụ để tìm các phần vượt quá của ngăn xếp và mảng toàn cầu. Nó hoạt động bằng cách sử dụng một cách tiếp cận heuristic xuất phát từ một quan sát về các dạng truy cập của ngăn xếp và mảng toàn cầu.
Vì vậy, tôi đã không rất ngạc nhiên khi nó không tìm thấy lỗi:
valgrind --tool=exp-sgcheck ./a.out
Thông báo lỗi sẽ trông như thế này rõ ràng: Lỗi thiếu Valgrind
GDB
Một quan sát quan trọng là nếu bạn chạy chương trình thông qua GDB, hoặc kiểm tra core
tệp sau khi thực tế:
gdb -nh -q a.out core
sau đó, như chúng ta đã thấy trên hội đồng, GDB sẽ chỉ cho bạn đến cuối chức năng đã kiểm tra canary:
(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)
Và do đó, vấn đề có thể xảy ra ở một trong những cuộc gọi mà chức năng này thực hiện.
Tiếp theo, chúng tôi cố gắng xác định chính xác cuộc gọi thất bại bằng cách bước đầu tiên ngay sau khi hoàng yến được thiết lập:
400581: 64 48 8b 04 25 28 00 mov %fs:0x28,%rax
400588: 00 00
40058a: 48 89 45 f8 mov %rax,-0x8(%rbp)
và xem địa chỉ:
(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
Bây giờ, điều này không cho chúng ta theo hướng dẫn vi phạm đúng: len = 5
và i = 4
, trong trường hợp cụ thể này, đã chỉ chúng ta đến dòng 12 thủ phạm.
Tuy nhiên, backtrace bị hỏng và chứa một số rác. Một backtrace chính xác sẽ trông như:
#0 myfunc (src=0x7fffffffcf14 "abcd", len=4) at main.c:3
#1 0x00000000004005b8 in main () at main.c:11
vì vậy có lẽ điều này có thể làm hỏng ngăn xếp và ngăn bạn nhìn thấy dấu vết.
Ngoài ra, phương pháp này yêu cầu biết cuộc gọi cuối cùng của chức năng kiểm tra canary là gì nếu không bạn sẽ có kết quả dương tính giả, điều này sẽ không luôn khả thi, trừ khi bạn sử dụng gỡ lỗi ngược .