[컴][리눅스] 간단한 hooking - wrapper function 사용하는 법

다이나믹 링커 를 이용한 후킹 / hooking with dynamic linker /

linux 에서 hooking 을 해보자. 여기서 하는 내용은 application 에 호출하는 libc 같은 shared library 의 함수 대신에 자신의 wrapper function 을 사용하는 방법을 설명한다. 여기서 replace 하는 function 은 application 에서 호출하는 shared library 까지 이다. shared library 에서 호출하는 function 을 다른 function 으로 replace 할 수 있는 방법은 아니다


Overview

개략적인 내용을 얘기하자면,
user program >> library >> system call
의 루틴으로 프로그램이 실행되는데, hooking 을 하는 것은 이 루틴을 아래처럼 바꾸는 것이다.[ref. 1, figure 2]
user program >> hook >> library >> system call
이것은 dynamic linker 가 executable(실행파일) 이 실행된 이후에 symbol 을 resolve 한다는 점을 이용하는 것이다. 그러니까, 원래 undefined symbol 을 실행파일이 가지고 있는데, 이 녀석을 resolve 하면 보통 우리가 흔히 쓰는 shared library 의 함수를 불러오게 되는데, 이 때 우리가 만든 shared library 를 먼저 검색하게 해서 우리의 함수를 호출하게 하는 것이다.

dynamic library 들을 이용하는 program을 컴파일 하면 binary 에 아래 2개의 list가 포함된다.
  • 이 program 이 사용하는 library 의 list
  • undefined symbols 의 list
dynamic linker 가 단순히 그 library 들을 뒤지면서 그 symbol 을 가지고 있는 첫 library 를 이용하게 된다.[ref. 6]

그렇기 때문에 우리가 만약 우리의 wrapper function들을 가지고 있는 library 를 program이 호출하게 한다면, 프로그램의 undefined symbol 들은 우리의 wrapper function 으로 해석될 것이다.


아쉽게도, 이 방법으로는 internal library(libc 같은 library) 를 interpose 할 수 없다. 왜냐하면, internal library 의 함수는 runtime 이전에 symbol 이 resolved 되기 때문이다.[ref. 1]

구현과 관련해서는 ref. 1을 참고하는 것이 좀 더 쉬울 것이다.


Test program

이제, 간단한 malloc 을 사용하는 program 하나를 만들어 보자.
#include <malloc.h>
#include <stdlib.h>

int main(void)
{
    int *p = (int *)malloc(10);

    free(p); 
}

이 녀석의 compile 은 그냥 기존의 program 처럼 해 주면 된다.


$> gcc main.c -o app

이제 app 이 호출하는 malloc 이 우리가 만든 malloc 이 되도록 해보자.



LD_PRELOAD

프로그램이 실행되면 library 를 load 하는 것은 loader(여기서는 dynamic linker 가 될 것이다.) 가 담당하게 된다. 그러면 loader 가 어떻게 우리의 library 를 load 하게 만들 것인가? 여기에 사용되는 것이 LD_PRELOAD 라는 변수이다. LD_PRELOAD 에 정의된 값을 loader 가 가장 먼저 load 하게 되어 있다.

그런데 LD_PRELOAD 는 SUID permission bit 이 set 되어 있으면 무시된다. 왜냐하면, 이 방법으로 어떤 일을 할 지 모르기 때문에 보안상의 이유로 다른 user 나 group 이 이 방법을 사용하지 못하게 막는 것이다.

만약 app 에서 malloc() 을 호출한다고 하자. 보통 이 함수는 libc 에서 호출한다. 근데 우리가 이 malloc 에 대한 wrapper function 을 만들어서 우리의 malloc wrapper 를 기존의 malloc 대신에 호출하게 하고 싶다고 하자.

일단 우리의 malloc wrapper 를 만드는 것은 나중에 설명하고, malloc wrapper 를 가지고 있는 library 가 libmine.so 라고 하자. 이 경우에 아래처럼 실행하면 기존의 malloc 대신에 우리의 malloc wrapper 가 수행된다.
$> LD_PRELOAD=/home/libmine.so ./foo
만약 library 를 2개 이상 설정하고 싶다면, 아래처럼 실행하면 된다.
$> LD_PRELOAD=/home/libmine.so;/home/libmin2.so ./app


원래 함수 호출하는 법

wrapper function 의 original function 을 쓰려고 한다면 어떻게 해야 할까? 다시 말하면, malloc 의 wrapper 를 만들었는데, 내가 만든 wrapper 내부에서 system 의 malloc 을 호출(libc 의 malloc ) 하고 싶다면 어떻게 해야 할까?

원래 우리의 wrapper function 에서 바로 libc 의 malloc 을 부를 수는 없다. 왜냐하면 compiler 가 malloc 을 내 자신을 호출하는 줄 알기 때문이다.(재귀함수를 얘기하는 것이다.) 정확히 이야기 한다면, library 를 iterate 하면서 libmin.so 에서 malloc 을 찾아버리기 때문에, 더 이상 search 를 하지 않을 것이다.

