четверг, 23 декабря 2010 г.

Чистка бинарного кода от морфинга

Хотел продемонстрировать простоту использования IDA Python, но както не попадалась интересной цели. И вот наконец, нашел кое-что интересное:

http://cracklab.ru/f/index.php?action=vthread&forum=6&topic=17494

Автор описывает это так:

"Вот семпл выводящий сообщение. Над исходным бинарем выполнен автоматический ресерч и морфинг, при котором выполнена генерация Gs-серий для линейных блоков http://indy-vx.narod.ru/Temp/GsTest.zip"

Загрузив в GsTest в IDA, можно увидеть интересный способ морфинга бинарного кода с использованием segment override prefix'ов. Выглядит это так:

start        proc near
        push    ebp
        mov    ebp, esp
        add    esp, 0FFFFFFD4h
        push    fs
        pop    gs
        mov    eax, large gs:30h
        push    ds
        pop    gs
        assume gs:_text
        mov    eax, gs:[eax+0Ch]
        push    ds
        pop    gs
        mov    eax, gs:[eax+0Ch]
        push    ds
        pop    gs
        mov    eax, gs:[eax]
        push    ds
        pop    gs
        mov    ebx, gs:[eax+18h]
        push    ss
        pop    gs
        assume gs:nothing
        mov    dword ptr gs:[ebp-10h],    59B88A67h

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

Скрипт для каждой ф-ции перечисляет все инструкции, с целью поиска pop gs, затем получает код префикса (в данном ехе используются лишь segment override prefixes), затем в базу данных IDA вносятся изменения( nop's и соответствующие коды префиксов там где юзается gs) для получения чистого кода. Так как у IDA немного едет крыша и она прячет часть кода в assume gs:xxx, в скрипте делается принудительный undefine. После работы скрипта IDA database будет содержать практически готовый код, за исключением нопов. В принципе ничего не мешает сохранить эту базу и сделать вывод уже без нопов.

Конечный результат выглядит так:

401000        push    ebp
401001        mov     ebp, esp
401003        add     esp, 0FFFFFFD4h
40100a        mov     eax, large fs:30h
401014        mov     eax, [eax+0Ch]
40101b        mov     eax, [eax+0Ch]
401022        mov     eax, [eax]
401028        mov     ebx, [eax+18h]
40102f         mov     [ebp+var_10], 59B88A67h
40103a        mov     [ebp+var_C], 0DB164279h
401045        mov     [ebp+var_8], 9E1E35CEh
401050        mov     [ebp+var_4], 0
401057        lea     eax, [ebp+var_10]
40105a        push    eax
40105b        push    0
40105d        push    ebx
40105e        call    sub_401103

По моему вполне читабельно, и скорее всего компилябельно.

Сам скрипт:

from idaapi import *
from idc import *
from idautils import *

def GetPreviousInstructionAddress( address ):
    prevInstructionAddress = PrevNotTail( address )
    return prevInstructionAddress

def GetNextInstructionAddress( address ):
    nextInstructionAddress = NextNotTail( address )
    return nextInstructionAddress

def GetSegmentOverridePrefixAndEliminatePush( address ):
    nopOpcode = 0x90

    if GetMnem( address ) == "push":
        if GetOpnd( address, 0 ) == "cs":
            PatchByte( address, nopOpcode ) # 0Eh
            return nopOpcode                
        elif GetOpnd( address, 0 ) == "ss":
            PatchByte( address, nopOpcode ) # 16h
            return nopOpcode                
        elif GetOpnd( address, 0 ) == "ds":
            PatchByte( address, nopOpcode ) # 1Eh
            return nopOpcode
        elif GetOpnd( address, 0 ) == "es":
            PatchByte( address, nopOpcode ) # 06h
            return nopOpcode                
        elif GetOpnd( address, 0 ) == "fs":
            PatchWord( address, 0x9090 )     # 0FA0h
            return 0x64
        elif GetOpnd( address, 0 ) == "gs":
            PatchWord( address, 0x9090 )    # 0FA8h   
            return nopOpcode                
        else:
            return 0

def EliminatePopSegmentOverridePrefix( address ):
    PatchWord( address, 0x9090 )

def SetSegmentOverridePrefix( address, segmentOverridePrefix ):
    PatchByte( address, segmentOverridePrefix )

def ProcessGs( address ):
    prevInstructionAddress = GetPreviousInstructionAddress( address )
    segmentOverridePrefix = GetSegmentOverridePrefixAndEliminatePush( prevInstructionAddress )
    nextInstructionAddress = GetNextInstructionAddress( address )

    MakeUnkn(nextInstructionAddress, DOUNK_SIMPLE)
    nextByte = nextInstructionAddress
    MakeCode( nextByte + 1 )

    SetSegmentOverridePrefix( nextInstructionAddress, segmentOverridePrefix )

def RemoveGsInFunction( functionStart, functionEnd ):
    for address in Heads( functionStart, functionEnd ):
        if GetMnem( address ) == "pop" and GetOpnd( address, 0 ) == "gs":
            ProcessGs( address )
            EliminatePopSegmentOverridePrefix( address )

def PostProcessMakeCode( functionStart, functionEnd ):
    start = functionStart
    end = functionEnd

    for address in Heads( functionStart, functionEnd ):
        prevByte = address
        prevByte = prevByte - 1
        MakeUnkn( address, DOUNK_SIMPLE )

    MakeCode( start )

#      
# entry point
#

ea = ScreenEA()

for func in Functions( SegStart(ea), SegEnd(ea) ):
    functionStart = func
    functionEnd   = FindFuncEnd( functionStart )

    RemoveGsInFunction( functionStart, functionEnd )
    PostProcessMakeCode( functionStart, functionEnd )

Данный скрипт собранный на коленке за 20 мин, естественно не претендует на generic деобфускатор( в нем не обрабатываются chunks, не убираются jxx следующие друг за другом и т.д., скрипт вобще не содержит никакой обработки control flow ), он лишь показывает, как быстро можно писать обработку бинарного кода, используя IDA Python.

среда, 22 декабря 2010 г.

Скрипт для нахождения id сисколов

Знакомый попросил о IDA скрипте, который находит сисколы в ntdll.dll, генерит текстовый файл с парами: имя сервисной ф-ции + id, я такой нашел у себя, и подумал, что скрипт может пригодиться кому-нибудь еще, поэтому выкладываю тут(формат вывода легко можно подстроить для себя).

#include <idc.idc>

//
// Info: This script gets pair: "Service Function Name + Service Function Id" and save it into the file("c:\ServiceFuncNames.txt").
// HowToUse: Just run script in IDA.
// Version: 1.0
// Author: TSS
// Data: 18.05.10
//

static Processing( currentAddress, startAddress, reportFile )
{
    auto functionName;
    auto functionId;
    auto movInstructionAddress;

    //
    // get "mov eax, index" instruction
    //

    movInstructionAddress = PrevHead( currentAddress, startAddress );

    //
    // make some validation
    //
   
    if ( GetOpType( movInstructionAddress, 1 ) != 5 || GetMnem( movInstructionAddress ) != "mov" )
    {
        Message( "Error: before syscall wrong instruction\n" );
        return;
    }

    //
    // get service function id
    //

    functionId = GetOperandValue( movInstructionAddress, 1 );

    //
    // get function name
    //

    functionName = GetFunctionName( currentAddress );

    //
    // save info into the file
    //

    fprintf( reportFile, "%s, Id = %x\n", functionName, functionId );
}

static EnumSyscall( startAddress, endAddress, reportFile )
{
    auto currentAddress;
   
    currentAddress = startAddress;

    while ( currentAddress != BADADDR )
    {
        if ( GetMnem( currentAddress ) == "syscall" )
            Processing( currentAddress, startAddress, reportFile );
       
        currentAddress = NextHead( currentAddress, endAddress );
    }

    return 0;
}

static main()
{
    auto addressInCodeSegment;
    auto codeSegmentStartAddress;
    auto codeSegmentEndAddress;
    auto reportFile;

    Message( "Script started. Wait a couple of seconds...\n" );

    //
    // choosing first ordinal, cause it placed in code segment anyway
    //

    addressInCodeSegment = GetEntryPoint( 1 );

    //
    // we will search in entire code section
    //

    codeSegmentStartAddress = SegStart( addressInCodeSegment );
    codeSegmentEndAddress = SegEnd( addressInCodeSegment );

    //
    // open file for report
    //

    reportFile = fopen( "c:\\ServiceFuncNames.txt", "wt" );

    //
    // find and process all syscall's
    //

    EnumSyscall( codeSegmentStartAddress, codeSegmentEndAddress, reportFile );

    //
    // close report file
    //

    fclose( reportFile );

    Message( "GetServiceFunctions script complete work, check file c:\ServiceFuncNames.txt for result...\n" );
}

четверг, 2 декабря 2010 г.

Python и реверсинг

Казалось бы - какая связь? А она есть =]



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

Что же касается плагинов, то их возможности почти безграничны, но есть один недостаток и он довольно серьезен ( по крайней мере для меня ): невозможность без перезагрузки IDA поменять что-то в плагине, и если проект большой - эта мелочь настолько тормозит разработку, что возникают мысли заменить это на чтото иное. И после непродолжительных поисков и тестов, лучшим выбором оказался Python встроенный в IDA.

Страничка проекта idapython: http://code.google.com/p/idapython/

С Python'ом до этого я не встречался, но основы языка учаться сверх быстро.
В итоге профит - написание графа code flow ( то есть то, что показывает нам WinGraph32 в IDA ) на питоне занимает вечер, скорость разработки очень радует, по сравнению с плагинами.

Да и понаписано довольно много на питоне, включая такие интересные штуки как, например, PaiMei - целый фреймворк для реверсера, включающий в себя кучу компонентов для статического и динамического анализа.


Теперь что касается WinDbg, у него тоже есть скриптовый язык и плагины, до плагинов у меня руки не дошли( но думается, что проблемы там могут возникнуть теже, что и с плагинами IDA ), а вот скриптовый язык... О нем без мата я говорить не могу, код такого типа приходится писать:


.for (r $t4 = poi(@$t3); (@$t4 != 0) & (@$t4 != @$t3); r $t4 = poi(@$t4))
{
                r? $t5 = #CONTAINING_RECORD(@$t4, nt!_ETHREAD, ThreadListEntry)

                r? $t6 = @$t5->Cid
                r? $t6 = (nt!_CLIENT_ID *)&@$t6
                r $t6 = @@c++(@$t6->UniqueThread)

                r? $t7 = (nt!_KTHREAD *)@$t5
                r $t8 = @@c++(@$t7->Alertable)

Вобщем юзать _это_ просто противно, однако и тут нам чип и дейл Python спешит на помощь.

Добрые люди интегрировали его и в WinDbg: http://pykd.codeplex.com/

И теперь скрипты на том же питоне пишуться уже с превеликим удовольствием.

среда, 10 ноября 2010 г.

Все MSR hooker's делают это

Из всех перехватчиков sysenter работающих через перезапись MSR_SYSENTER_EIP, я не видел ни одного правильно написанного.

Ну то есть они конечно работали, перехватывали, но тем не менее своим перехватом они делали возможным создать условие для Denial of Service атаки, то есть к банальному BSOD'у.

Теорию я описывать не буду, да там по сути и описывать нечего, все тривиально, а вот суть атаки опишу:

push EFLAGS_TF
popfd
sysenter

То есть, выполняем sysenter с взведенным TF флагом.

Далее, при неправильно написанном MSR перехватчике будет бсод.

Чтобы разобраться, почему так происходит нужно взглянуть на обработчик Single-step trap'а:

_KiTrap01:
...

// проверяется EIP при исключении, и если он равен KiFastCallEntry, прыгаем на TF_Dispatch


.text:00467393                 mov     ecx, [ebp+68h]     // KTRAP_FRAME.Eip
.text:00467396                 cmp     ecx, offset _KiFastCallEntry
.text:0046739C                 jz      TF_Dispatch

...

// а в нем переустанавливаем KTRAP_FRAME.Eip в KiFastCallEntry2 и прыгаем на общий код выхода из interrupt/exception:

.text:00467268 TF_Dispatch:
.text:00467268                 mov     dword ptr [ebp+68h], offset _KiFastCallEntry2
.text:0046726F                 and     byte ptr [ebp+71h], 0FEh
.text:00467273                 jmp     Kei386EoiHelper@0

Т.е. ядро имеет специальный case для обработки ситуации при single step на sysenter, перенаправляя управление на KiFastCallEntry2.
Самописные MSR hooker's этого не делают, и в этом их ошибка.

Правильно написанный MSR hooker должен пропатчить ядро, т.е. заменить
cmp     ecx, offset _KiFastCallEntry на cmp     ecx, offset _YourFastCallEntry в KiTrap01.

Интересно как в софтверных компаниях разруливают подобные ситуации, ведь баги подобные этим не обнаружит ни один тестер, как бы хорош он не был. Да и code review ничего не даст.

воскресенье, 7 ноября 2010 г.

Изменения SSDT на x64

В 64 битных windows внутренности System Service Table изменились, например, вместо адресов в KiServiceTable находятся смещения, а KiArgumentTable не используется вовсе.

Причем глядя в IDA на KiServiceTable все это как-то неочевидно:

.text:000000014005B000 KiServiceTable  dq offset NtMapUserPhysicalPagesScatter
.text:000000014005B008                 dq offset NtWaitForSingleObject
.text:000000014005B010                 dq offset NtCallbackReturn
.text:000000014005B018                 dq offset NtReadFile
.text:000000014005B020                 dq offset NtDeviceIoControlFile
.text:000000014005B028                 dq offset NtWriteFile
.text:000000014005B030                 dq offset NtRemoveIoCompletion

Однако стоит в отладчике посмотреть якобы "адреса" в KiServiceTable:

kd> dqs  nt!KiServiceTable
fffff800`01c6e000  025ff700`03935200
fffff800`01c6e008  022b7d05`fff95e00
fffff800`01c6e010  0268e605`026b1506
fffff800`01c6e018  021d6440`02389701

... чтобы увидеть, что на адреса это мало похоже(адреса то не полноценно 64х битные, а всего лишь 48ми битные). А если смотреть двойные слова:

kd> dds  nt!KiServiceTable
fffff800`01c6e000  03935200
fffff800`01c6e004  025ff700
fffff800`01c6e008  fff95e00
fffff800`01c6e00c  022b7d05 <== 5 это число стековых аргументов ф-ции
fffff800`01c6e010  026b1506

... то видно, что это офсеты.

А KiArgumentTable не используется, потому что аргументы закодированы в 4х младших битах офсета.

Напомню, что 4 аргумента всегда передаются через регистры ( rcx, rdx, r8, r9 ), остальные через стек, таким образом для ф-ций с <= 4 агрументами количество этих самых аргументов нигде не храниться.

Взглянув на обработчик syscall'ов, ф-цию KiSystemCall64 ( получить можно через MSR(LSTAR) ) видно следующее:

KiSystemCall64:

movsxd  r11, dword ptr [r10+rax*4] <== берем из KiServiceTable LONG ( именно LONG, ибо оффсет знаковый, может быть отрицательным )
mov     rax, r11
sar       r11, 4   <== 4 бита убираем, чтобы получить чистый офсет
add     r10, r11 <== KiServiceTable + полученный офсет = адрес сервисной ф-ции
...
далее если необходимо копируются стековые аргументы из юзермодного стека в ядерный, и вызывается сервисная ф-ция.

Преобразование таблички из той, что видно в IDA, в ту, которая на самом деле лежит в памяти делает ф-ция KeCompactServiceTable на этапе инициализации ядра.

Если взглянуть на nt!KiArgumentTable, то видно, что она тоже заполнена, однако не используется. Почему сделано так, а не иначе мне неведомо, если кто знает, опишитесь в комментах.

четверг, 4 ноября 2010 г.

Важность дополнительной информации при анализе крешдампов

IRQL_NOT_LESS_OR_EQUAL (a)
An attempt was made to access a pageable (or completely invalid) address at an
interrupt request level (IRQL) that is too high. This is usually
caused by drivers using improper addresses.
If a kernel debugger is available get the stack backtrace.
Arguments:

Arg1: e25393ec, memory referenced
Arg2: 0000001c, IRQL
Arg3: 00000000, bitfield
Arg4: 804fac65, address which referenced memory

Debugging Details:


READ_ADDRESS: e25393ec Paged pool

CURRENT_IRQL: 1c 

Именно такой BSOD начал вылезать на winXP sp2 в моем драйвере.

Анализ крешдампа проблему падения драйвера не решил, напротив, возникли новые, еще более интересные вопросы =]

Итак, из крешдампа стало ясно, что при вызове системой ф-ции KeWaitForSingleObject происходил доступ к выгружаемой памяти на высоком IRQL.

И все бы ничего, кроме двух мистических вещей.

Первая заключалась в том, что память к которой было обращение принадлежала первому параметру ф-ции KeWaitForSingleObject, а именно объекту.
Причем память эта была выгружаемой, а как известно, объекты ядра находятся в невыгружаемой памяти. Т.е. память явно не принадлежала объекту, что и подтвердила команда !object.

Вторая мистическая вещь - это запредельный IRQL, равный CLOCK LEVEL.

Самым очевидным выводом всей этой истории было предположение, что проблема в железе.

Однако спустя некоторое время у меня появился другой дамп, тоже с этим же багчеком, но стек вызовов был другим, уже без вызова KeWaitForSingleObject:

a334fbc4 804f9fdb badb0d00 e24801bc 804f9fbe ntKiTrap0E+0x238
a334fc40 806e498e e24801b4 00000000 00000000 ntKeSetEventBoostPriority+0x1f
a334fc50 80569d29 e243a4e0 e2480198 805593a0 halExReleaseFastMutex+0x1a
a334fc64 80569d77 e2480198 a334fc80 00000000 ntCmpCheckRecursionAndRecordThreadInfo+0x47
a334fc94 80634f9c 0000000e a334fcb8 00000000 ntCmpCallCallBacks+0x3b
a334fcb0 805b9e2d e38818f8 00000000 e38818e0 ntCmpDeleteKeyObject+0x22
a334fccc 805257b8 e38818f8 00000000 000004fc ntObpRemoveObjectRoutine+0xdf
a334fce4 805bad03 883a3020 e32ebc58 879c8020 ntObfDereferenceObject+0x4c
a334fcfc 805bad99 e32ebc58 e38818f8 000004fc ntObpCloseHandleTableEntry+0x155
a334fd44 805baed1 000004fc 00000001 00000000 ntObpCloseHandle+0x87
a334fd58 8054060c 000004fc 04e1fe10 7c90eb94 ntNtClose+0x1d
a334fd58 7c90eb94 000004fc 04e1fe10 7c90eb94 ntKiFastCallEntry+0xfc
04e1fdfc 7c90d592 77dc6bcc 000004fc 00000000 ntdllKiFastSystemCallRet
04e1fe00 77dc6bcc 000004fc 00000000 04e1fe1c ntdllZwClose+0xc

Дальше было просто, вместо отладчика я открыл...google! Да-да, он иногда бывает полезнее отладчика. И через несколько минут нашел решение своей проблемы: http://support.microsoft.com/kb/936456/ - виноват был не драйвер, а система.

Но вопрос о высоком IRQL оставался открытым.

Изучение ядерного дампа,  ничего не изменило, проблема оставалась.
Оставалось смотреть внутренности KeWaitForSingleObject и там я обнаружил одну интересную вещь, а именно, ф-цию KiUnlockDispatcherDatabase.

Однако захват и освобождение базы данных диспатчера делались на DISPATCH LEVEL'е.
Я знал, что решение где-то рядом, поэтому следующим шагом был обзор всех подобных ф-ций, и через определенное время забрежжил свет в конце туннеля. Нашлась ф-ция KeRaiseIrqlToSynchLevel, которая задирала IRQL до SYNCH_LEVEL.

#define IPI_LEVEL 29
#define POWER_LEVEL 30
#define HIGH_LEVEL 31
#define SYNCH_LEVEL (IPI_LEVEL-1) <= как раз 28 или 1Ch

Казалось, кусочки паззла наконец встали на свои места, для уверенности я полез в hal.dll, и увидел там совсем не то, что ожидал:

.text:80012700 @KeAcquireSpinLockRaiseToSynch@4 proc near
.text:80012700                 mov     edx, ds:0FFFE0080h
.text:80012706                 mov     eax, edx
.text:80012708                 shr     eax, 4
.text:8001270B                 movzx   eax, ds:_HalpVectorToIRQL[eax]
.text:80012712                 mov     dword ptr ds:0FFFE0080h, 41h <=== APIC[TASK PRIORITY REGISTER] = DPC VECTOR
.text:8001271C                 retn
.text:8001271C @KeAcquireSpinLockRaiseToSynch@4 endp

Никакого SYNCH_LEVEL'a не было и в помине...

Однако, открыв полный дамп с машины на которой был бсод, увидел следующее:

hal!KeAcquireSpinLockRaiseToSynch:
806e4870 mov     edx,dword ptr ds:[0FFFE0080h]
806e4876 mov     eax,edx
806e4878 shr     eax,4
806e487b movzx   eax,byte ptr hal!HalpVectorToIRQL (806ef218)[eax]
806e4882 test    dword ptr [ecx],1
806e4888 jne     hal!KeAcquireSpinLockRaiseToSynch+0x34 (806e48a4)
806e488a mov dword ptr ds:[0FFFE0080h],0D1h        <=== APIC[TASK PRIORITY REGISTER] = CLOCK VECTOR

Т.е. реализации hal также как и ядра, различаются на однопроцессорных и многопроцессорных системах, и вначале я смотрел однопроцессорную версию, в которой DISPATCH LEVEL'a вполне достаточно.

Теперь все встало на свои места, система действительно может вызывать код на запредельном IRQL( на MP системах ) и к поломке железа мой бсод не имел никакого отношения.

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

End of story... =]

четверг, 28 октября 2010 г.

BSOD из юзермода

На долю WinXP приходится более 60%в рынка десктопных ОС, это самая популярная ОС на сегодняшний день.

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

Но как насчет того, чтобы уронить ОС без загрузки драйвера в синий экран?
Уж такие штуки должны быть невозможны в принципе, ведь это не какая-нибудь 98я винда, однако, WinXP со всеми обновлениями падает в бсод, если послать сообщение серверу подсистемы win32(с кодом UserpActivateDebugger) и pid'ом равным pid'у процесса csrss.exe:


void MakeBsodFromUsermode( DWORD сsrssPid )
{
    CSR_API_MSG msg = {0};


    msg.u.ApiMessageData[0] = сsrssPid; // pid процесса csrss.exe

    CsrClientCallServer( &msg, NULL, 0x30404, sizeof( CLIENT_ID ) );
}


Я не буду рассказывать про сервер подсистемы win32, информации о нем достаточно на просторах интернета, скажу лишь, что сервер этот содержит несколько dispatch таблиц, с хендлерами, которые суть есть обработчики сообщений который он может принимать от процессов (они приходят по LPC), так вот, обработчик сообщения UserpActivateDebugger наивно полагает, что раз пришло сообщение об активации отладчика, то надо непременно сделать:

_SrvActivateDebugger:
...
.text:75B47975                 mov     esi, [ebp+arg_0]
.text:75B47978                 mov     eax, large fs:18h
.text:75B4797E                 mov     ecx, [esi+28h]
.text:75B47981                 cmp     ecx, [eax+20h]
.text:75B47984                 jnz     short loc_75B4798F
.text:75B47986                 call    _DbgBreakPoint@0


То есть, если pid текущего процесса, т.е. csrss.exe равен pid'у который пришел в теле сообщения, то вызывается DbgBreakPoint.
Он вызывается даже если отладчик не подключен, естественно, нет отладчика - некому и обработать исключение, отсюда и BSOD.

Данный баг я нашел в конце 2009го года, но он до сих пор не закрыт в XP(в более старших версиях windows баг закрыли).

Начиная с Висты Микрософт серьезно занялись безопасностью, переписали и проревьювили кучу своего кода( тот же сетевой стек ), более серьезный аудит и ревью позволили им сделать свой код более безопасным, однако как в любой сложной системе баги всегда есть и всегда будут.

Бросайте winXP и переходите на win7. Хотя я сам пока сижу на старой доброй XP =]

среда, 27 октября 2010 г.

Детект доступа к PhysicalMemory или почему "стандартные" решения это плохо

Предположим, нам нужно задетектировать открытие секции "\\Device\\PhysicalMemory" с правами на запись юзерским приложением.
Стандартным решением в такой ситуации будет перехват ф-ции ZwOpenSection в ssdt a.k.a сплайсинг.

Правильно ли это? Казалось бы да, других способов открыть секцию минуя эту ф-цию вроде бы не существует, и все обращения пользовательских процессов к данной секции будут найдены. Однако, это не так.

Секрет в том, что секция может быть открыта неявно.
К примеру, java.exe при определенных обстоятельствах обращается к драйверу videoprt.sys, а он делает:

PAGE:0001D089 push ds:_PhysicalMemorySection
PAGE:0001D08F call ds:impObOpenObjectByPointer@28


Все это конечно происходит в контексте вызывающего, и KTHREAD.PreviousMode равен UserMode. Таким образом никаким сплайсингом такое не задетектировать, нужно спуститься чуть глубже и установить OpenProcedure у объекта секция, в агрументах придет вся необходимая информация, которой достаточно для определения имени секции, вызвающего процесса, маски доступа и т.д.


вторник, 26 октября 2010 г.

Виртуализация файлов изнутри

После прочтения статьи Руссиновича мне стало интересно, каким образом реализована виртуализация файлов. Собсно, эта запись в блоге посвящена данному мини ресерчу.

Итак, рассмотрим внутренности драйвера Luafv.sys.

Для начала глянем реестр:

[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\services\luafv\Instances]
"DefaultInstance"="luafv"

[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\services\luafv\Instances\luafv]
"Altitude"="135000"
"Flags"=dword:00000000

Судя по альтитуде, мы имеем дело с минифильтром.
Почему так? Все просто, фильтр менеджер может присоединять в стеке драйверов другие устройства(виртуальные), каждое такое устройство называется фрейм, и может содержать в себе несколько минифильтров.

Альтитуда нужна для их идентификации( есть определенные группы, для каждой из которых определен диапазонам альтитуд, к примеру FSFilter Anti-Virus, FSFilter Virtualization и т.д., подробнее в msdn).

В описании драйвера сказано, что это LUA File Virtualization Filter Driver, 
альтитуда равна 135000, что попадает в диапазон альтитуд FSFilter Virtualization(130000-139999).
На 99% можно быть уверенными что мы имеем дело с минифильтром, чтобы исключить оставшийся процент, достаточно заглянуть в импорт драйвера и увидеть:

.idata:000131A4 ;
.idata:000131A4 ; Imports from FLTMGR.SYS
.idata:000131A4 ;
.idata:000131A4 ; __declspec(dllimport) __stdcall FltCancelIo(x)
.idata:000131A4                 extrn __imp__FltCancelIo@4:dword


FLTMGR.SYS это фильтр менеджер, драйвер предоставляющий высокоуровневую среду для создания минифильтров и скрывающий низкоуровневую работу, с ним написание фильтров становится довольно простым делом.

Итак, Luafv.sys это минифильтр и это радует, т.к. искать колбеки будет довольно просто.

Пару слов о минифильтрах. Минифильтр позволяет повесить колбеки(pre и post) на определенные события. Минифильтр устанавливает требуемые ему колбеки в составе структуры, затем структура идет в ф-цию FltRegisterFilter.

После начала фильтрации(FltStartFiltering), при наступлении события(к примеру открытие/создание файла) будет вызываться колбек.
Итак, нас будет интересовать pre колбек устанавливаемый на событие IRP_MJ_CREATE.
После непродолжительного анализа кода выше ф-ции FltRegisterFilter находим структуру с перечисленными колбеками, символы нам наглядно и доступно показывают то, что мы ищем: LuafvPreCreate и LuafvPostCreate(этот нам не интересен).

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

FLT_PREOP_CALLBACK_STATUS FLTAPI LuafvPreCreate( __inout PFLT_CALLBACK_DATA data, __in PCFLT_RELATED_OBJECTS fltObjects, __deref_out_opt PVOID *completionContext )
{   
  • определяется, включена ли виртуализация ( переменная DisableVirt )
  • определяется юзер, который сделал запрос на создание файла(LuafvQueryVirtualizationCaller)
  • принимается решение о том, виртуализовать ли запрос на создание файла или нет, в частности проверятся расширение файла LuafvCheckExcludedName (файлы с расширениями перечисленными ниже не будут виртуализированы )
  • после того как решение принято, вызывается LuafvQueryStoreFile которая создает путь, куда будет перенаправлен новый файл: LuafvQueryStoreFile => LuafvFindUserStore => к примеру "user_name\AppData\Local\" + "\\VirtualStore\\" + имя файла   
  • вызывается LuafvReparse которая и выполняет перенаправление создаваемого файла в выбранный каталог, делается это стандартным способом, используя возможности минифильтра. Если бы данный драйвер не был минифильтром, для редиректа использовался бы скорее всего другой механизм - установка нового имени(вместе с путем) в FileObject + в установка в IRP IoStatus.Status равным STATUS_REPARSE + установка IoStatus.Information равным IO_REPARSE, в таком случае менеджер ввода-вывода инициировал бы повторную операцию открытия файла, послав IRP_CREATE, но уже с новым именем. Для минифильтра же вся работа по редиректу спрятана под высокоуровневой оболочкой, редирект выполняется попроще, см. ниже ф-цию LuafvReparse
}

NTSTATUS LuafvReparse( SOME_STRUCT *p, ... )
{
    //
    // файловый объект приходит в ф-цию через аргумент(в составе структуры) обычно в колбек приходит параметр
    // PCFLT_RELATED_OBJECTS fltObjects, в котором содержится файловый объект, но в данном случае до вызова
    // LuafvReparse данные рассовываются в собственные структуры
    PFILE_OBJECT fileObject = p->FileObject;

    fileObject->MaximumLength = newLength;   // устанавливается новая длина
    fileObject->Buffer = newBuffer;  // устанавливается буфер с новым именем файла ( включая путь )

    FltSetCallbackDataDirty();   // фильтр-менеджеру сообщается, что данные в колбеке изменились
}

Список расширений файлов, которые не будут виртуализированы:

"acm", "asa", "asp", "aspx", "x", "bat", "cer", "chm", "clb", "cmd", "cnt", "cnv", "com", "cpl", "cpx", "crt", "dll", "drv", "exe", "fon", "grp", "hlp", "hta", "ime", "inf", "ins", "isp", "its", "js", "se", "nk", "sc", "si", "sp", "st", "ui", "ls", "cx", "al", "cd", if", eg", "cf", "cr", "ct", "hb", "hs", "ys", "lb", "sp", "rl", "b", "vbe", "vbs", "vsmacros", "s", "wsc", "wsf", "wsh"

Таким не хитрым образом работает виртуализация для файлов в windows vista.

пятница, 22 октября 2010 г.

О чем этот блог?

Блог посвящен низкоуровневому программированию(windows NT), обратной инженерии, декомпиляции, обзору различных защит, багам и недоработкам в ПО и прочим интересным вещам.


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

Связаться со мной можно:
  • Через e-mail(TSS_TSS@mail.ru)