Code Injection 방지

Code injection 차단에 관련 하여 이전에 간단히 짜두었던 프로그램이 있었습니다.

하지만 최근 User-Level에서 이것 까지 멋지게 차단 하는 방법들이 많이 등장 했더군요.
이전에 작성 했던 Code는..
VOID SSDTHook(VOID)
{
        ZwWriteVirtualMemoryNext = (ZWWVM)SYSTEMSERVICE_NE(0x115);
        ZwAllocateVirtualMemoryNext = (ZWAVM)SYSTEMSERVICE_NE(0x11);

        g_pmdlSystemCall = MmCreateMdl(NULL, KeServiceDescritorTable.ServiceTableBase,
                                            KeServiceDescritorTable.NumberOfServices*4);
        
        MmBuildMdlForNonPagedPool (g_pmdlSystemCall);

        g_pmdlSystemCall->MdlFlags = g_pmdlSystemCall->MdlFlags | MDL_MAPPED_TO_SYSTEM_VA;

        MappedSystemCallTable = MmMapLockedPages(g_pmdlSystemCall, KernelMode);

        ZwWriteVirtualMemoryNext = (ZWWVM)InterlockedExchange((PLONG)&SYSTEMSERVICE_NE(0x115),(LONG)ZwWriteVirtualMemoryHook);
        ZwAllocateVirtualMemoryNext = (ZWAVM)InterlockedExchange((PLONG)&SYSTEMSERVICE_NE(0x11),(LONG)ZwAllocateVirtualMemoryHook);
        InitializeListHead(&ListOfMalAllocHead);

}
로 SSDT
ZwWriteVirtualMemory,ZwAllocateVirtualMemory
두 NativeAPI를 SSDT 후킹 했었습니다.
OpenProcess나 CreateProcess를 후킹 해서 이를 차단 하는 방법도 소개 되었던거 같은데 DuplicateHandle을 사용하여 이를 우회 할 수 있습니다. 때문에
위의 두 녀석을 후킹 하게 되었죠.

NTSTATUS ZwAllocateVirtualMemoryHook
(
        IN HANDLE ProcessHandle,
        IN OUT PVOID *BaseAddress,
        IN ULONG ZeroBits,
        IN OUT PULONG AllocationSize,
        IN ULONG AllocateType,
        IN ULONG Protect
)
첫 인자인 ProcessHandle이 0xFFFFFFFF 이 아니라면 자신의 메모리가 아닌 타 프로세스의 메모리에 공간을 할당 하는 것으로 시스템 프로세스들이 자주 이것을 하곤 합니다.

아무튼 이렇게 다른 놈 공간에 할당 하는 놈들은 저장 해 둡니다.
(HANDLE CHandle = PsGetCurrentProcessId();)
INJECT_EXT *malAlloc= NULL;
malAlloc = (INJECT_EXT *)ExAllocatePool(NonPagedPool, sizeof(INJECT_EXT));
이런 식으로 저장 linked list로 만들 었었습니다.

NTSTATUS ZwWriteVirtualMemoryHook(
        IN HANDLE ProcessHandle,
        IN PVOID BaseAddress,
        IN PVOID Buffer,
        IN ULONG NumberOfBytesToWrite,
        OUT PULONG NumberOfBytesWritten
)
if(CHandle == ProcessHandle)
아무튼 첫 번째 인자인 ProcessHandle과 아래의 현재 프로세스 핸들을 비교 해서
HANDLE CHandle = PsGetCurrentProcessId();
틀리고 그 CHanlde이 이전 linked list로 저장 해 두었던 핸들과 일치 한다면 API를 실행 하지 않고 return 해 버리는 것입니다.

Dll injection과 Code Patching등은 차단이 되지만 SetWindowsHook()은 막을 수 없었습니다.
dll injection에서는 dll 이름을 타켓 프로세스에 기록 해야 하고, code injection에서는 Thread 루틴을 기록 해야 하는 것을 이용한 방법입니다.

자 다음은 User-level의 방법입니다. rootkit의 Opcode의 아이디어를 좀 더 분석 하고 쉽게 사용 할 수 있도록 일종의 selfmodify code화 하였습니다.

dll injection에 대하여 간단 하게 동작을 살펴 보자면 다음과 같은 API들을 실행 합니다.
AdjusttokenPrivileges()
OpenProcess() or CreateProcess()
VirtualProtectEx() or VirtualAllocEx()  게임 프로세스내 빈 공간 할당
WriteProcessMemory()                    주입 할 dll 이름을 할당 된 공간에 write
CreateRemoteProcess()                   LoadLibrary를 Thread 루틴으로 하여 원격 Thread 생성

