2017년 11월 13일 월요일

MBR 공부 정리 III - MBR의 부트 코드 분석 방법

다음은 MBR의 부트코드를 어떻게 분석해야할지에 대해 정리한다. 정리하면서 보니까 결국 그전 보고서 처럼 쭉쭉 분석한 내용만 다룬듯 해서, 분석하면서 정리된 내용을 정리?! 하려고 한다. 그럼 Start~!


우선, MBR에서 가장 먼저 실행되는 영역은 MBR의 BootCode 영역이다. 보통 악성코드들은 해당 영역에 악성행위를 실행하는 코드를 삽입하여 감염된 PC에서 악성행위가 발행하도록 만든다. 물론 해당 영역에서 악성행위를 직접적으로 실행하는 경우도 있지만, 과거에는 해당 영역에서 공격자(해커)가 삽입해둔 다른 영역(악성 행위 코드)으로 점프하도록 만들어서 코드를 실행 시키고 다시 정상적인 부팅을 통해, 사용자가 느끼지 못하게?! 혹은 탐지가 어렵게 만든다. 

BootCode는 BIOS에서 해석할 수 있는 16bit 형태로 이루어져 있기때문에 보통 분석하는 PE와는 다르게 어셈코드를 직접 하나하나 분석해야한다. 하지만, BootCode 영역의 크기가 작기 때문에 그렇게 큰 시간이 들진 않는다. 물론, BootCode 영역 밖에 영역을 사용한다면 상당히 긴 분석이 된다. 


다음 코드를 보면 알 수 있뜻이, 하나하나 어셈블리언어들의 행위를 정리한 것을 확인할 수 있다. 해당 부분에 대해서 설명하면 끝도 없으니 패스 (참조 링크에 Forensic-proof에 정상 부팅 bootcode 분석 내용을 보면 많은 도움이 된다.)

여기서 다루고자 하는 내용은 BootCode에서 호출에서 특정 함수를 호출하기 위해 발생하는 인터럽트(Interrupt)에 대해서 정리하려고 한다.

우선 인터럽트란 무엇일까? 인터럽트는 마이크로프로세서(CPU)가 프로그램을 실행하고 있을 때, 입출력 하드웨어 등의 장치나 또는 예외 상황이 발생하여 처리가 필요할 경우에 프로세서에게 알려 처리할 수 있도록 만들 때 사용된다. 본인은 과거 인터럽트는 예외처리시에 사용하기 위해 존재한다고 생각했다. 하지만 그외에 시스템 콜시에도 사용됬다.

사용 목적
  • 예외 처리
  • 시스템 콜

인터럽트는 보통 x86에서 INT 명령어를 사용해 실행된다. 리눅스 커널과 같은 운영체제에서 응용프로갦의 저수준 입출력 함수가 실행되면, 해당 실시간 라이브러리 함수에 의해 소프트웨어 인터럽트가 실행된다. 이를 바로 시스템 콜(System Call)이라고 하며, 함수의 기능에 따라 드라이버를 구별하고 드라이버 내의 함수를 지정함과 동시에 데이터를 레지스터를 통해 넘겨 준다.

그럼 어떤식으로 사용되는지 확인 해보자! (대상은 전에 확인한 Petya로!)

우선 Boot 코드를 처음 확인하면 다음과 같이 인터럽트 플래그(Interupt Flag)를 활성화 시키는 코드를 확인할 수 있다.

lea     sp, ds:7C00h    ; SP = 0x7C00
sti                     ; Interrupt Flag enable
mov     eax, 20h        ; EAX = 0x20
mov     ds:7C93h, dl    ; [0x7C93] = 0x80(HDD)
mov     ebx, 1          ; EBX = 1
mov     cx, 8000h       ; CX = 0x8000

다음과 같이 IF가 활성화작업을 진행한 후 부터 정상적으로 인터럽트가 실행 할 수 있게 된다.  다음은 인터럽트를 이용해 어떤식으로 함수가 실행되는지 코드를 통해 확인해보자.

