Notice
Recent Posts
Recent Comments
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
Today
Total
관리 메뉴

9oat's LAB

Dynamic Link 시 함수 호출 과정 본문

Study/System

Dynamic Link 시 함수 호출 과정

90at 2017. 6. 26. 23:22

저번 글의 연장선에 있는 글이다.

gnu-redirect-function이 호출될 때 /lib/ld-linux.so.2를 거쳐서 호출됐었는데, 저번엔 단순히 함수 호출 과정이겠거니 하고 넘어갔기 때문에 함수가 호출되는 과정 자체가 궁금해졌다. 


[사진 1] 먼 길을 떠나요


글의 전반적인 내용과 흐름은 bpsecblog의 PLT와 GOT 자세히 알기 게시물을 참고했다.

본 글은 가독성이 다소 떨어지므로 bpsecblog의 글과 함께 보는 것이 좋을 것 같다.


strcpy를 대상으로 하여 한 줄씩 의식의 흐름을 따라 실행해보자..!


[사진 2] PLT&GOT


함수를 호출하면 먼저 함수의 PLT로 간다. 그리고 PLT엔 함수의 GOTjmp하는 코드가 있고 GOT에는 함수의 주소(Library)가 저장되어 있어 함수가 호출되는 것.

하지만 함수의 최초 호출일 경우 GOT에는 함수 주소가 아닌 해당 함수 PLT주소+6이 저장되어있다. 

즉 처음엔 함수의 주소가 없기 때문에 함수의 주소를 구해야 할 것이며 PLT+6부터가 해당 함수의 주소를 구하는 메커니즘인 것. 

알아보려 하는 게 바로 이 함수 주소를 구하는 과정이다. 주소 구하는 게 곧 호출이니까..


push 0x8은 reloc_offset을 스택에 넣어두는 것으로, 나중에 쓰이니까 기억해두자.

0x8048340은 objdump로 확인하면 PLT 섹션의 시작주소인데, 여기에 함수 주소를 찾기 위한 코드가 있는 듯하다.


[사진 3] JMP


점프하고 나면 0x804a004push한다. 0x804a004의 값인 0xf7ffd918Link_map 구조체의 주소다. Link_map은 라이브러리의 정보를 담고있는 구조체로, 이것을 이용해 여러가지 테이블의 주소를 구할 수 있다. 이후에 계속 사용된다.

그리고 0x804a008의 값으로 jmp하는데 0xf7fedf00은 _dl_runtime_resolve함수의 주소다. 


[사진 4] _dl_runtime_resolve


별 다르게 볼 건 없고 표시한 부분을 보면


[esp+0x10]은 처음 push한 reloc_offset(0x8)이며 

[esp+0xc]는 아까 push한 Link_map 구조체다. 

그리고 0xf7fe76e0dl_fix_up이다.

즉 edx(reloc_offset)와 eax(Link_map)를 인자로 _dl_fix_up을 호출하는 것.


_dl_fix_up함수는 꽤 길고 아직은 모르는 부분들이 있으므로 해석 가능하고 의미있는 부분만 보고 넘어간다. (그래도 많다..)


[사진 5] _dl_fix_up<+2>~


Link_map 구조체의 주소를 edi로 복사하고, edi(Link_map)+0x7c의 값을 ecx로 복사한다.

그리고 eax(Link_map)+0x34의 값을 eax로 복사한다.

[edi+0x7c][eax+0x34]에 뭐가 있는 지 보자.


[사진 6] edi+0x7c


Link_map은 라이브러리 정보가 담겨있고 여러 테이블의 주소를 구할 수 있다고 했다.

Link_map+0x7c에 담겨있는 값은 어떤 8Byte 구조체 주소(0x08049f94)였고 구조체의 두 번째 4Byte JMPREL시작 주소(0x080482f0)가 담겨져 있었다.(objdump로 보면 .rel.plt에 해당)


JMPREL 영역은 8Byte 구조체로 이루어져 있으며 

구조체의 처음 4Byte GOT주소(0x0804a00c),다음 1Byte(0x07)는 재배치타입, 다음 3Byte(0x000001)는 DYNSYM 영역에서의 index를 나타낸다.


[사진 7] eax+0x34


