среда, 31 августа 2011 г.

Что будет, если ...

Если из windows убрать такой важный механизм как APC? Некоторые программисты вообще не слышали ни о каком APC, казалось бы, исчезни он вовсе - чего мы лишимся? Оказывается довольно многого.

Неполучиться замораживать потоки(suspend), убивать потоки, получать/устанавливать контекст для потока, устанавливать таймеры.
Но на самом деле это все мелочи, так как запрет на заморозку/уничтожение потоков просто меркнет перед тем фактом, что без APC не получится даже просто запустить пользовательский поток! Так как старт любого юзермодного потока начинается с доставки APC.

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

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

вторник, 30 августа 2011 г.

Вновь о трассировке

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

Для этого потребуется процессор с поддержкой технологии VT-X и немного шаманства. Шаманство будет заключаться в запуске windows в качестве гостевой системы. Далее с введенным Trap Flag запускается гостевая система, и при выполнении каждой инструкции будет выполнен VM Exit в хостовую систему.

Из гостевой системы детект хоста невозможен по определению, поэтому единственный способ который можно использовать для детекта такого вида трассировки - это тайминговые атаки(через RDTSC). Однако мало того, что RDTSC является одной из VM-Exit команд и для гостевой можно подсунуть все что угодно, так еще можно сэмулировать (через CPUID) факт того, что код выполняется на более медленном процессоре чем он есть на самом деле. Все это сведет тайминговые атаки ( которые сами по себе являются ненадежными ) на нет.

суббота, 6 августа 2011 г.

Препятствуем статическому анализу

Делать это можно по разному, можно через полиморфизм/метаморфизм/обфускацию, а можно изменить подход - убрать сам объект скрытия. Ведь нет кода - нечего и обнаруживать. Что же тогда процессор будет выполнять, если кода нет? Ну, на самом деле он есть, просто в другом модуле. Речь пойдет о Return Oriented Programming (rop).

Rонцепция rop проста - в уже загруженных модулях ищутся так называемые rop-gadgets: последовательности инструкций, заканчивающиеся инструкцией ret. Затем на эти готовые инструкции передается управление. Таким образом, весь исполняемый код программы как бы собирается из кусочков чужих модулей, что позволяет не иметь своего кода, вместо него будет лишь череда аргументов/адресов возврата.

В качестве примера напишем простейшее приложение на masm выводящее "Hello" через MessageBox:

.386
.model flat, stdcall
option casemap :none

include \masm32\include\windows.inc
include \masm32\include\kernel32.inc
include \masm32\include\user32.inc

includelib \masm32\lib\kernel32.lib
includelib \masm32\lib\user32.lib

.code

start:
    jmp @F
      szTitle    db "Hello",0
    @@:
   
    push MB_OK
    push offset szTitle
    push offset szTitle
    push 0
    call MessageBox

    push 0   
    call ExitProcess

end start

В IDA будет виден простой и понятный код, теперь перепишем его используя rop технику, избавившись от констант, статической линковки user32 и адресов функций ( для простоты вместо адресов ф-ций будет хардкод ).

Rop-gadgets будем искать в ntdll.dll, так как ее грузит ядро во все процессы, то есть она есть везде.

User32.dll для нашей программы придется грузить динамически, так что ищем соответствующий гаджет в ntdll.dll.
Чтобы не искать вручную, напишем простенький скрипт:

import idaapi
import idautils
import idc

def GetNodeDisasmView( nodeStart, nodeEnd ):
    result = ""
    instructions = Heads( nodeStart, nodeEnd )

    for address in instructions:
        result += hex(address) + "    " + GetDisasm( address ) + "\n"
       
    return result

def SaveTwoNodes( fileHandle, prevNodeStartEA, prevNodeEndEA, idaNodeStartEA, idaNodeEndEA ):
    result = GetNodeDisasmView( prevNodeStartEA, prevNodeEndEA )
    fileHandle.write( result )
    result = GetNodeDisasmView( idaNodeStartEA, idaNodeEndEA )
    result = result + "\n"
    fileHandle.write( result )

