Gcc और ld में स्थिति-स्वतंत्र निष्पादन के लिए -fPIE विकल्प क्या है?


94

यह कोड कैसे बदलेगा, जैसे फ़ंक्शन कॉल?

जवाबों:


100

PIE निष्पादन योग्य फ़ाइलों में एड्रेस स्पेस लेआउट रैंडमाइजेशन (ASLR) का समर्थन करना है ।

PIE मोड बनाए जाने से पहले, प्रोग्राम के निष्पादन योग्य को स्मृति में एक यादृच्छिक पते पर नहीं रखा जा सकता है, केवल स्थिति स्वतंत्र कोड (PIC) डायनेमिक लाइब्रेरीज़ को एक यादृच्छिक ऑफ़सेट पर स्थानांतरित किया जा सकता है। यह बहुत काम करता है जैसे कि गतिशील पुस्तकालयों के लिए पीआईसी क्या करता है, अंतर यह है कि एक प्रोसीजर लिंकेज टेबल (पीएलटी) नहीं बनाया गया है, इसके बजाय पीसी-सापेक्ष स्थानांतरण का उपयोग किया जाता है।

Gcc / लिंकर्स में PIE समर्थन को सक्षम करने के बाद, प्रोग्राम के शरीर को स्थिति-स्वतंत्र कोड के रूप में संकलित और लिंक किया जाता है। डायनेमिक लिंकर डायनेमिक लाइब्रेरी की तरह ही प्रोग्राम मॉड्यूल पर पूर्ण स्थानांतरण प्रक्रिया करता है। वैश्विक डेटा के किसी भी उपयोग को ग्लोबल ऑफ़सेट टेबल (जीओटी) के माध्यम से उपयोग करने के लिए परिवर्तित किया जाता है और जीओटी रिलेक्शंस जोड़े जाते हैं।

PIE को इस OpenBSD PIE प्रस्तुति में अच्छी तरह से वर्णित किया गया है ।

इस स्लाइड में कार्यों में परिवर्तन दिखाया गया है (PIE vs PIC)।

x86 pic बनाम पाई

स्थानीय वैश्विक चर और कार्य पाई में अनुकूलित हैं

बाहरी वैश्विक चर और फ़ंक्शन पिक के समान हैं

और इस स्लाइड में (PIE vs old-style linking)

x86 पाई बनाम झंडे (निश्चित)

स्थानीय वैश्विक चर और कार्य निश्चित के समान हैं

बाहरी वैश्विक चर और फ़ंक्शन पिक के समान हैं

ध्यान दें, कि PIE के साथ असंगत हो सकता है -static


3
विकिपीडिया में भी: en.wikipedia.org/wiki/…
osgx

5
Why -pie और -static ARM पर संगत हैं और x86 पर संगत नहीं हैं? मेरा SO प्रश्न: stackoverflow.com/questions/27082959/…
4ntoine

56

न्यूनतम रननीय उदाहरण: 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से एक पीसी रिश्तेदार स्थानांतरण है जो ldPIE निष्पादन योग्य के लिए संभाल सकता है।

इस प्रयोग ने हमें सिखाया कि लिंकर स्वयं कार्यक्रम की जाँच करता है और PIE हो सकता है।

फिर जब जीसीसी के साथ संकलन किया जाता है, तो -pieजीसीसी को स्थिति स्वतंत्र विधानसभा उत्पन्न करने के लिए कहता है।

लेकिन अगर हम स्वयं विधानसभा लिखते हैं, तो हमें स्वयं यह सुनिश्चित करना चाहिए कि हमने स्थिति स्वतंत्रता प्राप्त कर ली है।

ARMv8 anarch64 में, एडीआर के निर्देश के साथ स्वतंत्र हेल्लो वर्ल्ड की स्थिति हासिल की जा सकती है ।

कैसे निर्धारित किया जाए कि कोई ईएलएफ स्थिति स्वतंत्र है?

GDB के माध्यम से इसे चलाने के अलावा, कुछ स्थिर विधियों का उल्लेख किया गया है:

उबंटू 18.10 में परीक्षण किया गया।


1
हाय सीरो! क्या आप एएसएलआर-ऑफ पाई-ऑन स्टार्ट एड्रेस के लिए अलग प्रश्न बना सकते हैं और इसे यहां लिंक कर सकते हैं?
ऑग्सएक्स

1
@osgx किया। क्या आप पहले से ही जानते हैं या आप इसे मक्खी पर खोदने जा रहे हैं? :-) जब आप इस पर होते हैं, तो यह स्पष्ट करना अच्छा होगा कि लिनक्स कर्नेल / डायन लोडर यह निर्धारित करता है कि कुछ है या नहीं: unix.stackexchange.com/questions/89211/…
Ciro Santilli 郝海东 it it it it法轮功

मैं पहले से ही नहीं जानता, लेकिन मुझे पता है कि इसे glibc - glibc / elf github.com/lattera/glibc/tree/master/elf (यदि दुभाषिया अभी भी ld -linux.so है) के rtld से खोदना चाहिए । तीन साल पहले बेसिल को 0x55555555 पर भी stackoverflow.com/questions/29856044 के बारे में निश्चित नहीं था , लेकिन यह सवाल ld.so का पता शुरू करने के बारे में था, इसलिए कर्नेल की fs / binfmt.elf.c या readelf / objdump और लिंकर लिपियों में खुदाई करें ।
21
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.