본문 바로가기

Reversing/Anti-Debugging

[Dynamic 안티디버깅] Assembly instructions

'Anti-Debug Assembly instructions'

 

 

설명

CPU가 특정 명령을 실행할 때 디버거가 작동하는 방식에 따라 디버거의 존재를 탐지하는 방법

 

 

1. INT 3

2. INT 2D

3. DegubBreak

4. INT1

5. Stack Segment Register

6. POPF and Trap Flag

7. PREFIX REP

 

 

INT 3

INT3은 소프트웨어 브레이크 포인트로 사용되며 INT3이 호출되면

EXCEPTION_BREAKPOINT (0x80000003)가 발생하고 예외 처리기가 호출된다.

일반적인 실행 인 경우 예외가 발생되면 예외 처리기에 의해 제어되지만

디버깅중일 경우는 예외처리를 디버거가 받기 때문에 예외 처리기가 실행되지 않은 상태에서 코드가 실행되는 것을 이용

 

주로 SEH (Structured Exception Handling)를 통해 구현한다.

 

Windows OS 에서의 예외는 다음과 같다.

EXCEPTION DATATYPE MISALIGNMENT (0x80000002)
EXCEPTION BREAKPOINT (0x80000003)
EXCEPTION SINGLE STEP (0x80000004)
EXCEPTION ACCESS VIOLATION (0xC0000005)
EXCEPTION IN PAGE ERROR (0xC0000006)
EXCEPTION ILLEGAL INSTRUCTION (0xC000001D)
EXCEPTION NONCONTINUABLE EXCEPTION (0xC0000025)
EXCEPTION INVALID DISPOSITION (0xC0000026)
EXCEPTION ARRAY BOUNDS EXCEEDED (0xC000008C)
EXCEPTION FLT DENORMAL OPERAND (0xC000008D)
EXCEPTION FLT DIVIDE BY ZERO (0xC000008E)
EXCEPTION FLT INEXACT RESULT (0xC000008F)
EXCEPTION FLT INVALUD OPERATION (0xC0000090)
EXCEPTION FLT OPERATION (0xC0000091)
EXCEPTION FLT STACK CHECK (0xC0000092)
EXCEPTION FLT UNDERFLOW (0xC0000093)
EXCEPTION INT DIVIDE BY ZERO (0xC0000094)
EXCEPTION INT OVERFLOW (0xC0000095)
EXCEPTION PRIV INSTUCTION (0xC0000096)
EXCEPTION STACK OVERFLOW (0xC00000FD)

 

 

 

 

CODE

#include <stdio.h>
#include <Windows.h>

BOOL anti_debug = TRUE;

void IsDebugged()
{
    __try
    {
        __asm { int 3 }
    }
    __except (EXCEPTION_EXECUTE_HANDLER)
    {
        anti_debug = FALSE;
    }
}
int main() {
    IsDebugged();

    if (anti_debug) {
        printf("Detect!\n");
    }
    else {
        printf("Not!\n");
    }
}

 

 

실습

예외처리 추가

 

1. 예외처리를 등록하여 예외를 디버기에서 처리할 수 있도록 한다.

2. INT3 (0xCC) Opcode를 NOP (0x90)으로 패치한다.

 

OllyDbg인 경우는

Options -> Debugging options -> Exceptions -> INT3 breaks 체크

 

 

 

 

 

 

INT 2D

커널모드에서 동작하는 브레이크 포인트 예외를 발생시킨다.

유저모드에서도 예외를 발생시키나 디버거에 의해 실행 됐을 경우엔 예외를 발생시키지 않는다.

 

INT 2D 코드 이후의 1byte가 무시되고 

Code Byte Ordering을 변경해서 Obfuscated 효과를 보여준다.

 

Obfuscated 효과

 

 

 

CODE

#include <stdio.h>
#include <Windows.h>

BOOL anti_debug = TRUE;

void IsDebugged()
{
    __try
    {
        __asm { int 0x2d }
    }

    __except (EXCEPTION_EXECUTE_HANDLER)
    {
        anti_debug = FALSE;
    }
}

int main() {
    IsDebugged();

    if (anti_debug) {
        printf("Detect!\n");
    }
    else {
        printf("Not!\n");
    }
}

 

 

실습

INT 2D 구간을 NOP 같은 코드로 패치하여 실행한다.

 

 

 

 

 

 

 

DebugBreak

현재 프로세스에 브레이크 포인트 예외가 발생하도록 하여

이에 호출 스레드는 예외를 처리할 수 있도록 디버거에 신호를 보낼 수 있다.

 

https://learn.microsoft.com/en-us/windows/win32/api/debugapi/nf-debugapi-debugbreak

 

 

CODE

#include <stdio.h>
#include <Windows.h>

BOOL anti_debug = TRUE;

void IsDebugged()
{
    __try
    {
        DebugBreak();
    }
    __except (EXCEPTION_BREAKPOINT)
    {
        anti_debug = FALSE;
    }
}

int main() {
    IsDebugged();

    if (anti_debug) {
        printf("Detect!\n");
    }
}

 

 

 

실습

1. 예외처리를 디버기에게 맡기고 다음 코드에 브레이크 포인트를 설정하여 디버깅한다.

2. DebugBreak() 호출 부분을 nop 같은 명령어로 패치한다.

 

 

 

 

 

 

 

 

