9oat's LAB
Dynamic Link 시 함수 호출 과정 본문
저번 글의 연장선에 있는 글이다.
gnu-redirect-function이 호출될 때 /lib/ld-linux.so.2를 거쳐서 호출됐었는데, 저번엔 단순히 함수 호출 과정이겠거니 하고 넘어갔기 때문에 함수가 호출되는 과정 자체가 궁금해졌다.
[사진 1] 먼 길을 떠나요
글의 전반적인 내용과 흐름은 bpsecblog의 PLT와 GOT 자세히 알기 게시물을 참고했다.
본 글은 가독성이 다소 떨어지므로 bpsecblog의 글과 함께 보는 것이 좋을 것 같다.
strcpy를 대상으로 하여 한 줄씩 의식의 흐름을 따라 실행해보자..!
[사진 2] PLT&GOT
함수를 호출하면 먼저 함수의 PLT로 간다. 그리고 PLT엔 함수의 GOT로 jmp하는 코드가 있고 GOT에는 함수의 주소(Library)가 저장되어 있어 함수가 호출되는 것.
하지만 함수의 최초 호출일 경우 GOT에는 함수 주소가 아닌 해당 함수 PLT주소+6이 저장되어있다.
즉 처음엔 함수의 주소가 없기 때문에 함수의 주소를 구해야 할 것이며 PLT+6부터가 해당 함수의 주소를 구하는 메커니즘인 것.
알아보려 하는 게 바로 이 함수 주소를 구하는 과정이다. 주소 구하는 게 곧 호출이니까..
push 0x8은 reloc_offset을 스택에 넣어두는 것으로, 나중에 쓰이니까 기억해두자.
0x8048340은 objdump로 확인하면 PLT 섹션의 시작주소인데, 여기에 함수 주소를 찾기 위한 코드가 있는 듯하다.
[사진 3] JMP
점프하고 나면 0x804a004의 값을 push한다. 0x804a004의 값인 0xf7ffd918은 Link_map 구조체의 주소다. Link_map은 라이브러리의 정보를 담고있는 구조체로, 이것을 이용해 여러가지 테이블의 주소를 구할 수 있다. 이후에 계속 사용된다.
그리고 0x804a008의 값으로 jmp하는데 0xf7fedf00은 _dl_runtime_resolve함수의 주소다.
[사진 4] _dl_runtime_resolve
별 다르게 볼 건 없고 표시한 부분을 보면
[esp+0x10]은 처음 push한 reloc_offset(0x8)이며
[esp+0xc]는 아까 push한 Link_map 구조체다.
그리고 0xf7fe76e0은 dl_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)고 edx엔 reloc_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의 구조체로 구성되어있고 처음 16Byte는 0으로 세팅된다.
아까 [사진 6]을 설명할 때 JMPREL 구조체의 두 번째 4Byte(정확히는 끝 3Byte)엔 DYNSYM에서의 Index가 저장되어있다고 했다.
그래서 [사진 9]를 보면 strcpy의 DYNSYM Index는 0x000002라는 것을 알 수 있다.
이는 DYNSYM의 2번 구조체라는 것이고 배열과 마찬가지로 0부터 시작하기 때문에 0x080481cc + 32(구조체 2개)가 strcpy의 DYNSYM주소일 것이다.
[사진 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_name과 st_other 두가지로,
st_name은 함수 이름 위치의 Index인데 현재 이 구조체는 STRTAB에서의 Index를 가지고있다. 그래서 eax(STRTAB 시작주소)+Index(0x1a)를 했을 때 "strcpy"라는 문자열을 얻을 수 있는 것이다.
st_other는 3과 &연산을 해서 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 영역에서 해당 함수의 정보가 담긴 구조체 주소를 얻을 수 있다. 함수가 종료되면 eax에 Library Base 주소가 저장되고 스택 어딘가(?)에 SYMTAB(strcpy) 주소를 저장하는데, 이후에 ebx로 이 주소를 복사하여 이용한다.
[사진 14] _dl_lookup_symbol_x 종료 후 eax(Library Base), ebx(SYMTAB)
SYMTAB은 Library의 한 영역으로 DYNSYM과 같이 16Byte 구조체들로 이루어져 있다. 아마도 Elf32_Sym이거나 그와 동일한 구조인 듯.
첫 4Byte는 STRTAB이 아닌 다른 곳에서의 문자열("strcpy") offset(0x000018a7)이다.
두번째 4Byte는 Library 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번째 Byte를 edx로 옮긴 후, 0xf와 and연산(한 자리만 남기기 위함인 듯)하여 값이 0xa와 같으면 어딘가로 분기한다.
해당 구조체가 Elf32_Sym이라면 13번째 Byte는 st_info에 해당하고 이는 함수의 유형과 바인딩 속성 정보를 나타내는데, 이 구조체가 Elf32_Sym이 맞는 지 아닌 지를 몰라서 확신할 수 없다. 일단은 넘어가고..
strcpy는 [사진 14]를 보면 알 수 있듯이 해당 Byte가 0x1a로 분기에 걸린다.
[사진 16] 13번째 Byte가 0x1a인 다른 함수들
SYMTAB을 둘러보니 strcpy이외에도 13번째 Byte가 0x1a인 함수들을 찾을 수 있었고, 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
'Study > System' 카테고리의 다른 글
Pwnable.kr input 풀다가 궁금한 거 (0) | 2017.07.16 |
---|---|
Memory Leak 관련한 삽질(Feat. Buffering) (0) | 2017.07.02 |
Memory Leak 기법 (0) | 2017.06.30 |
Libc-Database를 이용한 함수 주소 구하기 (0) | 2017.06.28 |
함수 주소에 대한 궁금증 (0) | 2017.05.20 |