Tùy chọn -fPIE cho các tệp thực thi không phụ thuộc vào vị trí trong gcc và ld là gì?


94

Nó sẽ thay đổi mã như thế nào, ví dụ như các lệnh gọi hàm?

Câu trả lời:


100

PIE hỗ trợ ngẫu nhiên hóa bố cục không gian địa chỉ (ASLR) trong các tệp thực thi.

Trước khi chế độ PIE được tạo, tệp thực thi của chương trình không thể được đặt tại một địa chỉ ngẫu nhiên trong bộ nhớ, chỉ các thư viện động mã độc lập về vị trí (PIC) mới có thể được di chuyển đến một điểm bù ngẫu nhiên. Nó hoạt động rất giống với những gì PIC làm cho các thư viện động, sự khác biệt là một Bảng liên kết thủ tục (PLT) không được tạo, thay vào đó việc di chuyển tương đối PC được sử dụng.

Sau khi bật hỗ trợ PIE trong gcc / linkers, phần nội dung của chương trình được biên dịch và liên kết dưới dạng mã độc lập với vị trí. Trình liên kết động thực hiện xử lý tái định cư đầy đủ trên mô-đun chương trình, giống như các thư viện động. Mọi việc sử dụng dữ liệu toàn cầu đều được chuyển đổi thành quyền truy cập thông qua Bảng giá trị toàn cầu (GOT) và các vị trí GOT được thêm vào.

PIE được mô tả rõ ràng trong bài thuyết trình OpenBSD PIE này .

Các thay đổi đối với các chức năng được hiển thị trong slide này (PIE so với PIC).

x86 pic vs pie

Các biến và hàm toàn cục cục bộ được tối ưu hóa trong bánh

Các biến và hàm toàn cục bên ngoài giống như pic

và trong trang trình bày này (PIE so với liên kết kiểu cũ)

x86 pie vs no-flags (cố định)

Các biến và hàm toàn cục cục bộ tương tự như các biến cố định

Các biến và hàm toàn cục bên ngoài giống như pic

Lưu ý rằng PIE có thể không tương thích với -static


3
Cũng trong wikipedia: en.wikipedia.org/wiki/…
osgx

5
Tại sao -pie và -static tương thích trên ARM và KHÔNG tương thích ở x86? Câu hỏi SO của tôi: stackoverflow.com/questions/27082959/…
4ntoine

56

Ví dụ có thể chạy tối thiểu: GDB tệp thực thi hai lần

Đối với những người muốn xem một số hành động, hãy xem ASLR hoạt động trên tệp thực thi PIE và thay đổi địa chỉ qua các lần chạy:

C chính

#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

Đối với người có -no-pie, mọi thứ đều nhàm chán:

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

Trước khi bắt đầu thực hiện, hãy break mainđặt điểm ngắt tại 0x401126.

Sau đó, trong cả hai lần thực thi, rundừng lại ở địa chỉ 0x401126.

-pieTuy nhiên, một trong những thú vị hơn nhiều:

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

Trước khi bắt đầu thực hiện, GDB chỉ mất một địa chỉ "giả" đó là hiện diện trong thực thi: 0x1139.

Tuy nhiên, sau khi bắt đầu, GDB thông báo một cách thông minh rằng trình tải động đã đặt chương trình ở một vị trí khác và lần ngắt đầu tiên dừng lại ở 0x5630df2d6139.

Sau đó, lần chạy thứ hai cũng nhận thấy một cách thông minh rằng tệp thực thi lại di chuyển và cuối cùng bị phá vỡ 0x55763ab2e139.

echo 2 | sudo tee /proc/sys/kernel/randomize_va_spaceđảm bảo rằng ASLR đang bật (mặc định trong Ubuntu 17.10): Làm cách nào để tôi có thể tạm thời vô hiệu hóa ASLR (ngẫu nhiên hóa bố cục không gian địa chỉ)? | Hỏi Ubuntu .

set disable-randomization offNếu không, GDB là cần thiết, như tên cho thấy, theo mặc định, tắt ASLR cho quá trình để cung cấp các địa chỉ cố định trong các lần chạy để cải thiện trải nghiệm gỡ lỗi: Sự khác biệt giữa địa chỉ gdb và địa chỉ "thực"? | Tràn ngăn xếp .

readelf phân tích

Hơn nữa, chúng ta cũng có thể quan sát thấy rằng:

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

cung cấp địa chỉ tải thời gian chạy thực tế (pc trỏ đến lệnh sau 4 byte sau):

64: 0000000000401122    21 FUNC    GLOBAL DEFAULT   13 main

trong khi:

readelf -s ./pie.out | grep main

chỉ cung cấp một phần bù:

65: 0000000000001135    23 FUNC    GLOBAL DEFAULT   14 main

Bằng cách tắt ASLR (với một trong hai randomize_va_spacehoặc set disable-randomization off), GDB luôn cung cấp mainđịa chỉ 0x5555555547a9:, vì vậy chúng tôi suy ra rằng -pieđịa chỉ được tạo từ:

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

