ethical.blue Magazine

// Cybersecurity clarified.

Zmylić deasembler bajtem zabójcą

28.05.2022   Dawid Farbaniec
...
Narzędzia typu deasembler (ang. disassembler) to jedne z podstawowych programów, które są używane w inżynierii odwrotnej kodu (ang. Reverse Code Engineering). Pozwalają one uzyskać kod w języku Asembler z analizowanego pliku wykonywalnego *.exe. Dzięki tego typu narzędziom nie ma potrzeby pracy z kodem maszynowym, który jest praktycznie nieczytelny dla człowieka. W tym tekście zaprezentowano prostą metodę, która pozwala oszukać deasemblery, a w połączeniu z dodatkowymi technikami może nieco utrudnić pracę crackerom łamiącym zabezpieczenia oprogramowania czy niebieskim zespołom analizującym malware. Zapraszam do lektury, gdyż kto wie czy kiedyś pod debuggerem nie spotkają Państwo pliku zawierającego tę metodę.

Zadaniem deasemblera jest proces odwrotny do kompilacji (budowania programu). Narzędzia tego typu starają się odtworzyć kod w języku Asembler z podanego im pliku wykonywalnego (zawierającego kod maszynowy). Spotkałem się także z dekompilatorami, które z kodu maszynowego odtwarzały pseudokod podobny do języka C (np. http://www.backerstreet.com/rec/rec.htm). Celowo tutaj został użyty termin dekompilator, gdyż tworzy on kod o wyższym poziomie abstrakcji niż deasembler.

Technika rogue byte korzysta z możliwości umieszczania bajtów o określonej wartości w kodzie programu. Przykład będzie w Asemblerze MASM x64 (ML64.EXE), który jest dołączony do środowiska Microsoft Visual Studio. Aby zrozumieć działanie tej metody należy pomyśleć jak działa deasembler. W uproszczeniu pobiera on kolejne bajty kodu maszynowego i zgodnie z regułami kodowania instrukcji procesora zamienia je na określone rozkazy. Jeśli wewnątrz ciągu instrukcji programu wstawi się bajt o określonej wartości, to większość (a na pewno wiele) deasemblerów potraktuje go jako część kodu maszynowego i błędnie zinterpretuje rozkazy w pobliżu tego bajta.
extrn ExitProcess : proc

extrn MessageBoxA : proc

.data
szText db "https://ethical.blue/", 0

.code
Main proc
    jmp @f
    db 0E8h ;rogue byte
    @@:
    sub rsp, 28h
    xor r9, r9
    lea r8, szText
    lea rdx, szText
    xor rcx, rcx
    jmp @f
    db 0E9h ;rogue byte
    @@:
    call MessageBoxA
    add rsp, 28h
        
    sub rsp, 28h
    xor rcx, rcx
    call ExitProcess
Main endp
end

Gdy deasembler (często wbudowany w debugger) napotka na bajt 0E8h uzna, że jest to początek rozkazu w kodzie maszynowym. Dokładnie to rozkazu CALL (wywołanie procedury). Od tego momentu kod będzie błędnie interpretowany co spowoduje jego zaciemnienie (ang. obfuscation).

Bajt wstawiony w drugie miejsce ma kod operacyjny 0E9h, czyli rozkaz JMP (skok bezwarunkowy). Za tym miejscem również instrukcje zostaną błędnie rozkodowane.


Dla całkowitej jasności wspomnę, że kod z zastosowaną metodą rogue byte wykonuje się poprawnie. Dzieje się tak dlatego, że bajt zabójca jest przeskakiwany instrukcją skoku JMP.

Metoda ta jest skuteczna tylko w połączeniu z innymi rodzajami zabezpieczeń.

Wykaz literatury

https://x64dbg.com/ [access: 2019-07-30]
http://www.backerstreet.com/rec/rec.htm [access: 2019-07-30]
https://docs.microsoft.com/en-us/cpp/assembler/masm/masm-for-x64-ml64-exe [access: 2019-07-30]