PLT와 GOT 자세히 알기 #02

By rls1004 | March 8, 2016

Dynamic Linking 과정을 추적해 PLT와 GOT를 이해 해보자



이번 편에서는 Codegate 2015 본선 문제 였던 pwnable 분야의 yocto 를 통해 PLTGOT에 대해 자세히 알아보겠습니다.
과정이 조금 복잡하기 때문에, 보시면서 따라 해 보는 것이 조금 더 도움이 될 것 같습니다. :)




그럼 본격적으로 함수의 호출 과정을 살펴보겠습니다.



바이너리를 gdb로 열어보았습니다. 제일 처음 호출되는 함수는 setvbuf 군요.



여기서 call하는 주소로 가보면 PLT가 있습니다. PLT에서는 GOT를 참조한다고 배웠으니 이 주소는 GOT일 것입니다.



확인해보니 정말 GOT라고 나오네요.
지금은 처음 호출이라 실제 함수의 주소가 쓰여있지 않을 것입니다.
무언가 다른 값이 써있는 것 같은데 가만 보니 PLT+6의 주소입니다.

호출 관계를 정리해봤습니다.



함수가 처음 호출되면 PLT+6을 실행합니다. PLT+6에서는 0x10을 스택에 푸쉬하고 또 다른 주소로 뜁니다.



여기서부터 Dynamic Linking 의 시작입니다.
0x10reloc_offset 인데, 일단 기억하고 넘어가겠습니다.



그 주소로 점프했더니 또 어떤 값을 푸쉬하고 다른 곳으로 뛰고 있습니다.
여기서 push되는 값0xb7fff938link_map 구조체 포인터입니다.

Link_map 구조체는 말 그대로 ld loader가 참조하는 링크 지도로, 라이브러리의 정보를 담고 있습니다.
link_map 구조체를 통해 여러 가지 테이블의 주소를 구할 수 있는데 이것은 뒤에서 확인할 수 있습니다.
이 구조체를 스택에 push하고 점프하는 곳이 _dl_runtime_resolve 라는 함수입니다.



_dl_runtime_resolve 함수는 _dl_fixup 이라는 함수를 부릅니다.
이 함수는 eaxedx의 값을 인자로 받아오는데, 여기에 들어가는 값을 살펴봅시다.



지금까지 push 된 값들에 따른 스택의 상태입니다.

reloc_offset(0x10)을 push했고 link_map 구조체(0xb7fff938)를 push했습니다.

그리고 _dl_runtime_resolve함수로 들어와서 eax, ecx, edx를 push했습니다.
그 후 esp를 기준으로 +0x10위치에 있는 것을 edx 레지스터에 넣고, +0xc위치에 있는 것을 eax 레지스터에 넣었습니다.

_dl_fixup 함수는 인자로 reloc_offsetlink_map 구조체를 사용한다는 것을 알 수 있습니다.


그럼 이제 _dl_fixup 함수를 봐야겠죠.



볼게 좀 많습니다.
중요한 부분만 보도록 하겠습니다.



_dl_fixup+25 부분에 브레이크 포인트를 설정했습니다. (사용하는 라이브러리에 따라서 이 위치는 다를 수도 있습니다.) Link_map 구조체를 이용해서 문자열 테이블인 STRTAB의 주소를 알아냈습니다.

STRTAB에는 이 프로그램에서 사용되는 함수들의 이름이 있습니다.



이번엔 _dl_fixup+34 에도 브레이크 포인트로 설정하겠습니다.



eax+0x4 에 재배치 테이블의 시작점인 JMPREL의 주소를 얻어오게 되는데, 이것은 Elf32_Rel 형식의 구조체들로 이루어져있습니다.
8byte 구조체 형식인 Elf32_Rel 구조체의 처음 4byte는 GOT의 주소입니다.

또한 다음 4byte의 첫 번째 1byte는 재배치 타입, 두 번째부터 네 번째 byte가 DYNSYM이라는 테이블에서의 index 를 나타냅니다.


  • JMPREL
    • 재배치 정보를 담고 있는 재배치 테이블
    • Elf32_Rel 구조체로 이루어져 있다.



  • 재배치 타입
    • 어떤 비트를 변화시키고 그 값을 어떻게 계산할 것인지를 나타낸다.


_dl_fixup+34 에서는 edxeax+0x4에 있는 값을 더하고 있는데요, edx에는 reloc_offset이었던 0x10이 들어있습니다.



JMPRELreloc_offset을 더하면 setvbuf가 사용하는 Elf32_Rel 구조체의 주소가 됩니다.

GOT의 주소가 처음에 확인했던 setvbuf함수의 GOT와 일치합니다!
Index는 0x3이고 재배치 타입은 0x7입니다.



Index가 DYNSYM 테이블에서의 index를 말한다고 했습니다.
0x03 이었으므로, 네 번째가 해당 위치가 됩니다.
DYNSYM 테이블은 elf32_sym 구조체(4byte 6개)로 이루어져있는데 중요한 값은 첫 번째와 다섯 번째 값입니다.


  • DYNSYM
    • 동적 심볼 테이블
    • import 및 export 하는 모든 심볼의 정보가 담겨있다.
    • Elf32_Sym 구조체로 이루어져 있다.



