PLT와 GOT 자세히 알기 2 (with ‘yocto’)

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

PLT와 GOT 자세히 알기 1


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

got와plt-2-그림1

[그림 1] call setvbuf

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

got와plt-2-그림2

[그림 2] PLT 확인

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

got와plt-2-그림3

[그림 3] GOT 확인

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

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

got와plt-2-그림4

[그림 4] setvbuf 함수의 호출관계

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

got와plt-2-그림5

[그림 5] Dynamic Linking Start

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

got와plt-2-그림6

[그림 6] jmp 0x080482a0

그 주소로 점프했더니 또 어떤 값을 푸쉬하고 다른 곳으로 뛰고 있습니다.
여기서 push되는 값(0xb7fff938)은 link_map 구조체 포인터입니다.
Link_map 구조체는 말 그대로 ld loader가 참조하는 링크 지도로, 라이브러리의 정보를 담고 있습니다.
이 link_map 구조체를 통해 여러 가지 테이블의 주소를 구할 수 있는데 이것은 뒤에서 확인할 수 있습니다.
이 구조체를 스택에 push하고 점프하는 곳이 ‘_dl_runtime_resolve’ 라는 함수입니다.

got와plt-2-그림7

[그림 7] _dl_runtime_resolve

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

got와plt-2-그림8

[그림 8] 현재 스택의 상태

지금까지 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 함수를 봐야겠죠.

got와plt-2-그림9

[그림 9] 볼 것 많은 _dl_fixup

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

got와plt-2-그림A

[그림 10] _dl_fixup + 25

_dl_fixup+25 부분에 브레이크 포인트를 설정했습니다.
(사용하는 라이브러리에 따라서 이 위치는 다를 수도 있습니다.)

Link_map 구조체를 이용해서 문자열 테이블인 STRTAB의 주소를 알아냈습니다.

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

STRTAB(문자열 테이블)

  • 프로그램 내에서 쓰이는 각종 심볼들의 string 이 들어있다.
  • 참고

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

got와plt-2-그림B

[그림 11] _dl_fixup + 34

eax+0x4 에 재배치 테이블의 시작점인 JMPREL의 주소를 얻어오게 되는데, 이것은 Elf32_Rel 형식의 구조체들로 이루어져있습니다.
8바이트 구조체 형식인 Elf32_Rel 구조체의 처음 4byte는 GOT의 주소입니다. 또한 다음 4byte의 첫 번째 1byte는 재배치 타입, 두 번째부터 네 번째 byte가 DYNSYM이라는 테이블에서의 index 를 나타냅니다.

JMPREL

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

Elf32_Rel

재배치 타입

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

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

got와plt-2-그림C

[그림 12] JMPREL + reloc_offset

JMPREL에 reloc_offset을 더하면 setvbuf가 사용하는 Elf32_Rel 구조체의 주소가 됩니다.
GOT의 주소가 처음에 확인했던 setvbuf함수의 GOT와 일치합니다!
Index는 0x3이고 재배치 타입은 0x7입니다.

got와plt-2-그림D

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

DYNSYM

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

Elf32_Sym 구조체

 

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

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

got와plt-2-그림E

[그림 14] STRTAB + 0x2c

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

got와plt-2-그림G

[그림 15] 알아낸 setvbuf 문자열

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

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

got와plt-2-그림H

[그림 16] call _dl_lookup_symbol_x

_dl_lookup_symbol_x 함수를 호출합니다.

got와plt-2-그림I

[그림 17] …길다.

이 함수는 정말 길어요….

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

Symbol Table Pointer 

  • 심볼 테이블 포인터
  • 전역변수와 함수에 대한 정보를 담고 있다
  • SYMTAB 참고
got와plt-2-그림J

[그림 18]  _dl_lookup_symbol_x 함수 종료 후

