Hacking/Pwnable

Dynamic Link 시 함수 호출 과정 (Runtime Resolve)

GunP4ng 2024. 6. 28. 23:32

함수 호출 과정(Runtime Resolve)


1. Dynamic Link 시 함수 호출 과정

1. PLT, GOT

이전 글에서 짧게 설명하고 넘어갔던 Runtime Resolve 에 대해 자세히 알아보자

 

// Name : got.c
//Compile : gcc -o got got.c -no-pie -fno-stack-protector -m32

#include <stdio.h>

int main() {
  puts("Resolving address of 'puts'.");
  puts("Get address from GOT");
}

got.c 파일을 만들고 컴파일 한다.

 

Dump of assembler code for function main:
   0x08049176 <+0>:     lea    ecx,[esp+0x4]
   0x0804917a <+4>:     and    esp,0xfffffff0
   0x0804917d <+7>:     push   DWORD PTR [ecx-0x4]
   0x08049180 <+10>:    push   ebp
   0x08049181 <+11>:    mov    ebp,esp
   0x08049183 <+13>:    push   ebx
   0x08049184 <+14>:    push   ecx
   0x08049185 <+15>:    call   0x80490b0 <__x86.get_pc_thunk.bx>
   0x0804918a <+20>:    add    ebx,0x2e76
   0x08049190 <+26>:    sub    esp,0xc
   0x08049193 <+29>:    lea    eax,[ebx-0x1ff8]
   0x08049199 <+35>:    push   eax
   0x0804919a <+36>:    call   0x8049050 <puts@plt>
   0x0804919f <+41>:    add    esp,0x10
   0x080491a2 <+44>:    sub    esp,0xc
   0x080491a5 <+47>:    lea    eax,[ebx-0x1fdb]
   0x080491ab <+53>:    push   eax
   0x080491ac <+54>:    call   0x8049050 <puts@plt>
   0x080491b1 <+59>:    add    esp,0x10
   0x080491b4 <+62>:    mov    eax,0x0
   0x080491b9 <+67>:    lea    esp,[ebp-0x8]
   0x080491bc <+70>:    pop    ecx
   0x080491bd <+71>:    pop    ebx
   0x080491be <+72>:    pop    ebp
   0x080491bf <+73>:    lea    esp,[ecx-0x4]
   0x080491c2 <+76>:    ret
End of assembler dump.

main 함수의 어셈블리이다.

call puts@plt 부분에 break 를 걸고 실행해보자.

 

puts@plt 가 호출되는 부분에서 break 된 것을 확인할 수 있다.

 

call 하는 주소( 0X8049050 )로 이동하면 PLT 가 있다.

PLT 는 GOT 를 참조한다고 했으니 0x804c010GOT일 것이다.

 

0x804c010GOT 인 것을 확인할 수 있다.

함수가 처음 호출되었기 때문에 실제 함수의 주소값이 쓰여있지 않고,

PLT+6 의 주소가 쓰여있다.

 

함수를 호출하면 함수의 PLT 로 이동한다.

PLTGOTjmp 하고, GOT 에는 라이브러리에 있는 실제 함수의 주소가 저장되어 있어 함수가 호출된다.

함수가  처음 호출되는 경우는 GOT 에 함수의 주소가 아닌 PLT+6 가 저장되어 있다.

여기부터 Dynamic Linking 의 시작이다.

 

2. _dl_runtime_resolve 호출 전

PLT+6 로 이동하면 0x8 을 스택에 push 하고

0x8049030 으로 jmp 한다.

여기서 0x8reloc_offset 인데 자세한건 아래에서 설명하겠다.

 

0x8049030 으로 jmp 한 뒤

스택에 0x804c004 의 값인 0x7ffda40push 한다.

여기서 0x7ffda40Link_map 구조체의 주소다.

Link_map 은 라이브러리의 정보를 담고있는 구조체이다. 이것을 이용해 여러가지 테이블의 주소를 구한다.

 

그 다음 0x804c008의 값인 0xf7fd8ff0 으로 jmp 한다 .

0xf7fd8ff0_dl_runtime_resolve 의 주소이다.

 

 

2. _dl_runtime_resolve

1. _dl_runtime_resolve

Dump of assembler code for function _dl_runtime_resolve:
   0xf7fd8ff0 <+0>:     endbr32
   0xf7fd8ff4 <+4>:     push   eax
   0xf7fd8ff5 <+5>:     push   ecx
   0xf7fd8ff6 <+6>:     push   edx
   0xf7fd8ff7 <+7>:     mov    edx,DWORD PTR [esp+0x10]
   0xf7fd8ffb <+11>:    mov    eax,DWORD PTR [esp+0xc]
   0xf7fd8fff <+15>:    call   0xf7fd6ea0 <_dl_fixup>
   0xf7fd9004 <+20>:    pop    edx
   0xf7fd9005 <+21>:    mov    ecx,DWORD PTR [esp]
   0xf7fd9008 <+24>:    mov    DWORD PTR [esp],eax
   0xf7fd900b <+27>:    mov    eax,DWORD PTR [esp+0x4]
   0xf7fd900f <+31>:    ret    0xc
End of assembler dump.

_dl_runtime_resolve 의 어셈블리이다.

 

_dl_runtime_resolve 함수는 _dl_fixup 함수를 호출한다.

어셈블리를 순서대로 따라가보자.

위에서 reloc_offset(0x8) 을 스택에 가장 먼저 push 하고, 

그 다음으로 Link_map(0x7ffda40) 을 스택에 push 했다.

_dl_runtime_resolve 함수로 들어와서 eax, ecx, edx 를 순서대로 push 했다.

 

esp+0x10 에 있는 값을 edx 에 넣고,

esp+0xc 에 있는 값을 eax 에 넣었다.

 

그 다음 _dl_fixup 함수를 호출한다.

_dl_fixup 함수는 eax (Link_map)edx (0x8) 의 값을 인자로 받아오는 것을 알 수 있다.

 

 

3. _dl_fixup

1. _dl_fixup

_dl_fixup 함수는 길기 때문에 필요한 부분만 보도록 하자.

_dl_fixup <+5> ~

Link_map 구조체 (eax) 의 주소를 ebp 로 복사한다.

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

[eax+0x38] 에 있는 값을 확인해보자.

 

[eax+0x38]

[eax+0x38] 에는 어떤 구조체 주소가 들어있다.

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

DYNSYM 영역은 아래에서 자세히 설명하겠다.

 

그 다음 edx (relloc_offset) 의 값을 esp+0xc 에 복사한다.

 

2. DYNSYM 영역

_dl_fixup <+33> ~

ebp (Link_map)edx 에 복사한다.

그 다음 eax + 0x4 의 값을 edi 에 복사한다.

[eax+0x4] 에 있는 값을 확인해보자.

 

[eax+0x4]

아까 위에서 구한 DYNSYM 영역의 시작 주소 0x0804820c 주소가 들어있다.

 

DYNSYM

DYNSYM 영역은 16byte 의 구조체로 구성되어 있고, 처음 16byte 는 0으로 세팅된다.

 

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

위의 DYNSYM 테이블의 마지막 값으로 보면 아래와 같다.

0x00000022, 0x00000000, 0x00000000, 0x00, 0x00, 0x0012

 

중요한 것은 st_namest_other 두 가지이다.

st_name(1번째) 은 함수 이름 위치의 offset 이다. STRTAB 에서의 Index 를 가지고 있다.

st_other(5번째) 는 3과 AND(&) 연산을 해서 0인지 아닌지로 이미 로딩된 함수인지 아닌지를 판단한다.

 

3. STRTAB

_dl_fixup <+56> ~

_dl_fixup+56에서는

ebp(Link_map)+0x34 의 값을 eax 에 복사한다.

[ebp+0x34] 의 값을 확인해보자

 

[ebp+0x34]

8byte 구조체 주소를 확인할 수 있다.

 

ebp+0x34 (STRTAB)

구조체의 두 번째 4byte 에는 STRTAB의 시작주소 0x0804825c 가 담겨있다.

STRTAB 은 바이너리 내에서 사용되는 함수명이 문자열로 저장된 영역이다.

(.dynstr 영역에 해당한다)

 

_dl_fixup+59 에서는

esp+0xc 의 값을 ecx 에 복사한다.

[esp+0xc] 를 확인해보자

[esp+0xc]

relloc_offset (0x8) 이 담겨있는 것을 확인할 수 있다.

 

_dl_fixup+63 에서는

마지막으로 eax+4esi 에 복사한다.

아까 위에서 eax 에는 8byte 구조체 주소가 들어있는 것을 확인했다.

구조체의 두 번째 4byte 에는 STRTAB 의 시작 주소가 담겨있었다.

eax+4 의 값은 STRTAB 의 시작 주소일 것이다.

[eax+0x4]

eax+4 에는 STRTAB 의 시작 주소 0x0804825c 가 들어있는 것을 확인할 수 있다.

esi 에는 STRTAB 이 들어간다.

 

4. JMPREL

_dl_fixup <+66>

ebp(Link_map)+0x7c 에 있는 값을 eax 에 복사한다.

ebp+0x7c 의 값을 확인해보자

[ebp+0x7c]

ebp+0x7c 에는 8byte 구조체 주소 0x0804bf94 가 담겨있다.

JMPREL 시작 주소

구조체의 두 번째 4byte 에는 JMPREL 의 시작주소 0x080482f4 가 담겨있다.

(.rel.plt 영역에 해당한다)

JMPREL

JMPRELElf32_Rel 형식의 구조체들로 이루어져 있다.

Elf32_Rel 구조체 형식은 아래와 같다.

typedef struct {
  Elf32_Addr  r_offset;
  Elf32_Word  r_info;
} Elf32_Rel

여기서 r_offsetGOT 의 주소이다.

r_info 는 첫 번째 1byte 는 재배치 타입, 두 번째 byte 부터 DYNSYM 테이블에서의 index 이다.

 

위의 JMPREL 테이블의 마지막 값으로 보면

0x0804c010 0x000002 0x07

순서대로 GOT 주소, DYNSYM index, 재배치 타입이다.

 

5. GOT 주소 구하기

_dl_fixup <+69>

eax+4 의 값과 ecx 를 더하고 있다.

eax+4 와 ecx 을 확인해보자

[eax+4]

eax+4 에는 JMPREL 시작 주소가 담겨있다.

ecx

ecx 에는 relloc_offset0x8 이 담겨있다.

 

puts Elf32_Rel

JMPREL  relloc_offset 을 더하면 puts 가 사용하는 Elf32_Rel 구조체의 주소가 된다.

 

puts GOT 주소

GOT 의 주소가 처음 확인했던 puts 함수의 GOT 와 일치한다.

relloc_offsetJMPREL 영역에서 호출한 함수를 찾기 위한 값인 것을 알 수 있다.

puts 함수의 DYNSYM index0x2 이고, 재배치 타입0x7 이다

 

6. 함수 이름 구하기

위에서 얘기한 DYNSYM 테이블 (0x0804820c)을 다시 확인해보자

DYNSYM

JMPREL 테이블에서 index 는 DYNSYM 테이블에서의 index 를 말한다.

puts 함수의 index0x2 이므로 2번째 위치가 된다.

 

DYNSYM 은 Elf32_Sym 구조체로 이루어져 있다고 했다.

Elf32_Sym 구조체로 보면

0x00000022, 0x00000000, 0x00000000, 0x00, 0x00, 0x0012

 

첫 번째 (0x00000022) 는 함수 이름 위치의 offset 이다.

다섯 번째 (0x00) 는 3과 & 연산을 해서 0이냐 아니냐로 이미 로딩된 함수인지 아닌지 판단한다.

0x00 은 3과 & 연산했을 때 0이 나오는 값이므로 로딩되지 않은 함수라고 판단한다.

 

0x22 가 함수 이름 위치의 offset 인지 확인해보자

STRTAB + 0x22

전에 구했던 STRTAB0x22 를 더하면 puts 문자열이 나온다.

 

이제 gdb 에서 확인해보자

_dl_fixup <+228> ~

esp+0x30 에 있는 값을 esi 로 복사하고 있다.

[esp+0x30] 을 확인해보자

[esp+0x30]

esp+0x30 에는 STRTAB 주소 0x0804825c 가 담겨있다.

 

그 다음으로 esi (STRTAB)eax 를 더한다.

eax 의 값을 확인해보자

eax

eax 에는 DYNSYM 테이블에서 함수 이름의 offset 이 저장되어 있다.

 

아까 위에서 STRTAB + 함수 이름의 offset 을 더하면 함수 이름이 나온다고 했다.

puts 문자열

esi 에 함수 이름 puts 가 들어가 있는 것을 확인할 수 있다.

 

이렇게 알아낸 문자열의 주소를 esi 에 넣고

_dl_lookup_symbol_x 함수를 호출한다

 

 

4. _dl_fixup (_dl_lookup_symbol_x 호출 후)

1.  _dl_lookup_symbol_x

_dl_lookup_symbol_x 함수는 라이브러리 내의 SYMTAB 주소라이브러리 시작 주소를 얻어온다.

SYMTAB 에는 라이브러리에 존재하는 실제 함수 주소들의 offset (라이브러리 시작 주소에서부터의 offset) 이 담겨있다

 

_dl_lookup_symbol_x 함수가 종료되면 

eax(라이브러리 시작 주소)

eax라이브러리 시작 주소 (0xf7d82000) 가 저장된 걸 확인할 수 있다.

ecx (SYMTAB 주소)

ecx 에는 SYMTAB 주소 (0xf7d8da94) 가 저장된 걸 확인할 수 있다

 

2. 함수의 실제 주소 가져오기

다시 _dl_fixup 함수로 돌아와서 필요한 부분만 보도록 하겠다.

 

_dl_fixup <+240>

eax 에 저장된 라이브러리 시작 주소edi 에 복사한다.

_dl_fixup <+259>

esp+0x28 에 저장된 값을 eax 에 복사한다.

[esp+0x28] 에 저장된 값을 확인해보자

[esp+0x28]

esp+0x28 에는 SYMTAB 주소 (0xf7d8da94) 가 담겨있다.

 

_dl_fixup <+290> ~

edi 에 저장된 라이브러리 시작 주소edx 에 복사한다.

그 다음 edx(라이브러리 시작 주소) 와 eax+0x4 를 더한다.

[eax+0x4] 를 확인해보자

[eax+0x4]

eax+0x4 에는 아까 위에서 봤던 SYMTAB 내에 있는 실제 함수의 offset 이 담겨있다.

 

edx(라이브러리 시작 주소) 와 eax+0x4(실제 함수의 offset) 를 더해서 실제 함수의 주소를 구한다.

puts 실제 함수의 주소

puts 의 실제 주소0xf7df52a0edx 에 저장된다.

 

3. GOT 에 실제 함수 주소 쓰기

_dl_fixup <+470>

esp+0x2c 의 값을 eax 에 복사한다.

[esp+0x2c] 를 확인해보자

[esp+0x2c]

esp+0x2c 에는 라이브러리에 있는 puts 의 실제 주소가 담겨있다.

eaxputs 실제 주소가 들어간다.

 

_dl_fixup <+474>

_dl_fixup+354jmp 한다.

 

_dl_fixup <+368> ~

esp+0x18 의 값을 edi 에 복사한다.

[esp+0x18] 을 확인해보자

[esp+0x18]

esp+0x18 에는 putsGOT 주소가 담겨있다.

ediputsGOT 주소가 들어간다.

 

그 다음 eax 에 있는 값을 edi 에 복사한다.

아까 위에서 eax 에는 puts 실제 주소가 담겨있고, edi 에는 puts GOT 가 담겨있다고 했다.

그럼 puts 의 GOT 주소에 puts 의 실제 주소가 담기는지 확인해보자

puts GOT

PLT+6 를 가리키던 GOTputs 실제 함수의 주소로 바뀐 것을 확인할 수 있다.

 

 

Reference


https://blackperl-security.gitlab.io/blog/2016/03/08/2016-03-08-pltgot-02/

https://9oat.tistory.com/2