마지막 CreateRemoteProcess()가 실행 되게 되면, 타켓이 되는 게임 프로세스 내에서는 kernel32.dll의 BaseThreadStartThunk라는 undocument된 API가 실행 됩니다.
이 API가 실제 Thread를 실행 하게 됩니다.
그리고 Thread의 루틴인 LoadLibrary가 Abusing 코드를 담은 dll을 로드 합니다.

다음이 해당 코드 부분입니다.
text:7C810659 ; __stdcall BaseThreadStartThunk(x, x)
.text:7C810659 _BaseThreadStartThunk@8 proc near       ; DATA XREF: BaseInitializeContext(x,x,x,x,x)+67
.text:7C810659                 xor     ebp, ebp
.text:7C81065B                 push    ebx
.text:7C81065C                 push    eax
.text:7C81065D                 push    0
.text:7C81065F                 jmp     _BaseThreadStart@8 ; BaseThreadStart(x,x)
.text:7C81065F _BaseThreadStartThunk@8 endp

kernel32.dll은 항상 동일 한 위치에 로드 된다는 사실과 위의 정보를 조합하면 CreateRemoteThread를 이용한 Code injection을 차단 할 수 있습니다.
다음 부분의 코드를 수정 하는 것입니다.
.text:7C810659                 xor     ebp, ebp
.text:7C81065B                 push    ebx

이 부분의 코드를 RET이나 call exit로 바꾸어 버리면  툴을 사용 하여 injection 하는 것을 무력화 시킬 수 있습니다.

다음은 예제 코드 입니다.
MFC 기본 프로그램의 생성자에서 해당 코드 부분을 삽입 하여 dll injection을 차단 할 수 있었습니다.
CTestDlg::CTestDlg(CWnd* pParent /*=NULL*/)
        : CDialog(CTestDlg::IDD, pParent)
{
        //{{AFX_DATA_INIT(CTestDlg)
                // NOTE: the ClassWizard will add member initialization here
        //}}AFX_DATA_INIT
        // Note that LoadIcon does not require a subsequent DestroyIcon in Win32
        // original code
        m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);


        DWORD BaseThreadStart = 0x7C810659; //이 하드 코딩된 주소 값은 xpsp3 가 나오는 등의 큰 변화가 있지 않는 이상은 잘 변하지 않습니다.
        DWORD RET;
        HANDLE hProcess;                                
        MEMORY_BASIC_INFORMATION mbi;
        DWORD PRT2,savePRT2;
        hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, GetCurrentProcessId());
        
        RET = (DWORD )"\xc2\x00\x00\x50"; // RET을 주입 합니다. 프로세스에 injection 시도 시 return 하는 코드를 만나 종료 되게 됩니다.

        VirtualQueryEx(hProcess,(LPVOID)BaseThreadStart,&mbi,sizeof(mbi)); // 변경 해야 할 주소의 PAGE는 Read-Only 입니다.
                                                                           // 때문에 잠시 writeable 하게 변경 해 줍니다.
        savePRT2 = PRT2 = mbi.Protect;
        PRT2 &= ~(PAGE_READONLY | PAGE_EXECUTE_READ);
        PRT2 |= (PAGE_READWRITE);

        if(!VirtualProtectEx(hProcess, (LPVOID) BaseThreadStart, 4, PAGE_EXECUTE_READWRITE, &PRT2)) {
        }
        __asm {
                mov eax, RET;
                mov dword ptr ds:[0x7C810659], eax
        }
        if(!VirtualProtectEx(hProcess, (LPVOID) BaseThreadStart, 4, PAGE_READONLY, &savePRT2)) { //다시 readonly로 바꾸어 놓습니다.
        }
        CloseHandle(hProcess);
}

다음은 SetWindowsHook을 이용한 dll의 주입 차단 입니다.
SetWindowsHook를 실행 하면 게임 프로세스에서는 ClientLoadLibrary라는 아래와 같은 함수가 실행 되게 됩니다.
역시 user32.dll 역시 항상 동일 한 주소에 로드 되기 때문에 1번과 마찬가지 방법으로 RET이나 call exit 코드를 주입 하여 종료 시킵니다.

다음이 해당 코드의 시작 부분 입니다.
; __stdcall __ClientLoadLibrary(x)
___ClientLoadLibrary@4 proc near

var_C= dword ptr -0Ch
var_8= dword ptr -8
var_4= dword ptr -4
arg_0= dword ptr  8

mov     edi, edi
push    ebp
mov     ebp, esp
sub     esp, 0Ch
...
77D0DBD3     8B75 08        MOV ESI,DWORD PTR SS:[EBP+8]   -> ClientLoadLibrary의 인자 값

AC 00 00 00 84 00 00 00 01 00 00 00 7C B8 C7 A4  ?..?.....|맨?   -> Load할 DLL 정보가 포함되어 있음
24 00 00 00 00 00 00 00 82 00 84 00 28 00 00 00  $.......??(...
00 00 00 00 1C 00 00 00 43 00 3A 00 5C 00 50 00  .......C.:.\.P.
72 00 6F 00 67 00 72 00 61 00 6D 00 20 00 46 00  r.o.g.r.a.m. .F.
69 00 6C 00 65 00 73 00 5C 00 4D 00 69 00 63 00  i.l.e.s.\.M.i.c.
72 00 6F 00 73 00 6F 00 66 00 74 00 20 00 56 00  r.o.s.o.f.t. .V.
69 00 73 00 75 00 61 00 6C 00 20 00 53 00 74 00  i.s.u.a.l. .S.t.
75 00 64 00 69 00 6F 00 5C 00 43 00 6F 00 6D 00  u.d.i.o.\.C.o.m.
6D 00 6F 00 6E 00 5C 00 54 00 6F 00 6F 00 6C 00  m.o.n.\.T.o.o.l.
73 00 5C 00 53 00 70 00 79 00 48 00 6B 00 35 00  s.\.S.p.y.H.k.5.
35 00 2E 00 64 00 6C 00 6C                       5...d.l.l

...

역시 같은 방법으로 차단이 가능 하고 이를 차단 하는 예제 코드 입니다.

        MessageBox("TEST", NULL, MB_OK); // user32.dll의 경우 이와 같이 user32.dll에 정의된 API르 하나 실행 해 주는 것이 좋습니다.
        DWORD BaseThreadStart = 0x7C810659, ClientLoadLibrary = 0x77D0DBCB;
        DWORD RET;
        HANDLE hProcess;                                
        MEMORY_BASIC_INFORMATION mbi;
        DWORD PRT2,savePRT2;

        hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, GetCurrentProcessId());
        RET = (DWORD )"\xEB\x64\x55\x8B"; // 역시 그냥 return 해 버립니다.
        VirtualQueryEx(hProcess,(LPVOID)ClientLoadLibrary,&mbi,sizeof(mbi));
        savePRT2 = PRT2 = mbi.Protect;
        PRT2 &= ~(PAGE_READONLY | PAGE_EXECUTE_READ);
        PRT2 |= (PAGE_READWRITE);

        if(!VirtualProtectEx(hProcess, (LPVOID) ClientLoadLibrary, 4, PAGE_EXECUTE_READWRITE, &PRT2)) {
        }
        __asm {
                mov eax, RET;
                mov dword ptr ds:[0x77D0DBCB], eax
        }

        if(!VirtualProtectEx(hProcess, (LPVOID) ClientLoadLibrary, 4, PAGE_READONLY, &savePRT2)) {
        }
        CloseHandle(hProcess);
## 생성자에 넣는 이유는 injection 시도 전에 수행을 위하여 프로그램의 가장 앞 단에 코드를 넣기 위함입니다.

하지만 프로그램이 실행 되는 startup code를 전역 후킹 하는 방법을 사용 하면 위의 보호를 우회 할 수 있습니다.
이것은 해당 역시 코드 패치가 수행되기 전에 발생 하기 때문입니다. 이것을 막기 위해서는 startup code보다 먼저 수행되는 tls callback에 해당 코드를
주입하는 것도 한 방법 입니다.

void __stdcall tls_callback(void *, DWORD reason, void *)
{
        if(reason == DLL_PROCESS_ATTACH){
        //코드 삽입
        }
}

DWORD _tls_index = 0;
PIMAGE_TLS_CALLBACK callback = tls_callback;
extern "C" IMAGE_TLS_DIRECTORY _tls_used = { 0, 0, &_tls_index, &callback, 0, 0 };

by 제갈량 | 2008/08/28 07:51 | Reversing... | 트랙백 | 덧글(1)

트랙백 주소 : http://ryangs.egloos.com/tb/758078
☞ 내 이글루에 이 글과 관련된 글 쓰기 (트랙백 보내기) [도움말]
Commented by sethook at 2016/09/18 22:56
리버싱을 공부하고있는 대학생입니다.

setwindowex를 사용한 dll injection을 막는 방법을 알아보는 중인데

위의 코드는 어떤언어를 사용한건가요??

:         :

:

비공개 덧글

◀ 이전 페이지          다음 페이지 ▶