어셈블리(Assembly) 언어로 생성한 코드를 가지고 Injector 를 만들어 보도록 하겠습니다.

이전 강좌의 내용은 아래 링크를 참조하시기 바랍니다.




ThreadProc() 함수의 Binary 코드 얻기
지난 강좌에서 생성한 asmtest_patch.exe 파일을 OllyDbg 로 열어 보겠습니다. 우리가 프로그래밍한 ThreadProc() 의 주소는 401000 입니다. 메모리 윈도우에서 401000 주소로 갑니다.

 
<Fig. 1>

ThreadProc() 함수는 401000 ~ 401061 의 주소 영역입니다. 위 그림과 같이 이 영역을 선택하신 후 마우스 우측 메뉴의 "Copy - To file" 항목을 선택하시기 바랍니다. (아래 그림 참고)


<Fig. 2>

이렇게 저장한 파일을 텍스트 에디터로 열어보겠습니다. (GVIM 또는 AcroEdit 추천합니다.)


<Fig. 3>

위 텍스트 파일의 내용은 Hex 값으로 표현된 ThreadProc() 함수로써, IA-32 OpCode (Operation Code) 명령어 입니다. 이 내용은 곧 상대방 프로세스에 인젝션 시킬 코드가 되는 것입니다.

위 텍스트 파일을 아래와 같이 편집합니다. 불필요한 부분을 제거하고 모든 바이트마다 "0x" 표시를 붙여주고 ',' 로 연결합니다. 텍스트 에디터의 편집 기능(열 선택, 문자열 변경)을 적절히 사용하시면 편리합니다.


<Fig. 4>

위 그림에서 편집된 텍스트 내용을 보면 마치 C 언어의 BYTE 배열처럼 보이지 않습니까? 이게 바로 인젝션 시킬 코드 버퍼입니다. (아래 설명되는 CodeInjection2.cpp 파일에서 사용됩니다.)

편집된 텍스트 파일을 첨부합니다.



CodeInjection2.cpp
Injector 프로그램의 소스 코드입니다. 위에서 텍스트 에디터로 만든 코드 버퍼는 아래 소스에서 g_InjectionCode 배열에 사용되었습니다.

* 참고!
아래 소스코드는 MS Visual C++ 2008 Express Edition 에서 개발되었으며, Windows 7 32bit 환경에서 테스트 되었습니다. 또한 설명의 편의를 위하여 리턴 값 체크와 에러 처리 코드는 생략되었습니다. 원본 소스 코드는 위에 첨부된 CodeInjection2.cpp 파일을 참고하시기 바랍니다.

typedef struct _THREAD_PARAM 
{
    FARPROC pFunc[2];               // LoadLibraryA(), GetProcAddress()
} THREAD_PARAM, *PTHREAD_PARAM;

// ThreadProc()
BYTE g_InjectionCode[] = 
{
    0x55, 0x8B, 0xEC, 0x8B, 0x75, 0x08, 0x68, 0x6C, 0x6C, 0x00,
    0x00, 0x68, 0x33, 0x32, 0x2E, 0x64, 0x68, 0x75, 0x73, 0x65,
    0x72, 0x54, 0xFF, 0x16, 0x68, 0x6F, 0x78, 0x41, 0x00, 0x68,
    0x61, 0x67, 0x65, 0x42, 0x68, 0x4D, 0x65, 0x73, 0x73, 0x54,
    0x50, 0xFF, 0x56, 0x04, 0x6A, 0x00, 0xE8, 0x0C, 0x00, 0x00,
    0x00, 0x52, 0x65, 0x76, 0x65, 0x72, 0x73, 0x65, 0x43, 0x6F,
    0x72, 0x65, 0x00, 0xE8, 0x14, 0x00, 0x00, 0x00, 0x77, 0x77,
    0x77, 0x2E, 0x72, 0x65, 0x76, 0x65, 0x72, 0x73, 0x65, 0x63,
    0x6F, 0x72, 0x65, 0x2E, 0x63, 0x6F, 0x6D, 0x00, 0x6A, 0x00,
    0xFF, 0xD0, 0x33, 0xC0, 0x8B, 0xE5, 0x5D, 0xC3
};

/*
// ThreadProc()
004010ED    55               PUSH EBP
004010EE    8BEC             MOV EBP,ESP
004010F0    8B75 08          MOV ESI,DWORD PTR SS:[EBP+8] 
004010F3    68 6C6C0000      PUSH 6C6C                      
004010F8    68 33322E64      PUSH 642E3233
004010FD    68 75736572      PUSH 72657375
00401102    54               PUSH ESP                       
00401103    FF16             CALL DWORD PTR DS:[ESI] 
00401105    68 6F784100      PUSH 41786F
0040110A    68 61676542      PUSH 42656761
0040110F    68 4D657373      PUSH 7373654D
00401114    54               PUSH ESP                      
00401115    50               PUSH EAX                      
00401116    FF56 04          CALL DWORD PTR DS:[ESI+4]   
00401119    6A 00            PUSH 0                        
0040111B    E8 0C000000      CALL 0040112C
00401120    <ASCII>                                         
0040112C    E8 14000000      CALL 00401145
00401131    <ASCII>                                         
00401145    6A 00            PUSH 0                         
00401147    FFD0             CALL EAX                       
00401149    33C0             XOR EAX,EAX                        
0040114B    8BE5             MOV ESP,EBP
0040114D    5D               POP EBP                            
0040114E    C3               RETN
*/

