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

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

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

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

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

## 独習逆アセンブラへの道 - その4 「逆アセンブラ作成 - ジャンプ命令を逆アセンブルできるまで(続き)」
[前回](https://qiita.com/deta-mamoru/items/8f0591291fee437cd6f0)はJMP命令の相対アドレス指定バージョンを自作逆アセンブラを使って逆アセンブルさせることができました。今回はまずもうひとつのJMP命令について学びます。その次は、インクリメント・デクリメント命令について学びます。
[!]　ここでの検証環境はUbuntu16.04を使用して行っております。バイナリファイルもELFフォーマットを使ってます。

### JMP r/m
今回はJMP命令の絶対アドレス指定バージョンを自作逆アセンブラに実装してみます。
この命令はModr/mのデコードが必要になるようです。
命令のオペコードはffです。

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

```py:sample_ch18_1.asm
	BITS	64
	section .text
	global	_start

_start:
	jmp	QWORD [rax]
```

#### 逆アセンブラ
64bitモードではこの命令は、64bitのアドレス指定しかサポートされていないようなので、REXプレフィクスが付きませんでした。なので正しいのか分かりませんがmemory.pyとinstruction.pyでは、ELFファイルの5Byte目のEL_CLASSを見て、32bitなのか64bitなのか判断しています。

```py:memory.py
	...
    def run_mode(self):
        return self.dump[4] # el_class: 0->Invalid, 1->32bit, 2->64bit
	...
    def read_modrm(self, reg, rex_pref=False, opsize_pref=False, run_mode=False):
		...
		if rex_pref==REX_W_PREFIX or run_mode==RUN_MODE_64:
			SIZE = QWORD
		...
		if run_mode==RUN_MODE_64:
            operand1_name = operand1_name.replace("e", "r")
            operand2_name = operand2_name.replace("e", "r")
        return operand1_name, operand2_name, operand1, operand2
```

```py:instruction.py
...
def get_instructions(opcode):
	if 0x88<=opcode<=0x89:
        return mov_rm_r
    elif 0x8a<=opcode<=0x8b:
        return mov_r_rm
    elif 0xb8<=opcode<=0xb8+0x8:
        return mov_r_imm
    elif 0xe8<=opcode<=0xe9:
        return jmp_rel
    elif 0xff:
        return jmp_rm
    else:
        return False
...
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
    reg.RIP += 1*operand_size
    return "{:08x} jmp {}".format(reg.RIP-(reg.RIP-orig_rip), operand1_name)
```

### INC rm & DEC rm
これらの命令のオペコードがffでした。JMP命令のオペコードもffでした。
「どういうこっちゃ？」と思ったわけです。笑
IDMをよく見ると、オペコードの横に「/digit」が付いていることに気づきました。これは「/r」ならレジスタ、「/digit」なら命令を拡張するという意味になるようです。/digitはREG領域を見れば判別できるみたいです。
ここで、もう一度各命令について確かめてみると、INCが/0、DECが/1、JMPが/4でした。これを元に実装してみます。

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

```py:sample_ch18_2.asm
	BITS	64
	section .text
	global	_start

_start:
	inc	eax
```

```py:sample_ch18_3.asm
	BITS	64
	section .text
	global	_start

_start:
	dec	eax
```


#### 逆アセンブラ

```py:disasm.py
...
    # execute instruction
    instruction_dic = instructions.get_instructions(memory.dump[reg.RIP])
    if instruction_dic:
        if len(instruction_dic)==1:
            instruction = instruction_dic[0]
            result.append(instruction(memory, reg, rex_pref=rex_prefix, opsize_pref=opsize_prefix))
        else:
            instruction = instructions.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))
    # show result
...
```

```py:instructions.py
...
def get_instructions(opcode):
    if 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 select_instruction(memory, reg, instruction_dic, rex_pref=False, opsize_pref=False):
    reg.RIP += 1
    memory.read_modrm(reg, rex_pref=rex_pref, opsize_pref=opsize_pref)
    reg.RIP -= 1
    return instruction_dic[memory.REG>>3]

...
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
    reg.RIP += 1*operand_size
    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
    reg.RIP += 1*operand_size
    return "{:08x} dec {}".format(reg.RIP-(reg.RIP-orig_rip), operand1_name)
```

どんくさいコードで申し訳ないです。
上記の通りREG領域で判別するためにselect_instruction関数を作成しました。あとは、いつもどおりですね。

## まとめ

- JMPは64bitモードでは64bitのアドレス指定のみをサポート
- ELFフォーマットの5bit目はそのファイルのbitモードを表す
- 「/digit」は命令拡張の意味。REGフィールドで判別