def SaveOneNode( fileHandle, idaNodeStartEA, idaNodeEndEA ):
    result = GetNodeDisasmView( idaNodeStartEA, idaNodeEndEA )
    result = result + "\n"
    fileHandle.write( result )

#      
# entry point
#

print "Script Start..."

ea = ScreenEA()
fileHandle = file("C:\\retNodes.txt", "w");

for functionAddress in Functions( SegStart(ea), SegEnd(ea) ):
    rawNodes = idaapi.FlowChart( idaapi.get_func( functionAddress ), None, idaapi.FC_PREDS )

    for idaNode in rawNodes:
        if idaapi.is_ret_block( idaNode.type ):
            if idaNode.id > 0:
                prevNode = rawNodes[ idaNode.id - 1 ]
                SaveTwoNodes( fileHandle, prevNode.startEA, prevNode.endEA, idaNode.startEA, idaNode.endEA )
            else:
                SaveOneNode( fileHandle, idaNode.startEA, idaNode.endEA )

fileHandle.close()

print "Script End..."

Далее в текстовом файле среди rop-gadgets ищем подходящий, и находим:

0x7c927e1c    mov     edi, edi
0x7c927e1e    push    ebp
0x7c927e1f    mov     ebp, esp
0x7c927e21    push    [ebp+arg_C]
0x7c927e24    push    [ebp+arg_8]
0x7c927e27    push    [ebp+arg_4]
0x7c927e2a    call    [ebp+arg_0]
0x7c927e2d    pop     ebp
0x7c927e2e    retn    10h

Вызывается ф-ция с тремя аргументами, адрес ф-ции также передается через стек.
HMODULE WINAPI LoadLibrary( __in LPCTSTR lpFileName ); - один параметр, не подходит
Зато вполне подходит LoadLibraryEx:
HMODULE WINAPI LoadLibraryEx( __in LPCTSTR lpFileName, __reserved HANDLE hFile, __in DWORD dwFlags );
Собственно одного гаджета вполне хватит для нашей программы, дальнейшая работа будет заключаться только в подготовке 
параметров для последующих ф-ций. Конечный результат:
.386
.model flat, stdcall
option casemap :none

include \masm32\include\windows.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\kernel32.lib

.code

start:
    ; rop gadget:

    ; 0x7c927e1c    mov     edi, edi
    ; 0x7c927e1e    push    ebp
    ; 0x7c927e1f    mov     ebp, esp
    ; 0x7c927e21    push    [ebp+arg_C]
    ; 0x7c927e24    push    [ebp+arg_8]
    ; 0x7c927e27    push    [ebp+arg_4]
    ; 0x7c927e2a    call    [ebp+arg_0]
    ; 0x7c927e2d    pop     ebp
    ; 0x7c927e2e    retn    10h

    push 00006C6Ch ; "ll"
    push 642E3233h ; "32.d"
    push 72657375h ; "user"
    push 0000006Fh ; "o"
    push 6C6C6548h ; "Hell" (yeah! =])

    push 0         ; uExitCode for ExitProcess
    push 0deadc0deh; 
 
    push 0         ; 
    push 0012FFB0h ; ptr to "Hello"
    push 0012FFB0h ; ptr to "Hello"
    push 0         ; 
    push 7C81CB12h ; return address ( to ExitProcess ) 
  
    push 0                               ; arg_C
    push 0                               ; arg_8
    push 0012FFB8h ; ptr to "user32.dll" ; arg_4
    push 7C801D53h ; LoadLibraryExA      ; arg_0
    push 7E3A07EAh ; return address ( to MessageBoxA )

    push 7c927e1ch ; goto rop-gadget 0x7c927e1c
    ret
end start 
Теперь статическим анализом ничего понять из данного кода будет невозможно, вернее, именно для этого простейшего 
примера легко можно загрузить ntdll.dll, и найти соответствия адресов и инструкций. Однако в более сложных случаях 
код может собираться из многих модулей, модули изза ASLR/конфликтов адресов могут грузиться по разным адресам, и в 
таком случае поможет только динамический анализ( простейший трейсер ).