Skip to content

Unicorn fails to execute a page of nops in x86 #2196

Open
@Roguebantha

Description

@Roguebantha

Given a page of mostly nops and an unmapped page immediately after, if instruction execution starts at the beginning of the page PLUS any offset greater than zero (e.g. 0x1001), the instructions executed and the RIP reported upon an exception can be wrong in many many different ways.

The most obviously wrong behavior is that the emulation attempts to fetch from 0x2000 when RIP reaches 0x1e01, meaning that some 0x200 of the instructions may not be executed at all, and the emulation unexpectedly generates an invalid fetch instead.

I've created several pocs exhibiting a wide variety of different wrong behaviors. Given the simplicity of the poc I suspect this could be contributing to unicorn emulation being incorrect in a variety of subtle or obvious ways in production. An example python poc is as follows:

from unicorn import *
from unicorn.x86_const import *

def hook(mu,access,address,size,value,user_data):
        print("Unicorn fetch failure",address)
        print(f"!!! hook RIP is {mu.reg_read(UC_X86_REG_RIP):#0{18}x}")

def nop_hook(mu,address,size,user_data):
        print(">>> Tracing instruction at 0x%x, instruction size = 0x%x" % (address, size))
mu = Uc(UC_ARCH_X86, UC_MODE_64)
mu.mem_map(0x1000,0x1000)
mu.mem_write(0x1000,b'\x90' * 0x1000)
# Putting an int3 at the end will cause behavior to go back to normal
#mu.mem_write(0x1000,b'\x90' * 0xfff + b'\xcc')
# Putting a pop with an invalid rsp will rebreak it
#mu.mem_write(0x1000,b'\x90' * 0xfff + b'\x58')
mu.hook_add(UC_HOOK_MEM_FETCH_UNMAPPED,hook) 
mu.hook_add(UC_HOOK_CODE,nop_hook,begin=0x1e00,end=0x1e02)
# Works as expected, but if pop rsp is the last instruction then the rip is misreported at 0x1e02
try:
        mu.emu_start(0x1000,0xffff)
except UcError as err:
        print(f"!!! Unicorn Engine Exception: {err}")
        print(f"!!! RIP is {mu.reg_read(UC_X86_REG_RIP):#0{18}x}")
# Crashes at 0x1e01 attempting to fetch from address 0x2000
try:
        mu.emu_start(0x1001,0xffff)
except UcError as err:
        print(f"!!! Unicorn Engine Exception: {err}")
        print(f"!!! RIP is {mu.reg_read(UC_X86_REG_RIP):#0{18}x}")
# Works as expected, but if pop rsp is the last instruction then the rip is misreported at 0x1e02
try:
        mu.emu_start(0x1001,0x2000)
        print("Succeeded emulation");
except UcError as err:
        print(f"!!! Unicorn Engine Exception: {err}")
        print(f"!!! RIP is {mu.reg_read(UC_X86_REG_RIP):#0{18}x}")

This program will generate the output:

>>> Tracing instruction at 0x1e00, instruction size = 0x1 //This is emu_start number 1
>>> Tracing instruction at 0x1e01, instruction size = 0x1
>>> Tracing instruction at 0x1e02, instruction size = 0x1
Unicorn fetch failure 8192
!!! hook RIP is 0x0000000000002000
!!! Unicorn Engine Exception: Invalid memory fetch (UC_ERR_FETCH_UNMAPPED)
!!! RIP is 0x0000000000002000
>>> Tracing instruction at 0x1e00, instruction size = 0x1 //This is emu_start number 2
Unicorn fetch failure 8192
!!! hook RIP is 0x0000000000001e01
!!! Unicorn Engine Exception: Invalid memory fetch (UC_ERR_FETCH_UNMAPPED)
!!! RIP is 0x0000000000001e01
>>> Tracing instruction at 0x1e00, instruction size = 0x1 //This is emu_start number 3
>>> Tracing instruction at 0x1e01, instruction size = 0x1
>>> Tracing instruction at 0x1e02, instruction size = 0x1
Succeeded emulation

It's notable that if you put a pop rax at the very end of the page, the second emu_start failure behavior does not change - the pop does not appear to be architecturally executed, and the program still unexpectedly generates an invalid fetch at RIP 0x1e01 for address 0x2000. So this is more than just misreported register values, it appears to be incorrect architectural execution as well.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions