Gcc ve ld'deki konumdan bağımsız çalıştırılabilir dosyalar için -fPIE seçeneği nedir?


Yanıtlar:


100

PIE, yürütülebilir dosyalarda adres alanı düzenini (ASLR) desteklemektir .

PIE modu yaratılmadan önce, programın yürütülebilir dosyası belleğe rastgele bir adrese yerleştirilemezdi, yalnızca konumdan bağımsız kod (PIC) dinamik kitaplıkları rastgele bir ofsete yeniden yerleştirilebilirdi. Dinamik kitaplıklar için PIC'in yaptığı gibi çalışır, fark, Prosedür Bağlantı Tablosunun (PLT) yaratılmaması, bunun yerine PC'ye göre yeniden konumlandırmanın kullanılmasıdır.

Gcc / linkers'da PIE desteğini etkinleştirdikten sonra, programın gövdesi derlenir ve konumdan bağımsız kod olarak bağlanır. Dinamik bir bağlayıcı, tıpkı dinamik kitaplıklar gibi program modülünde tam yer değiştirme işlemi yapar. Global verilerin herhangi bir kullanımı, Global Offsets Table (GOT) aracılığıyla erişime dönüştürülür ve GOT yer değiştirmeleri eklenir.

PIE, bu OpenBSD PIE sunumunda iyi tanımlanmıştır .

İşlevlerdeki değişiklikler bu slaytta gösterilmektedir (PIE - PIC).

x86 pic vs turta

Yerel global değişkenler ve işlevler pastada optimize edilmiştir

Harici global değişkenler ve fonksiyonlar pic ile aynıdır

ve bu slaytta (PIE ve eski stil bağlantı)

x86 pastası vs işaretsiz (düzeltildi)

Yerel global değişkenler ve işlevler, sabit

Harici global değişkenler ve fonksiyonlar pic ile aynıdır

PIE'nin aşağıdakilerle uyumsuz olabileceğini unutmayın: -static


3
Ayrıca Wikipedia'da: en.wikipedia.org/wiki/…
osgx

5
-Pie ve -static neden ARM üzerinde uyumludur ve x86 ile uyumlu DEĞİLDİR? SO sorum: stackoverflow.com/questions/27082959/…
4ntoine

56

Minimum çalıştırılabilir örnek: GDB çalıştırılabilir iki kez

Biraz eylem görmek isteyenler için, ASLR'nin PIE çalıştırılabilir üzerinde çalıştığını ve çalıştırmalar arasında adresleri değiştirdiğini görelim:

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

Olan biri için -no-pieher şey sıkıcı:

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

Yürütmeye başlamadan önce, break mainbir kesme noktası belirler 0x401126.

Daha sonra her iki yürütme sırasında runadreste durur 0x401126.

İle bir -pieyandan çok daha ilginç:

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

Yürütme başlamadan önce, GDB sadece çalıştırılabilir bulunan bir "kukla" adresi alır: 0x1139.

Ancak başladıktan sonra GDB akıllıca dinamik yükleyicinin programı farklı bir konuma yerleştirdiğini ve ilk molanın burada durduğunu fark eder 0x5630df2d6139.

Ardından, ikinci çalıştırma da çalıştırılabilir dosyanın tekrar hareket ettiğini akıllıca fark etti ve bozulma noktasına geldi 0x55763ab2e139.

echo 2 | sudo tee /proc/sys/kernel/randomize_va_spaceASLR'nin açık olmasını sağlar (Ubuntu 17.10'da varsayılan): ASLR'yi geçici olarak nasıl devre dışı bırakabilirim (Adres alanı düzeni randomizasyonu)? | Ubuntu'ya sorun .

set disable-randomization offaksi takdirde GDB, adından da anlaşılacağı gibi, hata ayıklama deneyimini iyileştirmek için çalıştırmalar arasında sabit adresler vermek üzere varsayılan olarak işlem için ASLR'yi kapatır: gdb adresleri ve "gerçek" adresler arasındaki fark? | Yığın Taşması .

readelf analiz

Ayrıca şunu da gözlemleyebiliriz:

readelf -s ./no-pie.out | grep main

gerçek çalışma zamanı yükleme adresini verir (pc aşağıdaki talimatı 4 bayt sonra gösterir):

64: 0000000000401122    21 FUNC    GLOBAL DEFAULT   13 main

süre:

readelf -s ./pie.out | grep main

sadece bir ofset verir:

65: 0000000000001135    23 FUNC    GLOBAL DEFAULT   14 main

ASLR'yi kapatarak ( randomize_va_spaceveya ile set disable-randomization off), GDB her zaman mainadresi verir :, 0x5555555547a9bu nedenle -pieadresin şunlardan oluştuğunu anlıyoruz :

0x555555554000 + random offset + symbol offset (79a)

TODO Linux çekirdeğinde / glibc yükleyicide / nerede olursa olsun 0x555555554000 sabit kodludur? Linux'ta bir PIE yürütülebilir dosyasının metin bölümünün adresi nasıl belirlenir?

Minimal montaj örneği

Yapabileceğimiz bir başka harika şey de, PIE'nin ne anlama geldiğini daha somut bir şekilde anlamak için bazı montaj kodlarıyla oynamaktır.

Bunu bir Linux x86_64 bağımsız montaj merhaba dünya ile yapabiliriz:

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

GitHub yukarı akış

ve şunlarla birleştirilir ve iyi çalışır:

as -o main.o main.S
ld -o main.out main.o
./main.out

Bununla birlikte, onu PIE olarak bağlamaya çalışırsak ( --no-dynamic-linkerşu sayfada açıklandığı gibi gereklidir: Linux'ta statik olarak bağlantılı bir konum bağımsız çalıştırılabilir ELF nasıl oluşturulur? )

ld --no-dynamic-linker -pie -o main.out main.o

bağlantı şununla başarısız olur:

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

Çünkü satır:

mov $msg, %rsi  /* buffer */

movişlenen içindeki mesaj adresini sabit kodlar ve bu nedenle konumdan bağımsız değildir.

Bunun yerine bağımsız bir şekilde yazarsak:

lea msg(%rip), %rsi

PIE bağlantısı düzgün çalışıyor ve GDB bize yürütülebilir dosyanın her seferinde bellekte farklı bir konuma yüklendiğini gösteriyor.

Buradaki fark , sözdizimi nedeniyle geçerli PC adresine göre leaadresin kodlanmasıdır, ayrıca bkz: 64 bitlik bir derleme programında RIP Göreli Adresleme nasıl kullanılır?msgrip

Bunu, her iki sürümü de aşağıdakilerle sökerek de anlayabiliriz:

objdump -S main.o

sırasıyla verir:

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

Dolayısıyla , şu anki adres + 0x19 olarak kodlanmış leatam doğru adresin zaten olduğunu açıkça görüyoruz msg.

Ancak movsürüm adresi olarak ayarlamıştır 00 00 00 00, bu da orada bir yeniden konumlandırma yapılacağı anlamına gelir: Bağlayıcılar ne yapar? Şifreli R_X86_64_32Siçinde ldhata mesajı gerekti ve hangi PIE yürütülebilir içinde olamaz tehcir gerçek türüdür.

Yapabileceğimiz bir başka eğlenceli şey de, aşağıdakiler msgyerine veri bölümüne koymaktır .text:

.data
msg:
    .ascii "hello\n"
len = . - msg

Şimdi .omontajlar:

e:   48 8d 35 00 00 00 00    lea    0x0(%rip),%rsi        # 15 <_start+0x15>

bu yüzden RIP ofseti şimdi 0ve montajcı tarafından bir yeniden konumlandırma talep edildiğini tahmin ediyoruz. Bunu şu şekilde onaylıyoruz:

readelf -r main.o

hangi verir:

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

Bu nedenle R_X86_64_PC32, ldPIE çalıştırılabilirlerini idare edebilen bir PC'ye göre yeniden konumlandırmadır .

Bu deney bize, bağlayıcının programın PIE olup olmadığını kontrol ettiğini ve bu şekilde işaretlediğini öğretti.

Daha sonra GCC ile derlerken, GCC'ye -piekonumdan bağımsız derleme oluşturmasını söyler.

Ancak montajı kendimiz yazarsak, pozisyon bağımsızlığına ulaştığımızdan manuel olarak emin olmalıyız.

ARMv8 aarch64'te, pozisyondan bağımsız merhaba dünya ADR talimatı ile elde edilebilir .

Bir ELF'nin konumdan bağımsız olup olmadığı nasıl belirlenir?

Sadece GDB üzerinden çalıştırmanın yanı sıra, bazı statik yöntemlerden de bahsedilmektedir:

Ubuntu 18.10'da test edilmiştir.


1
Merhaba Ciro! ASLR-off pie-on başlangıç ​​adresi için ayrı soru oluşturup buraya bağlayabilir misiniz?
osgx

1
@osgx Bitti. Zaten biliyor musun yoksa anında kazacak mısın? :-) Siz oradayken
Ciro Santilli 郝海东 冠状 病 六四 事件法轮功

Zaten bilmiyorum, ancak glibc - glibc / elf github.com/lattera/glibc/tree/master/elf rtld'sinden çıkarılması gerektiğini biliyorum (yorumlayıcı hala ld-linux.so ise). Üç yıl önce Basile, 0x55555555'ten de stackoverflow.com/questions/29856044'ten emin değildi , ancak bu soru ld.so'nun başlangıç ​​adresiyle ilgiliydi, bu nedenle çekirdeğin fs / binfmt_elf.c veya readelf / objdump ve bağlayıcı betiklerine bakın.
osgx
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.