다음은 비디오 서비스를 호출하게 되는 INT 10h함수로 다음과 같다.

push    bp
mov     bp, sp          ;
                        ; Scroll Window Up (AH = 0x06)
mov     bh, [bp+arg_0]  ; BX = 0x0742(BH = 0x07, BL = 0x42)
xor     cx, cx          ; CX = 0x0000 (CH=0x00, CL= 0x00)
mov     dx, 184Fh       ; DX = 0x184F (DH = 0x18, DL=0x4F)
mov     ax, 600h        ; AX = 0x0600(AH=0x06, AL = 0x00)
                        ; INT 10h function set
int     10h             ; Interrupt !
                        ; Set Cursor Position (AH = 02)
xor     bh, bh          ; BX =0x0020(BH=0x00, BL= 0x20)
xor     dx, dx          ; DX = 0x0000
mov     ah, 2           ; AX = 0x0200(AH=0x02, AL = 0x20)
                        ; INT 10h Function set
int     10h             ; Interrup !
leave
retn


코드를 보면 두개의 인터럽트가 발생하는 것을 확인할 수 있으며, 각각 발생하는 시스템 콜은 다음과 같다.



곧 악성 MBR 내 화면 처리를 하는 코드인 것을 확인할 수 있다.

내가 알기론 10~13까지 아마 시스템 콜로 알고 있는데 확인이 필요함

여튼 이런 식으로 MBR에서는 시스템 콜이 되니, 분석할때 멘붕 하지 말고 하나하나 검색하면서 어떤 행위인지 확인하면 분석이 가능하다. AH와 AL이 뭔지 모르면 레지스트리 공부를 하면 됨! 이상 끝

참고
Wiki : 인터럽트
Wiki : INT 10h

[Anti Memory Dumping Techniques]KiUserExceptionDispatcher 함수 I

1) 예외 체인 순회

KiUserExceptionDispatcher API가 호출되면, 인자로 받은 ExceptionRecord와 ContextRecord 값을 RtlDispatchException API의 인자로 넣게 된다. 

RtldispatchException API의 값이 True면 정상 실행하게 되고, False를 반환하게 되면 예외(Exception)이 발생하면서 예외 처리하게 된다.

TRUE = 현재 스레드 문맥을 담고 있는 ContextRecord를 매개변수로 전달하여 NtContinue 커널 함수 호출
FALSE = NtRaiseException함수를 통해 예외 발생

코드 구조는 다음과 같다.

BOOLEAN NTAPI RtlDispatchException
(
    IN PEXCEPTION_RECORD     ExceptionRecord,
    IN PCONTEXT              ContextRecord
)

RtlDispatchException 함수

SEH프레임이 등장하는 API로 해당 함수의 코드는 다음과 같다.

#define EXCEPTION_CHAIN_END (DWORD)-1