우리는 그래서 다른 방법을 사용해야 한다. 이 방법은 처음 찾은 malloc 함수의 주소가 아니라, 2번째 찾은 malloc 함수의 주소를 가져와서 우리가 그 주소로 malloc 을 호출하는 것이다. 이 때 사용하는 것이 dlsym() 이다.
dlsym(RTLD_NEXT, "malloc");

dlsym()


  • dlsym : dynamic linker symbol lookup function
  • return : symbol(심볼) 의 address 를 return 해준다.

RTLD_NEXT option 에 의해, 이러면 malloc 을 제공하는 library 중에 2번째 library 를 택하게 된다.

RTLD_NEXT 는 GNU 에서 제공하는 녀석이어서
#define _GNU_SOURCE
가 필요하다.[ref. 2]

그러면 이제 wrapper 함수를 가지고 있는 libmine.so 를 만들어 보자.



libmine.so 만들기

#include <dlfcn.h>
void* malloc(size_t size){
 ...
 static void* (*my_malloc)(size_t) = NULL;
 ...
 my_malloc = dlsym(RTLD_NEXT, "malloc");
 ...
}

자세한 코드는 ref. 2 에 가면 볼 수 있다. 여기서는 대략적인 설명만 하도록 하자.

이 code 를 shared object 로 만들기 위해  ref.2 에서는 아래처럼 compile 을 하면 된다.
gcc -shared -fPIC -ldl  libmine.c -o libmine.so
-shared -fPIC -l 옵션에 관련해서는 ref. 3 을 참고 하자.

근데 이 상황에서는
symbol lookup error : … undefined symbol: dlsym
이 발생한다.



그런데, ref. 4 에 따르면 Ubuntu 11.10 부터 ld 의 기본적인 동작이 바뀌었다고 한다. 그래서 command 를 아래와 같이 주어야 한다.
gcc -shared -Wl,--no-as-needed -ldl -fPIC libmine.c -o libmine.so
-Wl,--no-as-needed 는 linker 에 --no-as-needed 옵션을 주는 gcc 옵션이다.[ref.3]


--no-as-needed

--no-as-needed 는 --as-needed 를 원상복귀 시키는 option 인데, --as-needed 동작은 아래와 같다.

--as-needed 뒤에 dynamic library 이름을 적게 되는데, 이 command line 에 적힌 dynamic liabrary 들에 대한 ELF DT_NEEDED tag 들에 영향을 준다.[ref. 5]

linker 는 일반적으로 library 가 실제로 필요한지 여부를 떠나서 command line 에 적혀있는 dynamic library 에 대한 DT_NEEDED tag 를 더하게 된다.[ref. 5]

근데 --as-needed 를 설정하면 동작이 바뀐다. "일반적인 object file 에 있는 undefined symbol reference" 또는 "다른 dynamic library 에 있는 undefined symbol reference" 를 만족하는 library 에 대해서만 DT_NEEDED tag 가 방출된다.[ref. 5]

다시 말하면, 실제로 필요한 라이브러리들이 무엇인지 파악해서 그녀석들만 DT_NEEDED 에 넣는다. -lm option등을 이용해서 추가로 library 를 넣어도 실제로 쓰이지 않으면 DT_NEEDED 에 넣지 않는다.[ref. 9]


DT_NEEDED tag 는 DT_NEEDED 영역에 쓰여있는 entry 라고 보면 될 듯 하다. DT_NEEDED tag 에 dynamic linker 가 사용할 shared object(shared library 같은) 를 정의해 놓는다.[ref. 8]


DT_NEEDED

linux 에서 dynamic linking 을 지원하기 위해 ELF 에 DT_NEEDED section 을 둔다. 그래서 executable 이 실행될 때 비로서 dynamic linker 가 DT_NEEDED 에서 필요한 library 들을 보고 load 하고 이 library 를 뒤지면서, undefined symbol 을 resolve 하게 된다. 이런 작업은 어느 symbol 이 어디에 위치하고 있는지를 알려주지 않는다. 그래서 필요한 symbol 을 찾기 위해 전부 뒤져봐야 하기 때문에 시간소모가 많다.[ref. 6, 7]

그래서 이 문제에 대한 해결책으로 Solaris 에서 Dynamic binding 을 제공한다. Dynamic binding 에서는 ELF 내에 section 하나를 만들고, 여기에 "pointer 들의 list"를 저장해 놓는다. 이 list 의 pointer 하나가  DT_NEEDED entry 를 가리키게 된다. 각각의 pointer 는 object 의 symbol 에 대응된다. 그렇게 symbol 과 DT_NEEDED entry 사이에 관계를 만들게 된다.[ref. 6]


References

  1. Tutorial: Function Interposition in Linux
  2. http://www.linuxforu.com/2011/08/lets-hook-a-library-function/
  3. http://gcc.gnu.org/onlinedocs/gcc/Link-Options.html
  4. gcc: symbol lookup error: ...libgendep.so: undefined symbol: dlsym
  5. http://www.kpitgnutools.com/manuals/ld.html
  6. http://en.wikipedia.org/wiki/Direct_binding
  7. http://docs.oracle.com/cd/E19957-01/806-0641/6j9vuqujs/index.html#chapter6-63352
  8. http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0458c/Beijedib.html
  9. http://www.bnikolic.co.uk/blog/gnu-ld-as-needed.html

댓글 없음:

댓글 쓰기