Last active
December 10, 2020 18:28
-
-
Save dbwodlf3/e763449fd7554c8aa6f2b42d7906f835 to your computer and use it in GitHub Desktop.
PIE(PIC) and NO PIE
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| =============================================================================== | |
| 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