첫 번째는 함수 이름 위치의 offset이고, 다섯 번째는 3과 &연산을 해서 0이냐 아니냐로 이미 로딩 된 함수인지 아닌지를 판단합니다.
만약 결과값이 0이 아니라면 이미 로딩 되었다고 판단하고 바로 호출 해 버립니다.
0x00은 3과 &연산 했을 때 0이 나오는 값이니 로딩되지 않은 함수라고 판단하는 것을 알 수 있습니다.

첫 번째가 함수 이름 위치의 offset이라고 했으니 확인 해 봅시다.



이전에 봤던 STRTAB에 0x2C를 더하니 setvbuf 문자열이 나왔습니다.



다시 gdb로 돌아오면, 우리가 확인했던 과정과 비슷한 과정을 거쳐 STRTAB에서 setvbuf 문자열을 찾아냅니다.

이렇게 알아낸 문자열의 주소를 eax 레지스터에 넣고,



_dl_lookup_symbol_x 함수를 호출합니다.



이 함수는 정말 길어요….

어쨌든, _dl_lookup_symbol_x 함수는 라이브러리 내의 SYMTAB 주소와 라이브러리 시작 주소를 얻어옵니다.
SYMTAB에는 라이브러리에 존재하는 실제 함수 주소들의 offset (라이브러리 시작 주소에서부터의 offset) 이 담겨있습니다.




_dl_lookup_symbol_x 함수가 종료되면 eax라이브러리의 시작 주소가 쓰여있고, ebp-0x1cSYMTAB의 주소가 쓰여있습니다.
SYMTAB에 적혀있는 여러 offset들 중에서도 우리가 부르는 함수에 해당하는 offset이 적힌 주소를 찾아옵니다.



이렇게 구해온 라이브러리 시작 주소와 SYMTAB 내에 있는 실제 함수의 오프셋을 더해서 실제 함수의 주소를 구하고, 이것을 GOT에 기록합니다.
PLT+6을 가리키던 GOT실제 함수의 주소로 바뀐 것을 확인할 수 있습니다!




다시 한 번 과정을 정리합시다!


1. reloc_offsest을 push하고 Dynamic Linker를 불렀습니다.

2. 그 후 link_map 구조체 포인터를 push하고 _dl_runtime_resolve를 불렀습니다.

3. push 했던 reloc_offset과 link_map 구조체 포인터를 인자로 하여 _dl_fixup함수가 불리고,
_dl_fixup함수에서는 프로그램 내에서 쓰인 함수 이름의 문자열들이 저장된 STRTAB 주소GOT 주소
재배치 정보를 담고 있는 재배치 테이블인 JMPREL의 주소를 알아냈습니다.

4. STRTAB내에 있는 함수이름의 주소를 넘겨주며 _dl_lookup_symbol_x함수를 부르고,
여기서는 라이브러리 시작 주소와 라이브러리 함수 내에 있는 SYMTAB의 주소를 얻어옵니다.

5. 다시 _dl_fixup함수로 돌아오면,
SYMTAB내의 실제 함수의 오프셋과 라이브러리 시작 주소를 더해 실제 함수의 주소를 알아내고 GOT에 기록합니다.



Dynamic Linker는 호출할 함수이름이 있는 메모리의 주소를 구하고 이를 이용해 최종적으로 공유라이브러리에 있는 실제 함수의 주소를 구해오는 것입니다.


만약 _dl_lookup_symbol_x 함수의 인자로 넘어가는 함수이름을 조작할 수 있다면, 공유라이브러리에 있는 어떤 함수라도 호출할 수 있지 않을까요?

함수 이름은 STRTAB 테이블에 있었는데 STRRAB 테이블은 write 권한이 없는 메모리에 있기 때문에 문자열을 직접 조작할 수는 없습니다.

하지만 STRTAB을 가리키는 포인터를 write 권한이 있는 다른 메모리 주소를 가리키도록 변경시키고 그곳에 원하는 함수이름을 적는다면…



참조되지 않은 함수를 바로 호출하는 익스플로잇도 가능합니다.
문자열을 조작하는 것 만으로도 원하는 함수를 호출할 수 있다는 점이 인상적이네요.

익스플로잇 과정은 아래와 같습니다.



Dynamic Linker를 호출하고 reloc_offset을 조작하여 조작된 문자열을 가리키도록 했습니다.


지금까지 두 편에 걸쳐서 Dynamic Linking 과정을 살펴봤습니다.
PLT와 GOT의 동작 과정을 알고 나니 새롭게 보이는 것들이 있으셨을텐데요,
Dynamic Linker를 이용해서 재미있는 익스플로잇을 작성해보는건 어떨까요? :)




[그림 8] Pictogram created by useiconic.com from Noun Project

comments powered by Disqus