Перейти к содержанию

[C++] Мониторинг системных вызовов (WOW64)


Рекомендуемые сообщения

Привет,

Пишу 32-битную DLL для мониторинга системных вызовов приложения, в которое она будет заинжекчена.

Вот, собственно, код (не выдержка из проекта):

Спойлер

#include <Windows.h>

static DWORD WINAPI Filter(DWORD magic, DWORD myclass, DWORD id, DWORD result, DWORD* args, DWORD argc)
{
  //void* mem = malloc(10);
  //if (mem) free(mem);
    return 0;
}

BOOL WINAPI IsContainsFrame(DWORD ebp, unsigned int count)
{
    DWORD lpEbp = ebp;
    DWORD lMax = __readfsdword(4);
    DWORD lMin = __readfsdword(8);
    DWORD _sig = 0x01020304;

    while (count && lpEbp < lMax && lpEbp > lMin)
    {
        if (*(DWORD*)(lpEbp + 8) == _sig)
            return true;

        count--;
        lpEbp = *(DWORD*)lpEbp;
    }

    return false;
}

BOOL APIENTRY DllMain( HMODULE hModule,
                       DWORD  ul_reason_for_call,
                       LPVOID lpReserved
                     )
{
    switch (ul_reason_for_call)
    {
    case DLL_PROCESS_ATTACH:
    {
        LPVOID lpGate = (LPVOID)__readfsdword(0xc0);

        BYTE pushret[8] = "\x68\xDD\xCC\xBB\xAA\xC3\xCC";
        BYTE asm_code[138] = "\x60\x6A\x10\x55\xBA\x33\x33\x33\x33\xFF\xD2\x85\xC0\x75\x77\x61\x55\x89\xE5\x60\x8B\x55\x04\x8B\x12\x80\xFA\xC3\x74\x5A\x80\xFA\xC2\x75\x59\xC1\xEA\x08\x81\xE2\xFF\x00\x00\x00\x52\x85\xD2\x74\x51\x8D\x4D\x0C\x51\x6A\xFF\x50\x68\x44\x44\x44\x44\x74\x0B\xFF\x74\x15\x08\x83\xEA\x04\x85\xD2\x75\xF5\x6A\x00\x68\x55\x55\x55\x55\x8B\x55\xF4\x8B\x4D\xF8\x90\x90\x90\x90\x90\x90\x90\x89\xEC\x60\x89\x45\xD4\x83\xEC\x14\xBA\x66\x66\x66\x66\x68\x04\x03\x02\x01\xFF\xD2\x61\x89\xE5\x5D\xC3\x31\xD2\xEB\xB0\x61\x89\xEC\x5D\xEB\xD5\x6A\x00\xEB\xAB\x61\xEB\xCE";

        BYTE OriginalGateBytes[7];

        memcpy_s(OriginalGateBytes, sizeof(OriginalGateBytes), lpGate, sizeof(OriginalGateBytes));

        byte* lpMyAsmCode = (byte*)VirtualAlloc(0, sizeof(asm_code), MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE);
        if (!lpMyAsmCode)
            break;

        memcpy_s(lpMyAsmCode, sizeof(asm_code), asm_code, sizeof(asm_code));

        *(void**)(lpMyAsmCode + 0x5) = IsContainsFrame;
        *(void**)(lpMyAsmCode + 0x4d) = lpMyAsmCode + 0x5e;
        *(void**)(lpMyAsmCode + 0x68) = Filter;

        memcpy_s(lpMyAsmCode + 0x57, sizeof(OriginalGateBytes), OriginalGateBytes, sizeof(OriginalGateBytes));

        *(void**)(pushret + 1) = lpMyAsmCode;

        MEMORY_BASIC_INFORMATION membasic;

        if (!VirtualQuery(lpGate, &membasic, sizeof(membasic)))
            break;

        if (membasic.AllocationProtect != PAGE_EXECUTE_READWRITE)
        {
            if (!VirtualProtect(membasic.BaseAddress, membasic.RegionSize, PAGE_EXECUTE_READWRITE, &membasic.Protect))
                return false;

            memcpy_s(lpGate, sizeof(pushret), pushret, sizeof(pushret));
        }

        break;
    }
    case DLL_THREAD_ATTACH:
    case DLL_THREAD_DETACH:
    case DLL_PROCESS_DETACH:
        break;
    }
    return TRUE;
}

 