BOOLEAN RtlDispatchException(PEXCEPTION_RECORD ExceptionRecord, PCONTEXT ContextRecord)
{
    DISPATCHER_CONTEXT*    dc;
    EXCEPTION_DISPOSITION    Disposition;
    PEXCEPTION_REGISTRATION_RECORD pCurFrame, pNestedFrame = NULL
    ULOG HighAddress, HightLimit, LowLimit;
    EXCEPTION_RECORD    er;
    
    RtlpGetStackLimits(&LowLimit, &HighLimit);    //현재 스래드의 스택 상/하한 값을 획득
    
    pCurFrame = RtlpGetRegistrationHead();    //SEH 프레임의 포인터를 획득, FS[:0]을 반환하는 함수

    while (pCurFrame != EXCEPTION_CHAIN_END)    //SEH 프레임을 0xFFFFFFFF가 아닐 동안 돔
    //EXCEPTION_CHAIN_END = 0xFFFFFFFF
    {
        HighAddress = (ULONG)pCurFrame + sizeof(EXCEPTION_REGISTRATION_RECORD);
        if (((ULONG)pCurFrame < LowLomit || HighAddress > HighLimit) || ((ULONG)pCurFrame & 0x3) != 0)    //현재 프레임 영역이 스택의 상/하한 내에 있는지와 프레임의 주소가 4바이트 경계로 정렬되어 있는지 확인
        {
            ExceptionRecord->ExceptionFalgs |=  EXCEPTION_STACK_INVALID;
            return FALSE;
            // 만족 안될 경우 ExceptionFlags에 EXCEPTION_STACK_INVALID 플래그를 설정 후 false를 반환한다.(false는 예외를 발생하게 된다.)
        }
        Disposition = RtlpExecuteHandlerForException(ExceptionRecord, pCurFrame, ContextRecord, &dc, (PEXCEPTION_ROUTINE)pCurFrame->Handler);
        //RtlpExecuteHandlerForException 함수 호출, SEH 프레임의 Handler 필드에 설정된 예외 헨들러를 호출,pCurFrame에 현재 프레임의 포인터를 전달, 마지막 매게 변수는 현재 프레임의 예외 핸들러를 별도로 전달
        if (pNestedFrame == pCurFrame)    //예외 중첩 발생 처리
        {
            ExceptionRecord->ExceptionFlags &= (~EXCEPTION_NESTED_CALL);
            pNestedFrame = NULL;
        }
        switch (Disposion)    //RtlExecuteHandlerForException 반환값인 Disposition변수를 확인
        {
            case ExceptionContinueExecution:    //예외가 발생한 지점에서 부터 다시 실행
            if ((ExceptionRecord->ExceptionFlags & EXCEPTION_NONCONTINUABLE) != 0)
            {
                er.ExceptionCode = STATUS_NONCONTINUABLE_EXCEPTION;
                er.ExceptionFlags = EXCEPTION_NONCONTINUABLE;    // 실행을 계속 할 수 없음을 의미, 이경우 STATUS_NONCONTINUABLE_EXCEPTION 예외를 발생
                er.ExceptionRecord = ExceptionRecord;
                er.NumberParameters = 0;
                RtlRaiseException(&er);
            }
            else
            {
                return TRUE;
                //이 외에는 TRUE를 반환하여 계속 실행하도록 함.(NtContunue 함수 호출로 계속 실행
            }
            case ExceptionContinueSearch:    //현재 RegistrationPointer가 가리키는 프레임이 예외를 처리하지 않을 경우(예외 프레임을 계속 검색하라는 의미)
            break;

            case ExceptionNestedException:    //예외 중첩 발생 처리
                ExceptionRecord->ExceptionFlags |= EXCEPTION_NESTED_CALL;
                if (dc.RegistrationFrame > pNestedFrame)
                {
                    pNestedFrame = dc.RegistrationFrame;
                }
            break;

            default:    //이외의 경우, ExceptionFlags에 EXCEPTION_NONCONTINUABLE 플래그를 설정하고 STATUS_INVALID_DISPOSITION 예외를 발생
                er.ExceptionCode = STATUS_INVALID_DISPOSITION;
                er.ExceptionFlags = EXCEPTION_NONCONTINUABLE;
                er.ExceptionRecord = ExceptionRecord;
                er.NumberParameters = 0;
            break;
        }
        pCurFrame = pCurFrame->Next;    //다음 예외 프레임 체크
    }
    return FALSE;    //전체르 봤는데 처리 못하면 false 반환
}


다음은 RtlpExecuteHandlerForException API(SEH 프레임을 읽어오는 API)에 대해서 정리한다!





2017년 11월 11일 토요일

MBR 공부 정리 II - MBR 접근 C코드 사본

다음은 MBR의 부트코드에 어떤식으로 ASM코드를 쓰는지(Overwrite)하는지에 대해 코드를 보고 예제 코드이 API의 사용법에 대해 새새하게 정리해본다.


우선 MBR을 공부하게된 Petya 랜섬웨어를 예로 들면 다음과 같인 API을 대표적으로 사용하는 것을 확인할 수 있다.

