Эмуляция VirtualProtect

Discussion in 'С/С++, C#, Rust, Swift, Go, Java, Perl, Ruby' started by 0x0c0de, 25 Sep 2007.

  1. 0x0c0de

    0x0c0de Elder - Старейшина

    Joined:
    25 May 2007
    Messages:
    441
    Likes Received:
    396
    Reputations:
    297
    Итак.... Представим ситуацию. Есть фаер/антитварь, ставящее хуки на нужные зловредному коду функции. И все бы ничего...
    Возьмем да и снимем поставленые хуки... а нет... на функции ZwProtectVirtualMemory тоже стоит перехват=\ что тогда? с утра сегодня пришла идея сэмулировать нужную функцию... мож кому-то будет интересно. Если не будет -тему дельнете. делов-то

    Посидела немного за дизасмом VirtualProtectEx и ZwProtectVirtualMemory. Я их обе сэмулирую. Значит с первой проблем вообще не возникло. Просто проследила за содержанием стека, чтобы узнать где какой параметр. Далее... Далее вызов ntdll функции, на начале которой стоит наш воображаемый хук. Пять байт затерто. А нам надо узнать номер функции

    Code:
      
    7C90DE77 n>/$  B8 86000000               MOV EAX,86
    7C90DE7C   |.  BA 0003FE7F               MOV EDX,7FFE0300
    7C90DE81   |.  FF12                      CALL DWORD PTR DS:[EDX]
    7C90DE83   \.  C2 0C00                   RETN 0C
    7C90DE86       90                        NOP
    7C90DE87       90                        NOP
    7C90DE88       90                        NOP
    7C90DE89       90                        NOP
    7C90DE8A       90                        NOP
    7C90DE8B       90                        NOP
    7C90DE8C n>/$  B8 87000000               MOV EAX,87
    7C90DE91   |.  BA 0003FE7F               MOV EDX,7FFE0300
    7C90DE96   |.  FF12                      CALL DWORD PTR DS:[EDX]
    7C90DE98   \.  C2 1800                   RETN 18
    7C90DE9B       90                        NOP
    7C90DE9C       90                        NOP
    7C90DE9D       90                        NOP
    7C90DE9E       90                        NOP
    7C90DE9F       90                        NOP
    7C90DEA0       90                        NOP
    7C90DEA1 n>/$  B8 88000000               MOV EAX,88
    7C90DEA6   |.  BA 0003FE7F               MOV EDX,7FFE0300
    7C90DEAB   |.  FF12                      CALL DWORD PTR DS:[EDX]
    7C90DEAD   \.  C2 1400                   RETN 14
    
    Мы видим, что номера функций идут строго по порядку. Это означает, что чтобы нам вычислить адрес ZwProtectVirtualMemory надо просто посмотреть номер предыдущей функции и прибавить 1 или последующей (но тогда надо вычитать 1). Второй вариант мне больше понравился.

    а тот же аутпост (в пример привожу его - это модный пример) ставит хук так

    Code:
    7C90EA32 n> $- E9 71DE7393               JMP wl_hook.1004C8A8 ; это поясняет мои стова относительно пяти байт
    7C90EA37    .  BA 0003FE7F               MOV EDX,7FFE0300
    7C90EA3C    .  FF12                      CALL DWORD PTR DS:[EDX]
    7C90EA3E    .  C2 1400                   RETN 14
    
    Значит нам потребуется вычислить номер функции. Теперь подробнее. Ищем байт 0B8, забираем двойное слово- номер функции декрементируем, вписываем значение в наш код.... Потом ищем 0BA, поступаем так же. По поводу записи непосредственно в секцию кода. VirtualProtect тут вызывать тоже не надо. Я установила характеристики секции в 0E0000020H, поэтому все в порядке=)

    Code:
    .386
    .model flat, stdcall
    include kernel32.inc
    include windows.inc
    includelib kernel32.lib
    .data
    ntdl_ db "ntdll.dll",0 
    vprotect db "ZwProtectVirtualMemory",0
    func_ dword ?
    .const
    .code
    
    system_call proc
    mov eax,0 ; сюда мы запишем номер функции
    mov edx,0 
    call dword ptr [edx] ;  KiFastSystemCall
    retn 14h ; количество передаваемых параметров я знаю точно
    system_call endp
    
    emulator_ proc  ; эмуляция VirtualProtectEx
    local old_protect:dword
    local size_c:dword
    local address:dword
    lea eax,old_protect
    push eax
    push 40h ; тип протекта запись-чтение-исполнение
    mov size_c,10
    lea eax,size_c
    push eax
    push func_ ; на ней же и протестим=)
    pop address
    lea eax,address
    push eax
    push -1 ; пишем в своем процессе, поэтому -1
    call system_call
    mov esi,func_
    mov byte ptr[esi],8 ; проверяем на запись. ошибки нет=) 
    ret
    emulator_ endp
    
    Main:
    invoke GetModuleHandleA,offset ntdl_
    invoke GetProcAddress,eax,offset vprotect 
    test eax,eax
    je ext_
    mov func_,eax
    add eax,4 ; пять байт мы смело пропускаем. тут четыре, да ниже inc - итого 5)
    loo:
    inc eax
    cmp byte ptr [eax],0b8h ; начало инструкции mov eax,xxxx. для надежности можно проверять длину инструкции. должно быть 5 байт. в этом нам поможет
    ; дизассемблер длин, его прикрутить вообще не проблема
    jnz loo
    inc eax
    mov edx,dword ptr[eax]
    mov edi,offset system_call
    dec edx
    inc edi
    mov dword ptr[edi],edx
    add eax,5
    add edi,5
    mov edx,dword ptr[eax]
    mov dword ptr[edi],edx
    call emulator_
    ext_:
    invoke ExitProcess, NULL
    end Main
    
    Не забудьте установить характеристики секции кода данной проги в 0E0000020H!
    Вообще наверняка есть способ лучше..
     
    #1 0x0c0de, 25 Sep 2007
    Last edited: 25 Sep 2007
    2 people like this.
  2. _Great_

    _Great_ Elder - Старейшина

    Joined:
    27 Dec 2005
    Messages:
    2,032
    Likes Received:
    1,119
    Reputations:
    1,139
    Это всё канешн круто. Только нормальные АВ и фаеры ставят хуки в ядре в SSDT или сплайсингом Nt-вариантов родного апи.
     
  3. gevara

    gevara Elder - Старейшина

    Joined:
    29 Nov 2006
    Messages:
    47
    Likes Received:
    7
    Reputations:
    5
    Хм.. Зачем что-то изобретать? если нужно анхучить ntdll - читаем из файла оригиналиное содержимое функции да и затираем все хуки... Опять-же как можно изменить аттрибуты секции файла? разобрать PE заголовок, найти эту секцию и прописать новые аттрибуты? WFP сразу вернёт всё на место.
    Да и грейт вообще-то прав. На моей памяти только аутпост СДТ не хучит.

    P.S. А вообще молодец. Хорошее предложение. Только в реальности работать не будет...
     
    #3 gevara, 25 Sep 2007
    Last edited: 25 Sep 2007
  4. Hellsp@wn

    Hellsp@wn Elder - Старейшина

    Joined:
    29 Apr 2007
    Messages:
    401
    Likes Received:
    153
    Reputations:
    48
    хм, мо-моему оутпост ничё не вякал и позволял норм анхукать =d
    покрайней мере в предпоследней версии... а ваще норм, пускай способ будет =)
     
  5. 0x0c0de

    0x0c0de Elder - Старейшина

    Joined:
    25 May 2007
    Messages:
    441
    Likes Received:
    396
    Reputations:
    297
    Неееее) Я ж не у ntdll меняю характеристики секции кода! (я об этом, канешн сначала думала, кстати на васме если мне память не изменяет был код отруба WFP). Да и в самом ntdll.dll не внесни никаких изменений - файл только для чтения. Я говорю про саму программу. Чтобы потом не было вопросов=) Просто я пишу непосредственно в секции кода, не установив права на запись, поэтому я предупредила, что характеристики секции это позволяют

    я ж пишу в эту процедуру непосредственно

    Code:
     
    system_call proc
    mov eax,0 ; сюда мы запишем номер функции
    mov edx,0 
    call dword ptr [edx] ;  KiFastSystemCall
    retn 14h ; количество передаваемых параметров я знаю точно
    system_call endp
    
    Дааааа ) Поэтому я рассматриваю теоретическую возможность перехвата. Но в скором времени, учитывая то, что в этом сезоне модно убивать аутпост, может стать практической))))

    Отчего же? У мну все заработало))))))

    А ты что юзаешь для того, чтобы затереть хук? VirtualProtect. VirtualProtect вызывает VirtualProtectEx, а та в свою очередь ZwProtectVirtualMemory. А если на ней хук?) Перехват этой функции - это возможное решение разработчиков против антисплайсинга. Причем довольно вероятное.
     
    #5 0x0c0de, 26 Sep 2007
    Last edited: 26 Sep 2007
    2 people like this.
  6. gevara

    gevara Elder - Старейшина

    Joined:
    29 Nov 2006
    Messages:
    47
    Likes Received:
    7
    Reputations:
    5
    А работать не будет вот почему:

    NtPrivilegeCheck 0x005d 0x005d 0x005d 0x005d 0x0074 0x0074 0x0074 0x0074 0x0074 0x0086 0x0086 0x0086 0x008c 0x008c 0x00cc
    NtPrivilegeObjectAuditAlarm 0x005f 0x005f 0x005f 0x005f 0x0076 0x0076 0x0076 0x0076 0x0076 0x0087 0x0087 0x0087 0x008d 0x008d 0x00cd
    NtPrivilegedServiceAuditAlarm 0x005e 0x005e 0x005e 0x005e 0x0075 0x0075 0x0075 0x0075 0x0075 0x0088 0x0088 0x0088 0x008e 0x008e 0x00ce
    NtPropagationComplete 0x0178
    NtPropagationFailed 0x0179
    NtProtectVirtualMemory 0x0060 0x0060 0x0060 0x0060 0x0077 0x0077 0x0077 0x0077 0x0077 0x0089 0x0089 0x0089 0x008f 0x008f 0x00cf
    NtPullTransaction 0x0176
    NtPulseEvent 0x0061 0x0061 0x0061 0x0061 0x0078 0x0078 0x0078 0x0078 0x0078 0x008a 0x008a 0x008a 0x0090 0x0090 0x00d0
    NtQueryAttributesFile 0x0063 0x0063 0x0063 0x0063 0x007a 0x007a 0x007a 0x007a 0x007a 0x008b 0x008b 0x008b 0x0091 0x0091 0x00d1

    В разных версиях осей за и перед NtProtectVirtualMemory идут разные функции.
     
    3 people like this.
  7. 0x0c0de

    0x0c0de Elder - Старейшина

    Joined:
    25 May 2007
    Messages:
    441
    Likes Received:
    396
    Reputations:
    297
    Работать не будет при неск. условиях.
    1. Функции идут не по порядку
    2. имеют не такой вид

    Code:
     
    MOV EAX,xxxx
    MOV EDX,yyyy
    CALL DWORD PTR DS:[EDX]
    RETN zzz
    
    То есть в 2k работать не будет. Я проверяла на winxp sp2.
    А то, что функции разные, так это вообще не имеет значения. лишь бы по порядку шли (или просто ты не так выразился).
    Я ж кусок кода в начале приводила из ntdll. Хотя так-то это все ненадежно. Лучше из файла читать номер функции. седня вечером накатаю код... так канешн, будет правильней.
     
  8. 0x0c0de

    0x0c0de Elder - Старейшина

    Joined:
    25 May 2007
    Messages:
    441
    Likes Received:
    396
    Reputations:
    297
    Ge(X)oR про конкретно эмуляцию функций я не слышала=\ Если говоришь, что было, так аргументируй (ссылки и тд)... а "что-то подобное" мне ни о чем не говорит)
     
  9. 0x0c0de

    0x0c0de Elder - Старейшина

    Joined:
    25 May 2007
    Messages:
    441
    Likes Received:
    396
    Reputations:
    297
    Переписала код.... в общем саму функцию эмуляции и получение номера функции.

    Code:
    emulator_vir_protect proc  hProcess:dword,lpAddress:dword,dwSize:dword,flNewProtect:dword,lpflOldProtect:dword 
    push lpflOldProtect
    push flNewProtect
    lea eax,dwSize
    push eax
    lea eax,lpAddress
    push eax
    push hProcess
    call system_call
    ret
    emulator_vir_protect endp
    
    Так теперь выглядит эмуляция VirtualProtectEx )

    Вызывать так

    Code:
    invoke emulator_vir_protect,-1,func_,10,40h,addr oldprot_
    
    То есть так же как обычный VirtualProtect

    Code:
    GetNumberNtdllFunction proc address_function:dword,imagebase:dword
    ; в eax возвращается номер функции, если произошла ошибка в eax 0
    local hFile:dword
    local dwSize:dword
    local hMap:dword
    local Base:dword
    local rva:dword
    mov edi,address_function
    sub edi,imagebase
    mov rva,edi
    invoke GetSystemDirectory,offset buf_,255
    test eax,eax
    je ret_
    invoke lstrcat,offset buf_,offset sl
    invoke lstrcat,offset buf_,offset ntdl_
    invoke CreateFile,offset buf_,GENERIC_READ,FILE_SHARE_READ,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL, NULL
    mov hFile,eax
    inc eax
    jz ret_
    invoke GetFileSize,hFile, NULL
    test eax,eax
    jz ret_
    mov dwSize,eax
    invoke CreateFileMapping,hFile, NULL,1000002h, 0, dwSize, NULL
    mov hMap,eax
    invoke MapViewOfFile,hMap, FILE_MAP_READ, 0, 0, dwSize
    mov Base,eax
    test eax,eax
    je ret_
    add eax,rva
    inc eax
    mov edi,dword ptr[eax]
    mov rva,edi
    add eax,5
    push dword ptr [eax]
    invoke UnmapViewOfFile,Base
    invoke CloseHandle,hMap
    invoke CloseHandle,hFile
    mov eax,rva
    pop edx
    jmp rt
    ret_:
    sub eax,eax
    sub edx,edx
    rt:
    ret
    GetNumberNtdllFunction endp
    
    А вот процедура получения номера функции....

    Пример вызова

    Code:
    invoke GetModuleHandleA,offset ntdl_
    mov ntdllbase,eax
    invoke GetProcAddress,eax,offset vprotect 
    test eax,eax
    je ext_
    mov func_,eax
    invoke GetNumberNtdllFunction, func_,ntdllbase
    
    Теперь имеет значение только формат

    Code:
    MOV EAX,xxxx
    MOV EDX,yyyy
    CALL DWORD PTR DS:[EDX]
    RETN zzz
    
     
    #9 0x0c0de, 29 Sep 2007
    Last edited: 29 Sep 2007
  10. 0x0c0de

    0x0c0de Elder - Старейшина

    Joined:
    25 May 2007
    Messages:
    441
    Likes Received:
    396
    Reputations:
    297
    Теперь и под 2000 работает и под XP, все-таки доделала.... Суть в том, что вся процедура копируется в выделенную память. Конец процедуры определяется по опкоду рет xxxx. Написала для этого функцию. В качестве первого ее аргумента принимается адрес функции, в качестве второго - адрес загрузки библиотеки ntdll. Ясное дело, что без нормального дизассемблера длин тут не обойтись. Поэтому я встроила VirXasm32 (на васме можно взять), вы же можете другой встроить или свой написать. Лишь бы длина инструкции возвращалась в eax. Теперь не надо никаких характеристик секций менять, как раньше. все просто и удобно)))

    Процедуре необходимо

    Code:
    .data
    ntdl_ db "ntdll.dll",0 
    buf_ db 300 dup(?)
    sl db "\",0
    
    Сам код
    Code:
    procedurecopy proc address_function:dword,imagebase:dword
    ; в eax возвращается адрес украденного кода, в edx - его размер. Если ошибка - регистры пусты
    ; освободить выделенную память вы должны сами! С помощью VirtualFree 
    local hFile:dword
    local dwSize:dword
    local hMap:dword
    local Base:dword
    local rva:dword
    local codesize:dword
    mov edi,address_function
    sub edi,imagebase
    mov rva,edi
    invoke GetSystemDirectory,offset buf_,255 ; системная директория
    test eax,eax
    je ret_
    invoke lstrcat,offset buf_,offset sl ; формируем путь к ntdll
    invoke lstrcat,offset buf_,offset ntdl_ 
    invoke CreateFile,offset buf_,GENERIC_READ,FILE_SHARE_READ,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL, NULL
    mov hFile,eax
    inc eax
    jz ret_
    invoke GetFileSize,hFile, NULL
    test eax,eax
    jz ret_
    mov dwSize,eax
    invoke CreateFileMapping,hFile, NULL,1000002h, 0, dwSize, NULL
    mov hMap,eax
    invoke MapViewOfFile,hMap, FILE_MAP_READ, 0, 0, dwSize
    mov Base,eax
    test eax,eax
    je ret_
    add eax,rva
    mov rva,eax
    mov esi,eax
    mov eax,codesize ; это на байт короче, чем mov codesize,0
    xor codesize,eax
    disasm:
    call VirXasm32 ; дизассемблер длин. любой вам понравившийся, можно даж самопальный ;-)
    cmp al,3 ; длина инструкции три байта? проверяем не ret xxxx?
    jz pr_ ; проверяем на рет 
    add codesize,eax ; если длина другая - идем дизасмить дальше, прибавляем длину к общей длине кода
    add esi,eax ; к esi добавляем длину инструкции
    jmp disasm 
    pr_:
    cmp byte ptr[esi],0c2h ; начало инструкции ret xxxx
    jnz disasm_ ; идем дальше
    jmp end_dasm_
    disasm_:
    add esi,eax
    jmp disasm
    end_dasm_:
    add codesize,eax
    inc codesize
    invoke VirtualAlloc,0,codesize,MEM_COMMIT or MEM_RESERVE,PAGE_EXECUTE_READWRITE	
    test eax,eax
    je ret_
    mov esi,rva
    mov edi,eax
    mov rva,eax
    mov ecx,codesize
    rep movsb 	 
    invoke UnmapViewOfFile,Base
    invoke CloseHandle,hMap
    invoke CloseHandle,hFile
    mov eax,rva
    mov edx,codesize
    jmp rt
    ret_:
    sub eax,eax ; если ошибка, то обнуляем
    sub edx,edx
    rt:
    ret
    procedurecopy endp
    
    emulator_vir_protect proc  hProcess:dword,lpAddress:dword,dwSize:dword,flNewProtect:dword,lpflOldProtect:dword,addr_native:dword
     ; теперь добавился еще один параметр. адрес, по которому располагается native функция. то, что вернула в eax procedurecopy 
    push lpflOldProtect
    push flNewProtect
    lea eax,dwSize
    push eax
    lea eax,lpAddress
    push eax
    push hProcess
    call addr_native
    ret
    emulator_vir_protect endp
    
     
    #10 0x0c0de, 30 Sep 2007
    Last edited: 30 Sep 2007