라이브러리 링킹 과정 분석

해킹에 대해서 입문,초급,중급,고급,연구 길잡이 [말머리] RULE

Moderator: amesianx

Post Reply
swoo1013
Posts: 31
Joined: Tue Sep 20, 2016 4:25 pm

라이브러리 링킹 과정 분석

Post by swoo1013 » Mon Jan 16, 2017 1:53 am

오늘 정리한 내용은 실제 ELF 바이너리에서 라이브러리를 링킹하는 과정에 대해서 작성을 해봤습니다.
gdb-peda$ x/3i 0x8048510
0x8048510 <socket@plt>: jmp DWORD PTR ds:0x804a034
0x8048516 <socket@plt+6>: push 0x50
0x804851b <socket@plt+11>: jmp 0x8048460
gdb-peda$ x/x 0x804a034
0x804a034 <socket@got.plt>: 0x08048516
gdb-peda$ x/x 0x8048460
0x8048460: 0xa00435ff
gdb-peda$ x/3i 0x8048460
0x8048460: push DWORD PTR ds:0x804a004
0x8048466: jmp DWORD PTR ds:0x804a008
0x804846c: add BYTE PTR [eax],al
0x8048460: push DWORD PTR ds:0x804a004 => link_map 구조체 포인터
0x8048466: jmp DWORD PTR ds:0x804a008 => _dl_runtime_resolv 함수 점프!
_dl_runtime_resolv_ =>

0xf7ff04b2: push edx
0xf7ff04b3: mov edx,DWORD PTR [esp+0x10]
0xf7ff04b7: mov eax,DWORD PTR [esp+0xc]
=> 0xf7ff04bb: call 0xf7fea080 => _dl_fixup_ 함수

_dl_runtime_resolve 함수는 esp 기준 0x10, 0xc의 값을 _dl_fixup_ 함수의 인자값으로 전달한다.

여기 들어가는 eax,edx 인자의 값을 확인해보면

gdb-peda$ x/x $esp+0x10
0xffffd718: 0x00000020
gdb-peda$ x/x $esp+0xc
0xffffd714: 0xf7ffd938

현재 스택의 값에서 esp+0x10, esp+0xc를 살펴보면 0x10에는 0x20이라는 값이, 0xc에는 f7ffd938이라는 라이브러리 주소로 보이는 값이 들어가 있음을 알 수 있다!

여기서 esp+0x10은 reloc_offset을 나타내고 esp+0xc는 _dl_runtime_resolve 함수로 jump 하기 전 stack에 push했던 link_map 구조체가 되겠다!

_dl_fixup_ 함수로 들어오면 코드가 상당히 길다.
동적 링킹하는 부분이라 그런거 같다.

[-----------------------------------------------code-----------------------------------------------]
0xf7fea08b: mov eax,DWORD PTR [eax+0x34]
0xf7fea08e: call 0xf7ff4768
0xf7fea093: add ebx,0x12f6d
=> 0xf7fea099: mov eax,DWORD PTR [eax+0x4]

dl_fixup+25 위치쯤 eax+0x4를 eax레지스터를 이용한다.
해당 값은 strtab이라고 해서 해당 프로그램에서 사용하는 함수들의 이름들을 넣어놓는 곳이다.

gdb-peda$ i r eax
eax 0x8049f54 0x8049f54
gdb-peda$ x/x 0x8049f54+4
0x8049f58: 0x080482cc
gdb-peda$ x/3s 0x080482cc
0x80482cc: ""
0x80482cd: "libc.so.6"
0x80482d7: "_IO_stdin_used"
gdb-peda$ x/10s 0x080482cc
0x80482cc: ""
0x80482cd: "libc.so.6"
0x80482d7: "_IO_stdin_used"
0x80482e6: "socket"
0x80482ed: "htonl"
0x80482f3: "htons"
0x80482f9: "fork"
0x80482fe: "__stack_chk_fai"...
0x804830d: "l"
0x804830f: "listen"

위와 같이 eax+0x4를 기준으로 strtab 테이블이 존재하는 것을 알 수 있으며, 해당 Link_map 구조체를 이용해 라이브러리에서 사용하는 함수들을 알 수 있다!

[-----------------------------------------------code-----------------------------------------------]
0xf7fea099: mov eax,DWORD PTR [eax+0x4]
0xf7fea09c: mov DWORD PTR [ebp-0x30],eax
0xf7fea09f: mov eax,DWORD PTR [esi+0x7c]
=> 0xf7fea0a2: add edx,DWORD PTR [eax+0x4]

