Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

UC_ERR_EXCEPTION when hook UC_HOOK_INSN_INVALID #1972

Open
tylzh97 opened this issue Jul 9, 2024 · 3 comments
Open

UC_ERR_EXCEPTION when hook UC_HOOK_INSN_INVALID #1972

tylzh97 opened this issue Jul 9, 2024 · 3 comments

Comments

@tylzh97
Copy link

tylzh97 commented Jul 9, 2024

I'm working on dev branch.

As mentioned at PR #1132 , my code is that:

from unicorn import *
from unicorn.x86_const import *

uc = Uc(UC_ARCH_X86, UC_MODE_64)

"""
    nop
    nop
    xsaves  byte ptr [rcx]
    <garbage>
"""

sc = b"\x90\x90\x0F\xC7\x29\xFF\xFF\xFF"
uc.mem_map(0, 0x1000)
uc.mem_write(0, sc)

def hook0(uc, user_data):
    addr = uc.reg_read(UC_X86_REG_RIP)
    print("hook0 at 0x{:x}".format(addr))
    return False

def hook1(uc, user_data):
    addr = uc.reg_read(UC_X86_REG_RIP)
    print("hook1 at 0x{:x}".format(addr))
    uc.reg_write(UC_X86_REG_RIP, addr+1)
    return True

uc.hook_add(UC_HOOK_INSN_INVALID, hook0)
uc.hook_add(UC_HOOK_INSN_INVALID, hook1)
uc.emu_start(0, 20, 5000)

but i got outputs:

$ python3 test.py 
hook0 at 0x2
hook1 at 0x2
Traceback (most recent call last):
  File "/home/xxx/Workspace/Testspace/unicorn_emu/test.py", line 30, in <module>
    uc.emu_start(0, 20, 5000)
  File "/home/xxx/.venv/unicorn-dev/lib/python3.10/site-packages/unicorn/unicorn_py3/unicorn.py", line 560, in emu_start
    raise UcError(status)
unicorn.unicorn_py3.unicorn.UcError: Unhandled CPU exception (UC_ERR_EXCEPTION)

It seems that return value of UC_HOOK_INSN_INVALID function not working. I want to ignore the invalid instruction, how could I do it?

@BitMaskMixer
Copy link
Contributor

@tylzh97

I have looked into your example script and used my playground (#1980) to verify the issue. However, I am using a single stepping approach, not multiple internal emulation loops as you do with emu_start. But I tested both versions.

Unicorn seems not to be the problem, it seems to me that the binding implementation contains the bug.

Your example should have thrown the exception directly after the first hook, as you return false, which should exit the loop. That is probably easy to fix in the binding script.

@BitMaskMixer
Copy link
Contributor

@tylzh97

I had another look - give this python script a try, you can continue the execution after the exception, if it has been catched.
The python binding (probably all bindings) can not communicate with unicorn itself I think, so you must continue calling unicorn, even if the exception has ben raised. Unicorn returns just an uc_err wich is != UC_ERR_OK incase that something went wrong and you (or the binding script) must treat it.

from unicorn import *
from unicorn.x86_const import *

uc = Uc(UC_ARCH_X86, UC_MODE_64)

"""
    nop
    nop
    xsaves  byte ptr [rcx]
    <garbage>
"""

code_to_execute = b"\x90\x90\x0F\xC7\x29\xFF\xFF\xFF"
code_length = len(code_to_execute)
base_address = 0x0
memory_size = 0x1000
pc = base_address

uc.mem_map(base_address, memory_size)
uc.mem_write(base_address, code_to_execute)

def hook_invalid_instruction_0(uc, user_data):
    current_pc = uc.reg_read(UC_X86_REG_RIP)
    print("Invalid instruction hooked (0): 0x{:x}".format(current_pc))

    return False

def hook_invalid_instruction_1(uc, user_data):
    current_pc = uc.reg_read(UC_X86_REG_RIP)
    print("Invalid instruction hooked (1): 0x{:x}".format(current_pc))
    uc.reg_write(UC_X86_REG_RIP, current_pc + 1)

    return True

def hook_code(uc, address, size, user_data):
    instruction_bytes = "<unknown>"

    if size < memory_size:
        instruction_bytes = uc.mem_read(address, size)
        instruction_bytes = instruction_bytes.hex()

    print("Executing: 0x{:x}, size: 0x{:x}, code: {}".format(address, size, instruction_bytes))
    
uc.hook_add(UC_HOOK_INSN_INVALID, hook_invalid_instruction_0)
uc.hook_add(UC_HOOK_INSN_INVALID, hook_invalid_instruction_1)
uc.hook_add(UC_HOOK_CODE, hook_code)

step_index = 0
print("Emulation start")

while pc < code_length:
    print("Execution step: {}, pc: {}".format(step_index, pc))
    
    try:
        uc.emu_start(begin = pc, until = code_length, count = 1)
    except:
        pass

    pc = uc.reg_read(UC_X86_REG_RIP)
    step_index += 1

print("Emulation end")

@tylzh97
Copy link
Author

tylzh97 commented Jul 26, 2024

@tylzh97

I had another look - give this python script a try, you can continue the execution after the exception, if it has been catched. The python binding (probably all bindings) can not communicate with unicorn itself I think, so you must continue calling unicorn, even if the exception has ben raised. Unicorn returns just an uc_err wich is != UC_ERR_OK incase that something went wrong and you (or the binding script) must treat it.

from unicorn import *
from unicorn.x86_const import *

uc = Uc(UC_ARCH_X86, UC_MODE_64)

"""
    nop
    nop
    xsaves  byte ptr [rcx]
    <garbage>
"""

code_to_execute = b"\x90\x90\x0F\xC7\x29\xFF\xFF\xFF"
code_length = len(code_to_execute)
base_address = 0x0
memory_size = 0x1000
pc = base_address

uc.mem_map(base_address, memory_size)
uc.mem_write(base_address, code_to_execute)

def hook_invalid_instruction_0(uc, user_data):
    current_pc = uc.reg_read(UC_X86_REG_RIP)
    print("Invalid instruction hooked (0): 0x{:x}".format(current_pc))

    return False

def hook_invalid_instruction_1(uc, user_data):
    current_pc = uc.reg_read(UC_X86_REG_RIP)
    print("Invalid instruction hooked (1): 0x{:x}".format(current_pc))
    uc.reg_write(UC_X86_REG_RIP, current_pc + 1)

    return True

def hook_code(uc, address, size, user_data):
    instruction_bytes = "<unknown>"

    if size < memory_size:
        instruction_bytes = uc.mem_read(address, size)
        instruction_bytes = instruction_bytes.hex()

    print("Executing: 0x{:x}, size: 0x{:x}, code: {}".format(address, size, instruction_bytes))
    
uc.hook_add(UC_HOOK_INSN_INVALID, hook_invalid_instruction_0)
uc.hook_add(UC_HOOK_INSN_INVALID, hook_invalid_instruction_1)
uc.hook_add(UC_HOOK_CODE, hook_code)

step_index = 0
print("Emulation start")

while pc < code_length:
    print("Execution step: {}, pc: {}".format(step_index, pc))
    
    try:
        uc.emu_start(begin = pc, until = code_length, count = 1)
    except:
        pass

    pc = uc.reg_read(UC_X86_REG_RIP)
    step_index += 1

print("Emulation end")

Thank you for your attempt and reply!

In fact, I am currently mitigating the exception in this way. Similar to your implementation, I will catch this exception after UC_HOOK_INSN_INVALID returns and skip the execution of the current code by modifying the PC register. However, I believe this is not an "elegant" solution.

As mentioned in PR #1132, initially Unicorn could skip illegal instructions without returning any error. I currently do not have time to delve into the code implementation of Unicorn. I believe that certain exceptions may have occurred in subsequent versions causing this feature to not work properly anymore.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants