IT 그리고 정보보안/Knowledge base

PLT (Procedure Linkage Table), GOT(Global Offset Table)

plummmm 2021. 4. 18. 20:04
반응형

PLT (Procedure Linkage Table)

한글 뜻대로 풀이하자면, 프로시져 연결 테이블이다.

사용자가 직접 제작한 프로시져인 경우 PLT를 참조한 호출이 불필요하지만

외부 라이브러리에서 땡겨와서 사용하는 프로시져인 경우

이 PLT를 참조하여 프로시져를 호출하게 된다.

 

GOT(Global Offset Table) 

프로시져들의 주소를 담고있는 테이블이다. 외부 프로시져를 호출할 때 

PLT가 GOT을 참조하여 프로시져를 호출한다.

 

LOB 이미지에서 예제 파일의 .plt와 .got를 readelf 명령으로 확인 해봤다.

 

[gate@localhost gate]$ readelf -S gremlin2 | grep plt

  [ 9] .rel.plt          REL             080482b0 0002b0 000030 08  A   4   b 4

  [11] .plt              PROGBITS        08048310 000310 000070 04  AX  0   0 4

[gate@localhost gate]$ readelf -S gremlin2 | grep got

  [ 8] .rel.got          REL             080482a8 0002a8 000008 08  A   4  13 4

  [19] .got              PROGBITS        08049510 000510 000028 04 WA   0   0 4

 

 

말로만 보면 이해가 잘 가지 않으니 그림으로 한번 보겠음.

두 가지 경우를 보아야 한다.

1. 프로시져의 호출이 처음일 때

2. 처음이 아닐 때

 

처음 프로시져를 호출할 때 내용부터 확인해보겠다.

 

(이해하기 쉽도록 개념도를 그린 것이므로 실제 구조와 약간 차이가 있을 수 있습니다.)

 

최초에 프로시져가 호출 되면 PLT[n]을 참조하게 된다. 그 안에는 GOT[n]에 있는 주소로 jmp하게 되는데

프로시져의 호출이 처음 일때는 심볼 변환이 되지 않은 상태다. 쉽게 말하자면 함수 호출 정보가 없다.

 

그럼 설정을 해주어야 할 것이다. PLT_resolv[n] 으로 간다.

PLT_resolv[n]에서 엔트리 인덱스를 스택에 push하고 resolver 라는 코드로 점프한다.

 

resolver에서 GOT[0]에 들어 있는 공유 오브젝트 식별 정보를 push하고 GOT[1]의 주소에 있는

_dl_runtime_resolve 라는 코드로 점프한다. 

 

_dl_runtime_resolve는 GOT[0]의 정보와 push된 엔트리 인덱스의 값을 참조하여 

실제 GOT[n]​의 심볼 변환값으로 변경한다. 그리고 GOT[n]으로 점프하여

명령을 수행하게 된다.

 

이걸 직접 한번 디버깅해서 따라가보자. 위에 본대로 하면 된다.

 

일단 printf 함수를 호출하는데, printf@plt 라고 되어 있다. call 하는 주소를 따라가 보자.

이제 부턴 귀찮으니까 캡쳐안한다.

 

x/10i 0x80482f0

   0x80482f0 <printf@plt>: jmp    DWORD PTR ds:0x804a00c

   0x80482f6 <printf@plt+6>: push   0x0

   0x80482fb <printf@plt+11>: jmp    0x80482e0

   0x8048300 <__gmon_start__@plt>: jmp    DWORD PTR ds:0x804a010

   0x8048306 <__gmon_start__@plt+6>: push   0x8

   0x804830b <__gmon_start__@plt+11>: jmp    0x80482e0

   0x8048310 <__libc_start_main@plt>: jmp    DWORD PTR ds:0x804a014

   0x8048316 <__libc_start_main@plt+6>: push   0x10

   0x804831b <__libc_start_main@plt+11>: jmp    0x80482e0

   0x8048320 <_start>: xor    ebp,ebp

 

​저렇게 따라가면 위에 처럼 나온다. 0x80482f0를 보면  ds에 저장된 어떤 주소로 점프한다.

지금 보는 것이 printf의 PLT이다. ds에 저장된 주소로 가면 아마 저곳이 GOT영역 이겠지. 가봅시다.

 

gdb-peda$ x/10x 0x804a00c

0x804a00c <printf@got.plt>: 0x080482f6 0x08048306 0xb7e30990 0x00000000

0x804a01c: 0x00000000 0x00000000 0x00000000 0x00000000

0x804a02c: 0x00000000 0x00000000

 

요래 나온다. 원래 이 위치에 printf의 주소가 있어야 하는데.. printf@plt+6 의 주소가 들어있네?

결론적으로 plt 로 돌아온다는 말이다.

그리고 나서 jmp 0x80482e0 로 점프를 하는데, 따라가보자.

 

gdb-peda$ x/10i 0x80482e0

   0x80482e0: push   DWORD PTR ds:0x804a004

   0x80482e6: jmp    DWORD PTR ds:0x804a008

   0x80482ec: add    BYTE PTR [eax],al

   0x80482ee: add    BYTE PTR [eax],al

   0x80482f0 <printf@plt>: jmp    DWORD PTR ds:0x804a00c

   0x80482f6 <printf@plt+6>: push   0x0

   0x80482fb <printf@plt+11>: jmp    0x80482e0

   0x8048300 <__gmon_start__@plt>: jmp    DWORD PTR ds:0x804a010

   0x8048306 <__gmon_start__@plt+6>: push   0x8

   0x804830b <__gmon_start__@plt+11>: jmp    0x80482e0

 

여기서 저 점프하는 부분이 이제 dl_runtime_resolve 하는 루틴이라고 한다. 하.. 그린 그림이랑 직접 쳐보는거랑 좀 달라서

당황스럽긴 한데;; 여튼 내가 저 모든 과정을 눈으로 확인할 수는 없다는 걸 알았음.

저기로 가서 printf의 코드 주소를 저장하는 과정을 거친다.

그래 해서 이제 함수를 호출하는 것임.

 

그렇다면 프로시져의 호출이 처음이 아닐 때는 어떨까.

위와 같은 비교적 복잡한 과정을 모두 거치지 않아도 된다.

GOT[n]에 심볼 변환값이 변경되어 있으므로.

원래 배웠던 내용 그대로이다.

PLT를 참조하고 PLT가 GOT을 참조하여 그 정보 그대로 프로시져를 호출한다.

 

PLT, GOT에서 핵심적인 내용은 ( 익스플로잇 하기 위해서 알아야 할)

PLT에는 GOT 또는 PLT+6 으로 점프하는 코드가 들어있고,

GOT에는 호출할 함수의 주소가 들어있다는 것이다.

 

참고 url : http://ezbeat.tistory.com/374

반응형