3
3
import sys
4
4
import traceback
5
5
from enum import Enum
6
- from typing import List , Union , NamedTuple
6
+ from typing import List , Union , NamedTuple , Callable
7
7
import inspect
8
8
from collections import OrderedDict
9
9
from dataclasses import dataclass , field
@@ -39,22 +39,41 @@ class ExceptionType(Enum):
39
39
ContextSwitch = 3
40
40
Terminate = 4
41
41
42
+ class MemoryViolation (Enum ):
43
+ Unknown = 0
44
+ ReadUnmapped = 1
45
+ WriteUnmapped = 2
46
+ ExecuteUnmapped = 3
47
+ ReadProtect = 4
48
+ WriteProtect = 5
49
+ ExecuteProtect = 6
50
+ ReadUnaligned = 7
51
+ WriteUnaligned = 8
52
+ ExecuteUnaligned = 9
53
+
42
54
@dataclass
43
55
class ExceptionInfo :
44
56
type : ExceptionType = ExceptionType .NoException
45
- memory_access : int = 0 # refers to UC_MEM_* values
57
+ # type == ExceptionType.Memory
58
+ memory_violation : MemoryViolation = MemoryViolation .Unknown
46
59
memory_address : int = 0
47
60
memory_size : int = 0
48
61
memory_value : int = 0
62
+ # type == ExceptionType.Interrupt
49
63
interrupt_number : int = 0
64
+
65
+ # Internal state
66
+ _handling : bool = False
67
+
68
+ @dataclass
69
+ class UnicornExceptionInfo (ExceptionInfo ):
70
+ final : bool = False
50
71
code_hook_h : Optional [int ] = None # represents a `unicorn.uc_hook_h` value (from uc.hook_add)
51
72
context : Optional [unicorn .UcContext ] = None
52
73
tb_start : int = 0
53
74
tb_size : int = 0
54
75
tb_icount : int = 0
55
76
step_count : int = 0
56
- final : bool = False
57
- handling : bool = False
58
77
59
78
def __str__ (self ):
60
79
return f"{ self .type } , ({ hex (self .tb_start )} , { hex (self .tb_size )} , { self .tb_icount } )"
@@ -305,8 +324,9 @@ def __init__(self, minidump_file, *, trace=False, quiet=False, thread_id=None, d
305
324
self .kill_me = None
306
325
self .exit_code = None
307
326
self .exports = self ._all_exports ()
308
- self .exception = ExceptionInfo ()
309
- self .last_exception : Optional [ExceptionInfo ] = None
327
+ self ._exception = UnicornExceptionInfo ()
328
+ self ._last_exception : Optional [UnicornExceptionInfo ] = None
329
+ self ._exception_hook : Optional [Callable [[ExceptionInfo ], Optional [int ]]] = None
310
330
if not self ._quiet :
311
331
print ("Memory map:" )
312
332
self .print_memory ()
@@ -896,15 +916,28 @@ def allocate(self, size, page_align=False):
896
916
self .memory .commit (self .memory .align_page (ptr ), self .memory .align_page (size ))
897
917
return ptr
898
918
899
- def handle_exception (self ):
900
- assert not self .exception .handling
901
- self .exception .handling = True
919
+ def set_exception_hook (self , exception_hook : Optional [Callable [[ExceptionInfo ], Optional [int ]]]):
920
+ previous_hook = self ._exception_hook
921
+ self ._exception_hook = exception_hook
922
+ return previous_hook
902
923
903
- if self .exception .type == ExceptionType .ContextSwitch :
924
+ def handle_exception (self ):
925
+ assert not self ._exception ._handling
926
+ self ._exception ._handling = True
927
+
928
+ if self ._exception_hook is not None :
929
+ hook_result = self ._exception_hook (self ._exception )
930
+ if hook_result is not None :
931
+ # Clear the pending exception
932
+ self ._last_exception = self ._exception
933
+ self ._exception = UnicornExceptionInfo ()
934
+ return hook_result
935
+
936
+ if self ._exception .type == ExceptionType .ContextSwitch :
904
937
self .info (f"context switch, cip: { hex (self .regs .cip )} " )
905
938
# Clear the pending exception
906
- self .last_exception = self .exception
907
- self .exception = ExceptionInfo ()
939
+ self ._last_exception = self ._exception
940
+ self ._exception = UnicornExceptionInfo ()
908
941
# NOTE: the context has already been restored using context_restore in the caller
909
942
return self .regs .cip
910
943
@@ -961,22 +994,23 @@ def handle_exception(self):
961
994
context_ex .XState .Offset = 0xF0 if self ._x64 else 0x20
962
995
context_ex .XState .Length = 0x160 if self ._x64 else 0x140
963
996
record = record_type ()
964
- if self .exception .type == ExceptionType .Memory :
965
- record .ExceptionCode = 0xC0000005
997
+ alignment_violations = [MemoryViolation .ReadUnaligned , MemoryViolation .WriteUnaligned , MemoryViolation .ExecuteUnaligned ]
998
+ if self ._exception .type == ExceptionType .Memory and self ._exception .memory_violation not in alignment_violations :
999
+ record .ExceptionCode = STATUS_ACCESS_VIOLATION
966
1000
record .ExceptionFlags = 0
967
1001
record .ExceptionAddress = self .regs .cip
968
1002
record .NumberParameters = 2
969
1003
types = {
970
- UC_MEM_READ_UNMAPPED : EXCEPTION_READ_FAULT ,
971
- UC_MEM_WRITE_UNMAPPED : EXCEPTION_WRITE_FAULT ,
972
- UC_MEM_FETCH_UNMAPPED : EXCEPTION_READ_FAULT ,
973
- UC_MEM_READ_PROT : EXCEPTION_READ_FAULT ,
974
- UC_MEM_WRITE_PROT : EXCEPTION_WRITE_FAULT ,
975
- UC_MEM_FETCH_PROT : EXCEPTION_EXECUTE_FAULT ,
1004
+ MemoryViolation . ReadUnmapped : EXCEPTION_READ_FAULT ,
1005
+ MemoryViolation . WriteUnmapped : EXCEPTION_WRITE_FAULT ,
1006
+ MemoryViolation . ExecuteUnmapped : EXCEPTION_READ_FAULT ,
1007
+ MemoryViolation . ReadProtect : EXCEPTION_READ_FAULT ,
1008
+ MemoryViolation . WriteProtect : EXCEPTION_WRITE_FAULT ,
1009
+ MemoryViolation . ExecuteProtect : EXCEPTION_EXECUTE_FAULT ,
976
1010
}
977
- record .ExceptionInformation [0 ] = types [self .exception . memory_access ]
978
- record .ExceptionInformation [1 ] = self .exception .memory_address
979
- elif self .exception .type == ExceptionType .Interrupt and self .exception .interrupt_number == 3 :
1011
+ record .ExceptionInformation [0 ] = types [self ._exception . memory_violation ]
1012
+ record .ExceptionInformation [1 ] = self ._exception .memory_address
1013
+ elif self ._exception .type == ExceptionType .Interrupt and self ._exception .interrupt_number == 3 :
980
1014
if self ._x64 :
981
1015
context .Rip -= 1 # TODO: long int3 and prefixes
982
1016
record .ExceptionCode = 0x80000003
@@ -990,11 +1024,11 @@ def handle_exception(self):
990
1024
record .ExceptionAddress = context .Eip
991
1025
record .NumberParameters = 1
992
1026
else :
993
- raise NotImplementedError (f"{ self .exception } " ) # TODO: implement
1027
+ raise NotImplementedError (f"{ self ._exception } " ) # TODO: implement
994
1028
995
1029
# Clear the pending exception
996
- self .last_exception = self .exception
997
- self .exception = ExceptionInfo ()
1030
+ self ._last_exception = self ._exception
1031
+ self ._exception = UnicornExceptionInfo ()
998
1032
999
1033
def write_stack (cur_ptr : int , data : bytes ):
1000
1034
self .write (cur_ptr , data )
@@ -1024,19 +1058,19 @@ def write_stack(cur_ptr: int, data: bytes):
1024
1058
1025
1059
def start (self , begin , end = 0xffffffffffffffff , count = 0 ) -> None :
1026
1060
# Clear exceptions before starting
1027
- self .exception = ExceptionInfo ()
1061
+ self ._exception = UnicornExceptionInfo ()
1028
1062
emu_begin = begin
1029
1063
emu_until = end
1030
1064
emu_count = count
1031
1065
while True :
1032
1066
try :
1033
- if self .exception .type != ExceptionType .NoException :
1034
- if self .exception .final :
1067
+ if self ._exception .type != ExceptionType .NoException :
1068
+ if self ._exception .final :
1035
1069
# Restore the context (unicorn might mess with it before stopping)
1036
- if self .exception .context is not None :
1037
- self ._uc .context_restore (self .exception .context )
1070
+ if self ._exception .context is not None :
1071
+ self ._uc .context_restore (self ._exception .context )
1038
1072
1039
- if self .exception .type == ExceptionType .Terminate :
1073
+ if self ._exception .type == ExceptionType .Terminate :
1040
1074
if self .exit_code is not None :
1041
1075
self .info (f"exit code: { hex (self .exit_code )} " )
1042
1076
break
@@ -1051,20 +1085,20 @@ def start(self, begin, end=0xffffffffffffffff, count=0) -> None:
1051
1085
emu_count = 0
1052
1086
else :
1053
1087
# If this happens there was an error restarting simulation
1054
- assert self .exception .step_count == 0
1088
+ assert self ._exception .step_count == 0
1055
1089
1056
1090
# Hook should be installed at this point
1057
- assert self .exception .code_hook_h is not None
1091
+ assert self ._exception .code_hook_h is not None
1058
1092
1059
1093
# Restore the context (unicorn might mess with it before stopping)
1060
- assert self .exception .context is not None
1061
- self ._uc .context_restore (self .exception .context )
1094
+ assert self ._exception .context is not None
1095
+ self ._uc .context_restore (self ._exception .context )
1062
1096
1063
1097
# Restart emulation
1064
1098
self .info (f"restarting emulation to handle exception..." )
1065
1099
emu_begin = self .regs .cip
1066
1100
emu_until = 0xffffffffffffffff
1067
- emu_count = self .exception .tb_icount + 1
1101
+ emu_count = self ._exception .tb_icount + 1
1068
1102
1069
1103
self .info (f"emu_start({ hex (emu_begin )} , { hex (emu_until )} , { emu_count } )" )
1070
1104
self .kill_me = None
@@ -1076,7 +1110,7 @@ def start(self, begin, end=0xffffffffffffffff, count=0) -> None:
1076
1110
except UcError as err :
1077
1111
if self .kill_me is not None and type (self .kill_me ) is not UcError :
1078
1112
raise self .kill_me
1079
- if self .exception .type != ExceptionType .NoException :
1113
+ if self ._exception .type != ExceptionType .NoException :
1080
1114
# Handle the exception outside of the except handler
1081
1115
continue
1082
1116
else :
@@ -1232,7 +1266,7 @@ def load_dll(self, file_name: str, file_data: bytes):
1232
1266
def _hook_code_exception (uc : Uc , address , size , dp : Dumpulator ):
1233
1267
try :
1234
1268
dp .info (f"exception step: { hex (address )} [{ size } ]" )
1235
- ex = dp .exception
1269
+ ex = dp ._exception
1236
1270
ex .step_count += 1
1237
1271
if ex .step_count >= ex .tb_icount :
1238
1272
raise Exception ("Stepped past the basic block without reaching exception" )
@@ -1246,18 +1280,27 @@ def _hook_mem(uc: Uc, access, address, size, value, dp: Dumpulator):
1246
1280
return True
1247
1281
1248
1282
fetch_accesses = [UC_MEM_FETCH , UC_MEM_FETCH_PROT , UC_MEM_FETCH_UNMAPPED ]
1249
- if access == UC_MEM_FETCH_UNMAPPED and address >= FORCE_KILL_ADDR - 0x10 and address <= FORCE_KILL_ADDR + 0x10 and dp .kill_me is not None :
1283
+ if access == UC_MEM_FETCH_UNMAPPED and FORCE_KILL_ADDR - 0x10 <= address <= FORCE_KILL_ADDR + 0x10 and dp .kill_me is not None :
1250
1284
dp .error (f"forced exit memory operation { access } of { hex (address )} [{ hex (size )} ] = { hex (value )} " )
1251
1285
return False
1252
- if dp .exception .final and access in fetch_accesses :
1286
+ if dp ._exception .final and access in fetch_accesses :
1253
1287
dp .info (f"fetch from { hex (address )} [{ size } ] already reported" )
1254
1288
return False
1255
1289
# TODO: figure out why when you start executing at 0 this callback is triggered more than once
1256
1290
try :
1291
+ violation = {
1292
+ UC_MEM_READ_UNMAPPED : MemoryViolation .ReadUnmapped ,
1293
+ UC_MEM_WRITE_UNMAPPED : MemoryViolation .WriteUnmapped ,
1294
+ UC_MEM_FETCH_UNMAPPED : MemoryViolation .ExecuteUnmapped ,
1295
+ UC_MEM_READ_PROT : MemoryViolation .ReadProtect ,
1296
+ UC_MEM_WRITE_PROT : MemoryViolation .WriteProtect ,
1297
+ UC_MEM_FETCH_PROT : MemoryViolation .ExecuteProtect ,
1298
+ }.get (access , MemoryViolation .Unknown )
1299
+ assert violation != MemoryViolation .Unknown , f"Unexpected memory access { access } "
1257
1300
# Extract exception information
1258
- exception = ExceptionInfo ()
1301
+ exception = UnicornExceptionInfo ()
1259
1302
exception .type = ExceptionType .Memory
1260
- exception .memory_access = access
1303
+ exception .memory_violation = violation
1261
1304
exception .memory_address = address
1262
1305
exception .memory_size = size
1263
1306
exception .memory_value = value
@@ -1269,7 +1312,7 @@ def _hook_mem(uc: Uc, access, address, size, value, dp: Dumpulator):
1269
1312
exception .tb_icount = tb .icount
1270
1313
1271
1314
# Print exception info
1272
- final = dp .trace or dp .exception .code_hook_h is not None
1315
+ final = dp .trace or dp ._exception .code_hook_h is not None
1273
1316
info = "final" if final else "initial"
1274
1317
if access == UC_MEM_READ_UNMAPPED :
1275
1318
dp .error (f"{ info } unmapped read from { hex (address )} [{ hex (size )} ], cip = { hex (dp .regs .cip )} , exception: { exception } " )
@@ -1295,25 +1338,25 @@ def _hook_mem(uc: Uc, access, address, size, value, dp: Dumpulator):
1295
1338
if final :
1296
1339
# Make sure this is the same exception we expect
1297
1340
if not dp .trace :
1298
- assert access == dp .exception . memory_access
1299
- assert address == dp .exception .memory_address
1300
- assert size == dp .exception .memory_size
1301
- assert value == dp .exception .memory_value
1341
+ assert violation == dp ._exception . memory_violation
1342
+ assert address == dp ._exception .memory_address
1343
+ assert size == dp ._exception .memory_size
1344
+ assert value == dp ._exception .memory_value
1302
1345
1303
1346
# Delete the code hook
1304
- uc .hook_del (dp .exception .code_hook_h )
1305
- dp .exception .code_hook_h = None
1347
+ uc .hook_del (dp ._exception .code_hook_h )
1348
+ dp ._exception .code_hook_h = None
1306
1349
1307
1350
# At this point we know for sure the context is correct so we can report the exception
1308
- dp .exception = exception
1309
- dp .exception .final = True
1351
+ dp ._exception = exception
1352
+ dp ._exception .final = True
1310
1353
1311
1354
# Stop emulation (we resume it on KiUserExceptionDispatcher later)
1312
1355
dp .stop ()
1313
1356
return False
1314
1357
1315
1358
# There should not be an exception active
1316
- assert dp .exception .type == ExceptionType .NoException
1359
+ assert dp ._exception .type == ExceptionType .NoException
1317
1360
1318
1361
# Remove the translation block cache for this block
1319
1362
# Without doing this single stepping the block won't work
@@ -1325,7 +1368,7 @@ def _hook_mem(uc: Uc, access, address, size, value, dp: Dumpulator):
1325
1368
exception .code_hook_h = uc .hook_add (UC_HOOK_CODE , _hook_code_exception , user_data = dp )
1326
1369
1327
1370
# Store the exception info
1328
- dp .exception = exception
1371
+ dp ._exception = exception
1329
1372
1330
1373
# Stop emulation (we resume execution later)
1331
1374
dp .stop ()
@@ -1452,7 +1495,7 @@ def _arg_type_string(arg):
1452
1495
def _hook_interrupt (uc : Uc , number , dp : Dumpulator ):
1453
1496
try :
1454
1497
# Extract exception information
1455
- exception = ExceptionInfo ()
1498
+ exception = UnicornExceptionInfo ()
1456
1499
exception .type = ExceptionType .Interrupt
1457
1500
exception .interrupt_number = number
1458
1501
exception .context = uc .context_save ()
@@ -1470,11 +1513,11 @@ def _hook_interrupt(uc: Uc, number, dp: Dumpulator):
1470
1513
dp .error (f"interrupt { number } ({ description } ), cip = { hex (dp .regs .cip )} , cs = { hex (dp .regs .cs )} " )
1471
1514
1472
1515
# There should not be an exception active
1473
- assert dp .exception .type == ExceptionType .NoException
1516
+ assert dp ._exception .type == ExceptionType .NoException
1474
1517
1475
1518
# At this point we know for sure the context is correct so we can report the exception
1476
- dp .exception = exception
1477
- dp .exception .final = True
1519
+ dp ._exception = exception
1520
+ dp ._exception .final = True
1478
1521
except AssertionError as err :
1479
1522
traceback .print_exc ()
1480
1523
raise err
@@ -1560,7 +1603,7 @@ def syscall_arg(index):
1560
1603
status = syscall_impl (dp , * args )
1561
1604
if isinstance (status , ExceptionInfo ):
1562
1605
print ("context switch, stopping emulation" )
1563
- dp .exception = status
1606
+ dp ._exception = status
1564
1607
raise dp .raise_kill (UcError (UC_ERR_EXCEPTION )) from None
1565
1608
else :
1566
1609
dp .info (f"status = { hex (status )} " )
@@ -1610,11 +1653,11 @@ def _hook_invalid(uc: Uc, dp: Dumpulator):
1610
1653
instr = next (dp .cs .disasm (code , address , 1 ))
1611
1654
if _emulate_unsupported_instruction (dp , instr ):
1612
1655
# Resume execution with a context switch
1613
- assert dp .exception .type == ExceptionType .NoException
1614
- exception = ExceptionInfo ()
1656
+ assert dp ._exception .type == ExceptionType .NoException
1657
+ exception = UnicornExceptionInfo ()
1615
1658
exception .type = ExceptionType .ContextSwitch
1616
1659
exception .final = True
1617
- dp .exception = exception
1660
+ dp ._exception = exception
1618
1661
return False # NOTE: returning True would stop emulation
1619
1662
except StopIteration :
1620
1663
pass # Unsupported instruction
0 commit comments