그리고 _dl_fixup+34 위치에 브레이크 포인트를 걸고 확인해보면 edx 레지스터에 eax+0x4의 위치에 있는 포인터 변수값을 더한다고 되있다. 우선 포인터변수에 어떤 값이 있는지 확인해봤다.

gdb-peda$ i r eax
eax 0x8049f94 0x8049f94
gdb-peda$ i r edx
edx 0x20 0x20
gdb-peda$ x/10x 0x8049f94+0x4
0x8049f98: 0x080483c8 0x00000011 0x080483c0 0x00000012
0x8049fa8: 0x00000008 0x00000013 0x00000008 0x6ffffffe
0x8049fb8: 0x08048390 0x6fffffff
gdb-peda$

우선 eax 레지스터에서 +0x4 위치에 있는 값을 add했음으로 0x8049f94+0x4의 위치가 되겠다 그리고 edx는 확인해보면 0x20이라는 값이 들어있고, 이 값은 reloc_offset이라고 _dl_fixup_ 함수로 들어오기전 push 했던 인자 값 중의 하나이다. eax+0x4에 주소를 확인해보면 0x80483c8의 주소를 가지고 있다.

gdb-peda$ x/30x 0x080483c8
0x80483c8: 0x0804a00c 0x00000107 0x0804a010 0x00000207
0x80483d8: 0x0804a014 0x00000307 0x0804a018 0x00000407
0x80483e8: 0x0804a01c 0x00000507 0x0804a020 0x00000607
0x80483f8: 0x0804a024 0x00000707 0x0804a028 0x00000807
0x8048408: 0x0804a02c 0x00000907 0x0804a030 0x00000a07
0x8048418: 0x0804a034 0x00000b07 0x0804a038 0x00000c07
0x8048428: 0x0804a03c 0x00000d07 0x0804a040 0x00000e07

확인해보면 위의 주소에 존재하는 값들은 JMPREL를 나타내고 있으며 JMPREL는 ELF32_REL 형식의 구조체들로 이루어져 있다.
8바이트 구조체 형식인 Elf32_Rel 구조체의 처음 4Byte는 0x80~으로 시작하는 값은 각 함수들의 GOT 주소가 되겠으며, 다음번에 나오는 4byte는 1byte는 재배치 타입, 두번째부터 네번째 byte가 DYNSYM이라는 테이블에서의 index를 나타낸다!
0x107~207이런 값들은 [INDEX][TYPE]이 되겠다.

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

Elf32_Rel => GOT의 주소와 재배치 정보들로 이루어져 있다

재비차타입 => 어떤 비트를 변화시키고, 그 값을 어떻게 계산할 것인지를 나타낸다.

typedef struct
650 {
651 Elf32_Addr r_offset; /* Address */
652 Elf32_Word r_info; /* Relocation type and symbol index */
653 } Elf32_Rel;

실제 elf.h 파일의 document를 참조해서 확인해보면 위와 같이 GOT의 주소를 나타내는 r_offset과, [index][type]을 나타내는 r_info 구조체의 정보들을 확인할 수 잇다!

gdb-peda$ x/2x 0x080483c8+0x20
0x80483e8: 0x0804a01c 0x00000507
gdb-peda$ x/x 0x0804a01c
0x804a01c <__libc_start_main@got.plt>: 0x080484b6

reloc_offset + JMPREL 구조체의 값을 add edx를 이용해 더했기 때문에 해당 값을 gdb로 까보면
gdb-peda$ x/2x 0x080483c8+0x20
0x80483e8: 0x0804a01c 0x00000507
gdb-peda$ x/x 0x0804a01c
0x804a01c <__libc_start_main@got.plt>: 0x080484b6

이렇게 출력된다는 점을 알 수 있다. 0x80483c8은 jmprel의 주소가 되겠고 0x20은 reloc_offset이 된다. 그리고 주소에 있는 값을 확인해보면 0x00000507이라는 값이 있는데 이 값은 index,type을 나눠서 index는 0x5이고 재배치타입은 0x7이 된다!

주소를 살펴보면 0x8048a01c에는 libc_start_main의 실제 __libc_start_main 함수의 GOT주소를 알 수가 있다!

