Skip to content

Instantly share code, notes, and snippets.

@dbwodlf3
Last active December 10, 2020 18:28
Show Gist options
  • Select an option

  • Save dbwodlf3/e763449fd7554c8aa6f2b42d7906f835 to your computer and use it in GitHub Desktop.

Select an option

Save dbwodlf3/e763449fd7554c8aa6f2b42d7906f835 to your computer and use it in GitHub Desktop.
PIE(PIC) and NO PIE
===============================================================================
GCC 에서 PIE와 Non-PIE의 직접적인 차이점.
본 글은 GCC와 C언어에 대해서 말하고 있다.
들어가기에 앞서 주의할 사항.
1. PIE와 Non-PIE는 하드웨어 아키텍처의 물리적인 특성이 아니다.
2. PIE와 Non-PIE는 컴파일러와 Linker의 소프트웨어적인 특성이다.
3. X86 아키텍처에서는 EIP를 경유하는 명령어가 존재하지 않아서 Helper Assembly
Procedure를 사용한다. (GCC의 경우 get_thunk_pc, clang의 경우 별도의 Procedure는
없고 call 명령어와 pop 명령어만으로 이를 구현한다.)
===============================================================================
1. Compiler, Linker, Loader
1.1. Compiler는 object 파일을 만든다.
1.2. Linker는 Object 파일을 바탕으로 ELF 파일을 만든다.
1.3. Loader는 ELF 파일을 프로세스로 만들어 실행한다.
2. Compiler가 PIE 코드를 만들 수 있다. Binary Code Generate 로직과 관련이 있다.
2.1. 기본적으로 Entry Address가 변한다. 코드의 Sequence가 변하지는 않는다.
2.2. entry address와 관련된 명령어들이 Offset을 이용하는 명령어로 치환된다.
3. PIE으로서 실행될지 안될지는 Linker가 결정한다.
3.1. Compiler가 PIE 코드를 만들었다고 하더라도, Linker가 PIE 포멧으로 ELF 파일을
만들지 않으면 NON-PIE로 실행된다.
3.2. Compiler가 NON-PIE 코드를 만들었는데, Linker가 PIE 포멧으로 ELF 파일을 만들면
Segfault 오류가 날 확률이 99퍼 센트이고. gcc 에서 그렇게 만들 수 없게한다.
실험.
GCC의 버전은 7.5.
Linux Ubuntu 18.04 AMD64 버전에서 테스트 하였다.
소스코드
```c
// example_pie.c
#include <stdio.h>
char global_var = 1;
int main(){
int *main_ptr = (int *)main;
printf("%p \n", main_ptr);
printf("%p \n", &global_var);
return 0;
}
```
컴파일
```console
target="example_pie"
gcc -m64 -fpie -pie ${target}.c -o gcc_m64_PIE_${target}.out
gcc -m64 -fno-pie -no-pie ${target}.c -o gcc_m64_NO_PIE_${target}.out
gcc -m32 -fPIE -pie ${target}.c -o gcc_m32_PIE_${target}.out
gcc -m32 -fno-PIE -no-pie ${target}.c -o gcc_m32_NO_PIE_${target}.out
gcc -m64 -fpie -no-pie ${target}.c -o gcc_m64_FPIE_NO_PIE_${target}.out
gcc -m32 -fpie -no-pie ${target}.c -o gcc_m32_FPIE_NO_PIE_${target}.out
```
실행
```console
./gcc_m64_PIE_${target}.out && ./gcc_m64_PIE_${target}.out
./gcc_m64_NO_PIE_${target}.out && ./gcc_m64_NO_PIE_${target}.out
./gcc_m32_PIE_${target}.out && ./gcc_m32_PIE_${target}.out
./gcc_m32_NO_PIE_${target}.out && ./gcc_m32_NO_PIE_${target}.out
./gcc_m64_FPIE_NO_PIE_${target}.out && ./gcc_m64_FPIE_NO_PIE_${target}.out
./gcc_m32_FPIE_NO_PIE_${target}.out && ./gcc_m32_FPIE_NO_PIE_${target}.out
```
실행 결과
```
gcc_m64_PIE_${target}.out
0x55a754ec964a
0x55a7550ca010
0x559051dfa64a
0x559051ffb010
gcc_m64_NO_PIE_${target}.out
0x4004e7
0x601030
0x4004e7
0x601030
gcc_m32_PIE_${target}.out
0x5659d51d
0x5659f008
0x5656251d
0x56564008
gcc_m64_FPIE_NO_PIE_${target}.out
0x4004e7
0x601030
0x4004e7
0x601030
gcc_m32_FPIE_NO_PIE_${target}.out
0x8048426
0x804a01c
0x8048426
0x804a01c
```
Compiler의 PIE 옵션은 -fPIE, Linker의 PIE 옵션은 Linker -pie 이다.
Compiler가 PIE 코드를 만들어 내도, Linker가 PIE ELF 파일을 만들어 내지 않으면,
코드는 PIE 이겠지만 메모리에 Load 되는 위치는 고정되어 있다.
===============================================================================
Binary 레벨에서 이를 살펴보면 더 극명하게 드러난다.
AMD64 PIE objdump
```
000000000000064a <main>:
64a: 55 push %rbp
64b: 48 89 e5 mov %rsp,%rbp
64e: 48 83 ec 10 sub $0x10,%rsp
652: 48 8d 05 f1 ff ff ff lea -0xf(%rip),%rax # 64a <main>
659: 48 89 45 f8 mov %rax,-0x8(%rbp)
65d: 48 8b 45 f8 mov -0x8(%rbp),%rax
661: 48 89 c6 mov %rax,%rsi
664: 48 8d 3d b9 00 00 00 lea 0xb9(%rip),%rdi # 724 <_IO_stdin_used+0x4>
66b: b8 00 00 00 00 mov $0x0,%eax
670: e8 ab fe ff ff callq 520 <printf@plt>
675: 48 8d 35 94 09 20 00 lea 0x200994(%rip),%rsi # 201010 <global_var>
67c: 48 8d 3d a1 00 00 00 lea 0xa1(%rip),%rdi # 724 <_IO_stdin_used+0x4>
683: b8 00 00 00 00 mov $0x0,%eax
688: e8 93 fe ff ff callq 520 <printf@plt>
68d: b8 00 00 00 00 mov $0x0,%eax
692: c9 leaveq
693: c3 retq
694: 66 2e 0f 1f 84 00 00 nopw %cs:0x0(%rax,%rax,1)
69b: 00 00 00
69e: 66 90 xchg %ax,%ax
```
AMD64 NO-PIE objdump
```
00000000004004e7 <main>:
4004e7: 55 push %rbp
4004e8: 48 89 e5 mov %rsp,%rbp
4004eb: 48 83 ec 10 sub $0x10,%rsp
4004ef: 48 c7 45 f8 e7 04 40 movq $0x4004e7,-0x8(%rbp)
4004f6: 00
4004f7: 48 8b 45 f8 mov -0x8(%rbp),%rax
4004fb: 48 89 c6 mov %rax,%rsi
4004fe: bf b4 05 40 00 mov $0x4005b4,%edi
400503: b8 00 00 00 00 mov $0x0,%eax
400508: e8 e3 fe ff ff callq 4003f0 <printf@plt>
40050d: be 30 10 60 00 mov $0x601030,%esi
400512: bf b4 05 40 00 mov $0x4005b4,%edi
400517: b8 00 00 00 00 mov $0x0,%eax
40051c: e8 cf fe ff ff callq 4003f0 <printf@plt>
400521: b8 00 00 00 00 mov $0x0,%eax
400526: c9 leaveq
400527: c3 retq
400528: 0f 1f 84 00 00 00 00 nopl 0x0(%rax,%rax,1)
40052f: 00
```
AMD64 PIE
```
int *main_ptr = (int *)main =>
652: 48 8d 05 f1 ff ff ff lea -0xf(%rip),%rax # 64a <main>
659: 48 89 45 f8 mov %rax,-0x8(%rbp)
printf("%p \n", &global_var) =>
675: 48 8d 35 94 09 20 00 lea 0x200994(%rip),%rsi # 201010 <global_var>
67c: 48 8d 3d a1 00 00 00 lea 0xa1(%rip),%rdi # 724 <_IO_stdin_used+0x4>
683: b8 00 00 00 00 mov $0x0,%eax
688: e8 93 fe ff ff callq 520 <printf@plt>
```
AMD64 NO-PIE
```
int *main_ptr = (int *)main =>
4004ef: 48 c7 45 f8 e7 04 40 movq $0x4004e7,-0x8(%rbp)
printf("%p \n", &global_var) =>
40050d: be 30 10 60 00 mov $0x601030,%esi
400512: bf b4 05 40 00 mov $0x4005b4,%edi
400517: b8 00 00 00 00 mov $0x0,%eax
40051c: e8 cf fe ff ff callq 4003f0 <printf@plt>
```
===============================================================================
amd64 의 경우에는, RIP를 offset으로 사용하는 명령어 셋이 있지만, x86의 경우에는
존재하지 않아서, helper procedure를 사용해야 한다.
참고. https://stackoverflow.com/a/4062434/12365658
gcc 에서는 get_thunk_pc 프로시저가 존재하고. 기본적으로, offset을 계산하기 위해서
ebx 레지스터를 이용한다.
바이너리를 살펴보면 자명하다.
x86 PIE objdump
```
0000051d <main>:
51d: 8d 4c 24 04 lea 0x4(%esp),%ecx
521: 83 e4 f0 and $0xfffffff0,%esp
524: ff 71 fc pushl -0x4(%ecx)
527: 55 push %ebp
528: 89 e5 mov %esp,%ebp
52a: 53 push %ebx
52b: 51 push %ecx
52c: 83 ec 10 sub $0x10,%esp
52f: e8 ec fe ff ff call 420 <__x86.get_pc_thunk.bx>
534: 81 c3 a4 1a 00 00 add $0x1aa4,%ebx
53a: 8d 83 45 e5 ff ff lea -0x1abb(%ebx),%eax
540: 89 45 f4 mov %eax,-0xc(%ebp)
543: 83 ec 08 sub $0x8,%esp
546: ff 75 f4 pushl -0xc(%ebp)
549: 8d 83 28 e6 ff ff lea -0x19d8(%ebx),%eax
54f: 50 push %eax
550: e8 5b fe ff ff call 3b0 <printf@plt>
555: 83 c4 10 add $0x10,%esp
558: 83 ec 08 sub $0x8,%esp
55b: 8d 83 30 00 00 00 lea 0x30(%ebx),%eax
561: 50 push %eax
562: 8d 83 28 e6 ff ff lea -0x19d8(%ebx),%eax
568: 50 push %eax
569: e8 42 fe ff ff call 3b0 <printf@plt>
56e: 83 c4 10 add $0x10,%esp
571: b8 00 00 00 00 mov $0x0,%eax
576: 8d 65 f8 lea -0x8(%ebp),%esp
579: 59 pop %ecx
57a: 5b pop %ebx
57b: 5d pop %ebp
57c: 8d 61 fc lea -0x4(%ecx),%esp
57f: c3 ret
```
x86 NO-PIE objdump
```
08048426 <main>:
8048426: 8d 4c 24 04 lea 0x4(%esp),%ecx
804842a: 83 e4 f0 and $0xfffffff0,%esp
804842d: ff 71 fc pushl -0x4(%ecx)
8048430: 55 push %ebp
8048431: 89 e5 mov %esp,%ebp
8048433: 51 push %ecx
8048434: 83 ec 14 sub $0x14,%esp
8048437: c7 45 f4 26 84 04 08 movl $0x8048426,-0xc(%ebp)
804843e: 83 ec 08 sub $0x8,%esp
8048441: ff 75 f4 pushl -0xc(%ebp)
8048444: 68 00 85 04 08 push $0x8048500
8048449: e8 92 fe ff ff call 80482e0 <printf@plt>
804844e: 83 c4 10 add $0x10,%esp
8048451: 83 ec 08 sub $0x8,%esp
8048454: 68 1c a0 04 08 push $0x804a01c
8048459: 68 00 85 04 08 push $0x8048500
804845e: e8 7d fe ff ff call 80482e0 <printf@plt>
8048463: 83 c4 10 add $0x10,%esp
8048466: b8 00 00 00 00 mov $0x0,%eax
804846b: 8b 4d fc mov -0x4(%ebp),%ecx
804846e: c9 leave
804846f: 8d 61 fc lea -0x4(%ecx),%esp
8048472: c3 ret
8048473: 66 90 xchg %ax,%ax
8048475: 66 90 xchg %ax,%ax
8048477: 66 90 xchg %ax,%ax
8048479: 66 90 xchg %ax,%ax
804847b: 66 90 xchg %ax,%ax
804847d: 66 90 xchg %ax,%ax
804847f: 90 nop
```
x86 PIE
```
int *main_ptr = (int *)main =>
52f: e8 ec fe ff ff call 420 <__x86.get_pc_thunk.bx>
534: 81 c3 a4 1a 00 00 add $0x1aa4,%ebx
53a: 8d 83 45 e5 ff ff lea -0x1abb(%ebx),%eax
540: 89 45 f4 mov %eax,-0xc(%ebp)
printf("%p \n", &global_var) =>
55b: 8d 83 30 00 00 00 lea 0x30(%ebx),%eax
561: 50 push %eax
562: 8d 83 28 e6 ff ff lea -0x19d8(%ebx),%eax
568: 50 push %eax
569: e8 42 fe ff ff call 3b0 <printf@plt>
```
x86 NO-PIE
```
int *main_ptr = (int *)main =>
8048437: c7 45 f4 26 84 04 08 movl $0x8048426,-0xc(%ebp)
printf("%p \n", &global_var) =>
8048454: 68 1c a0 04 08 push $0x804a01c
8048459: 68 00 85 04 08 push $0x8048500
804845e: e8 7d fe ff ff call 80482e0 <printf@plt>
```
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment