
本記事は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による柔軟なオペランド指定に加え、算術命令も実行できたことが確認できました。