0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

リバースエンジニアリングへの道 - その16

Last updated at Posted at 2018-08-10

リバースエンジニアリングへの道

出田 守です。
最近、情報セキュリティに興味を持ち、『リバースエンジニアリング-Pythonによるバイナリ解析技法』という本(以降、「教科書」と呼びます)を読みました。
「こんな世界があるのか!かっこいい!」と感動し、私も触れてみたいということでド素人からリバースエンジニアリングができるまでを書いていきたいと思います。
ちなみに、教科書ではPython言語が使用されているので私もPython言語を使用しています。
ここを見ていただいた諸先輩方からの意見をお待ちしております。

軌跡

リバースエンジニアリングへの道 - その15

環境

OS: Windows10 64bit Home (日本語)
CPU: Intel® Core™ i3-6006U CPU @ 2.00GHz × 1
メモリ: 2048MB
Python: 3.6.5

私の環境は、普段Ubuntu16.04を使っていますが、ここではWindows10 64bitを仮想マシン上で立ち上げております。
ちなみに教科書では、Windowsの32bitで紹介されています。

独習逆アセンブラへの道 - その2 「逆アセンブラ作成 - MOV命令を逆アセンブルできるまで(続き)」

前回は逆アセンブラとは何なのかや、作成にあたって必要そうな情報を集めました。そして勢い余って少しだけ、逆アセンブラの作成を行いました。やっぱり、情報をただ眺めるだけでは、ちゃんと頭に入らない感じでしたが、実際書いてみると色んな疑問にぶつかります。そして、それを調べているうちにだんだん理解していってるような気がしました。やはり、作ってみるという実践は大事ですね!あと実践のほうが楽しいですしね!
[!] ここでの検証環境はUbuntu16.04を使用して行っております。バイナリファイルもELFフォーマットを使ってます。

逆アセンブラ作成 - MOV命令を逆アセンブルできるまで(続き)

今回は前回の逆アセンブラ作成の続きをします。
前回レジスタをどのように見分けるのか問題(前回の追記参照)がありました。結論はプレフィクスで見分けるようです。今回まずは、このプレフィクスを見分けられるようにしようと思います。前回REXプレフィクスはおいておくと言いましたが、ついでなのでとりあえず66HとREX.Wプレフィクスだけ実装しちゃいます。

テストプログラム

sample_ch16_1.asm
	BITS 	 64
	section  .text
	global   _start

_start:
	mov     ax, 1000

逆アセンブラ

以下チェック処理を追加したバージョンです。

disasm.py
	...
    # check REX.W(48H) prefix
    rex_prefix = memory.check_rexWprefix(reg)
    if rex_prefix:
        reg.RIP += 1
    # check 66H prefix
    opsize_prefix = memory.check_66prefix(reg)
    if opsize_prefix:
        reg.RIP += 1
    # execute instruction
    instruction = instructions.get_instructions(memory.dump[reg.RIP])
    if instruction:
        result.append(instruction(memory, reg, rex_pref=rex_prefix, opsize_pref=opsize_prefix))
	...
memory.py
	...
    def read(self, reg, size):
        value = 0
        for i, v in enumerate(self.dump[reg.RIP:reg.RIP+size]):
            value |= v<<i*8
        return value

    def check_66prefix(self, reg):
        if self.dump[reg.RIP]==OP_SIZE_PREFIX:
            return True
        else:
            return False

    def check_rexWprefix(self, reg):
        if self.dump[reg.RIP]==REX_W_PREFIX:
            return True
        else:
            return False
	...
registers.py
	...
    def get_register_name(self, n, rex_pref=False, opsize_pref=False):
        if n==0:
            if opsize_pref:
                return "ax"
            elif rex_pref:
                return "rax"
            else:
                return "eax"
        elif n==1:
            if opsize_pref:
                return "cx"
            elif rex_pref:
                return "rcx"
            else:
                return "ecx"
        elif n==2:
            if opsize_pref:
                return "dx"
            elif rex_pref:
                return "rdx"
            else:
                return "edx"
        elif n==3:
            if opsize_pref:
                return "bx"
            elif rex_pref:
                return "rbx"
            else:
                return "ebx"
        elif n==4:
            if opsize_pref:
                return "sp"
            elif rex_pref:
                return "rsp"
            else:
                return "esp"
        elif n==5:
            if opsize_pref:
                return "bp"
            elif rex_pref:
                return "rbp"
            else:
                return "ebp"
        elif n==6:
            if opsize_pref:
                return "si"
            elif rex_pref:
                return "rsi"
            else:
                return "esi"
        elif n==7:
            if opsize_pref:
                return "di"
            elif rex_pref:
                return "rdi"
            else:
                return "edi"
        else:
            return False
	...
