API 후킹이라는 말을 많이 들어보셨을겁니다.
어떤 프로그램이 사용하는 API를 실행중에 가로채는 것을 말하는데요,
가로채서 그 API가 동작 못하게 할 수도 있고, 다른 동작을 수행하게 바꿀수도 있죠. 런타임에!
예를 들어, hostname 명령은 gethostname() 함수를 사용해서 현재 시스템의 호스트네임을 가져오고 있습니다.
제 노트북의 호스트네임이 출력이 되네요.
이제 API 후킹을 해볼까요?
런타임에 gethostname() 함수를 가로채서 다른 호스트네임을 출력하도록 바꿔보겠습니다.
gethostname() 함수는 libc에 정의되어 있습니다.
그렇다면 hostname 명령이 libc의 gethostname() 을 호출할 때, 새로 정의한 gethostname()이 호출되도록 해줘야 원하는 동작이 수행이 되겠네요.
hostname 명령은 libnsl.so 와 libc.so 에 의존하는 프로그램입니다.
gethostname()은 libc 에 정의되어 있습니다.
hostname 명령이 로더에 의해 로딩 될때 의존성을 가지는 libnsl, libc 라이브러리가 동적로딩이 될것인데,
그렇다면 어떻게 libc의 gethostname()이 아닌 다른 gethostname() 함수가 호출되게 할 수 있을까요?
우선 간단한 gethostname()을 작성해 봅시다.
이 소스를 이용해 gethostname.so 라이브러리를 생성합니다.
그리고 LD_PRELOAD 환경변수에 gethostname.so를 설정하고 hostname 명령을 실행 합니다.
와우~!! 어떻게 이게 가능한 걸까요?
strace 를 통해 hostname을 검사해 봅시다.
hostname 명령이 의존하는 라이브러리도 오픈하는게 보이네요.
open("/lib/i386-linux-gnu/libnsl.so.1", O_RDONLY) = 3
open("/lib/i386-linux-gnu/libc.so.6", O_RDONLY) = 3
앗 자세히 보니 preload 가 눈에 들어옵니다!!
access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory)
그런데 파일이 존재하지 않는군요.
여기서 잠깐, LD_PRELOAD 와 /etc/ld.so.preload 는 무슨 차이가 있을까요.
loader의 source code를 보면 두가지에 대해 알 수 있습니다.
| | | | |
elf/rtld.c |
|
로더는 LD_PRELOAD에 설정된 라이브러리를 먼저읽고, 그다음 /etc/ld.so.preload 파일에 기록된 라이브러리를 읽는 것을 알 수 있습니다.
다시 돌아와서, 다른 라이브러리가 open 되지 않는걸 보니,
hostname은 libc 내 정의된 gethostname() 을 이용하고 있는 것 같습니다.
그렇다면! LD_PRELOAD 환경변수를 설정한뒤 실행한 hostname은 어떻게 다를지 궁금하네요.
아하~ 다른 라이브러리보다 gethostname.so 라이브러리를 제일 먼저 읽네요.
그래서(?) libc.so 가 아닌 gethostname.so 내 gethostname()이 호출될꺼라고 얼렁뚱땅 넘어가게되면 안되겠죠? ㅎㅎ
라이브러리 오픈한 뒤, read, fstat, mmap2 를 통해 라이브러리를 프로세스의 Address space에 매핑을 하는것 같습니다.
gethostname.so, libc.so 모두 로더에 의해 로딩이되는데, 어떻게 gethostname.so의 gethostname() 함수가 실행이 되는 걸까요?
어렵네요... 우선 LD_PRELOAD 에 대해 알아보았으니 다음 시간에 이 문제에 대해 정리를 해보겠습니다.