ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • IsDebuggerPresent(feat. codeengn basic 4)과 TEB & PEB의 관계
    Knowledge/Reversing 2020. 1. 28. 03:49
     
     
     
     
    목차
     
     
    1. 머리말
    2. 일반적인 Codeengn Basic 4번 풀이방법
    3. IsDebuggerPresent API
    4. BeingDebugged 값을 이용한 풀이 방법
    5. 맺음말
     
     

     
     
    1. 머리말
     
     
     
    오늘은 흔히들 한번쯤 풀어봤을 법한 Codeengn Basic 4번 문제를 주제로 글을 써보려고 한다.
    code engn 에서는 그저 디버깅을 탐지하는 API의 이름만 맞추면 풀리지만 거기에 더 나가서 우회까지 해보는 사람들도 있을 것이다.
    아마 이런식으로 다들 풀어보지 않았을까?
     
     
     
     

     
     
    2. 일반적인 Codeengn Basic 4번 풀이방법
     
     
     
     
     
    4번 문제이다.
    이녀석은 그냥 실행하면 "정상" 이라는 문구를 출력한다.
     
    하지만 디버거로 잡고
     
     
     
     
    F9로 실행시키면
     
     
     
     
    디버깅 당함이라고 뜬다.
     
    참 신기한 녀석이다 디버거를 어찌 알아차리는 것일까?
    그 이유를 파해쳐보는게 문제였다.
     
    그리고 그 이유는 밑에 사진에 나온 IsDebuggerPresent()라는 api가 원인이다.
     
     
     
     
    여기서 더 나아가서 우회를 시도하는 사람들이 있을 것이다.
    우리도 F8을 천천히 진행하며 OEP를 찾아보자.
     
     
     
     
    00408454를 지나면서 "디버깅 당함"이라는 문자열이 떴다.
     
    이 함수를 들어가보자.
     
     
     
     
    JMP 401030으로 가는 명령어가 적혀있다.
     
    401030은 바로 밑에 보임으로 쉽게 확인할 수가 있다.
    40105E에서 IsDebuggerPresent가 쓰이는 것을 볼 수가 있다.
     
     
     
     
    eax를 1 반환하는 것을 볼수가 있다.
    그리고 TEST EAX, EAX를 후 JE를 하여 ZF 0이면 40107E로 1이면 40106F를 진행하게 된다.
     
     
     
     
     zf(zero flag)가 0이다. (test값이 1로 0이 아니기 때문에 False(0)값이 들어가 있다.)
    그런고로 40106F로 간다.
     
    가보자
     
     
     
     
    401074에서 F7을 해주자.
     
     
     
     
    (대충 프린트문 함수가 실행되며 '디버깅당함' 무한루프가 돌아간다는 뜻)
     
     
    그렇다면 ZF가 1이 되도록 해주면 어떨까?
     
     
     
     
    401064에서 멈추어 EAX값을 0으로 바꾸어주자.
     
     
     
     
     
    아까와 달리 ZF가 1로 되어있다.
    (TEST EAX(1), EAX = 0이기에 0은 0이 맞기 때문에 TRUE 값인 1이 들어간 것이다.)
    이렇게 해줌으로써 004017E로 점프하게 된다.
    이제 401083로 들어가주자.
     
     
     
     
    (대충 실행시 "정상" 프린트문이 출력되는 무한루프에 걸린다는 뜻)
     
     
    여기서 주의할 것은 이 방법은 1회용(?)이라는 것이다.
    결국엔 한번은 정상이라 프린트하지만 다시 값을 체크하고 "디버깅 당함" 문구 출력 함수로 이동당하게된다.
     
    그렇다면 우리는 어떠한 방법으로 온전히 우회가 가능할까?
    그 방법을 들어가기 전에 우선 IsDebuggerPresent API라는 녀석을 살펴보고 들어가겠다.
     
     
     
     

     
    3. IsDebuggerPresent API
     
     
     
     
     
    IsDebuggerPresent API의 어셈 코드이다.
     
    FS:[18]은 무엇이며 이 코드들은 무슨 뜻을 하고 있을까?
    그것을 알기 위해서는 TEB, PEB, BeingDebugged에 대해 알아야한다.
     
     
     
    TEB
    TEB : 프로세스에서 실행되는 스레드에 대한 정보를 담고 있는 구조체
    스레드별로 TEB 구조체가 하니씩 할당됩니다.
     
     
     
    typedef struct _TEB {
        BYTE Reserved1[1952];
        PVOID Reserved2[412];
        PVOID TlsSlots[64];
        BYTE Reserved3[8];
        PVOID Reserved4[26];
        PVOID ReservedForOle;
        PVOID Reserved5[4];
        PVOID TlsExpansionSlots;
    }TEB, *PTEB;
     
     
    MSDN을 통해 확인해 본 TEB 구조체는 상당히 숨겨져 있었습니다.
    이런 경우 WinDbg의 심볼파일을 이용하면 더 자세히 확인할 수가 있습니다.
    워낙 많은 내용이 담겨져 있어서 중요하게 살펴 볼 것 두가지만 적겠습니다.
     
     
     
    +0x000 NtTib : _NT_TIB
    .
    .
    .
    +0x030 ProcessEnviromentBlock : Ptr32 _PEB
    .
    .
    .
     
     
     
    우선 NTTIB의 구조체를 살펴보면
     
    typedef struct _NT_TIB {
        struct _EXCEPTION_REGISTRATION_RECORD *ExceptionList;
        PVOID StackBase;
        PVOID StackLimit;
        PVOID SubSystemTib;
        union {
            PVOID FiberData;
            DWORD Version;
        };
        PVOID ArbitraryUserPointer;
        struct _NT_TIB *Self;
    } NT_TIB;
    typedef NT_TIB *PNT_TIB;
     
     
     
    첫번째 멤버인 ExceptionList는 EXCEPTIONREGISTRATION_RECORD 구조체 연결리시트를 가리킵니다.

     

    여기서는 자세히 확인하지는 않을 것임으로 SEH라는 예외 처리 기능에 사용되는 녀석이라는 것만 집고 넘어가겠습니다.
     
     
     
    우리가 중요하게 볼 것은 PEB인데 TEB의 첫 시작주소를 찾을 수 있다면 PEB의 시작 주소도 찾아낼 수 있을 것입니다.
     
     
     
     
     
    연습파일인 codeengn basic 4번 파일을 디버거를 통해 열어보겠습니다.
    우클릭 후 Search for Name in all modules 을 선택해줍니다.
    거기서 NtCurrentTeb를 더블클릭 해줍니다.
     
     
     
     
     
    더블 클릭을 하면
     
     
     
     
     
    아주 짧은 코드를 확인할 수 있는데요.
    eax 에 주소 하나를 담아서 리턴 시킵니다.
     
    FS:[18]을 확인하면 ...
     
     
     
     
    ???
    ???????
    아니 왜 파싱을 못해오니...
     
     
     
    왜이런지 모르겠지만 플랜B를 통해 TEB의 주소를 알아낼 수 있습니다.
     
    우선 이 FS:[18]을 왜 확인하려고 하는지 부터 얘기하자면
    FS 세그먼트 레지스터는 현재 스레드의 TEB와 연관이 있습니다.
    Segment Descriptor Table의 Index에는 실제 TEB 주소를 담고 있는데 이 Table의 주소를 갖고 있는 것이 FS 세그먼트 레지스터입니다.
    그러한 이유로 FS의 시작 주소에 TEB 구조체가 위치해 있는 것입니다.
     
    자 그러면 왜 하필 0x18일까여?
    그 이유는 TEB의 첫번째 멤버인 NtTib를 통해 살펴봐야 합니다.
    NtTib 구조체에서 0x18에 해당하는 값을 살펴봅시다.
     
    (살짝 위로 올리면 NtTib 구조체를 복습할 수가 있습니다.)
     
     struct _EXCEPTION_REGISTRATION_RECORD *ExceptionList; //(4bytes)
        PVOID StackBase; //(4bytes)
        PVOID StackLimit; //(4bytes)
        PVOID SubSystemTib; //(4bytes)
        union {
            PVOID FiberData;
            DWORD Version;
        }; //(4bytes)
        PVOID ArbitraryUserPointer; //(4bytes)
     
     
    까지 더하면
    4 * 6 = 24(= 0x18)
    입니다.
     
     
    그래서 그 다음 멤버가 FS:[18]이 가리키고 있는 녀석인데요.
    그래서 18은
    struct _NT_TIB *Self;
    을 뜻합니다.
    NT_TIB의 주소라는 뜻이져.
     
     
    TEB.NtTib.Self = address of TIB = address of TEB로 결국 TEB의 시작주소를 나타내고 있습니다.
     
     
    그래서 우리가 NtCurrentTeb가 살펴봤을때 FS:[18]을 리턴하던 것입니다.
     
    아니 그나저나 왜안되지;
     
     
    팀블로그를 하다보니 경어체를 한동안 안썼네요. 이제 다시 경어체를 쓰겠습니다. 내블로그니까
     
     
    이유는 모르지만 ???로 뜬다.
    그래서 플랜B를 사용해서 FS:[18]의 값을 확인해 보겠다.
    그 방법은 바로 직접(?) 적어서 확인하는 방법니다.
     
     
     
     
     
     
     
    04를 실행 했을때의 화면이다.
    그냥 아무 곳에서나 바꿔서 확인해보자.
     
     
     
     
     
    아무곳이나 누른 뒤에 MOV EAX, DWORD PTR FS:[18] 라고 수정하고 눌러 보자.
     
     
     
     
     
     
    FS:[18] = [7FFDF018] 이라고 적혀있다.
    이곳이 정확히는 TEB.NtTib.Self의 값으로 TEB의 시작주소 이다.
     
    왜 FS:[0]을 가리키는 곳이 시작 주소가 아닐까?
    조금만 생각해보면 알수 있다.
     
    [0] 에도 어떠한 멤버를 가리키는 주소값이 들어있기 때문이다.
    [0]에는 TEB.NtTib에 첫 멤버인 SEH를 가리키는 포인터가 들어있기 때문이다.
    그래서 우리는 TEB의 시작주소를 FS:[18]을 통해 확인하는 것이다.
     
     
    이제 FS를 통해 TEB의 시작 주소를 알았으니 ctl + F2를 눌러 재시작해주자.
     
     
     
     
     
     
    다시 NtCurrentTeb로 가줍시다.
     
     
     
     
    하지만 이대로는 7FFDF018로 갈수가 없다.
     
     
     
     
    우클릭 Follow in Dump Selection
    (api를 찾지 않고 view ntdll 클릭 후 진행해도 무방하다.)
    을 하게되면
     
     
     
     
    짜잔~~
    ntdll 쪽으로 너머오게 됩니다.
     
    여기서 검색해줍시다.
     
     
     
     
    리틀엔디안으로 입력되어 있군요.
     
    7FFDF000이 TEB시작 위치값이라는 걸 알 수 있음과 동시에
    당연히 FS:[18]에 주소에서 18값만 빼면 시작 주소인 것을 왜 우리는 생각하지 않았는가를 되돌아볼 필요가 있다.
     
    그럼 이제 TEB 시작주소를 확인했으니 PEB 주소를 알아보자.
     
    TEB의 +0x00에는 NtTib 가 존재한다는 것을 배웠다.
    그리고 +0x30에 ProcessEnviromentBlock (Ptr32 _PEB)가 존재한다고 말했을 것이다.
     
    7FFDF000 + 30 을 한 곳을 가면 PEB 의 시작주소를 알수 있다.!
     
    (여기서 우리는 TEB의 구조를 어느정도 이해했다면 그냥 바로 FS:[30]으로 주소를 구해 접근해도 된다는 것을 떠올릴 수 있다.
    우리가 어셈코드로만 보기 때문에 [18]을 참조해서 힘들게 구하는게 부자연스러운 것이지 사실 프로그램이 FS:18]에 값을 가져오는 것은 코드상에서 self멤버를 참조하기 때문이지 코딩상에서 누가 어셈 직접 입력하고 있겠나.. )
     
     
    그럼 도대체 PEB는 왜 자꾸 말하는지 이제 PEB에 대해 Alaboza.
     
     
     
    PEB
     
     
     
     
     
    7FFDF030 을 보면 7FFDD000에서 PEB가 시작한다고 가리키고 있다.
     
    따라가보자.
     
     
     
    마치 속이 꽉찬 김수미 간장ㄱ..ㅔ읍읍..
     
     
    PEB에 도착하였다.
     
    우리는 이 값을 조작해서 코드엔진 베이직 4번문제를 풀수 있을 것이다.
     
    그럼 PEB의 구조체부터 살펴보자.
     
     typedef struct _PEB {
        BYTE    Reserved1[2];
        BYTE    BeingDebugged;
        BYTE    Reserved2[1];
        PVOID    Reserved3[2];
        PPEB_LDR_DATA    LDR;
        PRTL_USER_PROCESS_PARAMETERS    ProcessParameters;
        BYTE    Reserved4[104];
        PVOID    Reserved5[52];
        PPS_POST_PROCESS_INIT_ROUTINE    PostProcessInitRoutine;
        BYTE    Reserved6[128];
        PVOID    Reserved7[1];
        ULONG    SessionId;
    }PEB, *PPEB;
     
     
     
    아까보다는 구체적이지만 여전히 알송달송하다.
    하지만 오늘 우리가 볼 곳은 바로 BeingDebugged 부분이다!
    이곳이 오늘 포스트의 실마리이기 때문이다.
     
     
    그래도 주요 멤버 몇가지만 더 집고 가자면
     
     
    +0x002 BeingDebugged : UChar
    +0x008 ImageBaseAddress : Ptr32 Void
    +0x00C Ldr : Ptr32 PEBLDR_DATA
    +0x018 ProcessHeap : Ptr32 Void
    +0x068 NtGlobalFlag : Uint4B
     
     
    정도 참고할 필요가 있다.
     
     
    오늘 필자는 BeingDebugged만 살펴 볼 것이다.
    왜냐하면 이 BeingDebugged가 바로 IsDebuggerPresent()가 참고하는 녀석이기 때문이다.
    이 값을 조작함으로써 IsDebuggerPresent 결과를 조작하고 우회할 수 있게된다.
     
    이 곳은 덤프를 통해 확인하면
     
     
     
     
     
     
    바로 이곳이다.
     
    01로 되어있는데 디버깅 중이라면 1(True) 값이 들어가고 디버깅 중이 아닌 프로세스라면 0(False)값이 들어가는 곳이다.
     
     
    이 곳에 접근함을 통해서 프로세스가 디버깅에 탐지중인지 아닌지 값을 리턴받아서 확인 할 수 있게된다.
    그래서 이 값을 참조하는 IsDebuggerPresent를 우회하기 위해 이 영역에 접근하여 값을 조작해주는 것이다.
     
    이제는 아까의 IsDebuggerPresnt의 어셈코드를 이해할 수 있을것이다.
     
    MOV EAX, DWORD PTR FS:[18]        // TEB의 시작주소
    MOV EAX, DWORD PTR DS:[EAX+30]    // TEB+30 = PEB 시작주소
    MOVZX EAX, BYTE PTR DS:[EAX+2]    // PEB.BeingDebugged 값
    RETN                                // EAX <- BeingDebugged
     
     
     
    설명안해도 이제 IsDebuggerPresent가 어떻게 동작하는지 알 것이다.
     
     
    그러면 이제 기존 라업과 다르게 이 값을 0으로 수정하여  문제를 풀어보자!
     
     
     
     
     
     

     
     
    4. BeingDebugged 값을 이용한 풀이 방법
     
     
     
     
     
    필자의 올리디버거는 어째서인지 주소를 잘 못가져 옴으로 다른 방법을 통해서 주소를 가져왔다.
     
    위 사진을 보면 필자가 클릭한 곳의 명령어가 MOV EAX, DWORD PTR FS:[0] 이다.
    저기까지 실행해주면 FS:[0]에 담긴 TEB의 시작 주소를 알 수가 있다.
     
    0040837F를 누르고 F4를 눌러줍시다.
     
     
     
     
    TEB는 7FFDE000에서 시작한다는 것을 알 수가 있다.
     
    7FFDE030으로 가자.
     
     
     
     
    ntdll로 가준다.
     
     
     
     
     
    follow in dump를 해주자.
     
     
    덤프 후 7FFDE030로 가줍시다.
     
     
    7FFDF000에서 PEB가 시작된다고 한다.
     
    가보자.
     
     
     
     
    01을 00으로 바꿔주자.
     
    그리고 다시 F9를 누르면 완벽
     
     
     
     
    성공적으로 우회하였다.
     
     
     
     
     

     
     
     
    5. 맺음말
     
     
     
    이상으로 어째서 코드엔진 베이직 4번 문제를 풀때 1을 0으로 바뀌는지 좀더 구체적이고 자세하게 알아보는 시간을 가져봤다.
    솔직히 처음 풀때는 라업도 보며 바꾸라니까 바꾸고 여기라니까 그냥 접근해서 수정했는데 좀더 공부하면서 PEB도 배우고 BeingDebugged도 배우는 시간을 가지게 되었다.
     
    이상 끝!
     
     
     

    댓글

Designed by Tistory.