미친해커

[Reversing] LD_PRELOAD를 이용한 후킹 본문

Reversing/Hooking

[Reversing] LD_PRELOAD를 이용한 후킹

미친해커 2024. 5. 3. 00:19
반응형

LD_PRELOAD

LD_PRELOAD는 Linux 운영체제의 환경 변수이다.

 

LD_PRELOAD에는 Linux 운영체제의 공유 라이브러리(Shared Library) 파일이 설정되고 설정된 공유 라이브러리들을 모든 공유 라이브러리들보다 먼저 프로그램에 로드한다.

 

Linux 운영체제의 응용 프로그램은 라이브러리가 로드된 순서대로 사용하려는 함수가 존재하는지 확인한다. 이때 LD_PRELOAD로 인해 먼저 로드된 라이브러리에 사용하려는 함수가 존재한다면 LD_PRELOAD의 라이브러리의 함수 주소를 가져온다. 이러한 원리를 이용한 방법이 LD_PRELOAD를 이용한 후킹 기법이다,

*공유 라이브러리(Shared Libary) : Linux 운영체제의 공유 라이브러리로 확장자는 주로 .so 이며 Windows 운영체제의 DLL 파일과 같다.

LD_PRELOAD Environment Variable

Linux 운영체제에서 LD_PRELOAD 환경 변수를 설정하는 방법은 여러가지인데, 그중 자주 사용되는 두 가지 방법이다.

Inline

첫 번째 방법은 특정 프로그램에만 설정하는 방법이다. 이렇게 Shell에서 프로그램을 실행시킬 때 앞에 LD_PRELOAD를 설정해주면 해당 프로그램에만 적용해 실행할 수 있다.

root@ubuntu:~# LD_PRELOAD=/tmp/libtest.so ./example

In the current Shell

두 번째 방법은 현재 쉘에서 실행되는 모든 프로그램에 LD_PRELOAD를 설정하는 방법이다. 다음과 같이 export 명령어를 사용해 Shell에 환경 변수를 설정하면 이후 해당 쉘에서 실행되는 모든 프로그램에 LD_PRELOAD가 설정되어 실행된다.

root@ubuntu:~# export LD_PRELOAD=/tmp/libtest.so
root@ubuntu:~# ./example

Make a shared library to be used for LD_PRELOAD

Linux 운영체제에서 LD_PRELOAD를 사용하는 후킹은 그렇게 어렵지 않다. 기본적으로 함수명만 동일하다면 LD_PRELOAD를 사용해 후킹을 진행할 수 있다. 반환형, 인자 등 함수명을 제외한 그 무엇도 신경쓰지 않지만 정상적으로 인자를 받고 후킹을 진행하기 위해서는 해당 함수의 원형을 사용하는 것이 좋다.

 

다음과 같은 코드의 프로그램에 LD_PRELOAD를 적용한다고 했을 때의 공유 라이브러리를 만들어보겠다.

#include <stdio.h>
#include <string.h>

int main(int argc, char *argv[])
{
    char key[] = "This is secret key";
    char buf[64] = { 0 };

    strcpy(buf, key);

    return 0;
}

위 프로그램 비밀키를 buf로 strcpy 함수를 이용해 복사한다. 이때 strcpy 함수를 후킹해 비밀키의 내용을 출력해보려고 한다.

Check prototype of the function to hooking

먼저 strcpy 함수의 원형을 알아보자. 리눅스 매뉴얼에 따르면 strcpy 함수의 원형을 다음과 같다.

 

strcpy(3) - Linux manual page

strcpy(3) — Linux manual page strcpy(3) Library Functions Manual strcpy(3) NAME         top stpcpy, strcpy, strcat - copy or catenate a string LIBRARY         top Standard C library (libc, -lc) SYNOPSIS         top #include char *stpcpy(char

man7.org

char *stpcpy(char *restrict dst, const char *restrict src);

위 내용을 통해 후킹할 함수를 정의했다. 내용은 src 인자로 들어온 문자열을 출력하는 것이다.

#include <stdio.h>

char *strcpy(char *dst, const char *src)
{
    printf("Data : %s", src);
    return dst;
}

하지만 한가지 문제가 존재하는데 후킹한 함수가 정상적으로 해당 함수의 역할을 수행해야할 필요가 있다. 그렇기 때문에 원본 함수의 주소를 알아내는 방법을 알아야 한다.

How to get address of original function

Linux 운영체제에서는 dlsym 함수를 통해 원본 함수의 주소를 구할 수 있다.

 

dlsym(3) - Linux manual page

dlsym(3) — Linux manual page dlsym(3) Library Functions Manual dlsym(3) NAME         top dlsym, dlvsym - obtain address of a symbol in a shared object or executable LIBRARY         top Dynamic linking library (libdl, -ldl) SYNOPSIS         to

man7.org

void *dlsym(void *restrict handle, const char *restrict symbol);

 

해당 함수를 사용하기 위해서는 dlfcn.h 헤더 파일이 필요하고 _GNU_SOURCE 상수가 정의되어 있어야한다.

#define _GNU_SOURCE
#include <dlfcn.h>

handle에는 함수를 검색할 라이브러리가 들어가야 하지만 특정 라이브러리에서 함수 주소를 구하는 것이 아니라면 RTLD_NEXT를 전달하면 되고 symbol에는 함수명을 전달하면 된다.

 

RTLD_NEXT는 현재 라이브러리 다음으로 로드된 라이브러리를 의미하기 때문에 다음 라이브러리부터 차례대로 해당 함수가 존재하는지 라이브러리에서 확인해 주소를 구하게 된다. 

#define _GNU_SOURCE
#include <stdio.h>
#include <dlfcn.h>

char *(*origin_strcpy)(char *dst, const char *src);

char *strcpy(char *dst, const char *src)
{
    if (origin_strcpy == NULL)
    {
        origin_strcpy = dlsym(RTLD_NEXT, "strcpy");
    }
    printf("Data : %s", src);

    return origin_strcpy(dst, src);
}

위 소스코드를 리눅스에서 컴파일러를 이용해 컴파일 한 후에 LD_PRELOAD를 이용해 프로그램을 실행하면 다음과 같은 결과를 볼 수 있다.

root@ubuntu:/tmp# LD_PRELOAD=./libtest.so ./main
Data : This is secret key

원래는 비밀키를 복사한 후에 아무런 반응 없이 종료되어야할 프로그램이 LD_PRELOAD에 설정된 라이브러리로 인해 인자로 들어온 비밀키를 출력하는 모습을 볼 수 있다.

 

이렇게 LD_PRELOAD를 이용한 후킹을 진행해보았다. Linux 운영체제는 Windows 운영체제에 비해 후킹이 비교적 간단했으며 이런 기능이 운영체제 자체적으로 존재한다는 것이 신기했다.

반응형
Comments