четверг, 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.

Комментариев нет:

Отправить комментарий