Linux에서 pic의 중국어 의미는 "위치 독립적 코드"를 의미하며, 이는 코드가 로드되는 주소에 관계없이 정상적으로 실행될 수 있음을 의미합니다. PIC는 위치 독립적인 공유 라이브러리를 생성하는 데 사용됩니다. 소위 위치 독립적이라는 것은 공유 라이브러리의 코드 세그먼트가 읽기 전용이며 코드 세그먼트에 저장되지 않고 동시에 이 코드 세그먼트를 공유할 수 있음을 의미합니다. 사자.
이 튜토리얼의 운영 환경: linux7.3 시스템, Dell G3 컴퓨터.
Linux에서 pic의 전체 이름은 "위치 독립적 코드"이며 중국어로 "위치 독립적 코드"를 의미합니다.
1. 프로그램 가상 주소 공간 및 위치 관련 코드 개요
Linux 프로세스가 디스크에서 메모리로 로드되어 실행되면 커널은 해당 프로세스에 가상 주소 공간을 할당하고 가상 주소는 공간은 블록으로 구분되며, 가장 중요한 영역은 다음과 같습니다.
그림 1 - 응용 프로그램 가상 주소 공간 설명
커널 주소 공간은 모든 응용 프로그램에 동일하며, 이 안에 있는 응용 프로그램도 마찬가지입니다. 주소 공간의 일부는 직접 액세스할 수 없습니다. 커널 주소 공간은 이 기사의 초점이 아닙니다. 우리는 애플리케이션의 몇 가지 중요한 부분에 중점을 둡니다.
표 1 - 애플리케이션의 중요한 세그먼트에 대한 설명
시스템이 주소 무작위화(ASLR - Address Space Layout Randomization, 나중에 소개될 주소 무작위화)를 활성화하지 않으면 Linux는 다음에서 각 세그먼트를 사용합니다. 위의 표에서 주소 공간은 고정된 주소에 위치합니다.
Linux X86_64 머신에서 각 세그먼트의 주소가 어떻게 배열되어 있는지 확인하기 위해 실제 프로그램을 작성해 보겠습니다. 프로그램은 우리가 관심 있는 세그먼트를 다루고 있습니다.
그림 2 - 가상 주소 공간 데모 프로그램
Compiled
gcc -o addr_test addr_test.c -static
(여기서는 위치 관련 코드의 특성을 보여주기 위해 정적 링크가 사용됨)
이 프로그램을 3번 실행하면 모든 주소가 고정된 값입니다. 이는 ASLR 기능이 켜지지 않으면 시스템이 프로그램의 가상 주소 공간을 무작위로 할당하지 않고 프로그램의 모든 주소가 고정된 규칙에 따라 생성되기 때문입니다.
그림 3 - 고정된 세그먼트 주소 분포
objdump 명령으로 디스어셈블한 후 전역 변수 및 함수 호출에 액세스하기 위해 어셈블리 명령어의 주소가 위치에 따라 고정되어 있음을 알 수 있습니다. .
그림 4 - 위치 관련 코드 조립문의 예
이러한 코드는 주소가 하드 코딩되어 있기 때문에 지정된 주소에만 로드되고 실행될 수 있습니다. 코드에서 접근하는 변수와 함수 주소는 고정되어 있으며, 로딩 주소가 변경된 후에는 프로그램이 정상적으로 실행될 수 없습니다.
고정 주소 방식은 간단하지만 동적 라이브러리 지원과 같은 일부 고급 기능을 구현할 수 없습니다. 동적 라이브러리의 코드는 mmap() 시스템 호출을 통해 프로세스의 가상 주소 공간에 매핑됩니다. 다른 프로세스에서는 동일한 동적 라이브러리에 의해 매핑되는 가상 주소가 불확실합니다. 동적 라이브러리 구현에 위치 관련 코드를 사용하면 어떤 주소에서든 실행하려는 목적을 달성할 수 없습니다. 이 경우 위치 독립적 코드 PIC의 개념을 도입해야 합니다.
또한, 주소 무작위화 기능이 켜져 있지 않은 시스템에서는 프로그램의 각 세그먼트의 주소가 고정되어 있기 때문에 해커가 공격하기 더 쉬울 것임을 알 수 있습니다. (관심 있는 학생들은 Ret2shellcode 또는 Ret2libc를 검색할 수 있습니다) 공격), 이때 보호를 위해 ASLR과 함께 PIE 개념을 도입해야 합니다.
2. 위치 독립적 코드 PIC 및 동적 라이브러리 구현
PIC 위치 독립적 코드는 코드가 어떤 주소에 로드되든 정상적으로 실행될 수 있음을 의미합니다. gcc 옵션에 -fPIC를 추가하면 관련 코드가 생성됩니다.
PIC는 위치 독립적인 공유 라이브러리를 생성하는 데 사용됩니다. 소위 위치 독립적이라는 것은 공유 라이브러리의 코드 세그먼트가 읽기 전용이며 코드 세그먼트에 저장될 수 있음을 의미합니다. 복사하지 않고 시간을. 라이브러리의 변수(전역 변수 및 정적 변수)는 GOT 테이블을 통해 액세스되고, 라이브러리의 함수는 PLT->GOT->함수 위치를 통해 액세스됩니다. Linux에서 공유 라이브러리를 컴파일할 때 -fPIC 매개변수를 추가해야 합니다. 그렇지 않으면 링크하는 동안 오류 메시지가 표시됩니다(일부 정보에 따르면 이 오류는 AMD64 시스템에서만 발생하지만 내 Inter 시스템에서도 발생했습니다).
핵심 포인트 #1 - 코드 세그먼트와 데이터 세그먼트의 오프셋
코드 세그먼트와 데이터 세그먼트 사이의 오프셋은 링크 시 링커에 의해 제공되는데 이는 PIC에 매우 중요합니다. 링커가 각 개체 파일의 모든 p를 결합할 때 링커는 각 p의 크기와 이들 사이의 상대적 위치를 완전히 알고 있습니다.
그림 5 - 코드 세그먼트 및 데이터 세그먼트 오프셋 예
위 그림에서 볼 수 있듯이 이 예에서는 TEXT와 DATA가 서로 밀접하게 인접해 있습니다. 실제로 링커는 이 두 세그먼트의 오프셋을 알 수 있습니다. 이 오프셋을 기반으로 DATA 세그먼트의 시작 주소를 기준으로 TEXT 세그먼트에 있는 모든 명령어의 상대 오프셋을 계산할 수 있습니다. 위 그림에 표시된 것처럼 TEXT 세그먼트가 어떤 가상 주소에 배치되어 있는지에 관계없이 mov 명령어가 TEXT 내부의 0xe0 오프셋에 있다고 가정하면 DATA 세그먼트의 상대적 오프셋 위치는 다음과 같습니다. TEXT 세그먼트 - mov 명령은 TEXT 내부 오프셋 = 0xXXXXE000 - 0xXXXX00E0 = 0xDF20
요점 #2- X86에서 명령의 상대 오프셋 계산
처리에 상대 위치를 사용하면 코드는 위치 독립적일 수 있습니다. 하지만 X86 플랫폼에서 mov 명령에는 데이터 참조를 위한 절대 주소가 필요합니다. 그렇다면 어떻게 해야 할까요?
"Key Point 1"의 설명에서 현재 명령어의 주소를 알면 데이터 세그먼트의 주소를 계산할 수 있습니다. X86 플랫폼에는 현재 명령어 포인터 레지스터 IP의 값을 얻기 위한 명령어가 없지만(RIP는 X64에서 직접 액세스할 수 있음) 약간의 트릭을 통해 얻을 수 있습니다. 의사 코드를 살펴보겠습니다.
그림 6 - X86 플랫폼 획득 명령어 주소 어셈블리
이 코드가 실제로 실행되면 다음과 같은 일이 발생합니다.
CPU가 STUB 호출을 실행할 때, 다음 명령어의 주소를 스택에 저장한 다음 실행을 위해 STUB 라벨로 점프합니다.
STUB의 명령어는 pop ebx이므로 "pop ebx" 명령어의 주소가 스택에서 ebx 레지스터로 팝되어 IP 레지스터의 값을 가져옵니다.
1. 글로벌 오프셋 테이블 GOT
이전 내용을 이해한 후 X86에서 위치 독립적 데이터 참조가 어떻게 구현되는지 살펴보겠습니다. 이 기능은 글로벌 오프셋 테이블 테이블(GOT)을 통해 이루어집니다. .
GOT은 데이터 p에 저장된 테이블로 많은 주소 필드(항목)를 기록합니다. 명령어가 변수를 참조하려고 한다고 가정합니다. 이는 절대 주소를 직접 사용하지 않고 GOT의 항목을 참조합니다. 데이터 p의 GOT 테이블 주소는 명확하며 GOT 항목에는 변수의 절대 주소가 포함됩니다.
그림 7 - 코드 주소와 GOT 테이블 항목의 관계
위 그림과 같이 "Key Point 1"과 "Key Point 2"에 따라 먼저 현재 IP를 사용하고 GOT 테이블 주소의 절대값을 계산합니다. GOT 테이블에 있는 변수 주소 항목의 오프셋도 알고 있으므로 위치 독립적인 데이터 액세스가 가능합니다.
절대 주소 mov 명령어의 의사 코드를 예로 들어 보겠습니다(X86 플랫폼):
그림 8 - 위치 관련 mov 명령어의 예
이를 위치 독립적 코드로 바꾸려면, 몇 가지 단계가 더 필요합니다.
그림 9 - GOT와 결합된 위치 독립적 mov 명령어의 예
위 단계를 통해 변수에 대한 주소 독립적 코드 액세스를 달성할 수 있습니다. 하지만 GOT 테이블에 저장된 VAR_ADDR 값이 어떻게 실제 절대 주소가 되는지 의문이 남습니다.
libtest.so와 전역 변수 g_var이 있다고 가정합니다. readelf -r libtest.so를 전달하면 다음 출력이 표시됩니다
그림 10 - rel.dyn 세그먼트 전역 변수 리디렉션 설명 필드
동적 로더는 rel.dyn 세그먼트를 구문 분석하고 리디렉션 유형이 R_386_GLOB_DAT임을 확인하면 다음을 수행합니다. g_var 기호의 실제 주소 값을 오프셋 0x1fe4로 바꿉니다(즉, Sym. 값은 실제 주소 값으로 대체됨)
2. 함수 호출의 위치 독립적 구현
이론적으로 함수의 PIC 구현도 데이터 참조 GOT 테이블과 마찬가지로 위치 독립적일 수 있습니다. 함수의 주소를 직접 사용하는 대신, GOT를 조회하여 함수의 실제 절대 주소를 알아냅니다. 그러나 실제로 해당 기능의 PIC 특성은 이를 수행하지 않으며 실제 상황은 더욱 복잡합니다. 데이터 참조와 동일한 방법을 따르고 먼저 지연 바인딩이라는 개념을 살펴보는 것은 어떨까요?
동적 라이브러리 함수의 경우 함수가 프로그램의 주소 공간에 로드되기 전에는 함수의 실제 주소를 알 수 없습니다. 동적 로더는 이러한 문제를 처리하고 실제 주소를 해결합니다. 로더가 특별한 테이블 조회 및 교체 작업을 거쳐야 하기 때문에 바인딩 작업에는 시간이 좀 걸립니다.
如果动态库有成百上千个函数接口,而实际的进程只用到了其中的几十个接口,如果全部都在加载的时候进行绑定操作,没有意义并且非常耗时。因此提出了延迟绑定的概念,程序只有在使用到对应接口时才实时地绑定接口地址。
因为有了延迟绑定的需求,所以函数的PIC实现和数据访问的PIC有所区别。为了实现延迟绑定,就额外增加了一个间接表PLT(过程链接表)。
PLT搭配GOT实现延迟绑定的过程如下:
第一次调用函数
图11 - 首次调用PIC函数时PLT,GOT关系
首先跳到PLT表对应函数地址PLT[n],然后取出GOT中对应的entry。GOT[n]里保存了实际要跳转的函数的地址,首次执行时此值为PLT[n]的prepare resolver的地址,这里准备了要解析的函数的相关参数,然后到PLT[0]处调用resolver进行解析。
resolver函数会做几件事情:
(1)解析出代码想要调用的func函数的实际地址A
(2)用实际地址A覆盖GOT[n]保存的plt_resolve_addr的值
(3)调用func函数
首次调用后,上图的链接关系会变成下图所示:
图12 - 首次调用PIC函数后PLT,GOT关系
随后的调用函数过程,就不需要再走resolver过程了
三、位置无关可执行程序PIE
PIE,全称Position Independent Executable。2000年早期及以前,PIC用于动态库。对于可执行程序来讲,仍然是使用绝对地址链接,它可以使用动态库,但程序本身的各个segment地址仍然是固定的。随着ASLR的出现,可执行程序运行时各个segment的虚拟地址能够随机分布,这样就让攻击者难以预测程序运行地址,让缓存溢出攻击变得更困难。OS在使能ASLR的时候,会检查可执行程序是否是PIE的可执行程序。gcc选项中添加-fPIE会产生相关代码。
四、Linux ASLR机制和PIE的关系
ASLR的全称为 Address Space Layout Randomization。在Linux 2.6.12 中被引入到 Linux 系统,它将进程的某些虚拟地址进行随机化,增大了入侵者预测目的地址的难度,降低应用程序被攻击成功的风险。
在Linux系统上,ASLR有三个级别
表2 - ASLR级别描述
ASLR的级别通过两种方式配置:
echo level > /proc/sys/kernel/randomize_va_space
或
sysctl -w kernel.randomize_va_space=level
例子:
echo 0 > /proc/sys/kernel/randomize_va_space 关闭地址随机化
或
sysctl -w kernel.randomize_va_space=2 最大级别的地址随机化
我们还是以文章开头的那个程序来说明ASLR在不同级别下时如何表现的,首先在ASLR关闭的情况下,相关地址不变,输出如下:
图13 - ASLR=0时虚拟地址空间分配情况
我们把ASLR级别设置为1,运行两次,看看结果:
图14 - ASLR=1时虚拟地址空间分配情况
可以看到STACK和MMAP的地址发生了变化。堆、数据段、代码段仍然是固定地址。
接下来我们把ASLR级别设置为2,运行两次,看看结果:
图15 - ASLR=2,PIE不启用时虚拟地址空间分配情况
可以看到此时堆的地址也发生了变化,但是我们发现BSS,DATA,TEXT段的地址仍然是固定的,不是说ASLR=2的时候,是完全随机化吗?
这里就引出了PIE和ASLR的关系了。从上面的实验可以看出,如果不对可执行文件做一些特殊处理,ASLR即使在设置为完全随机化的时候,也仅能对STACK,HEAP,MMAP等运行时才分配的地址空间进行随机化,而可执行文件本身的BSS,DATA,TEXT等没有办法随机化。结合文章前面讲到的PIE相关知识,我们也很容易理解这一点,因为编译和链接过程中,如果没有PIE的选项,生成的可执行文件里都是位置相关的代码。如果OS不管这一点,ASLR=2时也将BSS,DATA,TEXT等随意排布,可想而知程序根本不能正常运行起来。
明白了原因,我们在编译时加入PIE选项,然后在ASLR=2时重新运行一下看看结果如何
图16 - ASLR=2,PIE启用时虚拟地址空间分配情况
PIE가 켜져 있고 ASLR=2인 경우 각 세그먼트의 가상 주소가 완전히 무작위화될 수 있음을 알 수 있습니다.
관련 추천: "Linux 비디오 튜토리얼"
위 내용은 리눅스 사진이 뭐야?의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!