안녕하세요~

저희 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"도 천천히 읽어주시면서 기다려 주시면 감사하겠습니다 :)