Disassembly विश्लेषण के साथ न्यूनतम प्रजनन उदाहरण
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;
}
गिटहब ऊपर ।
संकलित करें और चलाएं:
gcc -fstack-protector -g -O0 -std=c99 main.c
ulimit -c unlimited && rm -f core
./a.out
वांछित के रूप में विफल रहता है:
*** stack smashing detected ***: ./a.out terminated
Aborted (core dumped)
उबंटू 16.04, जीसीसी 6.4.0 पर परीक्षण किया गया।
disassembly
अब हम डिससैड को देखते हैं:
objdump -D a.out
जिसमें है:
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
सूचना आसान टिप्पणियों का स्वचालित रूप से जोड़ा objdump
है कृत्रिम बुद्धि मॉड्यूल ।
यदि आप GDB के माध्यम से इस कार्यक्रम को कई बार चलाते हैं, तो आप देखेंगे कि:
- कैनरी को हर बार एक अलग यादृच्छिक मूल्य मिलता है
- का अंतिम लूप
myfunc
बिल्कुल वही है जो कैनरी के पते को संशोधित करता है
कैनरी को इसके साथ सेट करके यादृच्छिक किया जाता है %fs:0x28
, जिसमें एक यादृच्छिक मूल्य होता है जैसा कि समझाया गया है:
डिबग के प्रयास
अब से, हम कोड को संशोधित करते हैं:
myfunc(arr, len + 1);
इसके बजाय:
myfunc(arr, len);
myfunc(arr, len + 1); /* line 12 */
myfunc(arr, len);
अधिक दिलचस्प होना।
फिर हम यह देखने की कोशिश करेंगे कि क्या हम + 1
पूरे स्रोत कोड को पढ़ने और समझने की तुलना में अधिक स्वचालित विधि से अपराधी कॉल को इंगित कर सकते हैं ।
gcc -fsanitize=address
Google के एड्रेस सैनिटाइज़र (आसन) को सक्षम करने के लिए
यदि आप इस झंडे के साथ घूमते हैं और कार्यक्रम चलाते हैं, तो यह आउटपुट करता है:
#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
कुछ और रंगीन आउटपुट के बाद।
यह स्पष्ट रूप से समस्याग्रस्त रेखा 12 को इंगित करता है।
इसके लिए स्रोत कोड इस प्रकार है: https://github.com/google/sanitizers लेकिन जैसा कि हमने उदाहरण से देखा कि यह पहले से ही जीसीसी में अपस्ट्रीम है।
आसन मेमोरी मेमोरी जैसी अन्य मेमोरी समस्याओं का भी पता लगा सकता है: C ++ कोड / प्रोजेक्ट में मेमोरी लीक कैसे खोजें?
वेलग्रिंड SGCheck
जैसा कि दूसरों ने उल्लेख किया है , वालग्रिंड इस तरह की समस्या को हल करने में अच्छा नहीं है।
इसमें एक प्रयोगात्मक उपकरण है जिसे SGCheck कहा जाता है :
SGCheck स्टैक और वैश्विक सरणियों के ओवररन को खोजने के लिए एक उपकरण है। यह स्टैक और वैश्विक सरणी एक्सेस के संभावित रूपों के बारे में अवलोकन से प्राप्त एक हेयुरिस्टिक दृष्टिकोण का उपयोग करके काम करता है।
इसलिए मुझे बहुत आश्चर्य नहीं हुआ जब इसमें त्रुटि नहीं मिली:
valgrind --tool=exp-sgcheck ./a.out
त्रुटि संदेश इस तरह से स्पष्ट रूप से दिखना चाहिए: लापता त्रुटि त्रुटि
GDB
एक महत्वपूर्ण अवलोकन यह है कि यदि आप GDB के माध्यम से प्रोग्राम चलाते हैं, या core
इस तथ्य के बाद फाइल की जांच करते हैं :
gdb -nh -q a.out core
फिर, जैसा कि हमने असेंबली में देखा था, जीडीबी आपको उस फ़ंक्शन के अंत में इंगित करना चाहिए जिसने कैनरी चेक किया था:
(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)
और इसलिए समस्या इस फ़ंक्शन द्वारा किए गए कॉल में से एक में होने की संभावना है।
अगला हम कैनरी सेट होने के तुरंत बाद पहले सिंगल स्टेपिंग द्वारा सटीक असफल कॉलिंग को इंगित करने का प्रयास करते हैं:
400581: 64 48 8b 04 25 28 00 mov %fs:0x28,%rax
400588: 00 00
40058a: 48 89 45 f8 mov %rax,-0x8(%rbp)
और पता देख रहे हैं:
(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
अब, यह हमें सही अपमानजनक निर्देश पर छोड़ देता है: len = 5
और i = 4
, और इस विशेष मामले में, हमें अपराधी लाइन 12 की ओर इशारा किया।
हालाँकि, बैकट्रेस दूषित है, और इसमें कुछ कचरा है। एक सही बैकट्रेस लगेगा:
#0 myfunc (src=0x7fffffffcf14 "abcd", len=4) at main.c:3
#1 0x00000000004005b8 in main () at main.c:11
तो शायद यह स्टैक को दूषित कर सकता है और आपको ट्रेस देखने से रोक सकता है।
इसके अलावा, इस विधि में यह जानना आवश्यक है कि कैनरी चेकिंग फ़ंक्शन की अंतिम कॉल क्या है अन्यथा आपके पास झूठी सकारात्मकताएं होंगी, जो हमेशा संभव नहीं होंगी, जब तक कि आप रिवर्स डिबगिंग का उपयोग न करें ।