BOOL InjectCode(DWORD dwPID)
{
    HMODULE         hMod            = NULL;
    THREAD_PARAM    param           = {0,};
    HANDLE          hProcess        = NULL;
    HANDLE          hThread         = NULL;
    LPVOID          pRemoteBuf[2]   = {0,};

    hMod = GetModuleHandleA("kernel32.dll");

    // set THREAD_PARAM
    param.pFunc[0] = GetProcAddress(hMod, "LoadLibraryA");
    param.pFunc[1] = GetProcAddress(hMod, "GetProcAddress");

    // Open Process
    hProcess = OpenProcess(PROCESS_ALL_ACCESS,    
                           FALSE,                     
                           dwPID);                    

    // Allocation for THREAD_PARAM
    pRemoteBuf[0] = VirtualAllocEx(hProcess,       
                                   NULL,               
                                   sizeof(THREAD_PARAM), 
                                   MEM_COMMIT,            
                                   PAGE_READWRITE);       

    WriteProcessMemory(hProcess,                      
                       pRemoteBuf[0],                  
                       (LPVOID)&param,                 
                       sizeof(THREAD_PARAM),          
                       NULL);                           

    // Allocation for g_InjectionCode
    pRemoteBuf[1] = VirtualAllocEx(hProcess,           
                                   NULL,                   
                                   sizeof(g_InjectionCode), 
                                   MEM_COMMIT,              
                                   PAGE_EXECUTE_READWRITE); 

    WriteProcessMemory(hProcess,                        
                       pRemoteBuf[1],                     
                       (LPVOID)&g_InjectionCode,         
                       sizeof(g_InjectionCode),          
                       NULL);                             

    hThread = CreateRemoteThread(hProcess,             
                                 NULL,                    
                                 0,                       
                                 (LPTHREAD_START_ROUTINE)pRemoteBuf[1], 
                                 pRemoteBuf[0],          
                                 0,                       
                                 NULL);                   

    WaitForSingleObject(hThread, INFINITE);

    CloseHandle(hThread);
    CloseHandle(hProcess);

    return TRUE;
}

<코드 1>

위의 코드와 지난번 CodeInjection.cpp 의 코드와의 가장 큰 차이점은 인젝션 시키는 코드 내에 필요한 문자열 데이터를 같이 포함시킨 것입니다. 

따라서 _THREAD_PARAM 구조체에서 문자열 멤버가 사라졌습니다. 그리고 기존 C로 된 ThreadProc() 함수 대신 <Fig. 4> 의 OpCode 버퍼(g_InjectionCode)가 사용됩니다. (이 OpCode 버퍼를 생성하기 위해 OllyDbg 의 “Assembly” 명령을 사용했던 것을 기억하시죠?)

조금 더 정교하게 프로그래밍을 했다면 _THREAD_PARAM 구조체조차 필요 없도록 만들 수도 있습니다. 세부적인 구현 방법은 어디까지나 구현하는 사람의 마음입니다. 

중요한 것은 어셈블리 프로그래밍을 통해서 생성된 OpCode 버퍼를 Injector 소스코드에 사용하여 상대방 프로세스에 인젝션 시킨다는 것입니다.

예전에 설명 드린 CodeInjection.cpp 소스 코드와 비교해서 보시면 차이점을 더 명확히 이해할 수 있을 것입니다. (위 <코드 1> 의 세부 설명은 생략합니다. Code Injection 의 구현 방법에 대해서는 밑의 링크된 설명을 참고하시기 바랍니다.)



다음 강좌에서 실제로 상대방 프로세스에 인젝션 시킨 후 디버깅을 하면서 저 어셈블리 코드가 어떤 의미를 가지고 있는지 알아보도록 하겠습니다.

☞ 어셈블리 언어를 이용한 Code Injection (4)


ReverseCore

위 글이 도움이 되셨다면 추천(VIEW ON) 부탁 드려요~

Trackback Address :: http://www.reversecore.com/trackback/86 관련글 쓰기

  1. 지나가던이 2010/07/03 08:01 댓글주소 | 수정 | 삭제 | 댓글

    좋은 글 감사합니다. 항상 잘 배우고 있습니다.
    전 그동안은 코드 인젝션 할땐 DLL 인젝션과 병행 해서, 인젝션 코드에서 DLL의 함수를 call 하도록 간단하게 만들었었는데, 이렇게 함수 코드를 직접 집어넣는 방법도 괜찮네요.

    • reversecore 2010/07/03 14:56 댓글주소 | 수정 | 삭제

      네, 상황에 맞게 적절히 선택해서 사용하시면 되는데요.
      보통은 DLL Injection 이 많이 쓰입니다.

      Code Injection 을 소개하는 이유는 리버싱에 대해 좀 더 깊이있는 공부를 해보자는 뜻입니다.

      감사합니다.


◀ PREV : [1] : ... [6] : [7] : [8] : [9] : [10] : [11] : [12] : [13] : [14] : ... [91] : NEXT ▶