_безопасный_ сплайсинг

Тема в разделе "WASM.NT.KERNEL", создана пользователем vlaman, 25 апр 2007.

  1. vlaman

    vlaman New Member

    Публикаций:
    0
    Регистрация:
    6 ноя 2004
    Сообщения:
    49
    хай всем! помогите плз
    кароч реализовал движок для сплайсинга с помощью дизассемблера длин от z0mbie. в общем делаю так из драйвера:

    __asm
    {
    cli
    mov eax,cr0
    mov CR0Reg,eax
    and eax,0xFFFEFFFF
    mov cr0,eax
    }

    тут делаю патчинг первых N байт функции (как правило 12-17 байт, тк я ещё передаю в стек адрес
    функции оригинала), которые переношу в отдельный буфер, после них jmp на продолжение фунции-оригинала.

    __asm
    {
    mov eax,CR0Reg
    mov cr0,eax
    sti
    }

    перехватываю функции работы с реестром. в вмвари и на системниках где тестил - всё окей.
    НО, обнаружил что на довольно многих машинах где реестр активно используется происходит бсод. в минидампах я вижу CODE_CORRUPTION, разрушенный стек и тп.

    читал тут на форуме что каким-то образом можно обезопасить себя от патчинга выполняемого в данный момент кода, но найденные ссылки на инфу оказались дохлыми. подскажите плиз где об этом можно почитать подробнее.

    сенкс
     
  2. wasm_test

    wasm_test wasm test user

    Публикаций:
    0
    Регистрация:
    24 ноя 2006
    Сообщения:
    5.582
    сплайсинг, вообще, штука очень опасная. если у тебя выполнение кода прервется в момент входа в пролог перехватываемой функций, произойдет переключение потоков и твой поток изуродует пролог, то когда первый поток восстановится, будет БСоД.
    Самое безопасное будет ставить сплайсинг 1 раз при загрузке компа и не снимать его потом вообще. Тогда вероятность падения минимальна.

    Это так, к слову. А по теме - ну покажи анализ хотя бы одного бсода.
     
  3. vlaman

    vlaman New Member

    Публикаций:
    0
    Регистрация:
    6 ноя 2004
    Сообщения:
    49
    ну собственно я один раз и ставлю при загрузке. вот пример анализа (тут test.sys мой модуль, к explorer-у никакого отношения не имеет):

    UNEXPECTED_KERNEL_MODE_TRAP_M (1000007f)
    ...
    Arguments:
    Arg1: 00000008, EXCEPTION_DOUBLE_FAULT
    Arg2: 80042000
    Arg3: 00000000
    Arg4: 00000000

    Debugging Details:
    ------------------


    BUGCHECK_STR: 0x7f_8

    DEFAULT_BUCKET_ID: CODE_CORRUPTION

    PROCESS_NAME: explorer.exe

    LAST_CONTROL_TRANSFER: from 00000000 to 804dd9a8

    STACK_TEXT:
    a9d60000 00000000 00000000 00000000 00000000 nt!KiFastCallEntry+0x105


    STACK_COMMAND: kb

    CHKIMG_EXTENSION: !chkimg -lo 50 -d !nt
    !chkimg -lo 50 -d !nt
    8058f60d-8058f613 7 bytes - nt!NtEnumerateValueKey

    [ 6a 54 68 68 c7 4f 80:e9 8a 65 d5 29 90 90 ]
    7 errors : !nt (8058f60d-8058f613)

    MODULE_NAME: test

    IMAGE_NAME: test.sys

    DEBUG_FLR_IMAGE_TIMESTAMP: 462ca159

    FOLLOWUP_NAME: MachineOwner

    MEMORY_CORRUPTOR: PATCH_test

    FAILURE_BUCKET_ID: MEMORY_CORRUPTION_PATCH_test

    BUCKET_ID: MEMORY_CORRUPTION_PATCH_test

    Followup: MachineOwner
    ---------

    eax=00000000 ebx=8058f60d ecx=f8e77b98 edx=00000000 esi=00d0f4b0 edi=a9d67d64
    eip=804dd9a8 esp=a9d60000 ebp=a9d60000 iopl=0 nv up ei ng nz na pe nc
    cs=0008 ss=0010 ds=0023 es=0023 fs=0030 gs=0000 efl=00010286
    nt!KiFastCallEntry+0x105:
    804dd9a8 8b553c mov edx,dword ptr [ebp+3Ch] ss:0010:a9d6003c=????????
    ChildEBP RetAddr Args to Child
    a9d60000 00000000 00000000 00000000 00000000 nt!KiFastCallEntry+0x105 (FPO: [0,0] TrapFrame-EDITED @ 00000000)


    --------------------------------

    по большей части дампы получаются разные, MEMORY_CORRUPTION_PATCH есть везде.
     
  4. wasm_test

    wasm_test wasm test user

    Публикаций:
    0
    Регистрация:
    24 ноя 2006
    Сообщения:
    5.582
    хм покаж полный код установки сплайсинга
     
  5. gilg

    gilg New Member

    Публикаций:
    0
    Регистрация:
    19 май 2005
    Сообщения:
    527
    vlaman
    Машины, на которых падает, гиперфрединговые или многопроцессорные?
     
  6. vlaman

    vlaman New Member

    Публикаций:
    0
    Регистрация:
    6 ноя 2004
    Сообщения:
    49
    gilg
    по этому поводу к сожалению ниче не могу сказать.

    Great
    вот код, по большей части взят был из blackrevealer-a by Cr4sh, немного модифицирован:

    void GenJmp(ULONG To, ULONG From)
    {
    InsertDword(From + 1, To - From - 5); // dst - src - 5
    InsertByte(From, ASMJMP); // jmp ...
    }


    void GenJmpParam( ULONG To, ULONG From, ULONG Param)
    {
    InsertByte(From, 0x58); /* pop eax */
    InsertByte(From+1, 0x68); /* push pchars */
    InsertDword(From+2,(ULONG)Param);
    InsertByte(From+6, 0x50); /* push eax */
    InsertByte(From+7, 0xe9); /* jmp new_fn */
    InsertDword(From+8, To - From - 12);
    }

    // set hook
    // OrigFuncInParam - если TRUE, то для NewFunc в виде первого параметра будет передаваться
    // указатель на оригинальную функцию, это не годится для FASTCALL функций

    VOID Hook(PVOID Addr, PVOID NewFunc, BOOL OrigFuncInParam, PVOID *OutCallGate)
    {
    ULONG CR0Reg;
    PVOID CallGate, Inst = Addr;
    ULONG Size = 0, CollectedSpace = 0, CallGateSize = 0;
    ULONG NeedSpace;

    if (Addr == NULL)
    {
    DbgPrint("BUG, tried to HOOK 0 addr");
    return 0;
    }
    NeedSpace = (OrigFuncInParam) ? SIZEOFJUMPPARAM : SIZEOFJUMP;


    __asm
    {
    cli
    mov eax,cr0
    mov CR0Reg,eax
    and eax,0xFFFEFFFF
    mov cr0,eax
    }


    while (CollectedSpace < NeedSpace)
    {
    GetInstLenght(Inst, &Size);
    (ULONG)Inst += Size;
    CollectedSpace += Size;
    }

    CallGateSize = CollectedSpace + SIZEOFJUMP;
    CallGate = (PVOID)ExAllocatePool(NonPagedPool, CallGateSize);

    if ( OutCallGate)
    {
    *OutCallGate = CallGate;
    }

    DbgPrint("Function addr : 0x%.8x\n", Addr);
    DbgPrint("Handler addr : 0x%.8x\n", NewFunc);
    DbgPrint("CallGate at : 0x%.8x; size : %d\n", CallGate, CallGateSize);

    memset(CallGate, ASMNOP, CallGateSize);

    memcpy(CallGate, Addr, CollectedSpace);

    memset(Addr, ASMNOP, CollectedSpace);

    GenJmp((ULONG)Addr + NeedSpace, (ULONG)CallGate + CollectedSpace);

    if ( OrigFuncInParam )
    {
    GenJmpParam((ULONG)NewFunc, (ULONG)Addr, CallGate);
    }
    else
    {
    GenJmp((ULONG)NewFunc, (ULONG)Addr);
    }

    __asm
    {
    mov eax,CR0Reg
    mov cr0,eax
    sti
    }

    return CallGate;
    }
     
  7. Guest

    Guest Guest

    Публикаций:
    0
    Или HyperThreading или многоядерность. БСОД происходит не сразу и хук успевать поработать? в cr0 ты только отрубаешь защиту от записи. Хук часто снимается?
     
  8. vlaman

    vlaman New Member

    Публикаций:
    0
    Регистрация:
    6 ноя 2004
    Сообщения:
    49
    im1111

    бсод похоже что происходит сразу, по крайней мере я это наблюдал один раз у себя на вмвари на двух ядерном проце (два ядра на вмварь выделено). в cr0 снимаю только защиту от записи. хук ставится при загрузке и не снимается более до выгрузки.

    но есть ещё одно но, похоже что на некоторых машинах бсод происходит не сразу, драйвер успевает поработать несколько минут. опять же в минидампах вижу то что я выше писал. этому объяснение найти пока что не могу.
     
  9. Guest

    Guest Guest

    Публикаций:
    0
    Исходник дать можешь?

    Вот еще момент:

    То есть по смещению? "To - From - 5" может "From - To - 5" (извините если гоню, не спал долго =) )
    Может использовать прямую адресацию?
     
  10. Guest

    Guest Guest

    Публикаций:
    0
    Попробуй так (сместив сброс WP и cli после ExAllocatePool):

    __asm
    {
    cli
    mov eax,cr0
    mov CR0Reg,eax
    and eax,0xFFFEFFFF
    mov cr0,eax
    }
    memset(CallGate, ASMNOP, CallGateSize);
    memcpy(CallGate, Addr, CollectedSpace);
    memset(Addr, ASMNOP, CollectedSpace);
    GenJmp((ULONG)Addr + NeedSpace, (ULONG)CallGate + CollectedSpace);

    if ( OrigFuncInParam ) { GenJmpParam((ULONG)NewFunc, (ULONG)Addr, CallGate); }
    else { GenJmp((ULONG)NewFunc, (ULONG)Addr); }

    __asm
    {
    mov eax,CR0Reg
    mov cr0,eax
    sti
    }
     
  11. vlaman

    vlaman New Member

    Публикаций:
    0
    Регистрация:
    6 ноя 2004
    Сообщения:
    49
    в общем реализовал я остановку всех процессоров с помощью DPC. стало гораздо лучше, но на отдельных машинах всё равно вылазит эта хрень с MEMORY_CORRUPTION.
     
  12. k3internal

    k3internal New Member

    Публикаций:
    0
    Регистрация:
    11 янв 2007
    Сообщения:
    607
    vlaman
    В большинстве случаев полезно штудировать матчасть и думать головой, интернет тут нужен постольку-поскольку. Если это гипертрейдинг то заблокировать все процессоры поднявшись на dispatch уровень там и делать своё дело. Кроме этого неплохо было бы изьять контексты потоков и проверить их на опасное место.
     
  13. vlaman

    vlaman New Member

    Публикаций:
    0
    Регистрация:
    6 ноя 2004
    Сообщения:
    49
    k3internal
    ну вот я сейчас перехожу в dispatch_level, делаю dpc на всех процессорах кроме текущего которые гоняют nop пока я не закончу патчинг. вот насчет контекстов надо поглядеть, сенкс.
     
  14. Deyton

    Deyton Member

    Публикаций:
    0
    Регистрация:
    7 мар 2007
    Сообщения:
    94
    Код (Text):
    1. #pragma pack (push, 1)
    2. typedef struct _FAR_JMP{
    3.     UCHAR Opcode1;
    4.     PVOID Address;
    5.     UCHAR Opcode2;
    6.     UCHAR Opcode3;
    7.     UCHAR Opcode4;
    8. } FAR_JMP, *PFAR_JMP;
    9. #pragma pack (pop)
    Код (Text):
    1. void __declspec(naked) OrigCode(void) //резервируем место для замененного участка оригинального кода (чтобы не трогать пул)
    2. {
    3.     __asm {
    4.         mov eax, eax
    5.         mov eax, eax
    6.         mov eax, eax
    7.         mov eax, eax
    8.         mov eax, eax
    9.         mov eax, eax
    10.         mov eax, eax
    11.         mov eax, eax
    12.         mov eax, eax
    13.         mov eax, eax
    14.     }
    15. }
    //============ поскипано ============ кусок функции
    Код (Text):
    1.     PFAR_JMP Fjmp;
    2.     FAR_JMP FarJmpCode;
    3.    
    4.     //Length - длина участка кода, который будет заменен переходником, его следует определять дизассемблером длин
    5.     //Length должно быть меньше, чем длина функции OrigCode
    6.  
    7.     UCHAR i;
    8.     __asm
    9.     {
    10.         mov eax, cr0
    11.         mov CR0Reg,eax
    12.         and eax,0xFFFEFFFF      // сбросить WP bit
    13.         mov cr0, eax
    14.     }
    15.     for(i=0; i<Length; i++) //копируем начальный участок кода функции в зарезервированное место нашего драйвера (void __declspec(naked) OrigCode(void))
    16.     {
    17.     *(PUCHAR)((ULONG)OrigCode+i) = *(PUCHAR)((ULONG)OrigFuncAddress+i);
    18.     }
    19.     Fjmp = (PFAR_JMP)((ULONG)OrigCode + i);
    20.     // делаем переходник на продолжение в оригинальную функцию
    21.     Fjmp->Opcode1   = 0x68;
    22.     Fjmp->Address   = pNextOpcode; // pNextOpcode - указатель на следующую инструкцию, после участка длиной Length
    23.     Fjmp->Opcode2   = 0xC3;
    24.     Fjmp = &FarJmpCode;
    25.     // далее формируем 8байт кода, который впишется в начало перехватываемой функции (Relative Jmp)
    26.     RtlCopyMemory(Fjmp, OrigFuncAddress, sizeof(FAR_JMP));
    27.  
    28.     Fjmp->Opcode1   = 0xE9;
    29.     Fjmp->Address   = (PVOID)((ULONG)NewFunction-(ULONG)OrigFuncAddress-5);
    30.    //на этот момент времени, если наш поток прервется ничего страшного не произойдет
    31.     __asm
    32.     {
    33.     mov esi, OrigFuncAddress
    34.     mov edi, Fjmp
    35.     mov eax, [esi]
    36.     mov edx, [esi+4]
    37.     mov ebx, [edi]
    38.     mov ecx, [edi+4]
    39.     lock CMPXCHG8B [esi] // здесь происходит вписывание джампа в одну инструкицю с залочиванием шины процессора
    40.     mov eax, CR0Reg    
    41.     mov cr0, eax            // востановить содержимое CR0
    42.     }
    И не забываем в обработчике перехваченной функции делать джамп на сохраненный участок начала функции, и еще не забываем про возможные команды с Relative Offset, это просто для примера я привел
     
  15. vlaman

    vlaman New Member

    Публикаций:
    0
    Регистрация:
    6 ноя 2004
    Сообщения:
    49
    Deyton
    ну а как быть если один из потоков в момент патчинга находится гденить в середине кода, который патчим?
     
  16. Deyton

    Deyton Member

    Публикаций:
    0
    Регистрация:
    7 мар 2007
    Сообщения:
    94
    vlaman
    это недостаток сплайсинга, как вариант можно сплайсить инструкцию длиной от 5 байт, ну или парсить контексты на предмет наличия EIP в этом месте. Но могу заверить, что вероятность этого ничтожно мала, во всяком случае мне не удалось получить BSOD именно по этой приячине, сколько я не пробовал. Кстати в начале функций частенько стоит 5ти байтный push хххххххх ;)
     
  17. asmfan

    asmfan New Member

    Публикаций:
    0
    Регистрация:
    10 июл 2006
    Сообщения:
    1.004
    Адрес:
    Abaddon
    Deyton ну, допустим, есть атомарность патчинга, но где гарантия что в 8 байт укладывается целое число команд?
     
  18. Deyton

    Deyton Member

    Публикаций:
    0
    Регистрация:
    7 мар 2007
    Сообщения:
    94
    asmfan
    1) Из 8 байт реально патчится только 5 (посмотри внимательно)
    2) дизассемблером длин ты получаешь указатель на _начало_ следующей инструкции после джампа до того как его вписываешь. если там останется мусор от какой-то инструкции на него управление не попадет после джампа.
     
  19. asmfan

    asmfan New Member

    Публикаций:
    0
    Регистрация:
    10 июл 2006
    Сообщения:
    1.004
    Адрес:
    Abaddon
    т.е. Length должна быть больше либо равно 8 иначе
    Код (Text):
    1. lock CMPXCHG8B [esi]
    затирается безвозвратно (8 - Length) байт или я не прав?
     
  20. Deyton

    Deyton Member

    Публикаций:
    0
    Регистрация:
    7 мар 2007
    Сообщения:
    94
    НЕТ

    Например, было:
    --------------------------------
    80000000 mov edi, edi (8B FF)
    80000002 push ebp (55)
    80000003 mov ebp, esp (8B EC)
    80000005 sub esp, 48 (83 EC 48)
    .....
    --------------------------------
    станет
    80000000 jmp MyFunc
    80000005 sub esp, 48
    --------------------------------
    MyFunc:
    ...... ......
    jmp OrigCode
    --------------------------------
    OrigCode:

    mov edi, edi
    push ebp
    mov ebp, esp
    push 80000005
    ret
    --------------------------------
    pNextOpcode = 80000005

    В эти 8 байт копируется начало функции, до патча, а потом только первые 5 байт заменяются джампом, и вписывается в начало через lock CMPXCHG8B [esi].