라이브러리
- '함수들'을 기계어로 변환 후, 파일 하나로 저장해 놓은 것(마치 바이너리 처럼..)
- 실행파일이 아니기 때문에, main 함수가 필요 없다.
.c
파일에서 이 기능이 필요할 때 같이 링크해서 쓸 수 있다.
libc
/lib/x86_64-linux-gnu/libc.so.6
에 존재
표준 라이브러리 경로
$ ld --verbose | grep SEARCH_DIR | tr -s ' ;' '\n'
SEARCH_DIR("=/usr/local/lib/x86_64-linux-gnu")
SEARCH_DIR("=/lib/x86_64-linux-gnu")
SEARCH_DIR("=/usr/lib/x86_64-linux-gnu")
SEARCH_DIR("=/usr/lib/x86_64-linux-gnu64")
SEARCH_DIR("=/usr/local/lib64")
SEARCH_DIR("=/lib64")
SEARCH_DIR("=/usr/lib64")
SEARCH_DIR("=/usr/local/lib")
SEARCH_DIR("=/lib")
SEARCH_DIR("=/usr/lib")
SEARCH_DIR("=/usr/x86_64-linux-gnu/lib64")
SEARCH_DIR("=/usr/x86_64-linux-gnu/lib")
static 라이브러리
- static 라이브러리와 링크하는 것을 정적 링킹
- 라이브러리 안에 있는 '기계어'를 최종 실행 파일에 가져다 복사한다.
- dynamic 링킹에 비해
- 실행 파일의 크기가 커진다.
- 메모리를 더 잡아먹을 수 있다.
- 실행 속도가 빠르다.
- 컴파일 옵션에 따라,
include
한 헤더의 함수가 모두 포함될 수도 있고, 아닐 수도 있다.
dynamic 라이브러리
- dynamic라이브러리와 링크하는 것을 동적 링킹
- 실행 파일에 여전히 구멍이 있음
- 실행 파일이 '실제로 실행할 때' 링킹이 일어난다.
- static 링킹에 비해
- 실행파일 크기가 작다.
- 여러 실행파일이 동일한 라이브러리를 공유할 수 있다. = 메모리 절약
- 여러 실행파일이 각각 다른 dynamic 라이브러리를 사용하면 dll 지옥
static, dynamic 간단비교
간단비교 1.
#include <stdio.h>
int main() {
puts("Hello, world!");
return 0;
}
$ gcc -o static hello-world.c -static
$ gcc -o dynamic hello-world.c -no-pie
main:
push rbp
mov rbp,rsp
lea rax,[rip+0x96880] # 0x498004
mov rdi,rax
call 0x40c140 <puts> # puts를 0x40c140에서 직접 호출
mov eax,0x0
pop rbp
ret
main:
push rbp
mov rbp,rsp
lea rdi,[rip+0xebf] # 0x402004
mov rdi,rax
call 0x401040 <puts@plt> # puts의 plt주소인 0x401040 을 호출한다.
mov eax,0x0
pop rbp
ret
PLT, GOT
- PLT, GOT: 라이브러리에서 동적 링크된 심볼의 주소를 찾을 때 사용하는 테이블
- 바이너리가 실행시, ASLR에 의해 라이브러리가 임의의 주소에 매핑된다.
- 이 상태에서, 라이브러리 함수를 호출하면, 함수의 이름을 바탕으로 라이브러리에서 심볼들을 탐색
해당 함수의 정의를 발견하면 그 주소로 실행 흐름을 옮긴다.
- 이 과정을 runtime resolve
- 만약 반복적으로 호출되는 함수의 정의를 매번 탐색해야한다면 비효율적이다.
그래서 ELF는 GOT라는 테이블을 두고, resolve된 함수의 주소를 해당 테이블에 저장한다.
그리고, 나중에 다시 해당 함수를 호출하면 저장된 주소를 꺼내서 사용한다.
#include <stdio.h>
int main() {
puts("Resolving address of 'puts'.");
puts("Get address from GOT");
}
$ gdb ./got
pwndbg> entry
pwndbg> got
got
을 실행 직후, GOT의 상태를 보여주는 명령인 got
을 사용
puts
의 GOT 엔트리인 0x404018
에는 아직 puts
의 주소를 찾기 전이므로, 함수 주소 대신 .plt
섹션
어딘가의 주소인 0x401030
이 적혀있다.
- 이제
main()
에서 puts@plt
를 호출하는 지점에 중단점 설정
pwndbg> c
pwndbg> si
- 좀있다
_dl_runtime_resolve_xsavec
함수가 실행되는데, 이 함수에서 puts
의 주소가 구해지고, GOT 엔트리에 주소를 쓴다.
실제로 ni
명령어를 반복적으로 수행해서 _dl_runtime_resolve_xsave
안으로 진입한 후, finish
명령어로 함수를 빠져나오면, puts
의 GOT 엔트리에 libc 영역내 실제 puts
주소인 0x7ffff7e02ed0
가 쓰여 있는 모습을 확인할 수 있다.
pwndbg> ni
...
pwndbg> ni
_dl_runtime_resolve_fxsave () at ../sysdeps/x86_64/dl-trampoline.h:67
67 ../sysdeps/x86_64/dl-trampoline.h: No such file or directory.
...
──────────────────────[ DISASM / x86-64 / set emulate on ]──────────────────────
0x401030 endbr64
0x401034 push 0
0x401039 bnd jmp 0x401020 <0x401020>
↓
0x401020 push qword ptr [rip + 0x2fe2] <_GLOBAL_OFFSET_TABLE_+8>
0x401026 bnd jmp qword ptr [rip + 0x2fe3] <_dl_runtime_resolve_fxsave>
↓
► 0x7ffff7fd8be0 <_dl_runtime_resolve_fxsave> endbr64
0x7ffff7fd8be4 <_dl_runtime_resolve_fxsave+4> push rbx
0x7ffff7fd8be5 <_dl_runtime_resolve_fxsave+5> mov rbx, rsp
0x7ffff7fd8be8 <_dl_runtime_resolve_fxsave+8> and rsp, 0xfffffffffffffff0
0x7ffff7fd8bec <_dl_runtime_resolve_fxsave+12> sub rsp, 0x240
0x7ffff7fd8bf3 <_dl_runtime_resolve_fxsave+19> mov qword ptr [rsp], rax
...
pwndbg> finish
Run till exit from
Resolving address of 'puts'.
...
──────────────────────[ DISASM / x86-64 / set emulate on ]──────────────────────
0x401148 <main+18> call puts@plt <puts@plt>
► 0x40114d <main+23> lea rax, [rip + 0xecd]
0x401154 <main+30> mov rdi, rax
0x401157 <main+33> call puts@plt <puts@plt>
0x40115c <main+38> mov eax, 0
0x401161 <main+43> pop rbp
0x401162 <main+44> ret
0x401163 add bl, dh
...
pwndbg> got
GOT protection: Partial RELRO | GOT functions: 1
[0x404018] puts@GLIBC_2.2.5 -> 0x7ffff7e02ed0 (puts) ◂— endbr64
pwndbg> vmmap 0x7ffff7e02ed0
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
Start End Perm Size Offset File
0x7ffff7daa000 0x7ffff7f3f000 r-xp 195000 28000 /usr/lib/x86_64-linux-gnu/libc.so.6 +0x58ed0
- resolve된 후
puts@plt
를 두 번째로 호출할 때는 puts
의 GOT엔트리에 실제 puts
의 주소인 0x7ffff7e02ed0
가 쓰여있어서 바로 puts
가 실행된다.
해킹의 관점에서 본 PLT와 GOT
- PLT, GOT은 동적 링크된 바이너리에서 라이브러리 함수의 주소를 찾고,
기록할 때 사용되는 중요한 테이블
- 그런데, PLT에서 GOT을 참조하여 실행흐름을 옮길 때, GOT의 값을 검증하지 않는 보안 약점있음
- 위의 예에서,
puts
의 GOT 엔트리에 저장된 값을 공격자가 임의로 변경할 수 있다면,
puts
가 호출될 때 공격자가 원하는 코드가 실행되게 할 수 있다.
- GOT 엔트리에 저장된 값을 임의로 변조할 수 있는 수단이 있음을 가정하고, 이 공격 기법이 가능한지
gdb로 간단하게 실험해 볼 수 있다.
got
바이너리에서 main()
내 두 번째 puts()
호출 직전에 puts
의 GOT 엔트리를 "AAAAAAAA"로 변경 한 후,
실행 시키면, 실제로 "AAAAAAAA"로 실행 흐름이 옮겨지는 것을 확인할 수 있다.
- 이런 기법을 GOT Overwrite