CreateFileA - 파일에 접근하기 위해 사용되는 API
DeviceIoControl - MBR 섹터에 대한 정보를 확인하는데 이용
SetFilePointEx - 파일의 포인터를 이동 시기킄데 사용
WriteFile / ReadFile - MBR 섹터에 암호화된 데이터를 쓸 때 사용되는 API

이들 중 다음 세개 API에 대해서 좀 더 자세히 알아보고 들어가 보자.
CreateFile / DeviceIoControl / SetFilePointEx

CreateFile API - MSDN

해당 API의 함수 형태는 다음과 같은 형태를 갖고 있다.

HANDLE WINAPI CreateFile(
  _In_     LPCTSTR               lpFileName,    #생성하고자 하는 파일
  _In_     DWORD                 dwDesiredAccess,    #파일에 대한 엑세스 권한 지정
  _In_     DWORD                 dwShareMode,    #파일의 공유 모드 지정
  _In_opt_ LPSECURITY_ATTRIBUTES lpSecurityAttributes,    #보안속성 지정, SECRITY_ATTRITUBES 구조체 포인터
  _In_     DWORD                 dwCreationDisposition,    #파일을 생성할 것인지 열것인지 지정
  _In_     DWORD                 dwFlagsAndAttributes,    #생성 파일의 속성을 지정
  _In_opt_ HANDLE                hTemplateFile    #생성된 파일의 속성을 제공할 탬블릿
);


보통 해당 함수는 다음과 같은 형태로 사용되며 목적은 PhysicalDrvie0에 접근하기 위해 사용된다. (MBR Handle 획득)

CreateFileA("\\\.\\PhysicalDrvie0", 0x40000000, 0x3, 0, 0x3, 0, 0)

    FileName : "\\\.\\PhysicalDrvie0"
    DesiredAccess : 0x40000000 => GENERIC_WRITE
        0x00000001 => CREATE_ALWAYS
        0x80000000 => GENERIC_READ
    ShareMode : 0x3 = 0x00000003 => FILE_SHARE_WRITE(0x01) | FILE_SHARE_READ(0x02)
        0x00000000 => NULL = File 또는 Device에 delete, read, write 접근이 가능
        0x00000004 = FILE_SHARE_DELETE => DELETE 가능
        0x00000001 = FILE_SHARE_READ => READ 가능
        0x00000002 = FILE_SHARE_WRITE => Write 가능
    SecurityAttributes : 0
    CreationDisposition : 0x3 = > OPEN_EXISTING
        0x2 : CREATE_ALAWAYS => 새로운 파일을 항상 만들 수 있음, Error code = 183
        0x1 : CREATE_NEW => 같은 파일이 존재하지 않은 경우, 새로운 파일을 만듬. Error code = 80
        0x4 : OPEN_SLAWAYS => 파일을 염, Error code = 183
        0x3 : OPEN_EXISTING => 파일이나 디바이스에 아무 접근 없을 시, 파일을 염, Error code = 2
        0x5 : TRUNACATE_EXISITING 
    FlagAndAttributes : 0
        0x20(32) : FILE_ATTRIBUTE_ARCHIVE
        0x4000(16384) : FILE_ATTRUBYTE_ENCRYPTED
        0x2(2) : FILE_ATTRIBUTE_HIDDEN
        0x80(128) : FILE_ATTRIBUTE_NORMAL
        0x1000(4096) : FILE_ATTRIBUTE_OFFLINE
        0x1(1) : FILE_ATTRUBYTE_READONLY
        0x4(4) : FILE_ATTRIBUTE_SYSTEM
        0x100(256) : FILE_ATTRIBUTE_TEMPORARY
        0x02000000 : FILE_FLAG_BACKUP_SEMANTICS
        0x04000000 : FILE_FLAG_DELETE_ON_CLOSE
        0x20000000 : FILE_FLAG_NO_BUFFERING
        0x00100000 : FILE_FLAG_OPEN_NO_RECALL
        0x00200000 : FILE_FLAG_OPEN_REPARSE_POINT
        0x40000000 : FILE_FLAG_OVERLAPPED
        0x01000000 : FILE_FLAG_POSIX_SEMANTICS
        0x10000000 : FILE_FLAG_RANDOM_ACCESS
        0x00800000 : FILE_FLAG_SESSION_AWARE
        0x08000000 : FILE_FLAG_SEQUENTIAL_SCAN
        0x80000000 : FILE_FLAG_WRITE_THROUGN
    TemplateFile : 0

