本記事はPythonで簡単なx86エミュレータを作成します。
前回では簡単なオペコード(mov命令とjmp命令のみ)を読み込み、実行するプログラムを作成しました。
今回はorg疑似命令を実装し、プログラムの配置場所を調整する方法について学びます。
org疑似命令とは
前回では、mov命令とjmp命令のみでCPUが動作する雰囲気を味わってみました。
その際、読み込まれたプログラムは、メモリ配列の先頭に格納されていましたが、プログラムを配置する番地を変更することが可能で、その際に使用するのがorg疑似命令です。
前回のアセンブリ言語プログラムにorg疑似命令を追加し、アセンブラにプログラムの配置場所を指定します。
;test.asm BITS 32 org 0x7c00 mov eax, 41 mov ebx, 42 mov ecx, 43 mov edx, 44 jmp 0
なお、BIOSでは標準的に0x7c00番地に配置するようです。
Pythonによるスクリプトの作成
それでは、Pythonでorg疑似命令に対応したスクリプトを作成します。
なお、前回のショートジャンプ命令では、ジャンプできる範囲が限定的のため、新しいジャンプ命令(ニアジャンプ命令)も実装します。
# emulator.py 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): 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 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 = 0 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) mem_size = 1024 * 1024 emu = Emulator() emu.create_emu(mem_size, 0x7c00, 0x7c00) binary = open('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 = 0xb8 EIP = 0x7c05, Code = 0xbb EIP = 0x7c0a, Code = 0xb9 EIP = 0x7c0f, Code = 0xba EIP = 0x7c14, Code = 0xe9 end of program. EAX = 0x00000029 ECX = 0x0000002b EDX = 0x0000002c EBX = 0x0000002a ESP = 0x00007c00 EBP = 0x00000000 ESI = 0x00000000 EDI = 0x00000000 EIP = 0x00000000
eip初期値を0x7c00に変更し、問題なく各レジスタに即値がコピーされたことが確認できました。