Link_map+0x34에 담겨져 있는 값도 8Byte 구조체 주소였다. 이 구조체의 두 번째 4Byte엔 STRTAB시작 주소(0x0804823c)가 담겨져 있는데, STRTAB은 바이너리 내에서 사용되는 함수명이 문자열로 저장된 영역이다. (objdump로 보면 .dynstr 해당)


[사진 8] _dl_fix_up<+30>~


ecx는 어떤 구조체 주소(0x08049f94)였고 그 안의 값은 [사진 6] 과 같았다. [ecx+4]가 가리키는 값은 JMPREL 시작 주소(0x080482f0)고 edxreloc_offset(0x8)이 있다. 그러므로 이 둘을 더한 값(0x080482f8)이 edx에 저장된다.

eax도 역시 어떤 구조체 주소고 그 안의 값 [사진 7] 과 같았다. [eax+4]는 STRTAB의 시작주소를 가리키고 있다. 그러므로 STRTAB이 eax에 저장된다.

그리고 edi(Link_map)+0x38 ecx로 복사된다. 

(아래 두 줄은 간단하므로 설명 생략.)


edx에 저장될 값(0x080482f8)과 ecx에 복사될 값[edi+0x38]을 확인해보자. 


[사진 9] 0x080482f8, strcpy의 JMPREL


JMPREL 시작주소에서 reloc_offset(0x8)을 더했다.

JMPREL 영역의 구조체가 8Byte니까 0x8을 더하면 두 번째 구조체를 가리키게 된 것.

JMPREL의 두 번째 구조체GOT주소0x0804a010인데, 이것은 strcpy의 GOT주소다.

reloc_offset은 JMPREL 영역에서 호출한 함수를 찾기 위한 값이였던 것.



[사진 10] edi+0x38


edi(Link_map)+0x38 에는 어떤 구조체 주소가 담겨져 있다. 

이 구조체는 8Byte로 구성되어있는데 두 번째 4Byte DYNSYM 영역의 시작주소(0x080481cc)가 저장되어 있다. 


DYNSYM 영역은 16Byte 구조체로 구성되어있고 처음 16Byte0으로 세팅된다.


아까 [사진 6]을 설명할 때 JMPREL 구조체의 두 번째 4Byte(정확히는 끝 3Byte)엔 DYNSYM에서의 Index가 저장되어있다고 했다.

그래서 [사진 9]를 보면 strcpyDYNSYM Index0x000002라는 것을 알 수 있다.

이는 DYNSYM의 2번 구조체라는 것이고 배열과 마찬가지로 0부터 시작하기 때문에 0x080481cc + 32(구조체 2개)가 strcpyDYNSYM주소일 것이다.


[사진 11] DYNSYM+32


DYNSYM을 이루는 구조체의 정확한 이름은 Elf32_Sym이다. 구조는 아래와 같다.


typedef struct {

  Elf32_Word  st_name        

  Elf32_Addr  st_value

  Elf32_Word  st_size        

  unsigned char   st_info     

  unsigned char   st_other   

  Elf32_Section   st_shndx   

} Elf32_Sym


유의해서 볼 것은 st_namest_other 두가지로, 

st_name함수 이름 위치의 Index인데 현재 이 구조체는 STRTAB에서의 Index를 가지고있다. 그래서 eax(STRTAB 시작주소)+Index(0x1a)를 했을 때 "strcpy"라는 문자열을 얻을 수 있는 것이다.

st_other3과 &연산을 해서 0인지 아닌지를 판단하여 0이라면 이미 로딩된 함수로 판단해 바로 호출한다고 한다.


[사진 12] _dl_fix_up<+39>~


위에서 설명한 것처럼 DYNSYM에서 strcpy 구조체를 구하는 과정이다.

간단히 설명하면 JMPREL영역의 strcpy구조체 2번째 4Byte를 가져오고 그 중에서도 끝 3Byte만(DYNSYM의 Index)가져와 ecx+0x4(DYNSYM 시작 주소)를 더해 DYNSYM에서 strcpy 구조체를 찾는 것.

DYNSYM strcpy구조체ebx에 저장됐다.


