LD_PRELOAD는 ELF 공유 객체를 다른 라이브러리보다 먼저 로드하도록 설정할 수 있는 환경 변수이다. 이를 이용하면 동적 링킹된 파일이 실행될 때, 다른 라이브러리가 로드되기 전에 공격자가 원하는 라이브러리를 로드하여 원하는 함수를 바꿔버릴 수가 있다. 이러한 공격 기법을 LD_PRELOAD hooking이라고 한다.
//test.c
#include <stdio.h>
int main(){
puts("Hello world");
return 0;
}
먼저, 위와 같이 'Hello world'라는 문자열을 출력하는 프로그램이 존재한다고 치자. 이 때, 문자열을 출력할 때 사용하는 puts 함수를 필자가 원하는 다른 함수로 바꿔치기가 가능하다는 뜻이다.
//preload.c
//gcc preload.c -o preload.so -fPIC -shared -ldl
#include <stdio.h>
int puts(const char *str) {
printf("PRELOAD");
return 0;
}
따라서 위와 같이 puts라는 동일한 이름을 가진 함수를 다른 파일에 정의해주고, 이를 공유 라이브러리로 컴파일하면 후킹 준비가 다 된 것이다. (preload.c에서 puts를 정의할 때, 인자는 원본과 동일하게 맞추어 주어야 한다.)
$ ./test
Hello world
$ LD_PRELOAD="./preload.so" ./test
PRELOAD
이제 'preload.so' 파일을 LD_PRELOAD로 가장 먼저 로드 되게 설정해 준다면 위와 같이 바뀐 결과를 확인할 수 있다.
//test2.c
#include <sys/ptrace.h>
#include <stdio.h>
#include <stdlib.h>
int main() {
if (ptrace(PTRACE_TRACEME, 0, NULL, NULL) == -1) {
printf("디버거 발견!\n");
exit(1);
} else {
printf("정상 작동 중~\n");
}
return 0;
}
이 기법을 사용하면 전에 공부했던 안티 디버깅 기법 또한 우회가 가능하다. 위는 ptrace 함수를 사용하여 디버거를 감지하는 코드이다.
//preload2.c
//gcc preload2.c -o preload2.so -fPIC -shared -ldl
#include <sys/types.h>
long ptrace(int request, pid_t pid, void *addr, void *data) {
return 0;
}
위와 같이 ptrace 함수가 무조건 0을 반환하도록 정의하는 라이브러리 파일을 생성해 준다.
gef➤ r
Starting program: /home/pdh/test2
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
디버거 발견!
[Inferior 1 (process 38805) exited with code 01]
gef➤ set environment LD_PRELOAD ./preload2.so
gef➤ r
Starting program: /home/pdh/test2
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
정상 작동 중~
[Inferior 1 (process 38808) exited normally]
그 다음 디버거로 열어보면 원래는 '디버거 발견!' 이라는 문자열이 뜨지만, 'preload.so' 파일을 LD_PRELOAD로 먼저 로드한 뒤 실행해 주면 '정상 작동 중~' 문자열이 출력되는 것을 볼 수 있다.