본문 바로가기

리버싱

64비트 프로세스 은닉

 

 

성공적으로 은폐된 프로세스의 모습!!

작업관리자에 서술되는 프로세스의 은닉을 위해서 2개의 과정이 필요했다.

1. code injection 
2. 64bit WINAPI hooking

1번같은 경우 32비트와 호환되는 부분이 상당히 많아서 쉽게 성과를 내었지만, 

2번같은경우 유감스럽게도 자료가 없다시피하여, 직접 삽질했다.

삽질결과 다음 결론에 도달했다.


JMP instruction의 relative address는 32비트에선 후킹시 유효했지만, 64비트에선 후킹시 유효하지않다.
이유는 상위 4바이트 때문인데, 상위4바이트는 모듈마다 달라지지만 상대주소로 JMP를 진행했을시 상위4바이트는 JMP instruction이 존재하는 모듈을 따라간다.

따라서 간접호출을 통하여 JMP instruction을 진행해야한다.

간접호출이란, JMP instruction의 인자값으로 레지스터를 사용하는것이다.

 

 

따라서 저 부분을 간접호출 코드로 바꾼다면 후킹에 성공한다.

 

 

성공적으로 후킹된모습

후킹 이전 호출흐름은 다음과 같다.

 

 


후킹 이후 호출흐름은 다음과 같다.

 

 

훅 코드를 만들기 위해서, WINAPI를 구현해야한다.

 

 

 

PEB를 얻는 함수중, 다음 함수가 있다.

 

 

 

 

 

windbg를 통해서 확인하였으며, TEB로 부터 60h 만큼 떨어져있는 PEB의 주소를 확인했다.

gs 레지스터가 TEB의 주소를 가지고 있기때문에,
다음 코드를 만들 수 있다.

 

 

 

그 다음으로, 호출한 프로세스의 매핑된 모듈의 주소를 얻는 API를 구현해야한다.
함수의 원형과 설명은 다음과 같다.

 

 

구조는 다음과 같다.

 

 