이후 코드들은 함수의 타입 검사 등의 과정이기에 크게 중요치 않다고 판단하여(사실 잘 몰라서.._dl_lookup_symbol_x 호출까지 넘어간다. 


[사진 13] _dl_fix_up<+162>~


표시한 부분이 STRTAB 시작 주소 DYNSYM strcpy 구조체에서 Index를 더해 "strcpy"문자열을 찾는 과정이다. 

eax"strcpy" 문자열의 주소가 들어가게 되고 몇 가지 모르는과정을 거친 뒤 _dl_lookup_symbol_x(0xf7fe2960)함수를 호출한다.


_dl_lookup_symbol_x와 인자로 넘긴 문자열을 이용해 Library Base주소와 SYMTAB 영역에서 해당 함수의 정보가 담긴 구조체 주소를 얻을 수 있다. 함수가 종료되면 eaxLibrary Base 주소가 저장되고 스택 어딘가(?)에 SYMTAB(strcpy) 주소를 저장하는데, 이후에 ebx로 이 주소를 복사하여 이용한다.


[사진 14] _dl_lookup_symbol_x 종료 후 eax(Library Base), ebx(SYMTAB)


SYMTABLibrary의 한 영역으로 DYNSYM 같이 16Byte 구조체들로 이루어져 있다. 아마도 Elf32_Sym이거나 그와 동일한 구조인 듯.


4Byte는 STRTAB이 아닌 다른 곳에서의 문자열("strcpy") offset(0x000018a7)이다. 

두번째 4ByteLibrary Base로부터 함수의 offset(0x00074e20)이다. 그래서 eax(Library base)과 이 값을 더하면 함수의 주소가 나온다.

나머지 Byte들은 아마 함수의 정보들인 듯하다.


이렇게 함수의 주소를 구할 수 있었고, 이후 이 주소를 GOT에 저장하고 함수를 호출 한 뒤 Main으로 돌아감으로써 함수 호출 과정종료된다.  


하지만 print로 출력되는 함수의 주소GOT에 저장된 주소가 다른 경우가 있었다.(gnu-indirect-function)

마지막으로 이 경우 GOT 주소를 저장하는 방법까지만 알아보자.


[사진 15] _dl_fix_up<+223>~


eax(Library Base)ebx+0x4(SYMTAB, offset)를 더해 함수의 주소를 구하는 부분이다. 이건 위에서 설명했으므로 넘어가고

ebx+0xc(SYMTAB, info?), SYMTAB의 구조체에서 13번째 Byteedx로 옮긴 후, 0xfand연산(한 자리만 남기기 위함인 듯)하여 값이 0xa와 같으면 어딘가로 분기한다. 


해당 구조체가 Elf32_Sym이라면 13번째 Bytest_info에 해당하고 이는 함수의 유형바인딩 속성 정보를 나타내는데, 이 구조체가 Elf32_Sym맞는 지 아닌 지를 몰라서 확신할 수 없다. 일단은 넘어가고..


strcpy는 [사진 14]를 보면 알 수 있듯이 해당 Byte가 0x1a로 분기에 걸린다.


[사진 16] 13번째 Byte가 0x1a인 다른 함수들


SYMTAB을 둘러보니 strcpy이외에도 13번째 Byte0x1a인 함수들을 찾을 수 있었고, offset을 계산해서 뽑아보니 다 gnu-indirect-function이였다. 이것이 0x1a 관계가 있는 건 확실한 듯하다. (물론 0x1a아닌 함수들은 gnu-indirect-function이 아니었다.)


[사진 17] 분기한 곳


분기한 곳을 보면 eax(Library Base+offset으로 구한 함수 주소)를 호출한 뒤 다시 분기하는데 이 때 분기하는 곳은 [사진 15]의 분기 바로 아래의 코드다.

즉 그냥 함수를 호출하고 다시 원래의 코드로 돌아가는 것. 


[사진 18] strcpy?


이 때 호출된 함수엔 저번 글에서 썼듯이 실제로 함수가 동작하는 코드 없다. 그땐 몰랐었지만 이 과정은 함수가 실제로 동작하는 코드의 주소를 구하는 과정이었다. 몇 가지 비교문으로 무언가를 검사하고 eax에 값을 저장한 뒤 분기하는, 이 때 저장되는 값이 실제 함수의 주소다. 

이후 GOT에 이 주소를 저장하고 함수를 호출하여 실행한 후 Main으로 돌아가는 것으로 함수 호출과정은 끝.


휴.. 드디어 끝났다

쓰고나서 살펴보니 부족한 것도 좀 있고 왠지 엉망이라 나중에 수정해야겠다.


Ubuntu 16.04 / gcc 5.4.0

Comments