본문 바로가기

High Level Technique/Reversing

Self Creation Debugging

Self Creation Debugging


Self Creation Debugging은 실행 중 자기 자신을 자식 프로세스로 생성시키는 프로그램을 디버깅하는 것을 말합니다.

같은 프로그램이지만 부모 프로세스로 실행될 때와 자식 프로세스로 실행될 때 다르게 동작합니다.





동작원리



Create Child Process (SUSPEND mode)


부모 프로세스가 실행되면 main()이 호출되는데 자식 프로세스를 SUSPEND 모드로 생성합니다. SUSPEND 모드로 생성되면 Import DLL은 로딩 되지만 Main Thread가 멈춘상태가 됩니다.

main thread는 프로세스가 생성될 때 기본적으로 생성되는 thread 인데, EP 코드를 실행시키는 역할을 담당하고 있습니다. SUSPEND 모드로 실행된 프로세스는 main thread가 멈추면 EP 코드가 실행되지 못하고 아무런 동작을 하지 않습니다.



Change EIP


부모 프로세스에서 자식 프로세스의 코드 실행 주소를 임의로 변경합니다. 현재 멈춰있는 자식 프로세스의 메인 쓰레드의 Context를 얻어서 EIP 멤버를 원하는 주소 값으로 변경하면 됩니다.



Resume Main Thread


자식 프로세스의 멈춰져 있는 메인 쓰레드를 실행합니다. 




실습예제인 DebugMe2.cpp 코드를 살펴보도록 하겠습니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
#include <windows.h>
#include <tchar.h>
#include <stdio.h>
 
 
void ChildProc()
{
    MessageBox(NULL, L"This is a child process!", L"DebugMe2", MB_OK);
 
    ExitProcess(0);
}
 
 
void _tmain(int argc, TCHAR *argv[]) 
{
    TCHAR                   szPath[MAX_PATH] = {0,};
    STARTUPINFO                si = {sizeof(STARTUPINFO),};
    PROCESS_INFORMATION        pi = {0,};
    CONTEXT                 ctx = {0,};
 
    _tprintf(L"This is a parent process!\n");
 
    if!GetModuleFileName(NULL, szPath, sizeof(TCHAR) * MAX_PATH) )
    {
        printf("GetModuleFileName() failed! [%d]\n", GetLastError());
        return;
    }
 
    // Create Child Process
    if!CreateProcess(
            szPath,
            NULL,
            NULL,
            NULL,
            FALSE,
            CREATE_SUSPENDED,
            NULL,
            NULL,
            &si,
            &pi) )
    {
        printf("CreateProcess() failed! [%d]\n", GetLastError());
        return;
    }
 
    // Change EIP
    ctx.ContextFlags = CONTEXT_FULL;
    if!GetThreadContext(pi.hThread, &ctx) )
    {
        printf("GetThreadContext() failed! [%d]\n", GetLastError());
        return;
    }
 
    ctx.Eip = (DWORD)ChildProc;
 
    if!SetThreadContext(pi.hThread, &ctx) )
    {
        printf("SetThreadContext() failed! [%d]\n", GetLastError());
        return;
    }
 
    // Resume Main Thread
    if-1 == ResumeThread(pi.hThread) )
    {
        printf("ResumeThread() failed! [%d]\n", GetLastError());
        return;
    }
 
    WaitForSingleObject(pi.hProcess, INFINITE);
 
    CloseHandle(pi.hProcess);
    CloseHandle(pi.hThread);
}
 
cs



일반적으로 실행시킨 경우 main()가 호출됩니다. CreateProcess를 이용해여 자기 자신을 SUSPEND모드로 실행 시킵니다.

새로 생성된 자식 프로세스는 Import DLL들을 모두 로딩시키지만 main thread는 멈춘 상태가 됩니다.


CreateProcess()가 리턴되면 마지막 파라미터인 PROCESS_INFORMATION 구조체 변수 pi에 새로 생성된 프로세스와 관련된 정보가 저장됩니다.



hThread 멤버가 자식 프로세스의 main thread handle 입니다. 이 hThread 핸들을 이용하면 해당 thread를 마음대로 제어할 수 있습니다.


GetThreadContext에 hThread 핸들을 넣으면 Thread Context 구조체를 얻어낼 수 있습니다. 


ctx.Eip = ChildProc을 통해서 EIP의 값을 ChildProc 주소로 변경합니다.


그리고 SetThreadContext를 호출해서 변경된 Context 구조체를 자식 프로세스의 메인 쓰레드에 세팅하고 ResumThread()를 호출하면 자식 프로세스의 메인 쓰레드가 실행되면서 변경된 EIP를 실행합니다.








디버깅


Self Creation 기법을 디버깅할때 서비스 디버깅을 한 것처럼 EP를 찾아 무한 루프 코드로 바꾸고 분석 시 다시 원본 코드로 변경하여 분석하는 방법이 있습니다.

이번에는 Just In Time(이하 JIT) 방법으로 디버깅을 해봅니다.


참고로 SUSPEND 모드로 만들어진 프로세스는 Attach를 할 수 없습니다.



Just In Time Debugging (JIT)


JIT 디버깅이란 실행 중인 프로세스에 예외가 발생했을 경우 OS에서 자동으로 지정된 디버거를 실행하여 해당 프로세스에 Attach 해주는 것입니다. 

예외가 발생한 위치에서부터 디버깅을 진행할 수 있기 때문에 예외 원인 파악에 좋습니다.



Make OllyDbg just-in-time debugger를 선택해 주면 됩니다.



DebugMe2.exe를 디버깅 해보도록 하겠습니다.




main 부분을 분석해 보면 CreateProcess 부분이 나타납니다.


CreateProcess API를 호출하면 SUSPEND 모드의 자식 프로세스가 생성되고 좀 더 진행하면 아래와 같이 SetThreadContext 부분이 나타납니다.



401186 주소에서 EBP-420 의 값이 CONTEXT.Eip이고 401000이 ChildPRoc의 시작 주소입니다.


pContext의 값과 hThread의 값은 스택을 보면 알 수 있습니다.


이제 자식 프로세스를 디버깅 해야 합니다. Stud_PE를 이용해서 401000주소를 file offset 으로 변환합니다.



그러면 400 위치로 이동해서 해당 값을 CC로 변경한 후 DebugMe2_CC.exe로 저장합니다.


0xCC는 INT 3으로 EXCEPTION_BREAK_POINT 예외가 발생하게 됩니다.



여기서 디버그를 눌러줍니다.



JIT로 설정을 해두었기 때문에 OllyDBG가 새로 실행되면서 에러가 발생한 부분에서 분석을 할 수 있습니다.


이 상태에서 다시 원래 코드로 변경해서 디버깅을 진행하면 됩니다.


'High Level Technique > Reversing' 카테고리의 다른 글

Debug Blocker  (0) 2016.08.10
PE Image Switching  (2) 2016.08.10
Service Debugging  (0) 2016.08.09
Advanced Anti Debugging  (0) 2016.08.09
Anti Debugging - Dynamic  (1) 2016.08.08