gcc 및 ld의 위치 독립적 실행 파일에 대한 -fPIE 옵션은 무엇입니까?
함수 호출과 같이 코드를 어떻게 변경합니까?
PIE는 실행 파일에서 ASLR (Address Space Layout Randomization) 을 지원 합니다.
PIE 모드가 생성되기 전에는 프로그램의 실행 파일을 메모리의 임의 주소에 배치 할 수 없었으며 위치 독립적 코드 (PIC) 동적 라이브러리 만 임의 오프셋으로 재배치 할 수있었습니다. PIC가 동적 라이브러리에 대해 수행하는 작업과 매우 유사하게 작동합니다. 차이점은 PLT (프로 시저 연결 테이블)가 생성되지 않고 대신 PC 기준 재배치가 사용된다는 것입니다.
gcc / linkers에서 PIE 지원을 활성화 한 후 프로그램 본문이 위치 독립적 코드로 컴파일되고 링크됩니다. 동적 링커는 동적 라이브러리와 마찬가지로 프로그램 모듈에서 전체 재배치 처리를 수행합니다. 글로벌 데이터의 모든 사용은 GOT (Global Offsets Table)를 통해 액세스로 변환되고 GOT 재배치가 추가됩니다.
PIE는 이 OpenBSD PIE 프레젠테이션 에서 잘 설명되어 있습니다.
이 슬라이드 에는 기능 변경 사항이 나와 있습니다 (PIE 대 PIC).
x86 그림 대 파이
로컬 전역 변수 및 함수는 파이에서 최적화됩니다.
외부 전역 변수 및 함수는 pic과 동일합니다.
과에서 이 슬라이드 (이전 스타일의 연결 대 PIE)
x86 파이 대 플래그 없음 (고정)
지역 전역 변수 및 함수는 고정과 유사합니다.
외부 전역 변수 및 함수는 pic과 동일합니다.
PIE는 다음과 호환되지 않을 수 있습니다. -static
최소 실행 가능 예 : GDB 실행 파일 두 번
몇 가지 작업을보고 싶은 사람들을 위해 ASLR이 PIE 실행 파일에서 작동하고 실행간에 주소를 변경하는 것을 살펴 보겠습니다.
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
address 에서 중지합니다 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
합니다..
그러나 시작 후 GDB는 동적 로더가 프로그램을 다른 위치에 배치하고 첫 번째 중단이 0x5630df2d6139
.
그런 다음 두 번째 실행에서도 실행 파일이 다시 이동하여 0x55763ab2e139
.
echo 2 | sudo tee /proc/sys/kernel/randomize_va_space
ASLR이 켜져 있는지 확인합니다 (Ubuntu 17.10의 기본값) : ASLR (주소 공간 레이아웃 무작위 화)을 일시적으로 비활성화하려면 어떻게해야합니까? | Ubuntu에 문의하십시오 .
set disable-randomization off
그렇지 않으면 GDB는 이름에서 알 수 있듯이 기본적으로 프로세스에 대해 ASLR을 해제하여 디버깅 환경을 개선하기 위해 실행간에 고정 주소를 제공합니다. gdb 주소와 "실제"주소의 차이? | 스택 오버플로 .
readelf
분석
또한 다음 사항도 관찰 할 수 있습니다.
readelf -s ./no-pie.out | grep main
실제 런타임로드 주소를 제공합니다 (pc는 4 바이트 뒤에 다음 명령어를 가리킴).
64: 0000000000401122 21 FUNC GLOBAL DEFAULT 13 main
동안:
readelf -s ./pie.out | grep main
오프셋 만 제공합니다.
65: 0000000000001135 23 FUNC GLOBAL DEFAULT 14 main
By turning ASLR off (with either randomize_va_space
or set disable-randomization off
), GDB always gives main
the address: 0x5555555547a9
, so we deduce that the -pie
address is composed from:
0x555555554000 + random offset + symbol offset (79a)
TODO where is 0x555555554000 hard coded in the Linux kernel / glibc loader / wherever? How is the address of the text section of a PIE executable determined in Linux?
Minimal assembly example
Another cool thing we can do is to play around with some assembly code to understand more concretely what PIE means.
We can do that with a Linux x86_64 freestanding assembly hello world:
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
and it assembles and runs fine with:
as -o main.o main.S
ld -o main.out main.o
./main.out
However, if we try to link it as PIE with:
ld --no-dynamic-linker -pie -o main.out main.o
then link will fail with:
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
Because the line:
mov $msg, %rsi /* buffer */
hardcodes the message address in the mov
operand, and is therefore not position independent.
--no-dynamic-linker
is required as explained at: How to create a statically linked position independent executable ELF in Linux?
If we instead write it in a position independent way:
lea msg(%rip), %rsi
then PIE link works fine, and GDB shows us that the executable does get loaded at a different location in memory every time.
The difference here is that lea
encoded the address of msg
relative to the current PC address due to the rip
syntax, see also: How to use RIP Relative Addressing in a 64-bit assembly program?
We can also figure that out by disassembling both versions with:
objdump -S main.o
which give respectively:
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
So we see clearly that lea
already has the full correct address of msg
encoded as current address + 0x19.
The mov
version however has set the address to 00 00 00 00
, which means that a relocation will be performed there: What do linkers do? The cryptic R_X86_64_32S
in the ld
error message is the actual type of relocation that was required and which cannot happen in PIE executables.
Another fun thing that we can do is to put the msg
in the data section instead of .text
with:
.data
msg:
.ascii "hello\n"
len = . - msg
Now the .o
assembles to:
e: 48 8d 35 00 00 00 00 lea 0x0(%rip),%rsi # 15 <_start+0x15>
so the RIP offset is now 0
, and we guess that a relocation has been requested by the assembler. We confirm that with:
readelf -r main.o
which gives:
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
so clearly R_X86_64_PC32
is a PC relative relocation that ld
can handle for PIE executables.
This experiment taught us that the linker itself checks the program can be PIE and marks it as such.
Then when compiling with GCC, -pie
tells GCC to generate position independent assembly.
But if we write assembly ourselves, we must manually ensure that we have achieved position independence.
In ARMv8 aarch64, the position independent hello world can be achieved with the ADR instruction.
How to determine if an ELF is position independent?
Besides just running it through GDB, some static methods are mentioned at:
- executable: https://unix.stackexchange.com/questions/89211/how-to-test-whether-a-linux-binary-was-compiled-as-position-independent-code/435038#435038
- library: How can I tell, with something like objdump, if an object file has been built with -fPIC?
Tested in Ubuntu 18.10.
'code' 카테고리의 다른 글
SQL Server : PRINT 출력이 즉시 나타나지 않습니다. (0) | 2020.11.18 |
---|---|
ASP.NET의 HttpHandler 란? (0) | 2020.11.18 |
Devise를 사용하는 Rails 3 : Facebook 계정을 사용하여 로그인하도록 허용하는 방법은 무엇입니까? (0) | 2020.11.18 |
벡터 맵에 비해 멀티 맵의 장점은 무엇입니까? (0) | 2020.11.18 |
힘내 푸시가 "비 빨리 감기"를 거부했습니다. (0) | 2020.11.18 |