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

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

## 軌跡
[リバースエンジニアリングへの道 - その18](https://qiita.com/deta-mamoru/items/06633de8ec08cde67f39)

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

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

## 独習逆アセンブラへの道 - その5 「逆アセンブラ作成 - ADD命令が逆アセンブルできるまで」
[前回](https://qiita.com/deta-mamoru/items/06633de8ec08cde67f39)はJMP命令とINC/DEC命令を実装しました。今回はADD命令(の一つ)を実装した後、複数命令を実行できるようにします。
[!]　ここでの検証環境はUbuntu16.04を使用して行っております。バイナリファイルもELFフォーマットを使ってます。

### ADD r/m imm8
この命令のオペコードは83/0です。
本当は、RFLAGSをADDやINC、DEC後に更新する機能を実装したかったのですが、逆アセンブラ自体には必要ないので実装しませんでした。

#### テストプログラム

```asm:sample_ch19_1.asm
	BITS	64
	section .text
	global	_start

_start:
	add	eax, 0x1
```

#### 逆アセンブラ

```py:instrcutions.py
...
def get_instructions(opcode):
    if 0x83==opcode:
        return {0:add_rm_imm8}
    elif 0x88<=opcode<=0x89:
        return {0:mov_rm_r}
    elif 0x8a<=opcode<=0x8b:
        return {0:mov_r_rm}
    elif 0xb8<=opcode<=0xb8+0x8:
        return {0:mov_r_imm}
    elif 0xe8<=opcode<=0xe9:
        return {0:jmp_rel}
    elif 0xfe<=opcode<=0xff:
        return {0:inc_rm, 1:dec_rm, 4:jmp_rm}
    else:
        return False
...
def add_rm_imm8(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, _, operand1, _ = memory.read_modrm(reg, rex_pref, opsize_pref)
    imm      = memory.read(reg.RIP, BYTE)
    value    = int(imm.to_bytes(BYTE, byteorder="little").hex(), 16)
    reg.RIP += 1*BYTE
    if not reg.set_register(memory.RM, BYTE, value):
        return False
    return "{:08x} add {} 0x{:02x}".format(reg.RIP-(reg.RIP-orig_rip), operand1_name, imm)
...
```

```py:memory.py
...
    def read_modrm(self, reg, rex_pref=False, opsize_pref=False, run_mode=False):
		...
        reg.RIP    += 1
		return operand1_name, operand2_name, operand1, operand2
...
```

### 複数命令実行
そうなんです。今まで1つの命令しか実行できませんでした。笑
そもそも実行ファイルをどこまで読みこめばよいのでしょうか。とりあえず、Cで作成したソースファイルをgccでコンパイルし逆アセンブルしてみるとHLT命令で止まっているみたいです。なので、同じようにHLT命令で停止するようにします。でも、恐らく私の理解は間違っています。HLTで命令ではCPUのフェッチは終了しないと思っています。

#### テストプログラム

```asm:sample_ch19_2.asm
	BITS	64
	section .text
	global	_start

_start:
	add	    eax, 0x1
	inc  	eax
	mov 	ebx, eax
	hlt
```

#### 逆アセンブラ

```py:disasm.py
...
    while True:
        # check REX.W(48H) prefix
        rex_prefix = memory.check_rexprefix(reg)
        if rex_prefix:
            reg.RIP += 1
        # check 66H prefix
        opsize_prefix   = memory.check_66prefix(reg)
        if opsize_prefix:
            reg.RIP += 1
        # check 67H prefix
        addrsize_prefix = memory.check_67prefix(reg)
        if addrsize_prefix:
            reg.RIP += 1
        # execute instruction
        instruction_dic = instructions.get_instructions(memory.dump[reg.RIP])
        if instruction_dic:
            if not instruction_dic.get(8):
                instruction = instruction_dic[0]
                result.append(instruction(memory, reg, rex_pref=rex_prefix, opsize_pref=opsize_prefix))
            else:
                select_instruction = instruction_dic[8]
                instruction = select_instruction(memory, reg, instruction_dic, rex_pref=rex_prefix, opsize_pref=opsize_prefix)
                result.append(instruction(memory, reg, rex_pref=rex_prefix, opsize_pref=opsize_prefix))
            if instruction==instructions.hlt:
                break
...
```

```py:instructions.py
...
def get_instructions(opcode):
    if 0x83==opcode:
        return {0:add_rm_imm8}
    elif 0x88<=opcode<=0x89:
        return {0:mov_rm_r}
    elif 0x8a<=opcode<=0x8b:
        return {0:mov_r_rm}
    elif 0xb8<=opcode<=0xb8+0x8:
        return {0:mov_r_imm}
    elif 0xe8<=opcode<=0xe9:
        return {0:jmp_rel}
    elif 0xf4==opcode:
        return {0:hlt}
    elif 0xfe<=opcode<=0xff:
        return {0:inc_rm, 1:dec_rm, 4:jmp_rm, 8:select_instruction}
    else:
        return False
...
def add_rm_imm8(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, _, operand1, _ = memory.read_modrm(reg, rex_pref, opsize_pref)
    imm      = memory.read(reg.RIP, BYTE)
    value    = int(imm.to_bytes(BYTE, byteorder="little").hex(), 16)
    reg.RIP += 1*BYTE
    if not reg.set_register(memory.RM, BYTE, value):
        return False
    return "{:08x} add {} 0x{:02x}".format(reg.RIP-(reg.RIP-orig_rip), operand1_name, imm)

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} mov {} {}".format(reg.RIP-(reg.RIP-orig_rip), operand1_name, operand2_name)

def mov_r_rm(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
    operand2_name, operand1_name, operand2, operand1 = memory.read_modrm(reg, rex_pref, opsize_pref)
    if operand1_name.startswith("["):
        memory.write(operand1, operand_size, operand2)
    else:
        reg.set_register(memory.REG, operand_size, operand2)
    return "{:08x} mov {} {}".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)
    value    = int(imm.to_bytes(operand_size, byteorder="little").hex(), 16)
    reg.RIP += 1*operand_size
    if not reg.set_register(reg_n, operand_size, value):
        return False
    return "{:08x} mov {} {}".format(reg.RIP-(reg.RIP-orig_rip), reg.get_register_name(reg_n, rex_pref=rex_pref, opsize_pref=opsize_pref), imm)

def jmp_rel(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
    rel      = memory.read(reg.RIP, operand_size)+operand_size
    # reg.RIP  += rel
    reg.RIP  = 1*operand_size
    return "{:08x} jmp 0x{:08x}".format(reg.RIP-(reg.RIP-orig_rip), rel)

def jmp_rm(memory, reg, rex_pref=False, opsize_pref=False):
    operand_size = DWORD
    orig_rip = reg.RIP
    run_mode = memory.run_mode()
    if run_mode==RUN_MODE_64:
        operand_size = QWORD
    elif 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, _, operand1, _ = memory.read_modrm(reg, rex_pref, opsize_pref, run_mode)
    # reg.RIP = operand1
    return "{:08x} jmp {}".format(reg.RIP-(reg.RIP-orig_rip), operand1_name)

def hlt(memory, reg, rex_pref=False, opsize_pref=False):
    return "{:08x} hlt".format(reg.RIP)

def inc_rm(memory, reg, rex_pref=False, opsize_pref=False):
    operand_size = DWORD
    orig_rip = reg.RIP
    run_mode = memory.run_mode()
    if run_mode==RUN_MODE_64:
        operand_size = QWORD
    elif 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, _, operand1, _ = memory.read_modrm(reg, rex_pref, opsize_pref)
    operand1 += 0x1
    if operand1_name.startswith("["):
        memory.write(operand1, operand_size, operand1)
    else:
        reg.set_register(memory.RM, operand_size, operand1)
    # reg.RIP = operand1
    return "{:08x} inc {}".format(reg.RIP-(reg.RIP-orig_rip), operand1_name)

def dec_rm(memory, reg, rex_pref=False, opsize_pref=False):
    operand_size = DWORD
    orig_rip = reg.RIP
    run_mode = memory.run_mode()
    if run_mode==RUN_MODE_64:
        operand_size = QWORD
    elif 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, _, operand1, _ = memory.read_modrm(reg, rex_pref, opsize_pref)
    operand1 -= 0x1
    if operand1_name.startswith("["):
        memory.write(operand1, operand_size, operand1)
    else:
        reg.set_register(memory.RM, operand_size, operand1)
    # reg.RIP = operand1
    return "{:08x} dec {}".format(reg.RIP-(reg.RIP-orig_rip), operand1_name)
```

```py:memory.py
...
    def read_modrm(self, reg, rex_pref=False, opsize_pref=False, run_mode=False, read_only=False):
	...
	orig_rip      = reg.RIP
	...
	if read_only:
		reg.RIP -= reg.RIP-orig_rip
	return operand1_name, operand2_name, operand1, operand2
...
```

案の定色々バグが残ってました(主にRIPの更新)。それらを修正してみるとちゃんとテストプログラムを逆アセンブルすることが出来ました。

## まとめ

- 算術演算後はRFLAGS(32bitではEFLAGS)を更新
- HLT命令でプログラム中のCPUのフェッチは終了？？
- (やっと)複数命令を実行できるようになった

よし！自作逆アセンブラはここまでにします！笑
次回は自作メモリエディタについて学ぶ予定です。