root@osboxes:~/t/tip/rop/johns-shuffle# objdump -R server2

server2: file format elf32-i386

DYNAMIC RELOCATION RECORDS
OFFSET TYPE VALUE
08049ffc R_386_GLOB_DAT __gmon_start__
0804a00c R_386_JUMP_SLOT __stack_chk_fail
0804a010 R_386_JUMP_SLOT htons
0804a014 R_386_JUMP_SLOT accept
0804a018 R_386_JUMP_SLOT __gmon_start__
0804a01c R_386_JUMP_SLOT __libc_start_main
0804a020 R_386_JUMP_SLOT bind
0804a024 R_386_JUMP_SLOT memset
0804a028 R_386_JUMP_SLOT fork
0804a02c R_386_JUMP_SLOT htonl

실제 바이너리의 GOT 구조를 살펴보면 위와 같이 __libc_start_main의 GOT 주소 값이 0x804a01c라는 값이 존재하며 이는 우리가 위에서 봤던 reloc_offset + JMPREL를 더한 주소의 값이 GOT주소로 저장된다는 점을 알 수 있다!

이제 DYNSYM 테이블의 Index를 살펴볼 건데 DYNSYM 디렉토리는 위에서 설명한것과 같이
동적 심볼 테이블 즉 라이브러리 파일을 동적링킹할 때 사용하는 함수를 모아놓은 테이블 정보를 나타낸다.
Elf32_sym 구조체로 이루어져있다.

우선 elf32_sym 구조체를 살펴보면