instrcutions.py
...
def mov_r_imm(memory, reg, rex_pref=False, opsize_pref=False):
    operand_size = DWORD
    orig_rip = reg.RIP
    if rex_pref:
        operand_size = QWORD
        orig_rip    -= 1
    elif opsize_pref:
        operand_size = WORD
        orig_rip    -= 1
    opcode = memory.dump[reg.RIP]
    reg_n    = opcode-0xb8
    reg.RIP += 1
    imm      = memory.read(reg, operand_size)
    reg.RIP += 1
    if not reg.set_register(reg_n, imm):
        return False
    return "{:08x} {} {}".format(reg.RIP-(reg.RIP-orig_rip), reg.get_register_name(reg_n, rex_pref=rex_pref, opsize_pref=opsize_pref), imm)
...

これで、66HとREX.Wプレフィクスには対応できたと思います。
あと、memory.pyのread関数をこっそり修正しました。

次は、ModR/Mの実装をしてみます。

逆アセンブラ作成 - ModR/M

とりあえずオペコード89HのMOV r/m16(または r/m32, r/m64) r16(または r32, r64)が、ModR/Mを使うのでこの命令を実装します。88Hは8bitを指定するのですが、これはひとまず保留です。

テストプログラム

テストプログラムは2つ用意しました。

asm:sample_ch16_2.asm
	BITS 	 64
	section  .text
	global   _start

_start:
	mov     eax, ebx
asm:sample_ch16_3.asm
	BITS 	 64
	section  .text
	global   _start

_start:
	mov     [eax], ebx

逆アセンブラ

ここで再びModR/Mのフォーマットと表を載せます。
image.png

image.png

表には規則性があり、それぞれMod、Reg、R/M、SIBの領域に分かれてますね。まずModR/M領域をこの各フィールドに分解します。次にModで大きくジャンルが分かれています。さらに、R/Mで各レジスタまたはSIBに分かれています。これらの表現を実装します。
以下はModR/Mを使うMOV命令を実装したバージョンです。

disasm.py
	...
    # check 67H prefix
    addrsize_prefix = memory.check_67prefix(reg)
    if addrsize_prefix:
        reg.RIP += 1
	...
	memory.show()
memory.py
	...
    def read(self, addr, size):
        value = 0
        for i, v in enumerate(self.dump[addr:addr+size]):
            value |= v<<i*8
        return value

    def write(self, addr, operand_size, value):
        value = value.to_bytes(operand_size, byteorder="little")
        for i in range(addr, operand_size):
            v = value[i].to_bytes(1, byteorder="little")
            if i==0:
                self.dump = v+self.dump[i+1:]
            else:
                self.dump = self.dump[:i]+v+self.dump[i+1:]
        return self
	
    def read_modrm(self, reg, rex_pref=False, opsize_pref=False):
        operand1_name = None
        operand2_name = None
        operand1      = None
        operand2      = None
        self.MODRM    = self.dump[reg.RIP]
        self.MOD      = self.MODRM&0xc0
        self.REG      = self.MODRM&0x38
        self.RM       = self.MODRM&0x7
        self.SIB      = None
        self.DISP     = None
        # reading size
        SIZE        = DWORD
        if rex_pref==REX_W_PREFIX:
            SIZE = QWORD
        elif opsize_pref:
            SIZE = WORD
        # check SIB and DISPLACEMENT
        if self.MOD==0x0 and self.RM==0x4:
            reg.RIP += 1
            self.SIB      = self.dump[reg.RIP]
        elif (self.MOD==0x40 or self.MOD==0x80) and self.RM==0x4:
            reg.RIP += 1
            self.SIB      = self.dump[reg.RIP]
            reg.RIP += 1
            self.DISP     = self.read(reg.RIP, DWORD)
        # check REGISTER and self.DISPLACEMENT
        elif self.MOD==0x0 and self.RM==0x5:
            reg.RIP += 1
            self.DISP     = self.dump[reg.RIP]
        elif self.MOD==0x0:
            operand1_name = "["+reg.get_register_name(self.RM, rex_pref, opsize_pref)+"]"
            operand1      = reg.get_register(self.RM)
            operand2_name = reg.get_register_name(self.REG>>3)
            operand2      = reg.get_register(self.REG>>3)
        elif self.MOD==0x40:
            reg.RIP    += 1
            self.DISP        = self.read(reg.RIP, BYTE)
            operand1_name = "["+reg.get_register_name(self.RM, rex_pref, opsize_pref)+"+"+str(self.DISP)+"]"
            operand1      = self.read(reg.get_register(self.RM)+self.DISP, SIZE)
            operand2_name = reg.get_register_name(self.REG>>3)
            operand2      = reg.get_register(self.REG>>3)
        elif self.MOD==0x80:
            reg.RIP    += 1
            self.DISP        = self.read(reg.RIP, DWORD)
            operand1_name = "["+reg.get_register_name(self.RM, rex_pref, opsize_pref)+"+"+str(self.DISP)+"]"
            operand1      = self.read(reg.get_register(self.RM)+self.DISP, SIZE)
            operand2_name = reg.get_register_name(self.REG>>3)
            operand2      = reg.get_register(self.REG>>3)
        elif self.MOD==0xc0:
            operand1_name = reg.get_register_name(self.RM, rex_pref, opsize_pref)
            operand1      = reg.get_register(self.RM)
            operand2_name = reg.get_register_name(self.REG>>3)
            operand2      = reg.get_register(self.REG>>3)
        return operand1_name, operand2_name, operand1, operand2
	...
    def check_67prefix(self, reg):
        if self.dump[reg.RIP]==ADDR_SIZE_PREFIX:
            return True
        else:
            return False
	...
    def show(self):
        n = 0x0
        for start_row in range(0, len(self.dump), ROWLENGTH):
            row = self.dump[start_row:start_row+ROWLENGTH].hex()
            print("|{:08x}|".format(n), end=" ")
            for start_part in range(0, len(row), PARTLENGTH):
                print(row[start_part:start_part+PARTLENGTH], end=" ")
            print()
            n += 0x10