CẦN LÀM ở đâu được mã hóa cứng 0x555555554000 trong nhân Linux / bộ tải glibc / ở đâu? Địa chỉ của phần văn bản của tệp thực thi PIE được xác định như thế nào trong Linux?

Ví dụ lắp ráp tối thiểu

Một điều thú vị khác mà chúng ta có thể làm là thử với một số mã lắp ráp để hiểu cụ thể hơn ý nghĩa của PIE.

Chúng tôi có thể làm điều đó với một hợp ngữ rời Linux x86_64 hello world:

chính.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 ngược dòng

và nó lắp ráp và chạy tốt với:

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

Tuy nhiên, nếu chúng tôi cố gắng liên kết nó dưới dạng PIE với ( --no-dynamic-linkerđược yêu cầu như được giải thích tại: Cách tạo ELF thực thi độc lập vị trí liên kết tĩnh trong Linux? ):

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

thì liên kết sẽ không thành công với:

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

Vì dòng:

mov $msg, %rsi  /* buffer */

mã hóa cứng địa chỉ tin nhắn trong movtoán hạng và do đó không độc lập về vị trí.

Nếu thay vào đó chúng ta viết nó theo một cách độc lập về vị trí:

lea msg(%rip), %rsi

thì liên kết PIE hoạt động tốt và GDB cho chúng ta thấy rằng tệp thực thi luôn được tải ở một vị trí khác trong bộ nhớ.

Sự khác biệt ở đây là được leamã hóa địa chỉ msgtương đối với địa chỉ PC hiện tại do ripcú pháp, xem thêm: Làm thế nào để sử dụng Địa chỉ tương đối RIP trong chương trình hợp ngữ 64 bit?

Chúng tôi cũng có thể tìm ra điều đó bằng cách tháo rời cả hai phiên bản với:

objdump -S main.o

cung cấp cho tương ứng:

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

Vì vậy, chúng tôi thấy rõ ràng rằng leađã có địa chỉ chính xác đầy đủ msgđược mã hóa là địa chỉ hiện tại + 0x19.

Các movtuy nhiên phiên bản đã thiết lập các địa chỉ để 00 00 00 00, có nghĩa là một di dời sẽ được thực hiện có: làm linkers làm gì? Điều khó hiểu R_X86_64_32Strong thông ldbáo lỗi là kiểu di dời thực tế được yêu cầu và không thể xảy ra trong tệp thực thi PIE.

Một điều thú vị khác mà chúng ta có thể làm là đưa msgphần dữ liệu vào phần dữ liệu thay vì .textbằng:

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

Bây giờ các .otập hợp để:

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

vì vậy phần bù RIP bây giờ là 0, và chúng tôi đoán rằng trình lắp ráp đã yêu cầu chuyển vị trí. Chúng tôi xác nhận điều đó với:

readelf -r main.o

mang lại:

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õ ràng R_X86_64_PC32là một sự di dời tương đối của PC ldcó thể xử lý cho các tệp thực thi PIE.

Thử nghiệm này đã dạy chúng tôi rằng trình liên kết tự kiểm tra chương trình có thể là PIE và đánh dấu nó như vậy.

Sau đó, khi biên dịch với GCC, -pieyêu cầu GCC tạo lắp ráp vị trí độc lập.

Nhưng nếu chúng ta tự viết assembly, chúng ta phải tự đảm bảo rằng chúng ta đã đạt được sự độc lập về vị trí.

Trong ARMv8 aarch64, có thể đạt được vị trí hello world độc lập với lệnh ADR .

Làm cách nào để xác định một ELF có độc lập về vị trí hay không?

Bên cạnh việc chỉ chạy nó qua GDB, một số phương thức tĩnh được đề cập tại:

Đã thử nghiệm trong Ubuntu 18.10.


1
Chào Ciro! Bạn có thể tạo câu hỏi riêng cho địa chỉ bắt đầu ASLR-off pie-on và liên kết nó ở đây không?
osgx, 14/07/18

1
@osgx Đã xong. Bạn đã biết chưa hay bạn sẽ nhanh chóng tìm hiểu nó? :-) Trong khi bạn đang ở đó, sẽ rất thú vị khi giải thích cách hạt nhân Linux / trình tải dyn xác định xem thứ gì đó có PIE hay không: unix.stackexchange.com/questions/89211/…
Ciro Santilli 郝海东 冠状 病 六四 事件法轮功

Tôi chưa biết, nhưng tôi biết rằng nó nên được đào từ rtld của glibc - glibc / elf github.com/lattera/glibc/tree/master/elf (nếu thông dịch viên vẫn là ld-linux.so). Ba năm trước Basile không chắc về 0x55555555 cũng stackoverflow.com/questions/29856044 , nhưng câu hỏi đó là về địa chỉ bắt đầu của chính ld.so, vì vậy hãy tìm hiểu kỹ các tập lệnh fs / binfmt_elf.c hoặc readelf / objdump và linker của kernel .
osgx, 14/07/18
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.