本記事はPythonで簡単なx86エミュレータを作成します。
前回ではorg疑似命令によるプログラムの配置場所を指定するプログラムを作成しました。
今回はModR/Mを実装し、オペランドを柔軟に指定する方法について学んでいきます。
ModR/Mとは
前回では、org疑似命令によるプログラムの配置場所を指定する方法について学びました。
なお、前回まででは指定したレジスタに即値をコピーするような単純なことしかできませんでしたが、今回のModR/Mを実装することで、[esp-16]
のようにオペランドを柔軟に指定することが可能となります。
ModR/Mは以下のような1バイトで表現されます。
上記のMod(上位2ビット)とR/M(下位3ビット)でアドレッシングモードを選びます。
また真ん中の3ビットはREGと呼ばれ、オペコードを拡張するために使われたり、8つある汎用レジスタから1つのレジスタを指定したりするために使用されます。
なお、使用するアセンブリ言語プログラムは、参考書籍と同じものを使用します。
;modrm-test.asm BITS 32 org 0x7c00 sub esp, 16 mov ebp, esp mov eax, 2 mov dword [ebp+4], 5 add dword [ebp+4], eax mov esi, [ebp+4] inc dword [ebp+4] mov edi, [ebp+4] jmp 0
Pythonによるスクリプトの作成
それでは、PythonでModR/Mに対応させ、併せて算術命令も実装していきます。
# emulator.py class ModRM: def __init__(self): self.modrm = { "mod" :0x00, "opecode" :0x00, "reg_index" :0x00, "rm" :0x00, "sib" :0x00, "disp8" :0x00, "disp32" :0x00 } class Emulator: def __init__(self): self.register_name = ["EAX", "ECX", "EDX", "EBX", "ESP", "EBP", "ESI", "EDI"] self.registers = { "EAX": 0x00, "ECX": 0x00, "EDX": 0x00, "EBX": 0x00, "ESP": 0x00, "EBP": 0x00, "ESI": 0x00, "EDI": 0x00 } self.eflags = None self.memory = None self.eip = None self.instructions = [None for i in range(256)] def init_instructions(self): self.instructions[0x01] = self.add_rm32_r32 self.instructions[0x83] = self.code_83 self.instructions[0x89] = self.mov_rm32_r32 self.instructions[0x8b] = self.mov_r32_rm32 for i in range(8): self.instructions[0xb8 + i] = self.mov_r32_imm32 self.instructions[0xeb] = self.short_jump self.instructions[0xe9] = self.near_jump self.instructions[0xc7] = self.mov_rm32_imm32 self.instructions[0xff] = self.code_ff def create_emu(self, size, eip, esp): self.eip = eip self.registers["ESP"] = esp self.memory = [0x00 for _ in range(size)] def dump_registers(self): for i in range(len(self.registers)): name = self.register_name[i] print("{} = 0x{:08x}".format(name, self.registers[name])) print("EIP = 0x{:08x}".format(self.eip)) def mov_r32_imm32(self): reg = self.get_code8(0) - 0xb8 value = self.get_code32(1) reg_name = self.register_name[reg] self.registers[reg_name] = value self.eip += 5 if self.eip >= 0x100000000: self.eip ^= 0x100000000 def short_jump(self): diff = self.get_sign_code8(1) if diff & 0x80: diff -= 0x100 self.eip += (diff + 2) def get_code8(self, index): code = self.memory[self.eip + index] if not type(code) == int: code = int.from_bytes(code, 'little') return code def get_sign_code8(self, index): code = self.memory[self.eip + index] code = int.from_bytes(code, 'little') return code & 0xff def get_code32(self, index): ret = 0x00 for i in range(4): ret |= self.get_code8(index + i) << (i * 8) return ret def get_sign_code32(self, index): return self.get_code32(index) def near_jump(self): diff = self.get_sign_code32(1) if diff & 0x80000000: diff -= 0x100000000 self.eip += (diff + 5) def parse_modrm(self): m = ModRM() code = self.get_code8(0) m.modrm["mod"] = ((code & 0xc0) >> 6) m.modrm["opecode"] = m.modrm["reg_index"] = ((code & 0x38) >> 3) m.modrm["rm"] = code & 0x07 self.eip += 1 if (m.modrm["mod"] != 3 and m.modrm["rm"] == 4): m.modrm["sib"] = self.get_code8(0) eip += 1 if (m.modrm["mod"] == 0 and m.modrm["rm"] == 5) or m.modrm["mod"] == 2: m.modrm["disp32"] = self.get_sign_code32(0) m.modrm["disp8"] = m.modrm["disp32"] & 0xff eip += 4 elif m.modrm["mod"] == 1: m.modrm["disp8"] = m.modrm["disp32"] = self.get_sign_code8(0) self.eip += 1 return m def mov_rm32_imm32(self): self.eip += 1 m = self.parse_modrm() value = self.get_code32(0) self.eip += 4 self.set_rm32(m, value) def set_rm32(self, m, value): if m.modrm["mod"] == 3: self.set_register32(m.modrm["rm"], value) else: address = self.calc_memory_address(m) self.set_memory32(address, value) def set_memory8(self, address, value): self.memory[address] = value & 0xff def set_memory32(self, address, value): for i in range(4): self.set_memory8(address+i, value >> (i*8)) def calc_memory_address(self, m): if m.modrm["mod"] == 0: if m.modrm["rm"] == 4: print("not implemented ModRM mod = 0, rm = 4") sys.exit(0) elif m.modrm["rm"] == 5: return m.modrm["disp32"] else: return self.get_register32(m.modrm["rm"]) elif m.modrm["mod"] == 1: if m.modrm["rm"] == 4: print("not implemented ModRM mod = 1, rm = 4") sys.exit(0) else: return self.get_register32(m.modrm["rm"]) + m.modrm["disp8"] elif m.modrm["mod"] == 2: if m.modrm["rm"] == 4: print("not implemented ModRM mod = 2, rm = 4") sys.exit(0) else: return self.get_register32(m.modrm["rm"]) + m.modrm["disp32"] else: print("not implemented ModRM mod = 3") sys.exit(0) def mov_rm32_r32(self): self.eip += 1 m = self.parse_modrm() r32 = self.get_r32(m) self.set_rm32(m, r32) def mov_r32_rm32(self): self.eip += 1 m = self.parse_modrm() rm32 = self.get_rm32(m) self.set_r32(m, rm32) def get_rm32(self, m): if m.modrm["mod"] == 3: return self.get_register32(m.modrm["rm"]) else: address = self.calc_memory_address(m) return self.get_memory32(address) def get_memory8(self, address): return self.memory[address] def get_memory32(self, address): ret = 0 for i in range(4): mem = self.get_memory8(address + i) if not type(mem) == int: mem = ord(mem) ret |= mem << (8*i) return ret def set_r32(self, m, value): self.set_register32(m.modrm["reg_index"], value) def get_r32(self, m): return self.get_register32(m.modrm["reg_index"]) def add_rm32_r32(self): self.eip += 1 m = self.parse_modrm() r32 = self.get_r32(m) rm32 = self.get_rm32(m) self.set_rm32(m, rm32 + r32) def sub_rm32_imm8(self, m): rm32 = self.get_rm32(m) imm8 = self.get_sign_code8(0) self.eip += 1 self.set_rm32(m, rm32 - imm8) # print("0x{:08x}".format(self.registers["ESP"])) def code_83(self): self.eip += 1 m = self.parse_modrm() if m.modrm["opecode"] == 5: self.sub_rm32_imm8(m) else: print("not implemented: 83 /{}".format(m.modrm["opecode"])) sys.exit(1) def inc_rm32(self, m): value = self.get_rm32(m) self.set_rm32(m, value + 1) def code_ff(self): self.eip += 1 m = self.parse_modrm() if m.modrm["opecode"] == 0: self.inc_rm32(m) else: print("not implemented: FF /{}".format(m.modrm["opecode"])) sys.exit(1) def get_register32(self, index): reg = self.register_name[index] return self.registers[reg] def set_register32(self, index, value): reg = self.register_name[index] self.registers[reg] = value mem_size = 1024 * 1024 emu = Emulator() emu.create_emu(mem_size, 0x7c00, 0x7c00) binary = open('modrm-test.bin', 'rb') offset = 0x7c00 while True: b = binary.read(1) if b == b'': break emu.memory[offset] = b offset += 1 binary.close() emu.init_instructions() while emu.eip < mem_size: code = emu.get_code8(0) print("EIP = 0x{:02x}, Code = 0x{:02x}".format(emu.eip, code)) if emu.instructions[code] == None: print("\n\nNot Implemented: 0x{:02x}".format(code)) break emu.instructions[code]() if emu.eip == 0x00: print("\n\nend of program.\n\n") break emu.dump_registers()
動作確認
それでは、上記で作成したスクリプトを実行してみます。
なお、事前にアセンブリ言語のプログラムはbinファイルとしてビルドしておきます。
> python emulator.py EIP = 0x7c00, Code = 0x83 EIP = 0x7c03, Code = 0x89 EIP = 0x7c05, Code = 0xb8 EIP = 0x7c0a, Code = 0xc7 EIP = 0x7c11, Code = 0x01 EIP = 0x7c14, Code = 0x8b EIP = 0x7c17, Code = 0xff EIP = 0x7c1a, Code = 0x8b EIP = 0x7c1d, Code = 0xe9 end of program. EAX = 0x00000002 ECX = 0x00000000 EDX = 0x00000000 EBX = 0x00000000 ESP = 0x00007bf0 EBP = 0x00007bf0 ESI = 0x00000007 EDI = 0x00000008 EIP = 0x00000000
ModR/Mによる柔軟なオペランド指定に加え、算術命令も実行できたことが確認できました。