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

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

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

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

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

## 独習逆アセンブラへの道 - その3 「逆アセンブラ作成 - ジャンプ命令を逆アセンブルできるまで」
[前回](https://qiita.com/deta-mamoru/items/d4390f16ee9a8382aad9)はMOV命令を自作逆アセンブラを使ってだいたい逆アセンブルさせることができました。今回はMOV r rmとジャンプ命令について学びます。
[!]　ここでの検証環境はUbuntu16.04を使用して行っております。バイナリファイルもELFフォーマットを使ってます。

### 逆アセンブラ - MOV r rm
前回MOV rm rを実装しました。今回はついでにそのオペランドが逆のMOV r rmを実装していきます。
オペランドが逆なだけなのですぐに出来そうですね。

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

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

_start:
	mov	eax, DWORD [ebx]
```

#### 逆アセンブラ

```py:instructions.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
    else:
        return False
...
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} {} {}".format(reg.RIP-(reg.RIP-orig_rip), operand1_name, operand2_name)
...
```

レジスタに値をセットするときにリトルエンディアンになおしていたのですが、不必要だと思いコメントアウトしました。

```py: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
...
```

```py:constants.py
OP_SIZE_PREFIX   = 0x66
ADDR_SIZE_PREFIX = 0x67
REX_W_PREFIX     = 0x48
# REX_PREFIX       = 0x48

ROWLENGTH  = 16
PARTLENGTH = 8
```

MOV rm rを逆にしただけですね。笑

### 逆アセンブラ - ジャンプ命令
ジャンプ命令を実装します。まずは無条件ジャンプを実装してみます。
ジャンプ命令にはショートジャンプ、ニアジャンプ、ファージャンプがあります。
今回実装する命令はJMP relです。これはニアジャンプです。ちなみにrelは相対アドレスです。

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

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

_start:
	jmp	0x10
```

instructions.pyは他の命令も少し修正しました。

```py:instructions.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
    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)
    reg.RIP += 1*operand_size
    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)
    reg.RIP += 1*operand_size
    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)
```

ジャンプ命令実装で詰まったことがあります。例えば、jmp 0x4と指定すると、バイナリにした時にrelの値が、0x0となります。つまり、e9 00...となります。なぜ、0x4と指定したのに4だけ減算されてるのかということです。これは色々考えた結果(恐らくですが)、relを取得した際オペランドサイズ分(この場合DWORD)RIPを進めます。その結果、この命令によってジャンプするときのRIPはオペランドサイズ分進んだ状態ということですね。それを考慮してオペランドサイズ分減算しているということです。試しにjmp short 0x1と指定すると、8bitオペランドサイズ(BYTE)になります。そのバイナリはeb 01...となっていたことが確認できました。つまり、RIPが次の命令を指した箇所を基準に考えるということですね。興味深いですね！

## まとめ

1. ジャンプ命令にはショートジャンプ、ニアジャンプ、ファージャンプがある
2. relは相対アドレス
3. ジャンプ命令のジャンプは次の命令を指し示したRIPを基準に考える