読者です 読者をやめる 読者になる 読者になる

Log.i53

Themidaのアンパックを目指すブログ改め使い物になるえんじにゃを目指すブログ

Pythonで隠蔽された文字列を抽出してみる その②

文字列を変数単位で分解する例

#include <stdio.h>

int main() {
    char H  = 0x48;
    char e  = 0x65;
    char l  = 0x6C;
    char o  = 0x6F;
    char _  = 0x20;
    char W  = 0x57;
    char r  = 0x72;
    char d  = 0x64;
    char ex = 0x21;

    char array[13];
    array[0]  = H;
    array[1]  = e;
    array[2]  = l;
    array[3]  = l;
    array[4]  = o;
    array[5]  = _;
    array[6]  = W;
    array[7]  = o;
    array[8]  = r;
    array[9]  = l;
    array[10] = d;
    array[11] = ex;
    array[12] = 0;
	
    printf("%s\n", array);
    return 0;
}

前記事で書いた"Hello World!"を変数単位で分解したサンプルです。

出力されるアセンブリ(Borland C++ Compiler 5.5)

push    ebp
mov     ebp, esp
add     esp, 0FFFFFFECh
push    esi
lea     esi, [ebp+var_14]   ; esi = *array
mov     cl, 'H'
mov     [ebp+var_1], 'e'
mov     al, 'l'
mov     dl, 'o'
mov     [ebp+var_2], ' '
mov     [ebp+var_3], 'W'
mov     [ebp+var_4], 'r'
mov     [ebp+var_5], 'd'
mov     [ebp+var_6], '!'
mov     [esi], cl
mov     cl, [ebp+var_1]
mov     [esi+1], cl
mov     [esi+2], al
mov     [esi+3], al
mov     [esi+4], dl
mov     cl, [ebp+var_2]
mov     [esi+5], cl
mov     cl, [ebp+var_3]
mov     [esi+6], cl
mov     [esi+7], dl
mov     dl, [ebp+var_4]
mov     [esi+8], dl
mov     [esi+9], al
mov     al, [ebp+var_5]
mov     [esi+0Ah], al
mov     al, [ebp+var_6]
mov     [esi+0Bh], al
mov     byte ptr [esi+0Ch], 0
push    esi
push    offset format   ; "%s\n"
call    _printf
add     esp, 8
xor     eax, eax
pop     esi
mov     esp, ebp
pop     ebp
retn

上記のサンプルをBCCコンパイルしてIDAで逆アセンブルしたものがこちらです。このコンパイラでは、今回のサンプルであればローカル変数としてal, cl, dlレジスタとスタック領域が利用されていることが分かります。前回の例とは異なりレジスタに何の値が格納されているのか評価しないことには隠蔽文字列を抽出することが出来ないため厄介です。

レジスタの評価

コンパイラやその最適化、難読化手法によっては論理演算などが利用される可能性もありますが、今回は簡単化のためMOV命令のみ抽出してレジスタを評価し隠蔽文字列の抽出を試みます。MOV命令のみを抽出してソースとディスティネーションの対応を辞書に記録することでレジスタの評価を行います。今回の例ではESIがポインタになっているのでdestオペランドにESIレジスタが含まれている場合に文字をはき出すようにスクリプトを構成しました。

l = [
    "PUSH EBP",
    "MOV EBP, ESP",
    "ADD ESP, -0x14",
    "PUSH ESI",
    "LEA ESI, [EBP-0x14]",
    "MOV CL, 0x48",
    "MOV BYTE [EBP-0x1], 0x65",
    "MOV AL, 0x6c",
    "MOV DL, 0x6f",
    "MOV BYTE [EBP-0x2], 0x20",
    "MOV BYTE [EBP-0x3], 0x57",
    "MOV BYTE [EBP-0x4], 0x72",
    "MOV BYTE [EBP-0x5], 0x64",
    "MOV BYTE [EBP-0x6], 0x21",
    "MOV [ESI], CL",
    "MOV CL, [EBP-0x1]",
    "MOV [ESI+0x1], CL",
    "MOV [ESI+0x2], AL",
    "MOV [ESI+0x3], AL",
    "MOV [ESI+0x4], DL",
    "MOV CL, [EBP-0x2]",
    "MOV [ESI+0x5], CL",
    "MOV CL, [EBP-0x3]",
    "MOV [ESI+0x6], CL",
    "MOV [ESI+0x7], DL",
    "MOV DL, [EBP-0x4]",
    "MOV [ESI+0x8], DL",
    "MOV [ESI+0x9], AL",
    "MOV AL, [EBP-0x5]",
    "MOV [ESI+0xa], AL",
    "MOV AL, [EBP-0x6]",
    "MOV [ESI+0xb], AL",
    "MOV BYTE [ESI+0xc], 0x0",
    "PUSH ESI",
    "PUSH DWORD 0x40a128",
    "CALL 0x402ecc",
    "ADD ESP, 0x8",
    "XOR EAX, EAX",
    "POP ESI",
    "MOV ESP, EBP",
    "POP EBP",
    "RET"
]

d = {}
def get_ascii(s):
    if "0x" in s and (len(s) == 3 or len(s) ==4):
        c = int(s, 16)
        if c >= 0x21 and c <= 0x7E:
            return c
        elif c == 0:
            return 0
    else:
        while(d.has_key(s)):
            s = d[s]
        return int(s, 16)
    return -1

def eval_mov(inst, target):
    if "MOV" in inst:
        s = inst.split(" ")
        if "BYTE" in s:
            s.remove("BYTE")
        if len(s) == 3:
            d[s[1][:-1]] = s[2]
        if target in s[1]:
            c = get_ascii(s[2])
            if c != -1:
                print chr(c)
    return

for i in l:
    eval_mov(i, "ESI")

# 関数の先頭からLEA命令をまず検索して配列ポインタを見つけるようにすればターゲットを自動で抽出できるようになるのかなあ…!

Shuffle (Binary, 100)

そういえばSECCONのonline予選でフラグがバイト単位で分解されている問題があったのを思い出した。

from distorm3 import Decode, Decode64Bits

def d(s):
    if "0x" in s and (len(s) == 3 or len(s) ==4):
        c = int(s, 16)
        if c >= 0x21 and c <= 0x7E:
            return c
        elif c == 0:
            return 0
    return -1

l = Decode(0x00400000, open('/root/Desktop/shuffle', 'rb').read(), Decode64Bits)
for i in l:
    j = i[2].split(" ")
    if "BYTE" in j:
        j.remove("BYTE")    
    if  j[0] == "MOV" and len(j) == 3:
        c = d(j[2])
        if c != -1:
            print chr(c)

一応distormで抽出することもできるけど、この問題はIDAで"R"キーをポチポチした方が早い…

終わりに

Python始めたばかりだけどもっとPythonに慣れよう…いかんせんもっと美しく記述する方法があるのだろうけど今回はとりあえず出力されるところまで…