_dl_lookup_symbol_x 함수가 종료되면 eax에 라이브러리의 시작 주소가 쓰여있고, ebp-0x1c에 SYMTAB의 주소가 쓰여있습니다.

SYMTAB에 적혀있는 여러 offset들 중에서도 우리가 부르는 함수에 해당하는 offset이 적힌 주소를 찾아옵니다.

got와plt-2-그림K

[그림 19] GOT에 실제 함수의 주소 기록

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


 

다시 한 번 정리합시다!

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

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

STRTAB내에 있는 함수이름의 주소를 넘겨주며 _dl_lookup_symbol_x함수를 부르고, 여기서는 라이브러리 시작 주소와 라이브러리 함수 내에 있는 SYMTAB의 주소를 얻어옵니다.
다시 _dl_fixup함수로 돌아오면, SYMTAB 내의 실제 함수의 오프셋과 라이브러리 시작 주소를 더해 실제 함수의 주소를 알아내고 GOT에 기록합니다.

got와plt-2-그림L

[그림 20] 실제 함수의 주소를 구해 GOT에 쓰기까지

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


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

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

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

 

got와plt-2-그림M

[그림 21] exploit !

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

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

got와plt-2-그림N

[그림 22] exploit 과정

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

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

written by rls1004


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

Advertisements

PLT와 GOT 자세히 알기 2 (with ‘yocto’)”에 대한 6개의 생각

  1. 핑백: Watermelon (feat.출제자 in09) – Hackerz on the ship

  2. DYNSYM 부분이 잘못되어 있습니다.
    Elf32_sym은 일반적으로 16바이트입니다.

    덤프한 그림에서 0x00000012가 16바이트간격으로 있는 것을 봐도 알 수 있죠.
    네번째 4바이트인 0x00000012 에서 0x12는 st_info 입니다.
    STB_GLOBAL 바인딩에 STT_FUNC 타입이라는 의미죠.

    또한, 사진에는 1부터 시작하는 이상한 방법으로 사용하고 있는데,
    4바이트씩 0 1 2 3 으로 index를 세어보면 일치하는 것을 볼 수 있습니다.
    그리고, index 0의 구조체는 전부 0으로 채워져 있다는 사실 또한 만족하고요.

    언급한 0x20은 index 4인 구조체의 첫번째 원소이며, 함수 이름의 offset입니다.
    SHT_STRTAB 타입인 .dynstr 섹션의 데이터에서 offset 0x20을 살펴보면 함수 이름이 정확히 나올겁니다.

    typedef struct
    {
    Elf32_Word st_name; /* Symbol name (string tbl index) */
    Elf32_Addr st_value; /* Symbol value */
    Elf32_Word st_size; /* Symbol size */
    unsigned char st_info; /* Symbol type and binding */
    unsigned char st_other; /* Symbol visibility */
    Elf32_Section st_shndx; /* Section index */
    } Elf32_Sym;

    좋아하기

  3. 안녕하세요, 글 너무 잘 읽었습니다.
    다름이 아니오라 DYNSYM 테이블의 주소가 0x0804818c 로 나와있는데, 이 주소는 어디서 나타난건지 제가 부족하여 찾지 못해서 이렇게 댓글로 여쭤봅니다.. 어디서 저 주소를 받아오는지 알 수 있을까요..?

    좋아하기

답글 남기기

아래 항목을 채우거나 오른쪽 아이콘 중 하나를 클릭하여 로그 인 하세요:

WordPress.com 로고

WordPress.com의 계정을 사용하여 댓글을 남깁니다. 로그아웃 / 변경 )

Twitter 사진

Twitter의 계정을 사용하여 댓글을 남깁니다. 로그아웃 / 변경 )

Facebook 사진

Facebook의 계정을 사용하여 댓글을 남깁니다. 로그아웃 / 변경 )

Google+ photo

Google+의 계정을 사용하여 댓글을 남깁니다. 로그아웃 / 변경 )

%s에 연결하는 중