일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
- 최강욱
- libtins
- 김재석
- 양적완화
- 유튜버 김재석
- 이상형 만들기
- 스페인 코로나
- 픽크루
- 학교 개학 연기 4월
- 해킹
- 성남은혜의강교회
- 이태원 클라쓰 15회 예고
- 임영규
- 제넥신
- 불가피
- 홍혜걸
- 폰폰테스트
- 성남 코로나 확진자
- 스콜피온킹
- 은혜의 강 교회
- 킹덤 고근희
- 이지혜
- 김영권 아내
- 금리인하
- 김영권
- 뭉쳐야 찬다
- 고민정
- 조희연
- 미국 금리인하
- 리리남매
- Today
- Total
Dork's port
[Linux] LD_PRELOAD의 이해와 LD_PRELOAD를 이용한 함수 후킹 구현하기 본문
안녕하세요.
오늘은 LD_PRELOAD를 이용해 함수 후킹을 구현해보려고 합니다.
최종적으로 SSL통신을 후킹하기 위해 SSL_write와 SSL_read를 후킹하여 프로그램에서 사용하는 통신을 출력하는 것이 목적입니다.
아직 공부 단계이므로 차근차근 정리하여 추후에 포스팅 하도록 하겠습니다.
#LD_PRELOAD |
우선 처음으로 사용될 개념인 LD_PRELOAD에 대해 알아 보도록하겠습니다.
저도 이번에 SSL을 후킹할 방법에 대해 찾아보다가 luke1337님을 통해 알게된 개념인데 개념은 아래와 같이 정리할 수 있습니다.
환경 변수 중 하나로 변수에 설정된 라이브러리를 기존 라이브러리가 로딩 되기 전에 로딩 시킨다. 이때 라이브러리에 중복된 이름의 함수가 있을 경우 LD_PRELOAD에 설정된 라이브러리가 먼저 로딩 되므로 LD_PRELOAD의 라이브러리에서 함수를 호출한다.
※SetUID가 걸린 파일에 대해서 LD_PRELOAD는 보안상의 이유로 무시 됩니다.
#LD_PRELOAD를 이용한 함수 후킹 |
환경은 아래와 같이 kali linux에서 진행하였습니다.
root@kali:~# uname -a
Linux kali 4.18.0-kali1-amd64 #1 SMP Debian 4.18.6-1kali1 (2018-09-10) x86_64 GNU/Linux
//main.cpp
#include <iostream>
#include <cstring>
using namespace std;
int main(int argc, char* argv[])
{
cout<<"String copy src : "<<argv[1]<<endl;
char tmp[100];
cout<<"Trying to string copy src to tmp buf..."<<endl;
strcpy(tmp,argv[1]);
cout<<"tmp buffer : "<<tmp<<endl;
return 0;
}
Compile은 아래의 명령어로 할 수 있습니다.$ g++ -o main main.cpp
그렇다면 이제 strcpy를 후킹 할 라이브러리를 만들어 보도록 할게요!
//hook_test.cpp
//#define _GNU_SOURCE
#include <dlfcn.h>
#include <iostream>
#include <cstring> //This header also contain _GNU_SOURCE
//_GNU_SOURCE needed by use RTLD Flags
using namespace std;
char* (*origin_strcpy)(char* dest, const char *src);
char* strcpy(char* dest, const char *src)
{
cout<<"strcpy hooked by hook_test.so!!!"<<endl;
cout<<"Hooked src Contents : " <<src<<endl;
origin_strcpy = (char * (*)(char*,const char*))dlsym(RTLD_NEXT, "strcpy");
return (*origin_strcpy)(dest,src);
}
하나하나 차근차근 알아 보도록하죠!
우선 dlfcn.h 헤더는 dlsym을 call하기 위해 사용되는 헤더 입니다.
dlsym이란 ?
라이브러리에서 symbol을 찾는 즉, 함수의 이름을 이용해 주소를 return해 주는 함수
void *dlsym(void *handle, const char *symbol);
이때 handle은 RTLD와 같은 flag가 들어가며, symbol은 함수의 이름
그 후 cpp이니 iostream과 strcpy를 사용하기 위해 cstring을 인클루드 하였습니다.
이때 RTLD와 관련된 Flag를 사용하기 위해 _GNU_SOURCE를 define해야 하지만 cstring안에 포함된 내용으로 따로 추가하지 않았습니다.
그리고 전역 변수로 원래의 strcpy를 호출할 포인터 변수를 선언합니다.
포인터 변수의 형태는 원형(리턴값 및 파라미터)과 같아야 하며 원형은 다음과 같이 man page를 참조하여 알 수 있습니다.
그리고 우리가 작성한 라이브러리의 호출을 확인하기 위해 디버깅 메세지를 cout을 통해 출력합니다.
그리고 다음 코드를 주목해주세요!
origin_strcpy = (char * (*)(char*,const char*))dlsym(RTLD_NEXT, "strcpy");
return (*origin_strcpy)(dest,src);
dlsy을 이용해 원래의 strcpy의 주소를 얻어 와 보도록 하겠습니다.
flag에는 RTLD_NEXT를 이용하며 함수의 이름에는 우리가 찾는 함수인 strcpy를 전달해 주게 되면 함수의 주소를 리턴하게 됩니다.
이때 RTLD_NEXT는 무엇을 의미 할까요??
RTLD_NEXT는 처음에 찾는 함수의 주소가 아닌 2번째 함수의 주소를 찾으라는 설정입니다. 왜 그래야 하는지 한번 알아보도록 할게요!
LD_PRELOAD를 이용한 프로그램의 실행 당시 현재 우리의 메모리에는 strcpy라는 이름의 함수가 2개가 로딩되어 있을 것 입니다
하나는 우리가 만든 hook_test.cpp를 컴파일하여 만든 라이브러리에서 만든 strcpy와 원래 라이브러리에서 제공하는 strcpy가 있겠죠!
LD_PRELOAD를 이용하여 우리가 만든 strcpy를 우선 로드하도록 하였으므로 처음에 찾는 strcpy의 주소는 우리가 만든 strcpy일 것 입니다!
즉! 만약 RTLD_NEXT를 해주지 않게되면 우리가 만든 라이브러리의 strcpy주소가 return 되어 결과적으론 재귀 함수가 되는 것이죠!
따라서 우리는 NEXT를 이용해 다음 strcpy의 주소를 참조하므로써 원래의 strcpy주소를 얻을 수 있습니다.
그리고 마지막엔 함수포인터를 실행하고 우리가 만든 함수의 인자(dest, src)를 함수포인터에게 전달하면 정상적으로 함수가 실행되며 return 값 또한 전달 될 것입니다.
조금 설명이 복잡할 수 도있습니다. 추가적인 문의는 댓글로 해주시면 상세하게 답변드릴 수 있도록 할게요!
그럼 이제 만들었으니 컴파일을 해보도록 할까요!?
$ g++ -fPIC -shared -o hook_test.so hook_test.cpp -ldl
위와 같이 컴파일 할 수 있습니다.
-fPIC은 object file을 만들때, 그 안의 symbol (function, variable)들이 어떤 위치에 있어도 동작을 하는 구조로 compile 하라는 것 이며, -fPIC을 하지 않아도 공유 라이브러리를 만들 수 있으나 재배치 시간이 소요되고 다른 프로세스와 코드 블록을 공유할 수 없다는 단점이 있다고 한다. 또한, 실제로 -fPIC을 빼고 컴파일을 시도해 보았다.
root@kali:~/hook_test# g++ -shared -o hook_test.so hook_test.cpp -ldl
/usr/bin/ld: /tmp/ccWUZGDV.o: relocation R_X86_64_PC32 against symbol `_ZSt4cout@@GLIBCXX_3.4' can not be used when making a shared object; recompile with -fPIC /usr/bin/ld: final link failed: bad value collect2: error: ld returned 1 exit status
위와 같이 오류가 발생하는데 아마 cpp의 namemangle규칙 때문일 것이라고 예측할 수 있다. 컴파일러에서 친절하게 -fPIC을 사용하라고 알려주니 쓰도록 하자!
-shared는 shared library를 만든다는 옵션이며 우리는 공유 라이브러리를 만들고 LD_PRELOAD를 통해 먼저 로드를 시켜줄 것이므로 컴파일 옵션에 추가해준다.
-ldl은 -l옵션을 이용해 libdl라이브러리를 로드하라는 의미이다. -l뒤에 나오는 라이브러리를 로드하여 컴파일 하라는 의미이며 예를들어 -lpcap은 libpcap을 로드하라는 말과 같다. dlsym을 이용하였는데 dlsym은 libdl 라이브러리에 존재하므로 컴파일 타임에서 libdl과 함께 컴파일 해줄 수 있도록 한다.
그럼 마지막으로 대망의 실행을 해보도록 하죠!
실행은 아래와 같이 먼저 로드될 라이브러리와 LD_PRELOAD를 이용해 실행할 파일을 뒤에 인자로 전달하면 LD_PRELOAD를 이용해 프로그램을 실행할 수 있습니다.
$ LD_PRELOAD=./hook_test ./main
그럼 결과를 확인해 볼까요?
정상적으로 우리가 작성한 라이브러리가 불려 디버깅 코드가 출력되는 것을 볼 수 있습니다.
이 경우 우리가 main에서 tmp를 출력하는 코드를 작성하여서 strcpy를 후킹한 것에 대한 큰 의미는 없지만 만약 main에서 strcpy를 이용하는 인자를 출력해주는 코드가 없다면 충분히 의미있는 후킹이 될 수 있겠죠!?
또한, 마지막에 원래의 strcpy가 정상적으로 호출되어 값이 tmp로 복사돼 main의 마지막에 출력한 것 처럼 tmp값에 인자가 복사된 것을 볼 수 있습니다.
이 개념을 ssl을 이용하는 ssl_write와 ssl_read에 적용하면 로컬에서 동작하는 프로그램에 대해 내용을 출력하거나 log file로 저장할 수 있을 것 입니다. 즉 ssl을 후킹하는 것과 같은 효과를 내는 것이지요! 다른 방법으로는 ssl strip도 있습니다.
다음엔 위의 내용으로 찾아올 수 있도록 하겠습니다.
code 및 make file은 깃허브(https://github.com/janghanbin/hook_test)에서 확인할 수 있습니다.
질문은 댓글로 받을게요!
thanks to : gilgil, luke1337 , umbum
참조 : http://i5on9i.blogspot.com/2013/05/hooking-wrapper-function.html
http://umbum.tistory.com/128
https://kldp.org/node/1107
http://devanix.tistory.com/198
'Linux' 카테고리의 다른 글
VIM auto Indent (0) | 2018.11.25 |
---|---|
Linux man page 번호의 의미 (2) | 2018.11.07 |
Linux Makefile에서 PHONY의 의미 (0) | 2018.10.28 |
Linux Makefile에서 $@ $< $^의 의미 (0) | 2018.10.28 |
libudev.so.0: cannot open shared object file: No such file or directory 에러 해결 (0) | 2018.10.01 |