INT1

INT1(0xF1)은 문서화되지 않은 인터럽트 중 하나로 디버깅 중인지 감지하는 데 사용할 수 있다.

INT1 인터럽트가 호출되면 EXCEPTION_SINGLE_STEP (0x80000004) 예외가 발생한다.

따라서 디버거에서는 예외 처리기가 호출되지 않고 실행되는 것을 이용하여 디버깅 중인지 감지할 수 있다.

 

 

 

CODE

#include <stdio.h>
#include <Windows.h>


bool IsDebugged()
{
    __try
    {
        __asm __emit 0xF1;
        return true;
    }
    __except (EXCEPTION_EXECUTE_HANDLER)
    {
        return false;
    }
}


int main() {
    bool Check = IsDebugged();

    if (Check) {
        printf("Detect!\n");
    }
}

 

 

 

실습

1. 예외처리를 디버기에게 맡기고 리턴 값을 조작한다.

2. 수행 부분을 nop 같은 명령어로 패치한다.

 

 

 

 

 

 

 

 

 

Stack Segment Register

현재 디버깅 중인지 감지하는 데 사용할 수 있는 Trick이다.

다음과 같은 instructions을 사용한다.

 

push ss 
pop ss 
pushf

 

해당 코드가 수행되면 TrapFlag(TF)가 설정된다.

이후 CPU는 Single Step 모드로 변경되고 변경 후 CPU는 Single Step 모드에서 하나의 명령어를 실행시킨 후

EXCEPTION SINGLE STEP 예외를 발생시킨다. 이후 Trap Flag(TF)은 0으로 초기화된다.

(Trap Flag(TF)는 EFLAGS 레지스터의 9번째(index 8) 비트이다.)

 

EXCEPTION SINGLE STEP 예외는 SEH 결합하여 디버거를 탐지하기 위한 안티 디버깅 기법에 사용된다.

 

 

 

CODE

bool IsDebugged()
{
    bool bTraced = false;

    __asm
    {
        push ss
        pop ss
        pushf
        test byte ptr [esp+1], 1
        jz movss_not_being_debugged
    }

    bTraced = true;

movss_not_being_debugged:
    __asm popf;

    return bTraced;
}

 

 

실습

1. 디버거에서 EXCEPTION SINGLE STEP 예외를 등록하고 예외를 디버기에게 처리하도록 한다.

2. 수행 부분을 nop 같은 명령어로 패치한다.

 

 

 

 

 

 

 

 

POPF and Trap Flag

현재 디버깅 중인지 추적할 수 있는 Trick이다.

Trap Flag(TF)를 설정하고 디버깅 중일 시 트랩 플래그가 지워지고 예외가 표시되지 않는 부분을 이용하여

디버깅을 탐지한다.

 

 

CODE

#include <stdio.h>
#include <Windows.h>


bool IsDebugged()
{
    __try
    {
        __asm
        {
            pushfd
            mov dword ptr[esp], 0x100
            popfd
            nop
        }
        return true;
    }
    __except (GetExceptionCode() == EXCEPTION_SINGLE_STEP
        ? EXCEPTION_EXECUTE_HANDLER
        : EXCEPTION_CONTINUE_EXECUTION)
    {
        return false;
    }
}


int main() {
    bool Check = IsDebugged();

    if (Check) {
        printf("Detect!\n");
    }
}

 

 

실습

1. 디버거에서 예외를 등록하고 예외를 디버기에게 처리하도록 한다.

2. 수행 부분을 nop 같은 명령어로 패치한다.

 

 

 

 

 

 

 

 

PREFIX REP

해당 Trick은 디버거가 instruction을 처리하는 방식을 남용한 Trick이다.

prefix rep 명령어는 원래 반복 용도로 쓰이나 이후에 INT1을 호출해버리면 디버깅 중에는 제대로

가동되지 않는다. 따라서 다음 스텝으로 진행되고 정상적인 실행 일 경우는 INT 1에 의해 예외 핸들러로

넘어가지만 디버깅 중이라면 핸들러로 넘어가지 않고 그대로 코드가 실행된다.

 

코드가 이런 식으로 흘러가는 이유는 pop ss가 실행되면 CPU가 스택이 망가지지 않도록

인터럽트 트리거를 막기 때문이다.

 

 

CODE

#include <stdio.h>
#include <Windows.h>


bool IsDebugged()
{
    __try
    {
        __asm __emit 0xF3
        __asm __emit 0x64
        __asm __emit 0xF1 //INT 1
        return true;
    }
    __except (EXCEPTION_EXECUTE_HANDLER)
    {
        return false;
    }
}


int main() {
    bool Check = IsDebugged();

    if (Check) {
        printf("Detect!\n");
    }
}

 

 

 

실습

1. 디버거에서 예외를 등록하고 예외를 디버기에게 처리하도록 한다.

2. 수행 부분을 nop 같은 명령어로 패치한다.

 

 

마무리하며

이를 통해 디버거가 작동하는 방식에 따라 디버깅을 탐지할 수 있는 Trick들을 알아보았고

해당 Trick들은 NOP 같은 명령으로 패치하거나 예외를 디버기에 처리할 수 있도록 설정해서

우회할 수 있는 것을 알 수 있다.