정리하면, 우선 CreateFile API은 PhysicalDrive에 접근하기 위해 주로 사용된다는 점을 알아두고 가자! 보통은 파일에 접근할 때 사용되지만, PhysicalDrive에 접근한다면, MBR 영역을 의심해보자!

DeviceIoControl - MSDN / EXAMPLE CODE

주로 파티션의 갯수를 확인하고자 할 때 사용된다. API의 형태는 다음과 같은 형태를 가지고 있다.

BOOL WINAPI DeviceIoControl(
  _In_        HANDLE       hDevice,    #CreateFile로 받은 값이 들어가고, 타겟이 된다.
  _In_        DWORD        dwIoControlCode,    #전달된 I/O Control Code가 들어간다.
  _In_opt_    LPVOID       lpInBuffer,    #InBuffer 설정
  _In_        DWORD        nInBufferSize,
  _Out_opt_   LPVOID       lpOutBuffer,    #OutBuffer 설정
  _In_        DWORD        nOutBufferSize,
  _Out_opt_   LPDWORD      lpBytesReturned,
  _Inout_opt_ LPOVERLAPPED lpOverlapped
);

해당 API에서는 IoControlCode에 따라 역할이 달라진다. IoControlCode는 다음과 같이 32bit 구조를 가지고 있다.


해당 API는 PhysicalDrive에 접근해서 PhysicalDrvie 번호를 설정하거나, 파티션의 정보를  읽어오거나 할때 사용된다.

IoControlCode -> 좀 정리되고 정의된 데이터가 있으면 좋겠는데 안보인다. 코드 볼때마다 IoControlCode 값이 어떤걸 의미하는지 검색해봐야겠다.

우선 내가 정의한 코드들은 다음과 같은 역할을 한다.

0x70000 => IOCTL_DISK_GET_DRVIE_GEOMETRY, 디스크 타입, 실런더 갯수, 실린더 당 트랙, 트랙당 섹터, 섹터당 바이트 등
0x90020 => FSCTL_DISMOUNT_VOLUME, FSCTL의 마운트를 해제하는 역할
0x560000 => IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS, PhysicalDrvie 번호를 설정할 때 사용
0x70048 => IOCTL_DISK_GET_PARTITION_INFO_EX,  파티션 정보를 읽어옴


이정도 인듯 하다. 위에 사용된 IoControlCode를 보면 알 수 있듯이,  DeviceIoControl API는 MBR의 정보를 획득하기 위해 사용되는 것을 알 수 있다.

SetFilePointerEx - MSDN

개발자 시선에서 의 파일 포인터
- 파일을 열면 초기 파일 포인터는 0이다.
- ReadFile, WriteFile 등의 함수를 이용하면 파일 포인터는 자동으로 증가한다.
- 이러한 파일 포인터를 이동시킬 때 사용되는 API가 SetFilePointer다.
- 오프셋이 32비트 

BOOL WINAPI SetFilePointerEx(
  _In_      HANDLE         hFile,    #File handle
  _In_      LARGE_INTEGER  liDistanceToMove,    #이동할 거리
  _Out_opt_ PLARGE_INTEGER lpNewFilePointer,    #옮겨진 포인터를 확인, 확인할 필요 없을 경우 NULL
  _In_      DWORD          dwMoveMethod    #어디로 이동할 것인지
);

dwMoveMethod Flag
    FILE_BEGIN : 파일 처음부터
    FILE_END : 파일 끝에서
    FILE_CURRENT : 현재 위치에서