뉴비에욤

PLT & GOT 본문

Linux

PLT & GOT

초보에욤 2016. 1. 30. 18:08

PLT ( Procedure Linkage Table )

실제 호출 코드를 담고 있는 테이블로서, 해당 내용을 참조하여 "_dl_runtime_resolve"가 수행되고, 실제 시스템

라이브러리 호출이 이루어지게 된다.

 => 쉽게 말하면 외부 라이브러리를 "연결"해주는 함수

 => 실제 바이너리에서도  사용하고자 하는 함수/라이브러리 주소 대신 PLT를 호출

 

 

GOT ( Global Offset Table )

PLT가 참조하는 테이블로써 프로시져들의 주소가 있다. PLT가 어떤 외부 프로시져를 호출할 때 이 GOT를

참조해서 해당 주소로 분기된다.

 => PLT 함수들은 GOT 라는 테이블의 주소로 분기한다.

 => GOT는 외부 라이브러리의 함수/변수의 주소를 저장한다.

 

 

 

ex ) printf 함수 호출이 처음인 경우

[printf 함수 호출]  =>  [PLT 이동]  =>  [GOT 참조]  =>  [다시 PLT 이동] 

=>  [_dl_runtime_resolve 수행]  =>  [GOT에 실제 주소 저장 후, 실제 함수 주소로 분기]

 

 

 

 

 

 

ex ) printf 함수 호출이 처음이 아닌 경우

[printf 함수 호출]  =>  [PLT 이동]  =>  [GOT 참조]  =>  printf 함수로 점프

* 이 때 GOT에는 이전에 printf를 호출했기 때문에 이미 실제  printf 함수의 주소가 있음

 

 

 

 

 

 

 

위 내용을 실제로 디버거를 이용해 알아보기 전에 실습 환경은 다음과 같다.

커널 버전 : 2.40.20-8

gcc 버전 : 3.2.2

gdb 버전 : 5.3

 

 

실습을 진행할 소스 코드이다. "-static" 옵션을 이용하지 않고 컴파일 후 실습해야 한다.

 

 

컴파일 후 생성된 elf 바이너리를 대상으로 "readelf" 명령어와 "-S" 옵션을 함께 사용하여 섹션 정보를 보면, plt와 got 섹션을 볼 수 있다. 참고로 실습 환경이 약간 구버전이기 때문에 정확히 딱 "plt,got"가 존재하지만 최근 환경의 경우 ".rel.plt", ".plt", ".got", ".got.plt" 등과 같이 에매한 섹션들이 생성될 수 있다. 이 경우 본 포스팅에서 다루는 plt, got는 ".plt" / ".got.plt" 섹션이 된다.

 

최근 환경 기준으로 ".got", ".got.plt" 섹션이 모두 존재할 경우..

".plt" 섹션에 대응되는 영역은 정확히 말해서 ".got.plt" 섹션이다. 

 

".got" 섹션은 global data와 관련된 섹션으로, 로딩 될 때 동적 링크가 재배치를 해준다.

 

".got.plt" 섹션은 함수 호출과 관련된 ".plt"와 대응되는 섹션으로, 마찬가지로 동적 링크가 재배치를 하지만

실제 함수가 호출되는 시점 (즉, run time)에 재배치가 일어난다. 이런 개념을 "lazy binding" 이라고 한다.

 

 

 

printf 함수를 호출하기 위해 "0x8048268" 주소를 호출한다.

 

 

"printf" 함수가 아직 한번도 호출되기 이전에, 호출되는 주소(PLT 영역)에서 3개의 명령어를 보면,

<printf>        = <printf@plt>            |  jmp *0x804952c

<printf+6>     = <printf@plt+6>         |  push 0x8

<printf+11>    = <printf@plt+11>       |  jmp 0x8048248

형태로 이루어져 있다. 처음 분기되는 주소를 보면 GOT 영역임을 알 수 있고, 해당 주소에는 "0x0804826e" 라는

값이 존재한다. 원래는 이 부분에 printf 함수의 실제 주소가 있어야 하지만, 아직 printf 함수의 주소를 구하지 않은

상황이다. 그런데 "0x0804826e" 값은 잘 보면 <printf@plt+6>의 주소가 된다.

 

따라서 제일 위쪽에서 설명한 순서로 끊어서 설명하면 다음과 같다.

 

처음 print 함수 호출을 위해 plt 영역의 "jmp *0x804952c" 명령어가 실행되면 got 영역을 참조하기 위해 분기되는데

"printf 함수 호출"  =>  "PLT 이동" ==> "GOT 참조"

 

아직 실제 주소가 없기 때문에 다시 <printf@plt+6> 위치로 분기 된다.

"다시 PLT 이동"

 

그 다음"jmp 0x8048248" 명령어가 실행되는데, 이 부분이 바로 "_dl_runtime_resolve" 함수를 실행하는 부분이다.

"_dl_runtime_resolve 실행"

 

 최종적으로는 실제 printf의 주소를 구하면 구한 주소를 GOT 영역에 저장하고, 해당 주소로 점프하는 것이다.

"GOT에 실제 주소 저장 후, 해당 주소로 분기"

 

 

 

자 그럼 첫 번째 printf 함수 호출 과정을 봤으니, 두 번째 호출 과정도 보도록 한다.

 

일단 한번 호출은 시켜야 하니, 처음 printf 함수가 호출된 주소와 두 번째 호출 사이 명령어에 breakpoint를

 설치하고 실행한다.

 

그리고 다시 이전처럼 "0x8048268" 주소의 명령어들을 살펴보면 이전과 동일한 것을 볼 수 있다. 달라진 점은 바로 "0x804952c" 주소에서 가지고 있는 값인데, 기존에는 "0x0804826e == <printf@plt+6>" 값이였는데 지금은 "0x4204f0e0" 이라는 값이 들어가 있다. 해당 주소가 바로 printf 함수의 실제 주소이다.

 

 

 

 

 

 

 

 

출처

 - http://bbolmin.tistory.com/33

 - http://ezbeat.tistory.com/374

 - http://lapislazull.tistory.com/54

 - http://stackoverflow.com/questions/11676472/what-is-the-difference-between-got-and-got-plt-section

Comments