By rls1004 | March 8, 2016
Dynamic Linking 과정을 추적해 PLT와 GOT를 이해 해보자
이번 편에서는 Codegate 2015 본선
문제 였던 pwnable 분야의 yocto
를 통해 PLT
와 GOT
에 대해 자세히 알아보겠습니다.
과정이 조금 복잡하기 때문에, 보시면서 따라 해 보는 것이 조금 더 도움이 될 것 같습니다. :)
그럼 본격적으로 함수의 호출 과정을 살펴보겠습니다.
바이너리를 gdb로 열어보았습니다. 제일 처음 호출되는 함수는 setvbuf
군요.
여기서 call하는 주소로 가보면 PLT
가 있습니다.
PLT에서는 GOT를 참조한다고 배웠으니 이 주소는 GOT
일 것입니다.
확인해보니 정말 GOT
라고 나오네요.
지금은 처음 호출이라 실제 함수의 주소가 쓰여있지 않을 것입니다.
무언가 다른 값이 써있는 것 같은데 가만 보니 PLT+6
의 주소입니다.
호출 관계를 정리해봤습니다.
함수가 처음 호출되면 PLT+6
을 실행합니다. PLT+6
에서는 0x10
을 스택에 푸쉬하고 또 다른 주소로 뜁니다.
여기서부터 Dynamic Linking
의 시작입니다.
0x10
은 reloc_offset
인데, 일단 기억하고 넘어가겠습니다.
그 주소로 점프했더니 또 어떤 값을 푸쉬하고 다른 곳으로 뛰고 있습니다.
여기서 push
되는 값0xb7fff938
은 link_map 구조체 포인터
입니다.
Link_map
구조체는 말 그대로 ld loader
가 참조하는 링크 지도로, 라이브러리의 정보를 담고 있습니다.
이 link_map
구조체를 통해 여러 가지 테이블의 주소를 구할 수 있는데 이것은 뒤에서 확인할 수 있습니다.
이 구조체를 스택에 push하고 점프하는 곳이 _dl_runtime_resolve
라는 함수입니다.
_dl_runtime_resolve
함수는 _dl_fixup
이라는 함수를 부릅니다.
이 함수는 eax
와 edx
의 값을 인자로 받아오는데, 여기에 들어가는 값을 살펴봅시다.
지금까지 push 된 값들에 따른 스택의 상태입니다.
reloc_offset(0x10)
을 push했고 link_map
구조체(0xb7fff938
)를 push했습니다.
그리고 _dl_runtime_resolve
함수로 들어와서 eax
, ecx
, edx
를 push했습니다.
그 후 esp를 기준으로 +0x10
위치에 있는 것을 edx
레지스터에 넣고, +0xc
위치에 있는 것을 eax
레지스터에 넣었습니다.
_dl_fixup
함수는 인자로 reloc_offset
과 link_map 구조체
를 사용한다는 것을 알 수 있습니다.
그럼 이제 _dl_fixup
함수를 봐야겠죠.
볼게 좀 많습니다.
중요한 부분만 보도록 하겠습니다.
_dl_fixup+25
부분에 브레이크 포인트를 설정했습니다.
(사용하는 라이브러리에 따라서 이 위치는 다를 수도 있습니다.)
Link_map
구조체를 이용해서 문자열 테이블인 STRTAB
의 주소를 알아냈습니다.
STRTAB
에는 이 프로그램에서 사용되는 함수들의 이름이 있습니다.
- STRTAB(문자열 테이블)
- 프로그램 내에서 쓰이는 각종 심볼들의 string 이 들어있다.
- 참고링크 - OpenADFortTk (including Open64 and OpenAnalysis references)
이번엔 _dl_fixup+34
에도 브레이크 포인트로 설정하겠습니다.
eax+0x4
에 재배치 테이블의 시작점인 JMPREL
의 주소를 얻어오게 되는데, 이것은 Elf32_Rel
형식의 구조체들로 이루어져있습니다.
8byte 구조체 형식인 Elf32_Rel
구조체의 처음 4byte는 GOT의 주소
입니다.
또한 다음 4byte의 첫 번째 1byte는 재배치 타입
, 두 번째부터 네 번째 byte가 DYNSYM
이라는 테이블에서의 index 를 나타냅니다.
- JMPREL
- 재배치 정보를 담고 있는 재배치 테이블
- Elf32_Rel 구조체로 이루어져 있다.
- Elf32_Rel
- GOT의 주소와 재배치 정보들로 이루어져 있다.
- Elf32_Rel 참고 - OpenADFortTk (including Open64 and OpenAnalysis references)
- 재배치 타입
- 어떤 비트를 변화시키고 그 값을 어떻게 계산할 것인지를 나타낸다.
_dl_fixup+34
에서는 edx
와 eax+0x4
에 있는 값을 더하고 있는데요, edx
에는 reloc_offset
이었던 0x10
이 들어있습니다.
JMPREL
에 reloc_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)
이 담겨있습니다.
- Symbol Table Pointer
- 심볼 테이블 포인터
- 전역변수와 함수에 대한 정보를 담고 있다
- SYMTAB 참고 - OpenADFortTk (including Open64 and OpenAnalysis references)
_dl_lookup_symbol_x
함수가 종료되면 eax
에 라이브러리의 시작 주소
가 쓰여있고, ebp-0x1c
에 SYMTAB의 주소
가 쓰여있습니다.
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