registers.py
	...
    def set_register(self, n, operand_size, value):
        ret = True
        value = int(value.to_bytes(operand_size, byteorder="little").hex(), 16)
        if n==0:
            self.RAX = value
        elif n==1:
            self.RCX = value
        elif n==2:
            self.RDX = value
        elif n==3:
            self.RBX = value
        elif n==4:
            self.RSP = value
        elif n==5:
            self.RBP = value
        elif n==6:
            self.RSI = value
        elif n==7:
            self.RDI = value
        else:
            ret = False
        return ret
	...
instructions.py
def get_instructions(opcode):
    if 0x88<=opcode<=0x89:
        return mov_rm_r
    elif 0xb8<=opcode<=0xb8+0x8:
        return mov_r_imm
    else:
        return False

def mov_rm_r(memory, reg, rex_pref=False, opsize_pref=False):
    operand_size = DWORD
    orig_rip = reg.RIP
    if rex_pref==REX_W_PREFIX:
        operand_size = QWORD
        orig_rip    -= 1
    elif opsize_pref:
        operand_size = WORD
        orig_rip    -= 1
    opcode = memory.dump[reg.RIP]
    reg.RIP += 1
    operand1_name, operand2_name, operand1, operand2 = memory.read_modrm(reg, rex_pref, opsize_pref)
    if operand1_name.startswith("["):
        memory.write(operand1, operand_size, operand2)
    else:
        reg.set_register(memory.RM, operand_size, operand2)
    return "{:08x} {} {}".format(reg.RIP-(reg.RIP-orig_rip), operand1_name, operand2_name)

def mov_r_imm(memory, reg, rex_pref=False, opsize_pref=False):
    operand_size = DWORD
    orig_rip = reg.RIP
    if rex_pref==REX_W_PREFIX:
        operand_size = QWORD
        orig_rip    -= 1
    elif opsize_pref:
        operand_size = WORD
        orig_rip    -= 1
    opcode = memory.dump[reg.RIP]
    reg_n    = opcode-0xb8
    reg.RIP += 1
    imm      = memory.read(reg.RIP, operand_size)
    reg.RIP += 1
    if not reg.set_register(reg_n, operand_size, imm):
        return False
    return "{:08x} {} {}".format(reg.RIP-(reg.RIP-orig_rip), reg.get_register_name(reg_n, rex_pref=rex_pref, opsize_pref=opsize_pref), imm)

read_modrm関数が非常に長くなってしまいました。笑
SIBは実装していません。
あと、わかりやすいように最後にメモリマップも表示するようにしてみました。sample_ch16_2ではレジスタ値が、sample_ch16_3ではメモリマップが逆アセンブラすると変化しているのが確認できます。
今回はmov_rm_rを実装しました。逆にmov_r_rmもあります。
めちゃくちゃ楽しくなってきましたね。

今回はここまでにします。
次回はまず、mov_r_rmをついでに実装し、その次は、ジャンプ命令を実装してみる予定です。

まとめ

  • レジスタサイズを見分けるためにプレフィクスをチェック
  • ModR/Mの表を見ると規則性がある
  • 命令によってレジスタとレジスタ/メモリが逆転することがある
0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?