LD_PRELOAD와 LD_LIBRARY_PATH

[작성 계기]

lob(lord of bof)를 복기하던 중 ld_preload로 참조하는 라이브러리 이름에 쉘코드를 담아 bof 취약점을 공격하는 문제가 있었습니다. 해당 문제에서의 조건은 ret 이후 스택영역을 모두 0으로 세팅하기 때문에 일반적으로 argv에 담겨있던 “LD_PRELOAD=[Library_name]”은 존재하지 않습니다…만 코드영역보다 앞의 영역(이를 뭐라고 불러야 할 지 잘 모르겠어서 우선 이렇게 적어둡니다. 추후 정확한 명칭을 알아낸 후 수정하도록 하겠습니다)에 LD_PRELOAD에 담았던 라이브러리의 이름(경로)이 부분적으로 남아있는 것을 확인할 수 있었습니다.(그림1. 참조) 때문에 해당 찌꺼기(?)가 왜 존재하게 되었는지에 대한 궁금증으로 LD_PRELOAD와 이와 유사한 LD_LIBRARY_PATH에 대해 알아보았습니다.

[그림 1] 코드영역보다 앞에 존재하는 LD_PRELOAD로 선언된 라이브러리의 이름 찌꺼기(?)

 

[배경 지식]

# LD_PRELOAD

 – 공유 라이브러리의 라이브러리보다 먼저 LD_PRELOAD로 지정해준 라이브러리를 바라봄

 – 후킹할때 사용할 수 있다.

 – export LD_PRELOAD=[LibraryPath]

  * ex. export LD_PRELOAD=library_A.so

 – LD_PRELOAD로 선언 시 preprocess에서 libc.so와 같은 라이브러리 보다 먼저 LD_PRELOAD로 선언된 라이브러리를 바라봄

  + 만약 실행파일에서 printf를 사용하여 문구를 출력해주고 있다면 printf()라는 함수명을 똑같이 맞춰준 뒤 그 안에 내가 원하는 코드를 작성하면, printf를 공유 라이브러리에서 불러오기 전에 LD_PRELOAD로 선언된 라이브러리의 printf() 안의 내용을 먼저 실행한다.

  + 때문에 우리가 생각하는 ‘Hooking’이 가능하게 된다.

  ++ 다만 공유 라이브러리에서 불러오는 함수를 실행하는것을 ‘대체’하여 LD_PRELOAD 라이브러리를 실행하기 때문에 본래 함수는 실행되지 않는다.

  !!! 때문에 본래 함수의 인자값을 맞추어 해당 기능까지 실행되게 한다면 프로그램 실행 – ‘후킹기능’ – ‘본래기능’ ~… 과 같은 흐름으로 실행될 수 있다.

 

# LD_LIBRARY_PATH

 – 작성 예정…

 

[실습(?)]

    #include <stdio.h>

  int main(int argc, char* argv[]) {

      printf(“Hello World”);

      return 0;

  } 

[코드 1] test.c

  printf(char* a) {

      puts(“Hooking!”);

      puts(a);

  } 

[코드 2] hook.c

[코드 1]과 [코드 2]는 간단히 printf 를 해주는 test.c와 printf를 후킹하는 hook.c이다. test.c 에서 사용하는 printf 함수를 후킹하기 위해 hook.c에 printf로 똑같이 선언하였다.

  $ gcc -o test test.c

  $ gcc -fPIC -shared -o hook.so hook.c

  $ export LD_PRELOAD=./hook.so

[코드 3] 컴파일 및 LD_PRELOAD 세팅

[코드 3]은 test.c와 hook.c를 각각 실행파일과 공유 라이브러리로 컴파일하고 컴파일된 공유 라이브러리(hook.so)를 LD_PRELOAD 환경변수에 세팅해주는 명령어이다. 위와같이 실행 후 다음 [그림 2]와 같은 결과를 얻을 수 있다.

[그림 2] LD_PRELOAD 세팅 후 실행 결과

 

[대응방안(?)]

위와 같이 LD_PRELOAD 환경변수를 이용하여 로컬단에서 후킹하는 것을 막기 위한 방법은 없을까? 라는 의문이 들었다. 찾아본 결과 이를 해결하기 위한 방안으로 몇 가지가 있었고, 이에 대해 소개하려 한다.(https://security.stackexchange.com/questions/63599/is-there-any-way-to-block-ld-preload-and-ld-library-path-on-linux)

 

1. setuid(setgid) 설정

  – LD_PRELOAD 환경변수는 자신이 권한을 가지지 않은 user의 파일에 대해선 적용되지 않는다. 허나 자신이 권한을 가지지 않은 user의 파일을 읽거나 실행시켜야 할 때가 있는데, 이 때 setuid를 설정한다. 파일의 소유 권한을 나눈 후 setuid를 설정해주면 LD_PRELOAD를 통한 후킹을 막을 수 있다.

[그림 3] root 권한, setuid가 걸린 바이너리 ‘gremlin’

[그림 4] setuid 바이너리 실행 결과

[그림 3]과 [그림 4]를 살펴보면 setuid가 걸린 바이너리를 실행하면 LD_PRELOAD로 세팅된 puts(“hooked!”); 가 실행되지 않는 것을 살펴볼 수 있다.

 

2. LD_PRELOAD를 사용하지 않는 secure loader

  – off the shell tool을 사용하여 LD_PRELOAD를 사용하지 않는(ld를 대체하는) secure loader를 사용한다. 자세한 내용은 참고.(http://hexhive.github.io/projects/#TRuE)

   * 해당 방법을 사용하면 몇몇 앱은 동작하지 않을수도 있다

 

3. LD_PRELOAD를 사용하지 않는 libc 사용

  – LD_PRELOAD를 사용하지 않는 libc를 사용한다. musl libc(http://www.musl-libc.org/)가 해당 기능을 제공한다고 한다.

 

4. Sandbox 적용

  – LD_PRELOAD를 사용하여 후킹을 하더라도 해당 앱이 다른 프로세스를 제어하지 않게끔 Sandbox 기술을 적용한다.

 

[정리]

위와 같이 LD_PRELOAD 환경변수를 세팅하여 프로그램 실행을 후킹할 수 있는 방법과 이에 대한 대응방안에 대해 알아보았다. 현재는 간단한 후킹에 대한 실험만 해놓았지만 본래 목적이었던 ‘LD_PRELOAD에 세팅된 공유 라이브러리의 이름이 왜 남는가?’에 대한 질문은 아직 해소되지 않았다. 아는 멋진 분께 질문하여 해당 질문에 대한 답은 얻었지만 아직 내 눈으로 직접 확인하지 못하였기에 elf Loader에 대해 분석(?)을 진행 후 이에 대한 내용을 추후 다시 작성하려 한다.

  * 참고로 해당 질문에 대한 답은 elf 로더에서 LD_PRELOAD 환경변수를 참조할 때 스택을 사용하게 되는데, 이 때 해당 정보를 읽으며 스택에 세팅된 공유 라이브러리의 이름(경로)을 남긴 후 지우지 않아 찌꺼기(?)가 남게 되는 것이라고 한다.

Thx. to singi

댓글 남기기

이메일은 공개되지 않습니다. 필수 입력창은 * 로 표시되어 있습니다