00804 // Symbol table entries for ELF32.
00805 struct Elf32_Sym {
00806 Elf32_Word st_name; // Symbol name (index into string table)
00807 Elf32_Addr st_value; // Value or address associated with the symbol
00808 Elf32_Word st_size; // Size of the symbol
00809 unsigned char st_info; // Symbol's type and binding attributes
00810 unsigned char st_other; // Must be zero; reserved
00811 Elf32_Half st_shndx; // Which section (header table index) it's defined in

ELF Symbol Table의 구조체이며 총 6개의 필드를 가지고 있다.
가장 먼저 나오는 st_name은 함수 이름 위치의 Offset을 나타낸다.
그리고 살펴봐야 되는 점은 5번째 필드인 st_other인데 이 값은 3과 &연산을 해서 0이냐 아니냐를 가르는데,
이는 해당 함수가 로딩이 되어있는 상태 즉, 바이너리가 실행되고 한번도 해당 함수가 실행된적이 있는지 없는지를 확인하는겁니다. 이 점을 이용해서 PWN 문제를 풀 때도 소켓을 이용한다면 recv_plt를 호출해서 recv_plt 함수를 실행 시키고 recv_got의 실제 libc 라이브러리 내부의 recv함수의 로딩된 주소를 이용해 libc_base를 구하고 system 함수를 호출해서 /bin/sh를 호출하는 형태로 풀이해나가는 방식이 최근 트렌드인거 같습니다.

본론으로 돌아와서 st_other를 비교해서 만약 결과 값이 0이 아니라면 로딩된 함수로 바로 호출을 하지만 그게 아니라면 0x00&3은 0x00이 나오기 때문에 로딩되지 않은 함수임을 나타내고, 링커는 해당 함수를 호출하기 위해 또 다른 함수를 호출하는 구조로 돌아갑니다

root@osboxes:~/t/tip/rop/johns-shuffle# objdump -x server2 | grep dyn
4 .dynsym 00000100 080481cc 080481cc 000001cc 2**2
5 .dynstr 000000a2 080482cc 080482cc 000002cc 2**0
8 .rel.dyn 00000008 080483c0 080483c0 000003c0 2**2
20 .dynamic 000000e8 08049f14 08049f14 00000f14 2**2


080481cc l d .dynsym 00000000 .dynsym

gdb-peda$
0x804821c: 0x0000006d 0x00000000 0x00000000 0x00000012

gdb를 이용해 reloc_offset + jmprel 주소에 존재하는 got 주소에 인덱스와 재배치 타입이 존재했는데,
0x507이였다. 0x5는 dynsym에서 libc_start_main의 offset이 될 것이고, 0x7은 재배치 타입이 될 것이다
위의 0x804821c는 dynsym의 libc_start_main의 주소 값을 참조해보면 0x0000006d 0x00000000 0x0000000
0x00, 0x00, 0x0012 이런식으로 elf32_sym 구조체에서 4바이트 6개로 이루어져 있다는 점을 알 수 있다.

즉 0x6d와 5번째있는 0x6d는 구조체에서 알아본 0x6d-> 함수의 offset, 5번째 존재하는 0x00은 아직 로딩되지 않은 함수임을 알 수 잇다. 즉 libc_start_main 함수는 아직 실행되지 않았다는 점을 알 수 있다!!

gdb-peda$ x/s 0x080482cc+0x6d
0x8048339: "__libc_start_ma"...

즉 우리는 _dl_fixup_ 함수에서 strtab 테이블에서 +0x6d offset에 존재하는 함수의 이름이 위와 같이
__libc_start_main이라는 점을 알 수 있다. 즉 로딩되지 않았을 것이기 때문에 위와 같이 함수의 이름을 받아오고 0x00을 비교해서 로딩되지 않았다면 _dl_lookup_symbol_x 함수를 호출한다.

gdb-peda$ x/3i $eip
=> 0xf7fea181: add edi,DWORD PTR [edx+0x4]
0xf7fea184: movzx eax,BYTE PTR [edx+0xc]
0xf7fea188: and eax,0xf

그리고 추가적으로 _dl_lookup_symbol_x 함수를 호출하고 난 뒤 라이브러리 내부의 SYMTAB 주소와 라이버르리 시작 주소를 얻어온다. 그 얻어온 주소를 이용해서 SYMTAB에 존재하는 실제 함수들의 offset을 이용해 라이브러리에 함수를 로딩하는 역할을 하는 것이다.


0xf7fea17f: mov edi,DWORD PTR [esi]
=> 0xf7fea181: add edi,DWORD PTR [edx+0x4]
0xf7fea184: movzx eax,BYTE PTR [edx+0xc]


위의 gdb 화면은 _dl_lookup_symbol_x 함수가 끝나고 난 뒤의 symtab에 존재하는 offset을 이용해 실제 로딩 주소를 받아온 화면을 나타낸다. 우선 esi 주소를 edi에 옴ㄹ기고 edx+0x4에 있는 값을 edi에 더한다.
해당 값을 살펴보자

gdb-peda$ i r edi
edi 0xf7e10000 0xf7e10000
gdb-peda$ i r edx
edx 0xf7e1cc34 0xf7e1cc34
gdb-peda$ x/x 0xf7e1cc34+0x4
0xf7e1cc38: 0x000199e0
gdb-peda$ x/3i 0xf7e10000+0x000199e0
0xf7e299e0 <__libc_start_main>: push ebp
0xf7e299e1 <__libc_start_main+1>: push edi
0xf7e299e2 <__libc_start_main+2>: push esi

edi를 edx+0x4와 더하는데 더한 값을 살펴보면 0xf7e299e0라는 주소가 나타날 것이다. 즉 SYMTAB에 적혀있는 __libc_start_main의 offset을 실제 로딩된 라이브러리의 주소와 더했을 경우 __libc_start_main의 실제 로딩된 주소를 볼 수 있었다. 이런 방식으로 라이브러리는 링킹이된다는 사실을 알 수 가 있다!!!

해당 주소는 실제 GOT의 주소에 들어갈 것이고, PLT+6을 가리키던 값은 위와 같은 주소로 변경되고 위와 같은 링킹 작업을 하지않아도 될 것이다!

##################################라이브러리 주소 로딩전##############
gdb-peda$ p __libc_start_main
$1 = {<text variable, no debug info>} 0x80484b0 <__libc_start_main@plt>

##################################실제 라이브러리 주소 ###############
##################################라이브러리 주소 로딩후###############
gdb-peda$ p __libc_start_main
$1 = {<text variable, no debug info>} 0xf7e299e0 <__libc_start_main>

실제 프로그램을 메인 함수로 진입시키고 __Libc_start_main의 로딩된 주소를 살펴보면 링킹 과정을 거치기 전 후의 주소가 다르다는 것을 알 수 있다. 우리는 이 개념은 실제 pwn 공부를 하면서 정말 중요하다고 생각을 했기에 세부적으로 찾아봤는데, 위와 같은 과정을 거친다는 점을 알 수 있었다. 또한 이런 방식은 ASLR을 우회하는데 좀 더 세부적으로 공부를 해봐야할 것 같다.

Post Reply

Who is online

Users browsing this forum: No registered users and 1 guest