Вот код asm_code с моим беглым описанием:

Спойлер

pushad #сохраняем регистры
push 0x10 #кол-во проверок ebp
push ebp #пушим текущий ebp
mov edx, 0x33333333 #кладем адрес IsContainsFrame
call edx
test eax, eax
jnz magicDetected #если находим, то восстанавливаем регистры и выполняем без логирования
popad

#начало обработки
push ebp #сохраняем текущий ebp. Он будет опорным элементом для удобной адресации по стеку
mov ebp, esp
pushad #сохраняем регистры
mov edx, dword ptr [ebp + 4] #берем адрес возврата в Nt
mov edx, [edx] #читаем оттуда dword, чтобы извлечь кол-во аргументов, которые принимает функция
cmp dl, 0xC3 #если C3, значит аргументов нет
jz zeroEdx
cmp dl, 0xC2 #если C2, значит есть аргументы. Иначе просто выполняем без логирования
jnz restoreAndExecuteJmp33
shr edx, 8 #двигаем направо, чтобы получить байт == кол-во аргументов
and edx, 0xff #нужен только один байт. Сейчас тут находится кол-во аргументов функции

Filter_SaveArgc:
push edx #пуш кол-во аргументов
test edx, edx
jz zeroArgs #если аргументов нет, то и пушим 0

Filter_PushOther:
lea ecx, dword ptr [ebp + 12] #иначе берем указатель на первый аргумент
push ecx #и пушим его
push 0xffffffff #пуш результат выполнения (пока что -1)
push eax #пуш id вызова
push 0x44444444 #пуш указателя на класс
je FakeFrameHijack

FakeFramePushArgs:
push dword ptr [ebp + edx + 8] #пушим аргументы, с которыми была вызвана функция
sub edx, 4
test edx, edx
jne FakeFramePushArgs

FakeFrameHijack:
push 0 #не важно какой вызов был перед Nt
push 0x55555555 #вызов моего кода, который будет адресом возврата из x64 (вместо Nt)

mov edx, dword ptr [ebp - 12] #восстанавливаем старый edx
mov ecx, dword ptr [ebp - 8] #восстанавливаем старый ecx

jmp33: #здесь будут оригинальные байты прыжка
nop
nop
nop
nop
nop
nop
nop


AfterFakeFrameHandler:
mov esp, ebp #возвращаемся к ebp, чтобы сохранить регистры
pushad #сохраняем регистры
mov dword ptr [ebp - 0x2c], eax #сохраняем результат вызова в фильтр
sub esp, 0x14 #переходим к стеку с фильтром
mov edx, 0x66666666 #адрес моей функции-фильтра
push 0x01020304 #пушим magic
call edx #вызываем фильтр
popad #восстанавливаем сохраненные регистры
mov ebp, esp #восстанавливаем старый ebp, с которым была вызвана фнукция
pop ebp
ret #возвращаем управление

zeroEdx:
xor edx, edx
jmp Filter_SaveArgc

restoreAndExecuteJmp33:
popa
mov esp, ebp
pop ebp
jmp jmp33

zeroArgs:
push 0
jmp Filter_PushOther

magicDetected:
popad
jmp jmp33

 

