यह कोड कैसे बदलेगा, जैसे फ़ंक्शन कॉल?
जवाबों:
PIE निष्पादन योग्य फ़ाइलों में एड्रेस स्पेस लेआउट रैंडमाइजेशन (ASLR) का समर्थन करना है ।
PIE मोड बनाए जाने से पहले, प्रोग्राम के निष्पादन योग्य को स्मृति में एक यादृच्छिक पते पर नहीं रखा जा सकता है, केवल स्थिति स्वतंत्र कोड (PIC) डायनेमिक लाइब्रेरीज़ को एक यादृच्छिक ऑफ़सेट पर स्थानांतरित किया जा सकता है। यह बहुत काम करता है जैसे कि गतिशील पुस्तकालयों के लिए पीआईसी क्या करता है, अंतर यह है कि एक प्रोसीजर लिंकेज टेबल (पीएलटी) नहीं बनाया गया है, इसके बजाय पीसी-सापेक्ष स्थानांतरण का उपयोग किया जाता है।
Gcc / लिंकर्स में PIE समर्थन को सक्षम करने के बाद, प्रोग्राम के शरीर को स्थिति-स्वतंत्र कोड के रूप में संकलित और लिंक किया जाता है। डायनेमिक लिंकर डायनेमिक लाइब्रेरी की तरह ही प्रोग्राम मॉड्यूल पर पूर्ण स्थानांतरण प्रक्रिया करता है। वैश्विक डेटा के किसी भी उपयोग को ग्लोबल ऑफ़सेट टेबल (जीओटी) के माध्यम से उपयोग करने के लिए परिवर्तित किया जाता है और जीओटी रिलेक्शंस जोड़े जाते हैं।
PIE को इस OpenBSD PIE प्रस्तुति में अच्छी तरह से वर्णित किया गया है ।
इस स्लाइड में कार्यों में परिवर्तन दिखाया गया है (PIE vs PIC)।
x86 pic बनाम पाई
स्थानीय वैश्विक चर और कार्य पाई में अनुकूलित हैं
बाहरी वैश्विक चर और फ़ंक्शन पिक के समान हैं
और इस स्लाइड में (PIE vs old-style linking)
x86 पाई बनाम झंडे (निश्चित)
स्थानीय वैश्विक चर और कार्य निश्चित के समान हैं
बाहरी वैश्विक चर और फ़ंक्शन पिक के समान हैं
ध्यान दें, कि PIE के साथ असंगत हो सकता है -static
न्यूनतम रननीय उदाहरण: GDB निष्पादन योग्य दो बार
उन लोगों के लिए जो कुछ कार्रवाई देखना चाहते हैं, आइए देखें PIE निष्पादन योग्य ASLR काम करते हैं और पूरे रन में पते बदलते हैं:
main.c
#include <stdio.h>
int main(void) {
puts("hello");
}
main.sh
#!/usr/bin/env bash
echo 2 | sudo tee /proc/sys/kernel/randomize_va_space
for pie in no-pie pie; do
exe="${pie}.out"
gcc -O0 -std=c99 "-${pie}" "-f${pie}" -ggdb3 -o "$exe" main.c
gdb -batch -nh \
-ex 'set disable-randomization off' \
-ex 'break main' \
-ex 'run' \
-ex 'printf "pc = 0x%llx\n", (long long unsigned)$pc' \
-ex 'run' \
-ex 'printf "pc = 0x%llx\n", (long long unsigned)$pc' \
"./$exe" \
;
echo
echo
done
के साथ एक के लिए -no-pie
, सब कुछ उबाऊ है:
Breakpoint 1 at 0x401126: file main.c, line 4.
Breakpoint 1, main () at main.c:4
4 puts("hello");
pc = 0x401126
Breakpoint 1, main () at main.c:4
4 puts("hello");
pc = 0x401126
निष्पादन शुरू करने से पहले, break main
एक विराम बिंदु सेट करता है 0x401126
।
फिर, दोनों निष्पादन के दौरान, run
पते पर रुक जाता है 0x401126
।
के साथ एक -pie
तथापि और अधिक दिलचस्प है:
Breakpoint 1 at 0x1139: file main.c, line 4.
Breakpoint 1, main () at main.c:4
4 puts("hello");
pc = 0x5630df2d6139
Breakpoint 1, main () at main.c:4
4 puts("hello");
pc = 0x55763ab2e139
निष्पादन शुरू करने से पहले, GDB सिर्फ एक "डमी" पता लेता है जो निष्पादन योग्य में मौजूद है 0x1139
:।
हालांकि यह शुरू होने के बाद, जीडीबी ने समझदारी से नोटिस किया कि डायनेमिक लोडर ने प्रोग्राम को एक अलग स्थान पर रखा, और पहला ब्रेक रुक गया 0x5630df2d6139
।
फिर, दूसरे रन ने भी समझदारी से देखा कि निष्पादन योग्य फिर से चला गया, और अंत में टूट गया 0x55763ab2e139
।
echo 2 | sudo tee /proc/sys/kernel/randomize_va_space
यह सुनिश्चित करता है कि ASLR (Ubuntu 17.10 में डिफ़ॉल्ट) है: मैं ASLR (एड्रेस स्पेस लेआउट रैंडमाइजेशन) को अस्थायी रूप से कैसे अक्षम कर सकता हूं? | उबंटू से पूछें ।
set disable-randomization off
अन्यथा GDB की आवश्यकता है, जैसा कि नाम से पता चलता है, डिबगिंग अनुभव को बेहतर बनाने के लिए डिफ़ॉल्ट रूप से निश्चित पते देने के लिए ASLR को प्रक्रिया के लिए डिफ़ॉल्ट रूप से बंद कर देता है: gdb पतों और "वास्तविक" पतों के बीच अंतर? | ढेर अतिप्रवाह ।
readelf
विश्लेषण
इसके अलावा, हम यह भी देख सकते हैं:
readelf -s ./no-pie.out | grep main
वास्तविक रनटाइम लोड पता देता है (पीसी निम्नलिखित निर्देश 4 बाइट के बाद बताया गया है):
64: 0000000000401122 21 FUNC GLOBAL DEFAULT 13 main
जबकि:
readelf -s ./pie.out | grep main
बस एक ऑफसेट देता है:
65: 0000000000001135 23 FUNC GLOBAL DEFAULT 14 main
ASLR को बंद करने के साथ ( randomize_va_space
या तो set disable-randomization off
), GDB हमेशा main
पता देता है: 0x5555555547a9
इसलिए, हम यह मानते हैं कि -pie
पता निम्न से बना है:
0x555555554000 + random offset + symbol offset (79a)
TODO जहां लिनक्स कर्नेल / ग्लिबक लोडर में 0x555555554000 हार्ड कोडित है / कहां है? लिनक्स में निर्धारित PIE निष्पादन योग्य के पाठ अनुभाग का पता कैसे है?
न्यूनतम विधानसभा उदाहरण
एक और मस्त चीज़ जो हम कर सकते हैं वह है कुछ विधानसभा कोड के साथ खेलने के लिए और अधिक संक्षेप में समझने के लिए कि PIE का अर्थ क्या है।
हम लिनक्स x86_64 फ्रीस्टैंडिंग असेंबली हैलो दुनिया के साथ ऐसा कर सकते हैं:
main.S
.text
.global _start
_start:
asm_main_after_prologue:
/* write */
mov $1, %rax /* syscall number */
mov $1, %rdi /* stdout */
mov $msg, %rsi /* buffer */
mov $len, %rdx /* len */
syscall
/* exit */
mov $60, %rax /* syscall number */
mov $0, %rdi /* exit status */
syscall
msg:
.ascii "hello\n"
len = . - msg
और यह असेंबल करता है और ठीक चलता है:
as -o main.o main.S
ld -o main.out main.o
./main.out
हालाँकि, अगर हम इसे PIE के रूप में लिंक करने का प्रयास करते हैं ( --no-dynamic-linker
जैसा कि इसके बारे में बताया गया है: लिनक्स में स्टेटिकली एक्ज़ीक्यूटेबल इंडिपेंडेंट ELF कैसे बनाएं? )।
ld --no-dynamic-linker -pie -o main.out main.o
तब लिंक के साथ विफल हो जाएगा:
ld: main.o: relocation R_X86_64_32S against `.text' can not be used when making a PIE object; recompile with -fPIC
ld: final link failed: nonrepresentable section on output
क्योंकि लाइन:
mov $msg, %rsi /* buffer */
mov
ऑपरेंड में संदेश पते को हार्डकोड करता है , और इसलिए यह स्वतंत्र नहीं है।
अगर हम इसके बजाय इसे स्वतंत्र रूप से लिखते हैं:
lea msg(%rip), %rsi
तब PIE लिंक ठीक काम करता है, और GDB हमें दिखाता है कि निष्पादन योग्य हर बार मेमोरी में एक अलग स्थान पर लोड हो जाता है।
यहां अंतर यह है कि वाक्य रचना के कारण वर्तमान पीसी lea
पते के msg
सापेक्ष पते को एन्कोड किया गया rip
है, यह भी देखें: 64-बिट असेंबली प्रोग्राम में RIP रिलेटिव एड्रेसिंग का उपयोग कैसे करें?
हम यह भी पता लगा सकते हैं कि दोनों संस्करणों को अलग करके:
objdump -S main.o
जो क्रमशः देते हैं:
e: 48 c7 c6 00 00 00 00 mov $0x0,%rsi
e: 48 8d 35 19 00 00 00 lea 0x19(%rip),%rsi # 2e <msg>
000000000000002e <msg>:
2e: 68 65 6c 6c 6f pushq $0x6f6c6c65
तो हम स्पष्ट रूप से देखते हैं कि lea
पहले से ही msg
वर्तमान पते + 0x19 के रूप में एन्कोडेड का पूरा सही पता है ।
mov
हालाँकि , संस्करण ने पता सेट कर दिया है 00 00 00 00
, जिसका अर्थ है कि एक स्थानांतरण वहां किया जाएगा: लिंकर क्या करते हैं? त्रुटि संदेश R_X86_64_32S
में क्रिप्टोकरेंसी ld
वास्तविक प्रकार का स्थानांतरण है जिसकी आवश्यकता थी और जो पीआईई निष्पादनयोग्य में नहीं हो सकता है।
एक और मजेदार बात जो हम कर सकते हैं वह यह है कि msg
इसके बजाय डेटा सेक्शन में रखें .text
:
.data
msg:
.ascii "hello\n"
len = . - msg
अब इसके लिए .o
कोडांतरण:
e: 48 8d 35 00 00 00 00 lea 0x0(%rip),%rsi # 15 <_start+0x15>
इसलिए RIP ऑफसेट अब है 0
, और हम मानते हैं कि कोडांतरक द्वारा एक स्थानांतरण का अनुरोध किया गया है। हम इसकी पुष्टि करते हैं:
readelf -r main.o
जो देता है:
Relocation section '.rela.text' at offset 0x160 contains 1 entry:
Offset Info Type Sym. Value Sym. Name + Addend
000000000011 000200000002 R_X86_64_PC32 0000000000000000 .data - 4
तो स्पष्ट रूप R_X86_64_PC32
से एक पीसी रिश्तेदार स्थानांतरण है जो ld
PIE निष्पादन योग्य के लिए संभाल सकता है।
इस प्रयोग ने हमें सिखाया कि लिंकर स्वयं कार्यक्रम की जाँच करता है और PIE हो सकता है।
फिर जब जीसीसी के साथ संकलन किया जाता है, तो -pie
जीसीसी को स्थिति स्वतंत्र विधानसभा उत्पन्न करने के लिए कहता है।
लेकिन अगर हम स्वयं विधानसभा लिखते हैं, तो हमें स्वयं यह सुनिश्चित करना चाहिए कि हमने स्थिति स्वतंत्रता प्राप्त कर ली है।
ARMv8 anarch64 में, एडीआर के निर्देश के साथ स्वतंत्र हेल्लो वर्ल्ड की स्थिति हासिल की जा सकती है ।
कैसे निर्धारित किया जाए कि कोई ईएलएफ स्थिति स्वतंत्र है?
GDB के माध्यम से इसे चलाने के अलावा, कुछ स्थिर विधियों का उल्लेख किया गया है:
उबंटू 18.10 में परीक्षण किया गया।