코드는 다음과 같다.

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
ADDRESS MyGetModuleHandle(const char *lpKernelModuleStringName)
{
    PEB *peb;
    PLDR_DATA_TABLE_ENTRY pDataEntry;
 
    ADDRESS hModule = 0;
    ADDRESS ProcAddress = 0;
 
    //get peb base address
    peb = GetPEB();
 
    //get ntdll address
 
    //first flink = self
    //second flink = ntdll.dll
    //...
 
    int flag = TRUE;
 
    ADDRESS *Flink = NULL;
    ADDRESS *FirstFlink = NULL;
 
    ADDRESS DllBase = 0;
    ADDRESS DllName = 0;
 
    Flink = (ADDRESS *)peb->Ldr->InMemoryOrderModuleList.Flink;
    FirstFlink = Flink;
 
    for (;;)
    {
        Flink = (ADDRESS *)*Flink;
 
        DllBase = (ADDRESS)Flink;
        DllBase += sizeof(PVOID) * 4;
 
        DllName = (ADDRESS)Flink;
 
#ifdef _WIN64
        DllName += (sizeof(PVOID) * 4 + sizeof(PVOID) * 5 + (sizeof(USHORT) * 2* 2);
#else
        DllName += (sizeof(PVOID) * 4 + sizeof(PVOID) * 5 + sizeof(USHORT) * 2);
#endif // _WIN64
 
        int i = 0, j = 0;
        WCHAR *lpDllName = (WCHAR *)*(ADDRESS *)DllName;
 
        for (;;)
        {
            char word = ((char *)lpDllName)[i];
 
            if (word >= 'A' && word <= 'Z') word += 32;
            if (word == 0break;
            if (lpKernelModuleStringName[j] == 0break;
            if (lpKernelModuleStringName[j] != word) break;
 
            i += 2;
            j++;
        }
 
        if (((char *)lpDllName)[i] == 0break;
    }
 
    hModule = *(ADDRESS *)DllBase;
 
    return hModule;
}
 
 

 

DLL이 제공하는 함수의 주소를 구해주는 API도 구현해야한다.

 

 

구조는 다음과 같다.

 

 

코드는 다음과 같다.

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
ADDRESS MyGetProcAddress(ADDRESS hModule, const char *lpProcName)
{
    PIMAGE_DOS_HEADER pDosHeader;
    PIMAGE_NT_HEADERS pNtHeader;
    int *ExportTableRva;
    int NameIndex = 0;
    ADDRESS ExportTableVa;
    ADDRESS ExportNames;
    PIMAGE_EXPORT_DIRECTORY ExportTableHeader;
 
    pDosHeader = (PIMAGE_DOS_HEADER)hModule;
    pNtHeader = (PIMAGE_NT_HEADERS)(pDosHeader->e_lfanew + hModule);
 
    ExportTableRva = (int *)(&pNtHeader->OptionalHeader.DataDirectory[0]);
    ExportTableVa = (ADDRESS)*ExportTableRva;
    ExportTableVa += hModule;
 
    ExportTableHeader = (PIMAGE_EXPORT_DIRECTORY)ExportTableVa;
    ExportNames = ExportTableHeader->AddressOfNames;
    ExportNames += hModule;
 
    int *NameRVA = (int *)((ADDRESS)ExportTableHeader->AddressOfNames + hModule);
 
    int i = ExportTableHeader->NumberOfNames;
 
    for (; i != 0;)
    {
        int j = 0;
        for (; ((char *)(((int)NameRVA[i]) + hModule))[j] != 0; j++)
        {
            if (lpProcName[j] == 0)
                break;
 
            if (lpProcName[j] != ((char *)(((int)NameRVA[i]) + hModule))[j])
                break;
        }
 
        if (((char *)(((int)NameRVA[i]) + hModule))[j] == 0)
            break;
        i--;
    }
 
    WORD *lpOrdinals = (WORD *)(ADDRESS)(ExportTableHeader->AddressOfNameOrdinals + hModule);
    int *lpAddresses = (int *)(ADDRESS)(ExportTableHeader->AddressOfFunctions + hModule);
 
    return lpAddresses[lpOrdinals[i]] + hModule;
}
 
 

 

unhook을 진행하여, 원본 API의 코드를 복구하고, rehook을 진행하여 다음 호출에 대해서 대비하여야한다.

하지만 unhook을 진행하려면 이전 instruction을 기억해야하는데 이는 hookcode 단독으로는 어렵다.
이유는 static변수의 구현이 필요하기 때문이다. 
함수의 내부 스택은 호출될 때마다 초기화 되기 때문에, hooker가 직접 지역변수를 만들어주어야한다.
따라서 mov instruction을 runtime에서 만들어주어야한다.

코드는 다음과 같다.

 

 



API코드 변경 과정은 다음과 같다.

unhook을 진행하고, API를 호출하는 코드는 다음과 같다.

 

1
2
3
4
5
6
7
    //unhook
    ((BOOL(WINAPI *)(HANDLE, LPCVOID, LPVOID, SIZE_T, SIZE_T *))lpWriteProcessMemory)
        (hProcess, (LPVOID)lpHookAPI, lpPointerOfOriginalInstruction, szOriginalInstructionSize, NULL);
 
    //call query API
    volatile NTSTATUS ret = ((NTSTATUS(WINAPI *)(IN SYSTEM_INFORMATION_CLASS, OUT PVOID, IN ULONG, OUT PULONG))lpHookAPI)
        (SystemInformationClass, SystemInformation, SystemInformationLength, ReturnLength);
 

성공적으로 unhook이 진행된 상태

 

 



rehook을 진행하는 코드는 다음과 같다.

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
#ifdef _WIN64
 
    /*
    movabs rax, 0
    jmp rax
    */
 
    volatile BYTE lpInstruction[] = {0x480xb8,
                                     0x000x000x000x00,
                                     0x000x000x000x00,
                                     0xFF0xE0};
    for (int i = 0; i != sizeof(ADDRESS); i++)
        lpInstruction[i + 2= ((BYTE *)&HookProcAddress)[i];
#else
 
    /*
    mov eax, 0
    jmp eax
    */
 
    volatile BYTE lpInstruction[] = {0xb8,
                                     0x000x000x000x00,
                                     0xFF0xE0};
    for (int i = 0; i != sizeof(ADDRESS); i++)
        lpInstruction[i + 1= ((BYTE *)&HookProcAddress)[i];
#endif // _WIN64
 
    ((BOOL(WINAPI *)(HANDLE, LPCVOID, LPVOID, SIZE_T, SIZE_T *))lpWriteProcessMemory)
        (hProcess, (LPVOID)lpHookAPI, (LPVOID)lpInstruction, sizeof(lpInstruction), NULL);
 

 

성공적으로 rehook이 진행된 상태

 

 


다음은 프로세스를 은닉시키는 코드를 구현해야한다.

구조는 다음과 같다.

 

 

프로세스의 정보를 담는 구조체는 다음과 같은데, 이를 조작하여 프로세스를 은닉시켜야한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
typedef struct _SYSTEM_PROCESS_INFORMATION
{
    ULONG NextEntryOffset;
    ULONG NumberOfThreads;
    BYTE Reserved1[48];
    PVOID Reserved2[3];
    HANDLE UniqueProcessId;
    PVOID Reserved3;
    ULONG HandleCount;
    BYTE Reserved4[4];
    PVOID Reserved5[11];
    SIZE_T PeakPagefileUsage;
    SIZE_T PrivatePageCount;
    LARGE_INTEGER Reserved6[6];
} SYSTEM_PROCESS_INFORMATION;
 


NextEntryOffset 멤버를 조작하여 프로세스를 은닉할수있다. 
코드는 다음과 같다.

 

 

 

'리버싱' 카테고리의 다른 글

About process hollowing  (0) 2020.09.02
해킹의 방어에 대한 개인적인 생각  (1) 2020.09.02
reversing.kr (Replace.exe)  (1) 2020.03.27
reversing.kr (Music_Player.exe)  (0) 2020.03.27
reversing.kr (Easy_UnpackMe.exe)  (0) 2020.03.27