Когда происходит системный вызов, управление передается на мой asm_code (lpMyAsmCode). Предварительно функция IsContainsFrame() проверяет нет ли фрейма функции Filter (то есть не системный ли это вызов из моего фильтра, иначе не заходим снова в Filter, а просто выполняем). Далее на стеке наращиваю аргументы под функцию Filter, оставляю место для сохранения регистров (чтобы после всего этого регистры остались прежними) и копирую аргументы, с которыми вызывалась функция, подменяя адрес возврата. Делаю системный вызов (адресом возврата будет lpMyAsmCode + 0x5e), которая в последующем передаст управление.

Проблема заключается в том, что я не могу обрабатывать получаемую информацию в Filter, так как любой вызов необходимой функции (malloc, sprintf_s) — краш (но не при первом вызове, по моим наблюдениям).

 

В частности такая проблема с .NET приложениями. Я, конечно, грешу в том числе на метод инжекта — CreateProcess (CREATE_SUSPENDED) + CreateRemoteThread + LoadLibraryA.

Но все же хочется услышать мнение знающих людей.

Вот так примерно выглядит стэк вызовов (да, там моего кода не наблюдается, но он точно причастен!):

Спойлер

verifier!AVrfpDphPlaceOnUnusedList+8	    1891000	    cd22000	    cd23000	    cd22000	  
verifier!AVrfpDphAddNewPool+35	    1891000	    0001000	    0000000	    0000000	  
verifier!AVrfpDphAllocateNode+197	    1891000	    0000000	    ca1ec7c	    ca1ec80	  
verifier!AVrfpDphFindAvailableMemory+12b	    1891000	    0002000	    0000001	    866a670	  
verifier!AVrfDebugPageHeapAllocate+185	    1890000	    1280002	    0000010	    261202c	  
ntdll!RtlDebugAllocateHeap+39	    0000010	    26123f0	    1890000	    0000010	  
ntdll!RtlpAllocateHeap+f0	    0000010	    0000018	    0000000	    ca1ef48	  
ntdll!RtlpAllocateHeapInternal+104c	    0280000	    0000000	    ca1f248	    26122b4	  
ntdll!RtlAllocateHeap+3e	    1890000	    0280000	    0000010	    261389c	  
ntdll!LdrpLoadDependentModule+bfa	    731cf58	    0000001	    ca1f5ac	    ca1f5b0	  
ntdll!LdrpResolveForwarder+8a	    1374f58	    ca1f600	    2613b6c	    7e95d00	  
ntdll!LdrpSnapModule+50b	    2613bc0	    7e95d00	    1368f74	    7e95ce0	  
ntdll!LdrpProcessWork+4d	    7da7210	    c652f70	    c68afb8	    7da6d6d	  
ntdll!LdrpWorkCallback+59	    ca1f7fc	    0000000	    c652f70	    c3a8ee0	  
ntdll!TppWorkpExecuteCallback+1dd	    ca1f7fc	    c652fe8	    26135c4	    7da5900	  
ntdll!TppWorkerThread+472	    c3a8ee0	    633fa10	    ca1f93c	    7dd7a9e	  
kernel32!BaseThreadInitThunk+19	    c3a8ee0	    2613428	    0000000	    0000000	  
ntdll!__RtlUserThreadStart+2f	    fffffff	    7df8a4f	    0000000	    0000000	  
ntdll!_RtlUserThreadStart+1b	    7da5900	    c3a8ee0	    0000000	    0000000	  

 

 

Ссылка на комментарий
Поделиться на другие сайты

Видимо, никто не в курсе в чем может быть проблема.

Закинул случайно ассемблерный код с ошибкой — неправильно обрабатывает системные вызовы, которые вызываются без аргументов.

Решил избавиться от CRT-зависимых функций (malloc, sprintf_s) — перешел на функции в ntdll + не использую стандартную кучу, а создаю свою — вроде работает.

Ссылка на комментарий
Поделиться на другие сайты

×
×
  • Создать...

Важная информация

Находясь на нашем сайте, Вы автоматически соглашаетесь соблюдать наши Условия использования.