엄범


`` LD_PRELOAD``는 prefix로 `` LD_``가 붙은, ld.so에 속하는 환경변수로,

windows의 `` AppInit_Dlls`` 레지스트리와 비슷한 역할을 한다.

`` LD_PRELOAD``에 설정된 shared object는 libc를 비롯한 다른 모든 shared object보다 먼저 로딩된다.

`` LD_PRELOAD``에 설정된 shared library의 함수 중에 이후 로딩된 libc의 함수 이름과 동일한 함수가 있다면 먼저 로딩된( = `` LD_PRELOAD``에 설정된) shared library의 함수를 호출하게 된다. 그래서 결과적으로 보면 자동으로 후킹을 수행하는 것과 같다.


    • 타인 소유 파일에도 동작한다.
    • secure-execution mode로 실행되면 제약이 걸린다.
    • overloading을 따지지 않는다. 즉 파라미터, 리턴 타입 상관 없이 이름만 일치하면 후킹한다.
    • main은 후킹이 안된다
    • `` LD_PRELOAD``에 등록할 library에 대해 읽기 권한이 있어야 한다.
    • `` LD_PRELOAD``에 등록한 library가 위치한 path가 `` LD_LIBRARY_PATH``나 `` /etc/ld.so.conf``에 등록되어 있을 필요는 없다.
    • `` LD_LIBRARY_PATH``나 `` /etc/ld.so.conf``에 등록된 디렉토리에 위치한 library일 경우 library 파일 이름만 적어도 인식한다.

    <library path에 위치한 library일 경우 그냥 filename만 적어도 인식한다.>

    <실행하려는 파일이 타인 소유 파일이고, setUID가 걸려있는 경우(secure-execution mode) library를 찾을 수 없을 때의 오류메시지와 동일한 오류가 나온다.>

    Usage

    특정 바이너리 실행시만 환경변수로 지정해 바로 사용할 경우 :

    ``bash LD_PRELOAD=pathname ./binary``


    쉘에 등록한 다음 사용할 경우 ( 어떤 바이너리든 실행시 마다 LD_PRELOAD 동작 ) :

    ``bash export LD_PRELOAD=pathname``


    /etc/ld.so.preload에 삽입하는 경우

    없으면 만들어주면 된다. 계정 상관 없이 시스템 전역 적으로 후킹된다.

    명령어에도 적용된다.

    이를 이용해 실행할 바이너리가 `` LD_PRELOAD``로 넘긴 library를 로딩하는지 확인할 수 있다.
    ``bash LD_PRELOAD=pathname ldd ./binary``


    * 명령어도 `` /bin``같은 command path에 위치한 파일이기 때문에, `` ldd``에도 `` LD_PRELOAD``가 적용되어 library를 로딩하게 된다. `` constructor`` 등이 있는 경우 `` ldd``에서 `` constructor``가 실행된다. binary에 setUID가 걸려있어도 명령어에 적용된 `` constructor``는 실행되므로 이를 보고 setUID 걸려있어도 `` LD_PRELOAD``가 적용된다고 생각해서는 안된다.

    * binary를 `` fork`` 등으로 실행하게 되는 명령어라면 명령어 자체와 `` fork``로 실행된 자식프로세스 모두가 library를 로딩한다.


    hooking

    `` LD_PRELOAD``로 프로세스 실행 시 내가 원하는 library를 로딩하도록 할 수는 있는데, library를 로딩하는 것과 library 내의 함수를 호출하는 것은 별개다. 

    프로세스가 내가 원하는 library 내의 함수를 호출하도록 하려면, GCC attribute로 library를 로딩하면서 자동으로 호출하는 함수를 만들거나, 프로세스가 원래 호출하는 함수를 hooking해야한다.


    GCC attribute

    2017/01/13 - [System/LINUX & UNIX] - 생성자, 소멸자 / GCC __attribute__((constructor / destructor))


    hooking은 다음과 같이 진행한다.

    1. `` nm -D``로 심볼을 출력해 해당 프로그램이 사용하는 함수 이름을 얻는다.
    2. user_library에 hooking 타겟 함수 이름과 동일한 함수를 만든다.
    3. `` LD_PRELOAD``로 대상 프로세스가 library를 로딩하도록 하면, 대상 프로세스가 hooking 타겟 함수를 호출할 때 마다 user_library의 함수가 대신 호출된다.
    4. 추가) user_library의 함수(wrapper function)가 실행된 다음 원래 함수(hooking 타겟 함수)가 호출되어야 하는 경우, ``c dlsym(RTLD_NEXT, ...)``를 사용한다.

    * 프로그램 실행 시 기본적으로 사용되는 libc를 hooking 할 경우 4번 과정을 수행하지 않으면 제대로 동작하지 않는 경우가 대부분이다.

    * hooking 함수에서 ``c dlsym(RTLD_NEXT,... )``을 이용해 원래 함수 포인터를 얻은 뒤 ``c return orig_func`` 하면 된다.

       ( `` RTLD_NEXT``를 주면 symbol과 일치하는 다음 함수의 포인터를 반환한다. )

    * ``c dlsym()``을 이용하기 위해 ``c #include <dlfcn.h>``해야 하며 library를 컴파일할 때 `` -ldl`` 옵션을 줘야 한다.

    * `` RTLD_NEXT``는 `` dlfcn.h``에 있는게 아니라 ``c #define _GNU_SOURCE`` 해야 정의된다. 


    e.g.

    ``c getchar()``를 hooking해서 wrapping function이 동작하도록 했다.

    wrapping function은 ``c "hooked getchar!"`` 문자열을 출력한 다음 original function을 호출한다.


    libhook.so :

    ```c
    #include <dlfcn.h>
    #define _GNU_SOURCE
     
    typedef int (*orig_getchar_ftype)(void);
     
    int getchar(){
        orig_getchar_ftype orig_getchar;
        orig_getchar = (orig_getchar_ftype)dlsym(RTLD_NEXT, "getchar");
        puts("hooked getchar!");
        return orig_getchar();
    }
    ```

    tes :

    ```c
    #include <stdio.h>
     
    int main(){
        getchar();
        return 0;
    }
    ```


    LD_PRELOAD를 이용해 자식 프로세스를 생성하는 생성자, 소멸자를 포함하는 .so를 로딩하는 경우?

    일반적으로 사용할 경우 `` constructor`` 안에 `` system``이나 `` fork``가 있어도 전혀 문제가 되지 않는다.

    자식 프로세스를 만들면서 현재 프로세스의 상태를 복사하기 때문에 `` constructor`` 내부의 `` system``이나 `` fork``가 다시 실행되지 않기 때문.

    그러나 환경변수는 parent의 환경변수를 child가 물려 받으면서 재적용 되는 것 같다. `` LD_PRELOAD``가 자식 프로세스에서도 동작한다.


    그래서 `` LD_PRELOAD``를 사용해서 library를 로딩할 때, constructor 또는 destructor로 지정한 함수 내에 system 혹은 fork 등이 있을 경우 자식으로 생성된 프로세스도 `` LD_PRELOAD``가 적용되어 이 library를 로딩하게 되므로 연쇄적으로 `` system`` 혹은 `` fork``를 호출하게 된다.

    `` exec`` 계열의 경우는 동작하지 않는 것 같다.


    위 경우는 로딩할 library 내의 `` constructor``가 `` system, fork`` 하게 되는 경우로, 실행할 프로세스가 system, fork하는 경우는 상관없다. 이 경우 당연히 연쇄적인 child 생성은 일어나지 않지만, 자식 프로세스도 `` LD_PRELOAD``가 적용되기 때문에, 이 library를 로딩하게 되어 `` constructor`` 또는 `` destructor``를 호출하게 된다는 점은 유의.


    참고

    http://optumsoft.com/dangers-of-using-dlsym-with-rtld_next/


    * ``c malloc(), free()``를 wapping하면서 ``c dlsym(RTLD_NEXT, ...)``를 사용할 때 주의해야 할 점. 

    `` RTLD_NEXT``는 순서대로 NEXT 함수를 반환하므로 wapper function이 있는 `` .so``보다 `` libc.so``가 뒤에 있어야 `` RTLD_NEXT``로 `` libc.so``의 함수를 반환받을 수 있다.(`` ldd``로 확인 가능) 만약 wapper function의 `` .so``보다 `` libc.so``가 앞에 있다면, `` libc.so``의 원래 ``c malloc()``과 ``c func()``를 건너 뛰고 `` ld-linux...so``의 ``c malloc()``과 ``c free()``를 반환하게 된다. 이 경우 `` ld-linux...so`` 자신이 또 다른 ``c malloc(), free()``를 가지고 있기 때문에 memory leak이 발생할 수 있다.


    'Security > Reversing & Dbg' 카테고리의 다른 글

    gdb peda / gdb-multiarch  (0) 2017.07.13
    gdb  (0) 2017.07.12
    LD_PRELOAD를 이용한 so injection과 hooking. + wrapping function  (2) 2016.12.19
    ptrace - Linux injection ( code injection / so injection )  (0) 2016.12.12
    jmp, call instruction 주소 계산  (2) 2016.11.15
    Anti debugging  (0) 2016.08.31