mirror of
https://github.com/yuzu-emu/unicorn.git
synced 2025-05-31 17:30:22 +00:00
Solution refactoring and bug fixing
This commit is contained in:
parent
272fb8524f
commit
2c54f1a969
|
@ -22,7 +22,7 @@ type IBinding =
|
||||||
abstract MemUnmap : UIntPtr * UInt64 * UIntPtr -> Int32
|
abstract MemUnmap : UIntPtr * UInt64 * UIntPtr -> Int32
|
||||||
abstract MemProtect : UIntPtr * UInt64 * UIntPtr * UInt32 -> Int32
|
abstract MemProtect : UIntPtr * UInt64 * UIntPtr * UInt32 -> Int32
|
||||||
abstract HookAddNoarg : UIntPtr * UIntPtr * Int32 * UIntPtr * IntPtr -> Int32
|
abstract HookAddNoarg : UIntPtr * UIntPtr * Int32 * UIntPtr * IntPtr -> Int32
|
||||||
abstract HookAddArg0 : UIntPtr * UIntPtr * Int32 * UIntPtr * IntPtr * IntPtr -> Int32
|
abstract HookAddArg0 : UIntPtr * UIntPtr * Int32 * UIntPtr * IntPtr * Int32 -> Int32
|
||||||
abstract HookAddArg0Arg1 : UIntPtr * UIntPtr * Int32 * UIntPtr * IntPtr * UInt64 * UInt64 -> Int32
|
abstract HookAddArg0Arg1 : UIntPtr * UIntPtr * Int32 * UIntPtr * IntPtr * UInt64 * UInt64 -> Int32
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -63,7 +63,7 @@ module NativeBinding =
|
||||||
extern Int32 uc_hook_add_noarg(UIntPtr eng, UIntPtr hh, Int32 callbackType, UIntPtr callback, IntPtr userData)
|
extern Int32 uc_hook_add_noarg(UIntPtr eng, UIntPtr hh, Int32 callbackType, UIntPtr callback, IntPtr userData)
|
||||||
|
|
||||||
[<DllImport("unicorn", CallingConvention = CallingConvention.Cdecl, EntryPoint = "uc_hook_add")>]
|
[<DllImport("unicorn", CallingConvention = CallingConvention.Cdecl, EntryPoint = "uc_hook_add")>]
|
||||||
extern Int32 uc_hook_add_arg0(UIntPtr eng, UIntPtr hh, Int32 callbackType, UIntPtr callback, IntPtr userData, IntPtr arg0)
|
extern Int32 uc_hook_add_arg0(UIntPtr eng, UIntPtr hh, Int32 callbackType, UIntPtr callback, IntPtr userData, Int32 arg0)
|
||||||
|
|
||||||
[<DllImport("unicorn", CallingConvention = CallingConvention.Cdecl, EntryPoint = "uc_hook_add")>]
|
[<DllImport("unicorn", CallingConvention = CallingConvention.Cdecl, EntryPoint = "uc_hook_add")>]
|
||||||
extern Int32 uc_hook_add_arg0_arg1(UIntPtr eng, UIntPtr hh, Int32 callbackType, UIntPtr callback, IntPtr userData, UInt64 arg0, UInt64 arg1)
|
extern Int32 uc_hook_add_arg0_arg1(UIntPtr eng, UIntPtr hh, Int32 callbackType, UIntPtr callback, IntPtr userData, UInt64 arg0, UInt64 arg1)
|
||||||
|
|
|
@ -7,7 +7,7 @@ open System
|
||||||
[<AutoOpen>]
|
[<AutoOpen>]
|
||||||
module Arm =
|
module Arm =
|
||||||
|
|
||||||
// ARM registers
|
// ARM registers
|
||||||
|
|
||||||
let UC_ARM_REG_INVALID = 0
|
let UC_ARM_REG_INVALID = 0
|
||||||
let UC_ARM_REG_APSR = 1
|
let UC_ARM_REG_APSR = 1
|
||||||
|
@ -122,7 +122,7 @@ module Arm =
|
||||||
let UC_ARM_REG_S31 = 110
|
let UC_ARM_REG_S31 = 110
|
||||||
let UC_ARM_REG_ENDING = 111
|
let UC_ARM_REG_ENDING = 111
|
||||||
|
|
||||||
// alias registers
|
// alias registers
|
||||||
let UC_ARM_REG_R13 = 12
|
let UC_ARM_REG_R13 = 12
|
||||||
let UC_ARM_REG_R14 = 10
|
let UC_ARM_REG_R14 = 10
|
||||||
let UC_ARM_REG_R15 = 11
|
let UC_ARM_REG_R15 = 11
|
||||||
|
|
|
@ -7,7 +7,7 @@ open System
|
||||||
[<AutoOpen>]
|
[<AutoOpen>]
|
||||||
module Arm64 =
|
module Arm64 =
|
||||||
|
|
||||||
// ARM64 registers
|
// ARM64 registers
|
||||||
|
|
||||||
let UC_ARM64_REG_INVALID = 0
|
let UC_ARM64_REG_INVALID = 0
|
||||||
let UC_ARM64_REG_X29 = 1
|
let UC_ARM64_REG_X29 = 1
|
||||||
|
@ -270,11 +270,11 @@ module Arm64 =
|
||||||
let UC_ARM64_REG_V30 = 258
|
let UC_ARM64_REG_V30 = 258
|
||||||
let UC_ARM64_REG_V31 = 259
|
let UC_ARM64_REG_V31 = 259
|
||||||
|
|
||||||
// pseudo registers
|
// pseudo registers
|
||||||
let UC_ARM64_REG_PC = 260
|
let UC_ARM64_REG_PC = 260
|
||||||
let UC_ARM64_REG_ENDING = 261
|
let UC_ARM64_REG_ENDING = 261
|
||||||
|
|
||||||
// alias registers
|
// alias registers
|
||||||
let UC_ARM64_REG_IP1 = 215
|
let UC_ARM64_REG_IP1 = 215
|
||||||
let UC_ARM64_REG_IP0 = 216
|
let UC_ARM64_REG_IP0 = 216
|
||||||
let UC_ARM64_REG_FP = 1
|
let UC_ARM64_REG_FP = 1
|
||||||
|
|
|
@ -7,7 +7,7 @@ open System
|
||||||
[<AutoOpen>]
|
[<AutoOpen>]
|
||||||
module M68k =
|
module M68k =
|
||||||
|
|
||||||
// M68K registers
|
// M68K registers
|
||||||
|
|
||||||
let UC_M68K_REG_INVALID = 0
|
let UC_M68K_REG_INVALID = 0
|
||||||
let UC_M68K_REG_A0 = 1
|
let UC_M68K_REG_A0 = 1
|
||||||
|
|
|
@ -7,11 +7,11 @@ open System
|
||||||
[<AutoOpen>]
|
[<AutoOpen>]
|
||||||
module Mips =
|
module Mips =
|
||||||
|
|
||||||
// MIPS registers
|
// MIPS registers
|
||||||
|
|
||||||
let UC_MIPS_REG_INVALID = 0
|
let UC_MIPS_REG_INVALID = 0
|
||||||
|
|
||||||
// General purpose registers
|
// General purpose registers
|
||||||
let UC_MIPS_REG_PC = 1
|
let UC_MIPS_REG_PC = 1
|
||||||
let UC_MIPS_REG_0 = 2
|
let UC_MIPS_REG_0 = 2
|
||||||
let UC_MIPS_REG_1 = 3
|
let UC_MIPS_REG_1 = 3
|
||||||
|
@ -46,7 +46,7 @@ module Mips =
|
||||||
let UC_MIPS_REG_30 = 32
|
let UC_MIPS_REG_30 = 32
|
||||||
let UC_MIPS_REG_31 = 33
|
let UC_MIPS_REG_31 = 33
|
||||||
|
|
||||||
// DSP registers
|
// DSP registers
|
||||||
let UC_MIPS_REG_DSPCCOND = 34
|
let UC_MIPS_REG_DSPCCOND = 34
|
||||||
let UC_MIPS_REG_DSPCARRY = 35
|
let UC_MIPS_REG_DSPCARRY = 35
|
||||||
let UC_MIPS_REG_DSPEFI = 36
|
let UC_MIPS_REG_DSPEFI = 36
|
||||||
|
@ -59,13 +59,13 @@ module Mips =
|
||||||
let UC_MIPS_REG_DSPPOS = 43
|
let UC_MIPS_REG_DSPPOS = 43
|
||||||
let UC_MIPS_REG_DSPSCOUNT = 44
|
let UC_MIPS_REG_DSPSCOUNT = 44
|
||||||
|
|
||||||
// ACC registers
|
// ACC registers
|
||||||
let UC_MIPS_REG_AC0 = 45
|
let UC_MIPS_REG_AC0 = 45
|
||||||
let UC_MIPS_REG_AC1 = 46
|
let UC_MIPS_REG_AC1 = 46
|
||||||
let UC_MIPS_REG_AC2 = 47
|
let UC_MIPS_REG_AC2 = 47
|
||||||
let UC_MIPS_REG_AC3 = 48
|
let UC_MIPS_REG_AC3 = 48
|
||||||
|
|
||||||
// COP registers
|
// COP registers
|
||||||
let UC_MIPS_REG_CC0 = 49
|
let UC_MIPS_REG_CC0 = 49
|
||||||
let UC_MIPS_REG_CC1 = 50
|
let UC_MIPS_REG_CC1 = 50
|
||||||
let UC_MIPS_REG_CC2 = 51
|
let UC_MIPS_REG_CC2 = 51
|
||||||
|
@ -75,7 +75,7 @@ module Mips =
|
||||||
let UC_MIPS_REG_CC6 = 55
|
let UC_MIPS_REG_CC6 = 55
|
||||||
let UC_MIPS_REG_CC7 = 56
|
let UC_MIPS_REG_CC7 = 56
|
||||||
|
|
||||||
// FPU registers
|
// FPU registers
|
||||||
let UC_MIPS_REG_F0 = 57
|
let UC_MIPS_REG_F0 = 57
|
||||||
let UC_MIPS_REG_F1 = 58
|
let UC_MIPS_REG_F1 = 58
|
||||||
let UC_MIPS_REG_F2 = 59
|
let UC_MIPS_REG_F2 = 59
|
||||||
|
@ -117,7 +117,7 @@ module Mips =
|
||||||
let UC_MIPS_REG_FCC6 = 95
|
let UC_MIPS_REG_FCC6 = 95
|
||||||
let UC_MIPS_REG_FCC7 = 96
|
let UC_MIPS_REG_FCC7 = 96
|
||||||
|
|
||||||
// AFPR128
|
// AFPR128
|
||||||
let UC_MIPS_REG_W0 = 97
|
let UC_MIPS_REG_W0 = 97
|
||||||
let UC_MIPS_REG_W1 = 98
|
let UC_MIPS_REG_W1 = 98
|
||||||
let UC_MIPS_REG_W2 = 99
|
let UC_MIPS_REG_W2 = 99
|
||||||
|
|
|
@ -7,7 +7,7 @@ open System
|
||||||
[<AutoOpen>]
|
[<AutoOpen>]
|
||||||
module Sparc =
|
module Sparc =
|
||||||
|
|
||||||
// SPARC registers
|
// SPARC registers
|
||||||
|
|
||||||
let UC_SPARC_REG_INVALID = 0
|
let UC_SPARC_REG_INVALID = 0
|
||||||
let UC_SPARC_REG_F0 = 1
|
let UC_SPARC_REG_F0 = 1
|
||||||
|
|
|
@ -7,7 +7,7 @@ open System
|
||||||
[<AutoOpen>]
|
[<AutoOpen>]
|
||||||
module X86 =
|
module X86 =
|
||||||
|
|
||||||
// X86 registers
|
// X86 registers
|
||||||
|
|
||||||
let UC_X86_REG_INVALID = 0
|
let UC_X86_REG_INVALID = 0
|
||||||
let UC_X86_REG_AH = 1
|
let UC_X86_REG_AH = 1
|
||||||
|
@ -253,7 +253,7 @@ module X86 =
|
||||||
let UC_X86_REG_R15W = 241
|
let UC_X86_REG_R15W = 241
|
||||||
let UC_X86_REG_ENDING = 242
|
let UC_X86_REG_ENDING = 242
|
||||||
|
|
||||||
// X86 instructions
|
// X86 instructions
|
||||||
|
|
||||||
let UC_X86_INS_INVALID = 0
|
let UC_X86_INS_INVALID = 0
|
||||||
let UC_X86_INS_AAA = 1
|
let UC_X86_INS_AAA = 1
|
||||||
|
@ -378,7 +378,7 @@ module X86 =
|
||||||
let UC_X86_INS_CVTPS2DQ = 120
|
let UC_X86_INS_CVTPS2DQ = 120
|
||||||
let UC_X86_INS_CVTPS2PD = 121
|
let UC_X86_INS_CVTPS2PD = 121
|
||||||
let UC_X86_INS_CVTSD2SI = 122
|
let UC_X86_INS_CVTSD2SI = 122
|
||||||
(*let UC_X86_INS_CVTSD2SS = 123
|
let UC_X86_INS_CVTSD2SS = 123
|
||||||
let UC_X86_INS_CVTSI2SD = 124
|
let UC_X86_INS_CVTSI2SD = 124
|
||||||
let UC_X86_INS_CVTSI2SS = 125
|
let UC_X86_INS_CVTSI2SS = 125
|
||||||
let UC_X86_INS_CVTSS2SD = 126
|
let UC_X86_INS_CVTSS2SD = 126
|
||||||
|
@ -472,9 +472,9 @@ module X86 =
|
||||||
let UC_X86_INS_HSUBPS = 214
|
let UC_X86_INS_HSUBPS = 214
|
||||||
let UC_X86_INS_IDIV = 215
|
let UC_X86_INS_IDIV = 215
|
||||||
let UC_X86_INS_FILD = 216
|
let UC_X86_INS_FILD = 216
|
||||||
let UC_X86_INS_IMUL = 217*)
|
let UC_X86_INS_IMUL = 217
|
||||||
let UC_X86_INS_IN = 218
|
let UC_X86_INS_IN = 218
|
||||||
(*let UC_X86_INS_INC = 219
|
let UC_X86_INS_INC = 219
|
||||||
let UC_X86_INS_INSB = 220
|
let UC_X86_INS_INSB = 220
|
||||||
let UC_X86_INS_INSERTPS = 221
|
let UC_X86_INS_INSERTPS = 221
|
||||||
let UC_X86_INS_INSERTQ = 222
|
let UC_X86_INS_INSERTQ = 222
|
||||||
|
@ -754,9 +754,9 @@ module X86 =
|
||||||
let UC_X86_INS_MWAIT = 496
|
let UC_X86_INS_MWAIT = 496
|
||||||
let UC_X86_INS_NEG = 497
|
let UC_X86_INS_NEG = 497
|
||||||
let UC_X86_INS_NOP = 498
|
let UC_X86_INS_NOP = 498
|
||||||
let UC_X86_INS_NOT = 499*)
|
let UC_X86_INS_NOT = 499
|
||||||
let UC_X86_INS_OUT = 500
|
let UC_X86_INS_OUT = 500
|
||||||
(*let UC_X86_INS_OUTSB = 501
|
let UC_X86_INS_OUTSB = 501
|
||||||
let UC_X86_INS_OUTSD = 502
|
let UC_X86_INS_OUTSD = 502
|
||||||
let UC_X86_INS_OUTSW = 503
|
let UC_X86_INS_OUTSW = 503
|
||||||
let UC_X86_INS_PACKUSDW = 504
|
let UC_X86_INS_PACKUSDW = 504
|
||||||
|
@ -953,9 +953,9 @@ module X86 =
|
||||||
let UC_X86_INS_FSUB = 695
|
let UC_X86_INS_FSUB = 695
|
||||||
let UC_X86_INS_FISUB = 696
|
let UC_X86_INS_FISUB = 696
|
||||||
let UC_X86_INS_FSUBP = 697
|
let UC_X86_INS_FSUBP = 697
|
||||||
let UC_X86_INS_SWAPGS = 698*)
|
let UC_X86_INS_SWAPGS = 698
|
||||||
let UC_X86_INS_SYSCALL = 699
|
let UC_X86_INS_SYSCALL = 699
|
||||||
(*let UC_X86_INS_SYSENTER = 700
|
let UC_X86_INS_SYSENTER = 700
|
||||||
let UC_X86_INS_SYSEXIT = 701
|
let UC_X86_INS_SYSEXIT = 701
|
||||||
let UC_X86_INS_SYSRET = 702
|
let UC_X86_INS_SYSRET = 702
|
||||||
let UC_X86_INS_T1MSKC = 703
|
let UC_X86_INS_T1MSKC = 703
|
||||||
|
@ -1594,4 +1594,4 @@ module X86 =
|
||||||
let UC_X86_INS_FDISI8087_NOP = 1336
|
let UC_X86_INS_FDISI8087_NOP = 1336
|
||||||
let UC_X86_INS_FENI8087_NOP = 1337
|
let UC_X86_INS_FENI8087_NOP = 1337
|
||||||
let UC_X86_INS_ENDING = 1338
|
let UC_X86_INS_ENDING = 1338
|
||||||
*)
|
|
||||||
|
|
21
bindings/dotnet/UnicornManaged/ConvertUtility.fs
Normal file
21
bindings/dotnet/UnicornManaged/ConvertUtility.fs
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
namespace UnicornManaged
|
||||||
|
|
||||||
|
open System
|
||||||
|
|
||||||
|
[<AutoOpen>]
|
||||||
|
module internal ConvertUtility =
|
||||||
|
|
||||||
|
let int64ToBytes(v: Int64) =
|
||||||
|
let res = Array.zeroCreate<Byte> 8
|
||||||
|
let mutable uv = uint64 v
|
||||||
|
for i = 0 to res.Length-1 do
|
||||||
|
res.[i] <- byte (uv &&& uint64 0xFF)
|
||||||
|
uv <- uv >>> 8
|
||||||
|
res
|
||||||
|
|
||||||
|
let bytesToInt64(v: Byte array) =
|
||||||
|
let mutable res = uint64 0
|
||||||
|
for i = 0 to v.Length-1 do
|
||||||
|
let tmpV = v.[i] &&& byte 0xFF
|
||||||
|
res <- res + (uint64 tmpV <<< (i * 8))
|
||||||
|
int64 res
|
|
@ -20,10 +20,10 @@ type internal MemReadHookInternal = delegate of IntPtr * Int64 * Int32 * IntPtr
|
||||||
type internal MemWriteHookInternal = delegate of IntPtr * Int64 * Int32 * Int64 * IntPtr -> unit
|
type internal MemWriteHookInternal = delegate of IntPtr * Int64 * Int32 * Int64 * IntPtr -> unit
|
||||||
|
|
||||||
[<UnmanagedFunctionPointer(CallingConvention.Cdecl)>]
|
[<UnmanagedFunctionPointer(CallingConvention.Cdecl)>]
|
||||||
type internal EventMemHookInternal = delegate of IntPtr * Int64 * Int32 * Int64 * IntPtr -> unit
|
type internal EventMemHookInternal = delegate of IntPtr * Int32 * Int64 * Int32 * Int64 * IntPtr-> Boolean
|
||||||
|
|
||||||
[<UnmanagedFunctionPointer(CallingConvention.Cdecl)>]
|
[<UnmanagedFunctionPointer(CallingConvention.Cdecl)>]
|
||||||
type internal InHookInternal = delegate of IntPtr * Int32 * Int32 * IntPtr -> unit
|
type internal InHookInternal = delegate of IntPtr * Int32 * Int32 * IntPtr -> Int32
|
||||||
|
|
||||||
[<UnmanagedFunctionPointer(CallingConvention.Cdecl)>]
|
[<UnmanagedFunctionPointer(CallingConvention.Cdecl)>]
|
||||||
type internal OutHookInternal = delegate of IntPtr * Int32 * Int32 * Int32 * IntPtr -> unit
|
type internal OutHookInternal = delegate of IntPtr * Int32 * Int32 * Int32 * IntPtr -> unit
|
|
@ -4,6 +4,7 @@ open System
|
||||||
open System.Threading
|
open System.Threading
|
||||||
open System.Collections.Generic
|
open System.Collections.Generic
|
||||||
open System.Runtime.InteropServices
|
open System.Runtime.InteropServices
|
||||||
|
open System.Linq
|
||||||
open UnicornManaged.Const
|
open UnicornManaged.Const
|
||||||
open UnicornManaged.Binding
|
open UnicornManaged.Binding
|
||||||
|
|
||||||
|
@ -13,8 +14,8 @@ and BlockHook = delegate of Unicorn * Int64 * Int32 * Object -> unit
|
||||||
and InterruptHook = delegate of Unicorn * Int32 * Object -> unit
|
and InterruptHook = delegate of Unicorn * Int32 * Object -> unit
|
||||||
and MemReadHook = delegate of Unicorn * Int64 * Int32 * Object -> unit
|
and MemReadHook = delegate of Unicorn * Int64 * Int32 * Object -> unit
|
||||||
and MemWriteHook = delegate of Unicorn * Int64 * Int32 * Int64 * Object -> unit
|
and MemWriteHook = delegate of Unicorn * Int64 * Int32 * Int64 * Object -> unit
|
||||||
and EventMemHook = delegate of Unicorn * Int64 * Int32 * Int64 * Object -> unit
|
and EventMemHook = delegate of Unicorn * Int32 * Int64 * Int32 * Int64 * Object -> Boolean
|
||||||
and InHook = delegate of Unicorn * Int32 * Int32 * Object -> unit
|
and InHook = delegate of Unicorn * Int32 * Int32 * Object -> Int32
|
||||||
and OutHook = delegate of Unicorn * Int32 * Int32 * Int32 * Object -> unit
|
and OutHook = delegate of Unicorn * Int32 * Int32 * Int32 * Object -> unit
|
||||||
and SyscallHook = delegate of Unicorn * Object -> unit
|
and SyscallHook = delegate of Unicorn * Object -> unit
|
||||||
|
|
||||||
|
@ -22,30 +23,36 @@ and SyscallHook = delegate of Unicorn * Object -> unit
|
||||||
and Unicorn(arch: Int32, mode: Int32, binding: IBinding) =
|
and Unicorn(arch: Int32, mode: Int32, binding: IBinding) =
|
||||||
|
|
||||||
// hook callback list
|
// hook callback list
|
||||||
let _codeHooks = new Dictionary<IntPtr, (CodeHook * Object)>()
|
let _codeHooks = new List<(CodeHook * Object)>()
|
||||||
let _blockHooks = new Dictionary<IntPtr, (BlockHook * Object)>()
|
let _blockHooks = new List<(BlockHook * Object)>()
|
||||||
let _interruptHooks = new Dictionary<IntPtr, (InterruptHook * Object)>()
|
let _interruptHooks = new List<(InterruptHook * Object)>()
|
||||||
let _memReadHooks = new Dictionary<IntPtr, (MemReadHook * Object)>()
|
let _memReadHooks = new List<(MemReadHook * Object)>()
|
||||||
let _memWriteHooks = new Dictionary<IntPtr, (MemWriteHook * Object)>()
|
let _memWriteHooks = new List<(MemWriteHook * Object)>()
|
||||||
let _memEventHooks = new Dictionary<IntPtr, (EventMemHook * Object)>()
|
let _memEventHooks = new Dictionary<Int32, List<(EventMemHook * Object)>>()
|
||||||
let _inHooks = new Dictionary<IntPtr, (InHook * Object)>()
|
let _inHooks = new List<(InHook * Object)>()
|
||||||
let _outHooks = new Dictionary<IntPtr, (OutHook * Object)>()
|
let _outHooks = new List<(OutHook * Object)>()
|
||||||
let _syscallHooks = new Dictionary<IntPtr, (SyscallHook * Object)>()
|
let _syscallHooks = new List<(SyscallHook * Object)>()
|
||||||
let _disposablePointers = new List<nativeint>()
|
let _disposablePointers = new List<nativeint>()
|
||||||
|
|
||||||
|
let _eventMemMap =
|
||||||
|
[
|
||||||
|
(UC_HOOK_MEM_READ_UNMAPPED, UC_MEM_READ_UNMAPPED)
|
||||||
|
(UC_HOOK_MEM_WRITE_UNMAPPED, UC_MEM_WRITE_UNMAPPED)
|
||||||
|
(UC_HOOK_MEM_FETCH_UNMAPPED, UC_MEM_FETCH_UNMAPPED)
|
||||||
|
(UC_HOOK_MEM_READ_PROT, UC_MEM_READ_PROT)
|
||||||
|
(UC_HOOK_MEM_WRITE_PROT, UC_MEM_WRITE_PROT)
|
||||||
|
(UC_HOOK_MEM_FETCH_PROT, UC_MEM_FETCH_PROT)
|
||||||
|
] |> dict
|
||||||
|
|
||||||
let mutable _eng = [|UIntPtr.Zero|]
|
let mutable _eng = [|UIntPtr.Zero|]
|
||||||
|
|
||||||
let checkResult(errCode: Int32, errMsg: String) =
|
let checkResult(errCode: Int32, errMsg: String) =
|
||||||
if errCode <> Common.UC_ERR_OK then raise(ApplicationException(String.Format("{0}. Error: {1}", errMsg, errCode)))
|
if errCode <> Common.UC_ERR_OK then raise(ApplicationException(String.Format("{0}. Error: {1}", errMsg, errCode)))
|
||||||
|
|
||||||
let getId =
|
let hookDel(callbacks: List<'a * Object>) (callback: 'a)=
|
||||||
let counter = ref 0
|
|
||||||
fun () -> new IntPtr(Interlocked.Increment(counter))
|
|
||||||
|
|
||||||
let hookDel(callbacks: Dictionary<IntPtr, 'a * Object>) (callback: 'a)=
|
|
||||||
// TODO: invoke the native function in order to not call the trampoline anymore
|
// TODO: invoke the native function in order to not call the trampoline anymore
|
||||||
callbacks.Keys
|
callbacks
|
||||||
|> Seq.tryFind(fun k -> match callbacks.[k] with | (c, _) -> c = callback)
|
|> Seq.tryFind(fun item -> match item with | (c, _) -> c = callback)
|
||||||
|> (fun k -> if k.IsSome then callbacks.Remove(k.Value) |> ignore)
|
|> (fun k -> if k.IsSome then callbacks.Remove(k.Value) |> ignore)
|
||||||
|
|
||||||
let allocate(size: Int32) =
|
let allocate(size: Int32) =
|
||||||
|
@ -54,6 +61,12 @@ and Unicorn(arch: Int32, mode: Int32, binding: IBinding) =
|
||||||
mem.ToPointer()
|
mem.ToPointer()
|
||||||
|
|
||||||
do
|
do
|
||||||
|
// initialize event list
|
||||||
|
_eventMemMap
|
||||||
|
|> Seq.map(fun kv -> kv.Key)
|
||||||
|
|> Seq.iter (fun eventType -> _memEventHooks.Add(eventType, new List<EventMemHook * Object>()))
|
||||||
|
|
||||||
|
// init engine
|
||||||
_eng <- [|new UIntPtr(allocate(IntPtr.Size))|]
|
_eng <- [|new UIntPtr(allocate(IntPtr.Size))|]
|
||||||
let err = binding.UcOpen(uint32 arch, uint32 mode, _eng)
|
let err = binding.UcOpen(uint32 arch, uint32 mode, _eng)
|
||||||
checkResult(err, "Unable to open the Unicorn Engine")
|
checkResult(err, "Unable to open the Unicorn Engine")
|
||||||
|
@ -101,10 +114,18 @@ and Unicorn(arch: Int32, mode: Int32, binding: IBinding) =
|
||||||
match binding.RegWrite(_eng.[0], regId, value) |> this.CheckResult with
|
match binding.RegWrite(_eng.[0], regId, value) |> this.CheckResult with
|
||||||
| Some e -> raise e | None -> ()
|
| Some e -> raise e | None -> ()
|
||||||
|
|
||||||
|
member this.RegWrite(regId: Int32, value: Int64) =
|
||||||
|
this.RegWrite(regId, int64ToBytes value)
|
||||||
|
|
||||||
member this.RegRead(regId: Int32, regValue: Byte array) =
|
member this.RegRead(regId: Int32, regValue: Byte array) =
|
||||||
match binding.RegRead(_eng.[0], regId, regValue) |> this.CheckResult with
|
match binding.RegRead(_eng.[0], regId, regValue) |> this.CheckResult with
|
||||||
| Some e -> raise e | None -> ()
|
| Some e -> raise e | None -> ()
|
||||||
|
|
||||||
|
member this.RegRead(regId: Int32) =
|
||||||
|
let buffer = Array.zeroCreate<Byte> 8
|
||||||
|
this.RegRead(regId, buffer)
|
||||||
|
bytesToInt64 buffer
|
||||||
|
|
||||||
member this.EmuStart(beginAddr: Int64, untilAddr: Int64, timeout: Int64, count: Int64) =
|
member this.EmuStart(beginAddr: Int64, untilAddr: Int64, timeout: Int64, count: Int64) =
|
||||||
match binding.EmuStart(_eng.[0], uint64 beginAddr, uint64 untilAddr, uint64 timeout, uint64 count) |> this.CheckResult with
|
match binding.EmuStart(_eng.[0], uint64 beginAddr, uint64 untilAddr, uint64 timeout, uint64 count) |> this.CheckResult with
|
||||||
| Some e -> raise e | None -> ()
|
| Some e -> raise e | None -> ()
|
||||||
|
@ -129,18 +150,18 @@ and Unicorn(arch: Int32, mode: Int32, binding: IBinding) =
|
||||||
|
|
||||||
member this.AddCodeHook(callback: CodeHook, userData: Object, beginAddr: Int64, endAddr: Int64) =
|
member this.AddCodeHook(callback: CodeHook, userData: Object, beginAddr: Int64, endAddr: Int64) =
|
||||||
let trampoline(u: IntPtr) (addr: Int64) (size: Int32) (user: IntPtr) =
|
let trampoline(u: IntPtr) (addr: Int64) (size: Int32) (user: IntPtr) =
|
||||||
let (exist, (callback, userData)) = _codeHooks.TryGetValue(user)
|
_codeHooks
|
||||||
if exist then callback.Invoke(this, addr, size, userData)
|
|> Seq.iter(fun (callback, userData) -> callback.Invoke(this, addr, size, userData))
|
||||||
|
|
||||||
let id = getId()
|
if _codeHooks |> Seq.isEmpty then
|
||||||
_codeHooks.Add(id, (callback, userData))
|
let funcPointer = Marshal.GetFunctionPointerForDelegate(new CodeHookInternal(trampoline))
|
||||||
|
let hh = new UIntPtr(allocate(IntPtr.Size))
|
||||||
|
match binding.HookAddArg0Arg1(_eng.[0], hh, Common.UC_HOOK_CODE, new UIntPtr(funcPointer.ToPointer()), IntPtr.Zero, uint64 beginAddr, uint64 endAddr) |> this.CheckResult with
|
||||||
|
| Some e -> raise e | None -> ()
|
||||||
|
|
||||||
let funcPointer = Marshal.GetFunctionPointerForDelegate(new CodeHookInternal(trampoline))
|
_codeHooks.Add(callback, userData)
|
||||||
let hh = new UIntPtr(allocate(IntPtr.Size))
|
|
||||||
match binding.HookAddArg0Arg1(_eng.[0], hh, Common.UC_HOOK_CODE, new UIntPtr(funcPointer.ToPointer()), id, uint64 beginAddr, uint64 endAddr) |> this.CheckResult with
|
|
||||||
| Some e -> raise e | None -> ()
|
|
||||||
|
|
||||||
member this.AddCodeHook(callback: CodeHook, beginAddr: Int64, endAddr: Int64) =
|
member this.AddCodeHook(callback: CodeHook, beginAddr: Int64, endAddr: Int64) =
|
||||||
this.AddCodeHook(callback, null, beginAddr, endAddr)
|
this.AddCodeHook(callback, null, beginAddr, endAddr)
|
||||||
|
|
||||||
member this.HookDel(callback: CodeHook) =
|
member this.HookDel(callback: CodeHook) =
|
||||||
|
@ -148,32 +169,32 @@ and Unicorn(arch: Int32, mode: Int32, binding: IBinding) =
|
||||||
|
|
||||||
member this.AddBlockHook(callback: BlockHook, userData: Object, beginAddr: Int64, endAddr: Int64) =
|
member this.AddBlockHook(callback: BlockHook, userData: Object, beginAddr: Int64, endAddr: Int64) =
|
||||||
let trampoline(u: IntPtr) (addr: Int64) (size: Int32) (user: IntPtr) =
|
let trampoline(u: IntPtr) (addr: Int64) (size: Int32) (user: IntPtr) =
|
||||||
let (exist, (callback, userData)) = _blockHooks.TryGetValue(user)
|
_blockHooks
|
||||||
if exist then callback.Invoke(this, addr, size, userData)
|
|> Seq.iter(fun (callback, userData) -> callback.Invoke(this, addr, size, userData))
|
||||||
|
|
||||||
let id = getId()
|
if _blockHooks |> Seq.isEmpty then
|
||||||
_blockHooks.Add(id, (callback, userData))
|
let funcPointer = Marshal.GetFunctionPointerForDelegate(new BlockHookInternal(trampoline))
|
||||||
|
let hh = new UIntPtr(allocate(IntPtr.Size))
|
||||||
|
match binding.HookAddArg0Arg1(_eng.[0], hh, Common.UC_HOOK_BLOCK, new UIntPtr(funcPointer.ToPointer()), IntPtr.Zero, uint64 beginAddr, uint64 endAddr) |> this.CheckResult with
|
||||||
|
| Some e -> raise e | None -> ()
|
||||||
|
|
||||||
let funcPointer = Marshal.GetFunctionPointerForDelegate(new BlockHookInternal(trampoline))
|
_blockHooks.Add(callback, userData)
|
||||||
let hh = new UIntPtr(allocate(IntPtr.Size))
|
|
||||||
match binding.HookAddArg0Arg1(_eng.[0], hh, Common.UC_HOOK_BLOCK, new UIntPtr(funcPointer.ToPointer()), id, uint64 beginAddr, uint64 endAddr) |> this.CheckResult with
|
|
||||||
| Some e -> raise e | None -> ()
|
|
||||||
|
|
||||||
member this.HookDel(callback: BlockHook) =
|
member this.HookDel(callback: BlockHook) =
|
||||||
hookDel _blockHooks callback
|
hookDel _blockHooks callback
|
||||||
|
|
||||||
member this.AddInterruptHook(callback: InterruptHook, userData: Object) =
|
member this.AddInterruptHook(callback: InterruptHook, userData: Object) =
|
||||||
let trampoline(u: IntPtr) (intNumber: Int32) (user: IntPtr) =
|
let trampoline(u: IntPtr) (intNumber: Int32) (user: IntPtr) =
|
||||||
let (exist, (callback, userData)) = _interruptHooks.TryGetValue(user)
|
_interruptHooks
|
||||||
if exist then callback.Invoke(this, intNumber, userData)
|
|> Seq.iter(fun (callback, userData) -> callback.Invoke(this, intNumber, userData))
|
||||||
|
|
||||||
|
if _interruptHooks |> Seq.isEmpty then
|
||||||
|
let funcPointer = Marshal.GetFunctionPointerForDelegate(new InterruptHookInternal(trampoline))
|
||||||
|
let hh = new UIntPtr(allocate(IntPtr.Size))
|
||||||
|
match binding.HookAddNoarg(_eng.[0], hh, Common.UC_HOOK_INTR, new UIntPtr(funcPointer.ToPointer()), IntPtr.Zero) |> this.CheckResult with
|
||||||
|
| Some e -> raise e | None -> ()
|
||||||
|
|
||||||
let id = getId()
|
_interruptHooks.Add(callback, userData)
|
||||||
_interruptHooks.Add(id, (callback, userData))
|
|
||||||
|
|
||||||
let funcPointer = Marshal.GetFunctionPointerForDelegate(new InterruptHookInternal(trampoline))
|
|
||||||
let hh = new UIntPtr(allocate(IntPtr.Size))
|
|
||||||
match binding.HookAddNoarg(_eng.[0], hh, Common.UC_HOOK_INTR, new UIntPtr(funcPointer.ToPointer()), id) |> this.CheckResult with
|
|
||||||
| Some e -> raise e | None -> ()
|
|
||||||
|
|
||||||
member this.AddInterruptHook(callback: InterruptHook) =
|
member this.AddInterruptHook(callback: InterruptHook) =
|
||||||
this.AddInterruptHook(callback, null)
|
this.AddInterruptHook(callback, null)
|
||||||
|
@ -183,104 +204,113 @@ and Unicorn(arch: Int32, mode: Int32, binding: IBinding) =
|
||||||
|
|
||||||
member this.AddMemReadHook(callback: MemReadHook, userData: Object, beginAddr: Int64, endAddr: Int64) =
|
member this.AddMemReadHook(callback: MemReadHook, userData: Object, beginAddr: Int64, endAddr: Int64) =
|
||||||
let trampoline(u: IntPtr) (addr: Int64) (size: Int32) (user: IntPtr) =
|
let trampoline(u: IntPtr) (addr: Int64) (size: Int32) (user: IntPtr) =
|
||||||
let (exist, (callback, userData)) = _memReadHooks.TryGetValue(user)
|
_memReadHooks
|
||||||
if exist then callback.Invoke(this, addr, size, userData)
|
|> Seq.iter(fun (callback, userData) -> callback.Invoke(this, addr, size, userData))
|
||||||
|
|
||||||
let id = getId()
|
if _memReadHooks |> Seq.isEmpty then
|
||||||
_memReadHooks.Add(id, (callback, userData))
|
let funcPointer = Marshal.GetFunctionPointerForDelegate(new MemReadHookInternal(trampoline))
|
||||||
|
let hh = new UIntPtr(allocate(IntPtr.Size))
|
||||||
|
match binding.HookAddArg0Arg1(_eng.[0], hh, Common.UC_HOOK_MEM_READ, new UIntPtr(funcPointer.ToPointer()), IntPtr.Zero, uint64 beginAddr, uint64 endAddr) |> this.CheckResult with
|
||||||
|
| Some e -> raise e | None -> ()
|
||||||
|
|
||||||
let funcPointer = Marshal.GetFunctionPointerForDelegate(new MemReadHookInternal(trampoline))
|
_memReadHooks.Add(callback, userData)
|
||||||
let hh = new UIntPtr(allocate(IntPtr.Size))
|
|
||||||
match binding.HookAddArg0Arg1(_eng.[0], hh, Common.UC_HOOK_MEM_READ, new UIntPtr(funcPointer.ToPointer()), id, uint64 beginAddr, uint64 endAddr) |> this.CheckResult with
|
|
||||||
| Some e -> raise e | None -> ()
|
|
||||||
|
|
||||||
member this.HookDel(callback: MemReadHook) =
|
member this.HookDel(callback: MemReadHook) =
|
||||||
hookDel _memReadHooks callback
|
hookDel _memReadHooks callback
|
||||||
|
|
||||||
member this.AddMemWriteHook(callback: MemWriteHook, userData: Object, beginAddr: Int64, endAddr: Int64) =
|
member this.AddMemWriteHook(callback: MemWriteHook, userData: Object, beginAddr: Int64, endAddr: Int64) =
|
||||||
let trampoline(u: IntPtr) (addr: Int64) (size: Int32) (value: Int64) (user: IntPtr) =
|
let trampoline(u: IntPtr) (addr: Int64) (size: Int32) (value: Int64) (user: IntPtr) =
|
||||||
let (exist, (callback, userData)) = _memWriteHooks.TryGetValue(user)
|
_memWriteHooks
|
||||||
if exist then callback.Invoke(this, addr, size, value, userData)
|
|> Seq.iter(fun (callback, userData) -> callback.Invoke(this, addr, size, value, userData))
|
||||||
|
|
||||||
|
if _memWriteHooks |> Seq.isEmpty then
|
||||||
|
let funcPointer = Marshal.GetFunctionPointerForDelegate(new MemWriteHookInternal(trampoline))
|
||||||
|
let hh = new UIntPtr(allocate(IntPtr.Size))
|
||||||
|
match binding.HookAddArg0Arg1(_eng.[0], hh, Common.UC_HOOK_MEM_WRITE, new UIntPtr(funcPointer.ToPointer()), IntPtr.Zero, uint64 beginAddr, uint64 endAddr) |> this.CheckResult with
|
||||||
|
| Some e -> raise e | None -> ()
|
||||||
|
|
||||||
let id = getId()
|
_memWriteHooks.Add(callback, userData)
|
||||||
_memWriteHooks.Add(id, (callback, userData))
|
|
||||||
|
|
||||||
let funcPointer = Marshal.GetFunctionPointerForDelegate(new MemWriteHookInternal(trampoline))
|
|
||||||
let hh = new UIntPtr(allocate(IntPtr.Size))
|
|
||||||
match binding.HookAddArg0Arg1(_eng.[0], hh, Common.UC_HOOK_MEM_WRITE, new UIntPtr(funcPointer.ToPointer()), id, uint64 beginAddr, uint64 endAddr) |> this.CheckResult with
|
|
||||||
| Some e -> raise e | None -> ()
|
|
||||||
|
|
||||||
member this.HookDel(callback: MemWriteHook) =
|
member this.HookDel(callback: MemWriteHook) =
|
||||||
hookDel _memWriteHooks callback
|
hookDel _memWriteHooks callback
|
||||||
|
|
||||||
member this.AddEventMemHook(callback: EventMemHook, eventType: Int32, userData: Object) =
|
member this.AddEventMemHook(callback: EventMemHook, eventType: Int32, userData: Object) =
|
||||||
let trampoline(u: IntPtr) (addr: Int64) (size: Int32) (value: Int64) (user: IntPtr) =
|
let trampoline(u: IntPtr) (eventType: Int32) (addr: Int64) (size: Int32) (value: Int64) (user: IntPtr) =
|
||||||
let (exist, (callback, userData)) = _memEventHooks.TryGetValue(user)
|
_memEventHooks.Keys
|
||||||
if exist then callback.Invoke(this, addr, size, value, userData)
|
|> Seq.filter(fun eventFlag -> (eventType &&& eventFlag) <> 0)
|
||||||
|
|> Seq.map(fun eventflag -> _memEventHooks.[eventflag])
|
||||||
let registEventMemHook(check: Int32) =
|
|> Seq.concat
|
||||||
let id = getId()
|
|> Seq.map(fun (callback, userData) -> callback.Invoke(this, eventType, addr, size, value, userData))
|
||||||
_memEventHooks.Add(id, (callback, userData))
|
|> Seq.forall id
|
||||||
|
|
||||||
|
// register the event if not already done
|
||||||
|
_memEventHooks.Keys
|
||||||
|
|> Seq.filter(fun eventFlag -> (eventType &&& eventFlag) <> 0)
|
||||||
|
|> Seq.filter(fun eventFlag -> _memEventHooks.[eventFlag] |> Seq.isEmpty)
|
||||||
|
|> Seq.iter(fun eventFlag ->
|
||||||
let funcPointer = Marshal.GetFunctionPointerForDelegate(new EventMemHookInternal(trampoline))
|
let funcPointer = Marshal.GetFunctionPointerForDelegate(new EventMemHookInternal(trampoline))
|
||||||
let hh = new UIntPtr(allocate(IntPtr.Size))
|
let hh = new UIntPtr(allocate(IntPtr.Size))
|
||||||
match binding.HookAddNoarg(_eng.[0], hh, check, new UIntPtr(funcPointer.ToPointer()), id) |> this.CheckResult with
|
match binding.HookAddNoarg(_eng.[0], hh, eventFlag, new UIntPtr(funcPointer.ToPointer()), IntPtr.Zero) |> this.CheckResult with
|
||||||
| Some e -> raise e | None -> ()
|
| Some e -> raise e | None -> ()
|
||||||
|
)
|
||||||
|
|
||||||
// test all the events types agains the input eventType
|
// register the callbacks
|
||||||
[
|
_memEventHooks.Keys
|
||||||
Common.UC_HOOK_MEM_READ_UNMAPPED
|
|> Seq.filter(fun eventFlag -> (eventType &&& eventFlag) <> 0)
|
||||||
Common.UC_HOOK_MEM_WRITE_UNMAPPED
|
|> Seq.iter(fun eventFlag -> _memEventHooks.[eventFlag].Add((callback, userData)))
|
||||||
Common.UC_HOOK_MEM_FETCH_UNMAPPED
|
|
||||||
Common.UC_HOOK_MEM_READ_PROT
|
member this.AddEventMemHook(callback: EventMemHook, eventType: Int32) =
|
||||||
Common.UC_HOOK_MEM_WRITE_PROT
|
this.AddEventMemHook(callback, eventType, null)
|
||||||
Common.UC_HOOK_MEM_FETCH_PROT
|
|
||||||
]
|
|
||||||
|> List.filter(fun eventFlag -> eventType &&& eventFlag <> 0)
|
|
||||||
|> List.map registEventMemHook
|
|
||||||
|> List.rev |> List.head
|
|
||||||
|
|
||||||
member this.HookDel(callback: EventMemHook) =
|
member this.HookDel(callback: EventMemHook) =
|
||||||
hookDel _memEventHooks callback
|
let callbacks = (_memEventHooks.Values |> Seq.concat).ToList()
|
||||||
|
hookDel callbacks callback
|
||||||
|
|
||||||
member this.AddInHook(callback: InHook, userData: Object) =
|
member this.AddInHook(callback: InHook, userData: Object) =
|
||||||
let trampoline(u: IntPtr) (port: Int32) (size: Int32) (user: IntPtr) =
|
let trampoline(u: IntPtr) (port: Int32) (size: Int32) (user: IntPtr) =
|
||||||
let (exist, (callback, userData)) = _inHooks.TryGetValue(user)
|
_inHooks
|
||||||
if exist then callback.Invoke(this, port, size, userData)
|
|> Seq.map(fun (callback, userData) -> callback.Invoke(this, port, size, userData))
|
||||||
|
|> Seq.last
|
||||||
|
|
||||||
|
if _inHooks |> Seq.isEmpty then
|
||||||
|
let funcPointer = Marshal.GetFunctionPointerForDelegate(new InHookInternal(trampoline))
|
||||||
|
let hh = new UIntPtr(allocate(IntPtr.Size))
|
||||||
|
match binding.HookAddArg0(_eng.[0], hh, Common.UC_HOOK_INSN, new UIntPtr(funcPointer.ToPointer()), IntPtr.Zero, X86.UC_X86_INS_IN) |> this.CheckResult with
|
||||||
|
| Some e -> raise e | None -> ()
|
||||||
|
|
||||||
let id = getId()
|
_inHooks.Add(callback, userData)
|
||||||
_inHooks.Add(id, (callback, userData))
|
|
||||||
|
|
||||||
let funcPointer = Marshal.GetFunctionPointerForDelegate(new InHookInternal(trampoline))
|
member this.AddInHook(callback: InHook) =
|
||||||
let hh = new UIntPtr(allocate(IntPtr.Size))
|
this.AddInHook(callback, null)
|
||||||
match binding.HookAddArg0(_eng.[0], hh, Common.UC_HOOK_INSN, new UIntPtr(funcPointer.ToPointer()), id, new IntPtr(X86.UC_X86_INS_IN)) |> this.CheckResult with
|
|
||||||
| Some e -> raise e | None -> ()
|
|
||||||
|
|
||||||
member this.AddOutHook(callback: OutHook, userData: Object) =
|
member this.AddOutHook(callback: OutHook, userData: Object) =
|
||||||
let trampoline(u: IntPtr) (port: Int32) (size: Int32) (value: Int32) (user: IntPtr) =
|
let trampoline(u: IntPtr) (port: Int32) (size: Int32) (value: Int32) (user: IntPtr) =
|
||||||
let (exist, (callback, userData)) = _outHooks.TryGetValue(user)
|
_outHooks
|
||||||
if exist then callback.Invoke(this, port, size, value, userData)
|
|> Seq.iter(fun (callback, userData) -> callback.Invoke(this, port, size, value, userData))
|
||||||
|
|
||||||
|
if _outHooks |> Seq.isEmpty then
|
||||||
|
let funcPointer = Marshal.GetFunctionPointerForDelegate(new OutHookInternal(trampoline))
|
||||||
|
let hh = new UIntPtr(allocate(IntPtr.Size))
|
||||||
|
match binding.HookAddArg0(_eng.[0], hh, Common.UC_HOOK_INSN, new UIntPtr(funcPointer.ToPointer()), IntPtr.Zero, X86.UC_X86_INS_OUT) |> this.CheckResult with
|
||||||
|
| Some e -> raise e | None -> ()
|
||||||
|
|
||||||
let id = getId()
|
_outHooks.Add(callback, userData)
|
||||||
_outHooks.Add(id, (callback, userData))
|
|
||||||
|
|
||||||
let funcPointer = Marshal.GetFunctionPointerForDelegate(new OutHookInternal(trampoline))
|
member this.AddOutHook(callback: OutHook) =
|
||||||
let hh = new UIntPtr(allocate(IntPtr.Size))
|
this.AddOutHook(callback, null)
|
||||||
match binding.HookAddArg0(_eng.[0], hh, Common.UC_HOOK_INSN, new UIntPtr(funcPointer.ToPointer()), id, new IntPtr(X86.UC_X86_INS_OUT)) |> this.CheckResult with
|
|
||||||
| Some e -> raise e | None -> ()
|
|
||||||
|
|
||||||
member this.AddSyscallHook(callback: SyscallHook, userData: Object) =
|
member this.AddSyscallHook(callback: SyscallHook, userData: Object) =
|
||||||
let trampoline(u: IntPtr) (user: IntPtr) =
|
let trampoline(u: IntPtr) (user: IntPtr) =
|
||||||
let (exist, (callback, userData)) = _syscallHooks.TryGetValue(user)
|
_syscallHooks
|
||||||
if exist then callback.Invoke(this, userData)
|
|> Seq.iter(fun (callback, userData) -> callback.Invoke(this, userData))
|
||||||
|
|
||||||
|
if _syscallHooks |> Seq.isEmpty then
|
||||||
|
let funcPointer = Marshal.GetFunctionPointerForDelegate(new SyscallHookInternal(trampoline))
|
||||||
|
let hh = new UIntPtr(allocate(IntPtr.Size))
|
||||||
|
match binding.HookAddArg0(_eng.[0], hh, Common.UC_HOOK_INSN, new UIntPtr(funcPointer.ToPointer()), IntPtr.Zero, X86.UC_X86_INS_SYSCALL) |> this.CheckResult with
|
||||||
|
| Some e -> raise e | None -> ()
|
||||||
|
|
||||||
let id = getId()
|
_syscallHooks.Add(callback, userData)
|
||||||
_syscallHooks.Add(id, (callback, userData))
|
|
||||||
|
|
||||||
let funcPointer = Marshal.GetFunctionPointerForDelegate(new SyscallHookInternal(trampoline))
|
|
||||||
let hh = new UIntPtr(allocate(IntPtr.Size))
|
|
||||||
match binding.HookAddArg0(_eng.[0], hh, Common.UC_HOOK_INSN, new UIntPtr(funcPointer.ToPointer()), id, new IntPtr(X86.UC_X86_INS_SYSCALL)) |> this.CheckResult with
|
|
||||||
| Some e -> raise e | None -> ()
|
|
||||||
|
|
||||||
member this.AddSyscallHook(callback: SyscallHook) =
|
member this.AddSyscallHook(callback: SyscallHook) =
|
||||||
this.AddSyscallHook(callback, null)
|
this.AddSyscallHook(callback, null)
|
||||||
|
|
|
@ -57,7 +57,8 @@
|
||||||
<Compile Include="Binding\NativeBinding.fs" />
|
<Compile Include="Binding\NativeBinding.fs" />
|
||||||
<Compile Include="Binding\BindingFactory.fs" />
|
<Compile Include="Binding\BindingFactory.fs" />
|
||||||
<Compile Include="UnicornEngineException.fs" />
|
<Compile Include="UnicornEngineException.fs" />
|
||||||
<Compile Include="Hooks.fs" />
|
<Compile Include="InternalHooks.fs" />
|
||||||
|
<Compile Include="ConvertUtility.fs" />
|
||||||
<Compile Include="Unicorn.fs" />
|
<Compile Include="Unicorn.fs" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
|
|
|
@ -6,11 +6,11 @@ namespace UnicornSamples
|
||||||
{
|
{
|
||||||
static void Main(string[] args)
|
static void Main(string[] args)
|
||||||
{
|
{
|
||||||
// X86 tests
|
// X86 tests 32bit
|
||||||
X86Sample.X86Code32();
|
X86Sample32.X86Code32();
|
||||||
//X86Sample.X86Code32Loop();
|
X86Sample32.X86Code32InvalidMemRead();
|
||||||
X86Sample.X86Code32InvalidMemRead();
|
X86Sample32.X86Code32InvalidMemWriteWithRuntimeFix();
|
||||||
X86Sample.X86Code32InvalidMemWrite();
|
X86Sample32.X86Code32InOut();
|
||||||
|
|
||||||
// Run all shellcode tests
|
// Run all shellcode tests
|
||||||
ShellcodeSample.X86Code32Self();
|
ShellcodeSample.X86Code32Self();
|
||||||
|
|
|
@ -69,11 +69,7 @@ namespace UnicornSamples
|
||||||
|
|
||||||
// write machine code to be emulated to memory
|
// write machine code to be emulated to memory
|
||||||
u.MemWrite(address, code);
|
u.MemWrite(address, code);
|
||||||
|
|
||||||
//var read = new Byte[code.Length];
|
|
||||||
//u.MemRead(address, read);
|
|
||||||
//Console.WriteLine(Disassemble(disassembler, code));//
|
|
||||||
|
|
||||||
// initialize machine registers
|
// initialize machine registers
|
||||||
u.RegWrite(X86.UC_X86_REG_ESP, Utils.Int64ToBytes(address + 0x200000));
|
u.RegWrite(X86.UC_X86_REG_ESP, Utils.Int64ToBytes(address + 0x200000));
|
||||||
|
|
||||||
|
@ -88,7 +84,7 @@ namespace UnicornSamples
|
||||||
|
|
||||||
// handle SYSCALL
|
// handle SYSCALL
|
||||||
u.AddSyscallHook(SyscallHookCallback);
|
u.AddSyscallHook(SyscallHookCallback);
|
||||||
|
|
||||||
Console.WriteLine(">>> Start tracing code");
|
Console.WriteLine(">>> Start tracing code");
|
||||||
|
|
||||||
// emulate machine code in infinite time
|
// emulate machine code in infinite time
|
||||||
|
@ -134,7 +130,7 @@ namespace UnicornSamples
|
||||||
u.RegRead(X86.UC_X86_REG_EAX, eaxBuffer);
|
u.RegRead(X86.UC_X86_REG_EAX, eaxBuffer);
|
||||||
var eax = Utils.ToInt(eaxBuffer);
|
var eax = Utils.ToInt(eaxBuffer);
|
||||||
|
|
||||||
Console.WriteLine("Syscall >>> EAX = 0x{0}", eax.ToString("X"));
|
Console.WriteLine("[!] Syscall EAX = 0x{0}", eax.ToString("X"));
|
||||||
|
|
||||||
u.EmuStop();
|
u.EmuStop();
|
||||||
}
|
}
|
||||||
|
@ -159,10 +155,10 @@ namespace UnicornSamples
|
||||||
switch (eax)
|
switch (eax)
|
||||||
{
|
{
|
||||||
default:
|
default:
|
||||||
Console.WriteLine("Interrupt >>> 0x{0} num {1}, EAX=0x{2}", eip.ToString("X"), intNumber.ToString("X"), eax.ToString("X"));
|
Console.WriteLine("[!] Interrupt 0x{0} num {1}, EAX=0x{2}", eip.ToString("X"), intNumber.ToString("X"), eax.ToString("X"));
|
||||||
break;
|
break;
|
||||||
case 1: // sys_exit
|
case 1: // sys_exit
|
||||||
Console.WriteLine("Interrupt >>> 0x{0} num {1}, SYS_EXIT", eip.ToString("X"), intNumber.ToString("X"));
|
Console.WriteLine("[!] Interrupt 0x{0} num {1}, SYS_EXIT", eip.ToString("X"), intNumber.ToString("X"));
|
||||||
u.EmuStop();
|
u.EmuStop();
|
||||||
break;
|
break;
|
||||||
case 4: // sys_write
|
case 4: // sys_write
|
||||||
|
@ -186,7 +182,7 @@ namespace UnicornSamples
|
||||||
var content = Encoding.Default.GetString(buffer);
|
var content = Encoding.Default.GetString(buffer);
|
||||||
|
|
||||||
Console.WriteLine(
|
Console.WriteLine(
|
||||||
"Interrupt >>> 0x{0}: num {1}, SYS_WRITE. buffer = 0x{2}, size = , content = '{3}'",
|
"[!] Interrupt 0x{0}: num {1}, SYS_WRITE. buffer = 0x{2}, size = , content = '{3}'",
|
||||||
eip.ToString("X"),
|
eip.ToString("X"),
|
||||||
ecx.ToString("X"),
|
ecx.ToString("X"),
|
||||||
edx.ToString("X"),
|
edx.ToString("X"),
|
||||||
|
|
|
@ -69,7 +69,7 @@
|
||||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||||
<Compile Include="ShellcodeSample.cs" />
|
<Compile Include="ShellcodeSample.cs" />
|
||||||
<Compile Include="Utils.cs" />
|
<Compile Include="Utils.cs" />
|
||||||
<Compile Include="X86Sample.cs" />
|
<Compile Include="X86Sample32.cs" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<None Include="App.config" />
|
<None Include="App.config" />
|
||||||
|
|
|
@ -12,7 +12,7 @@ using UnicornManaged.Const;
|
||||||
|
|
||||||
namespace UnicornSamples
|
namespace UnicornSamples
|
||||||
{
|
{
|
||||||
internal class X86Sample
|
internal class X86Sample32
|
||||||
{
|
{
|
||||||
private const Int64 ADDRESS = 0x1000000;
|
private const Int64 ADDRESS = 0x1000000;
|
||||||
|
|
||||||
|
@ -25,17 +25,7 @@ namespace UnicornSamples
|
||||||
};
|
};
|
||||||
Run(X86_CODE32);
|
Run(X86_CODE32);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void X86Code32Loop()
|
|
||||||
{
|
|
||||||
Byte[] X86_CODE32_LOOP =
|
|
||||||
{
|
|
||||||
// INC ecx; DEC edx; JMP self-loop
|
|
||||||
0x41, 0x4a, 0xeb, 0xfe
|
|
||||||
};
|
|
||||||
Run(X86_CODE32_LOOP);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void X86Code32InvalidMemRead()
|
public static void X86Code32InvalidMemRead()
|
||||||
{
|
{
|
||||||
Byte[] X86_CODE32_MEM_READ =
|
Byte[] X86_CODE32_MEM_READ =
|
||||||
|
@ -46,16 +36,27 @@ namespace UnicornSamples
|
||||||
Run(X86_CODE32_MEM_READ, true);
|
Run(X86_CODE32_MEM_READ, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void X86Code32InvalidMemWrite()
|
public static void X86Code32InvalidMemWriteWithRuntimeFix()
|
||||||
{
|
{
|
||||||
Byte[] X86_CODE32_MEM_WRITE =
|
Byte[] X86_CODE32_MEM_WRITE =
|
||||||
{
|
{
|
||||||
// mov [0xaaaaaaaa], ecx; INC ecx; DEC edx
|
// mov [0xaaaaaaaa], ecx; INC ecx; DEC edx
|
||||||
0x89, 0x0D, 0xAA, 0xAA, 0xAA, 0xAA, 0x41, 0x4a
|
0x89, 0x0D, 0xAA, 0xAA, 0xAA, 0xAA, 0x41, 0x4a
|
||||||
};
|
};
|
||||||
Run(X86_CODE32_MEM_WRITE, true);
|
Run(X86_CODE32_MEM_WRITE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void X86Code32InOut()
|
||||||
|
{
|
||||||
|
Byte[] X86_CODE32_INOUT =
|
||||||
|
{
|
||||||
|
// INC ecx; IN AL, 0x3f; DEC edx; OUT 0x46, AL; INC ebx
|
||||||
|
0x41, 0xE4, 0x3F, 0x4a, 0xE6, 0x46, 0x43
|
||||||
|
};
|
||||||
|
Run(X86_CODE32_INOUT);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
private static void Run(Byte[] code, Boolean raiseException = false)
|
private static void Run(Byte[] code, Boolean raiseException = false)
|
||||||
{
|
{
|
||||||
Console.WriteLine();
|
Console.WriteLine();
|
||||||
|
@ -67,7 +68,7 @@ namespace UnicornSamples
|
||||||
Exception e = null;
|
Exception e = null;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
RunTest(code, ADDRESS);
|
RunTest(code, ADDRESS, Common.UC_MODE_32);
|
||||||
}
|
}
|
||||||
catch (UnicornEngineException ex)
|
catch (UnicornEngineException ex)
|
||||||
{
|
{
|
||||||
|
@ -83,9 +84,9 @@ namespace UnicornSamples
|
||||||
Console.WriteLine();
|
Console.WriteLine();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void RunTest(Byte[] code, Int64 address)
|
private static void RunTest(Byte[] code, Int64 address, Int32 mode)
|
||||||
{
|
{
|
||||||
using (var u = new Unicorn(Common.UC_ARCH_X86, Common.UC_MODE_32))
|
using (var u = new Unicorn(Common.UC_ARCH_X86, mode))
|
||||||
using (var disassembler = CapstoneDisassembler.CreateX86Disassembler(DisassembleMode.Bit32))
|
using (var disassembler = CapstoneDisassembler.CreateX86Disassembler(DisassembleMode.Bit32))
|
||||||
{
|
{
|
||||||
Console.WriteLine("Unicorn version: {0}", u.Version());
|
Console.WriteLine("Unicorn version: {0}", u.Version());
|
||||||
|
@ -93,12 +94,21 @@ namespace UnicornSamples
|
||||||
// map 2MB of memory for this emulation
|
// map 2MB of memory for this emulation
|
||||||
u.MemMap(address, 2 * 1024 * 1024, Common.UC_PROT_ALL);
|
u.MemMap(address, 2 * 1024 * 1024, Common.UC_PROT_ALL);
|
||||||
|
|
||||||
|
// initialize machine registers
|
||||||
|
u.RegWrite(X86.UC_X86_REG_EAX, 0x1234);
|
||||||
|
u.RegWrite(X86.UC_X86_REG_ECX, 0x1234);
|
||||||
|
u.RegWrite(X86.UC_X86_REG_EDX, 0x7890);
|
||||||
|
|
||||||
// write machine code to be emulated to memory
|
// write machine code to be emulated to memory
|
||||||
u.MemWrite(address, code);
|
u.MemWrite(address, code);
|
||||||
|
|
||||||
// initialize machine registers
|
// initialize machine registers
|
||||||
u.RegWrite(X86.UC_X86_REG_ESP, Utils.Int64ToBytes(address + 0x200000));
|
u.RegWrite(X86.UC_X86_REG_ESP, Utils.Int64ToBytes(address + 0x200000));
|
||||||
|
|
||||||
|
// handle IN & OUT instruction
|
||||||
|
u.AddInHook(InHookCallback);
|
||||||
|
u.AddOutHook(OutHookCallback);
|
||||||
|
|
||||||
// tracing all instructions by having @begin > @end
|
// tracing all instructions by having @begin > @end
|
||||||
u.AddCodeHook((uc, addr, size, userData) => CodeHookCallback(disassembler, uc, addr, size, userData), 1, 0);
|
u.AddCodeHook((uc, addr, size, userData) => CodeHookCallback(disassembler, uc, addr, size, userData), 1, 0);
|
||||||
|
|
||||||
|
@ -108,14 +118,119 @@ namespace UnicornSamples
|
||||||
// handle SYSCALL
|
// handle SYSCALL
|
||||||
u.AddSyscallHook(SyscallHookCallback);
|
u.AddSyscallHook(SyscallHookCallback);
|
||||||
|
|
||||||
|
// intercept invalid memory events
|
||||||
|
u.AddEventMemHook(MemMapHookCallback, Common.UC_HOOK_MEM_READ_UNMAPPED | Common.UC_HOOK_MEM_WRITE_UNMAPPED);
|
||||||
|
|
||||||
Console.WriteLine(">>> Start tracing code");
|
Console.WriteLine(">>> Start tracing code");
|
||||||
|
|
||||||
// emulate machine code in infinite time
|
// emulate machine code in infinite time
|
||||||
u.EmuStart(address, address + code.Length, 0u, 0u);
|
u.EmuStart(address, address + code.Length, 0u, 0u);
|
||||||
|
|
||||||
|
// print registers
|
||||||
|
var ecx = u.RegRead(X86.UC_X86_REG_ECX);
|
||||||
|
var edx = u.RegRead(X86.UC_X86_REG_EDX);
|
||||||
|
var eax = u.RegRead(X86.UC_X86_REG_EAX);
|
||||||
|
Console.WriteLine("[!] EAX = {0}", eax.ToString("X"));
|
||||||
|
Console.WriteLine("[!] ECX = {0}", ecx.ToString("X"));
|
||||||
|
Console.WriteLine("[!] EDX = {0}", edx.ToString("X"));
|
||||||
|
|
||||||
Console.WriteLine(">>> Emulation Done!");
|
Console.WriteLine(">>> Emulation Done!");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static Int32 InHookCallback(Unicorn u, Int32 port, Int32 size, Object userData)
|
||||||
|
{
|
||||||
|
var eip = u.RegRead(X86.UC_X86_REG_EIP);
|
||||||
|
Console.WriteLine("[!] Reading from port 0x{0}, size: {1}, address: 0x{2}", port.ToString("X"), size.ToString("X"), eip.ToString("X"));
|
||||||
|
var res = 0;
|
||||||
|
switch (size)
|
||||||
|
{
|
||||||
|
case 1:
|
||||||
|
// read 1 byte to AL
|
||||||
|
res = 0xf1;
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
// read 2 byte to AX
|
||||||
|
res = 0xf2;
|
||||||
|
break;
|
||||||
|
case 4:
|
||||||
|
// read 4 byte to EAX
|
||||||
|
res = 0xf4;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
Console.WriteLine("[!] Return value: {0}", res.ToString("X"));
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void OutHookCallback(Unicorn u, Int32 port, Int32 size, Int32 value, Object userData)
|
||||||
|
{
|
||||||
|
var eip = u.RegRead(X86.UC_X86_REG_EIP);
|
||||||
|
Console.WriteLine("[!] Writing to port 0x{0}, size: {1}, value: 0x{2}, address: 0x{3}", port.ToString("X"), size.ToString("X"), value.ToString("X"), eip.ToString("X"));
|
||||||
|
|
||||||
|
// confirm that value is indeed the value of AL/ AX / EAX
|
||||||
|
var v = 0L;
|
||||||
|
var regName = String.Empty;
|
||||||
|
switch (size)
|
||||||
|
{
|
||||||
|
case 1:
|
||||||
|
// read 1 byte in AL
|
||||||
|
v = u.RegRead(X86.UC_X86_REG_AL);
|
||||||
|
regName = "AL";
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
// read 2 byte in AX
|
||||||
|
v = u.RegRead(X86.UC_X86_REG_AX);
|
||||||
|
regName = "AX";
|
||||||
|
break;
|
||||||
|
case 4:
|
||||||
|
// read 4 byte in EAX
|
||||||
|
v = u.RegRead(X86.UC_X86_REG_EAX);
|
||||||
|
regName = "EAX";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
Console.WriteLine("[!] Register {0}: {1}", regName, v.ToString("X"));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Boolean MemMapHookCallback(Unicorn u, Int32 eventType, Int64 address, Int32 size, Int64 value, Object userData)
|
||||||
|
{
|
||||||
|
if (eventType == Common.UC_MEM_WRITE_UNMAPPED)
|
||||||
|
{
|
||||||
|
Console.WriteLine("[!] Missing memory is being WRITE at 0x{0}, data size = {1}, data value = 0x{2}. Map memory.", address.ToString("X"), size.ToString("X"), value.ToString("X"));
|
||||||
|
u.MemMap(0xaaaa0000, 2 * 1024 * 1024, Common.UC_PROT_ALL);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void CodeHookCallback1(
|
||||||
|
CapstoneDisassembler<X86Instruction, X86Register, X86InstructionGroup, X86InstructionDetail> disassembler,
|
||||||
|
Unicorn u,
|
||||||
|
Int64 addr,
|
||||||
|
Int32 size,
|
||||||
|
Object userData)
|
||||||
|
{
|
||||||
|
Console.Write("[+] 0x{0}: ", addr.ToString("X"));
|
||||||
|
|
||||||
|
var eipBuffer = new Byte[4];
|
||||||
|
u.RegRead(X86.UC_X86_REG_EIP, eipBuffer);
|
||||||
|
|
||||||
|
var effectiveSize = Math.Min(16, size);
|
||||||
|
var tmp = new Byte[effectiveSize];
|
||||||
|
u.MemRead(addr, tmp);
|
||||||
|
|
||||||
|
var sb = new StringBuilder();
|
||||||
|
foreach (var t in tmp)
|
||||||
|
{
|
||||||
|
sb.AppendFormat("{0} ", (0xFF & t).ToString("X"));
|
||||||
|
}
|
||||||
|
Console.Write("{0,-20}", sb);
|
||||||
|
Console.WriteLine(Utils.Disassemble(disassembler, tmp));
|
||||||
|
}
|
||||||
|
|
||||||
private static void CodeHookCallback(
|
private static void CodeHookCallback(
|
||||||
CapstoneDisassembler<X86Instruction, X86Register, X86InstructionGroup, X86InstructionDetail> disassembler,
|
CapstoneDisassembler<X86Instruction, X86Register, X86InstructionGroup, X86InstructionDetail> disassembler,
|
||||||
|
@ -148,7 +263,7 @@ namespace UnicornSamples
|
||||||
u.RegRead(X86.UC_X86_REG_EAX, eaxBuffer);
|
u.RegRead(X86.UC_X86_REG_EAX, eaxBuffer);
|
||||||
var eax = Utils.ToInt(eaxBuffer);
|
var eax = Utils.ToInt(eaxBuffer);
|
||||||
|
|
||||||
Console.WriteLine("Syscall >>> EAX = 0x{0}", eax.ToString("X"));
|
Console.WriteLine("[!] Syscall EAX = 0x{0}", eax.ToString("X"));
|
||||||
|
|
||||||
u.EmuStop();
|
u.EmuStop();
|
||||||
}
|
}
|
||||||
|
@ -173,10 +288,10 @@ namespace UnicornSamples
|
||||||
switch (eax)
|
switch (eax)
|
||||||
{
|
{
|
||||||
default:
|
default:
|
||||||
Console.WriteLine("Interrupt >>> 0x{0} num {1}, EAX=0x{2}", eip.ToString("X"), intNumber.ToString("X"), eax.ToString("X"));
|
Console.WriteLine("[!] Interrupt 0x{0} num {1}, EAX=0x{2}", eip.ToString("X"), intNumber.ToString("X"), eax.ToString("X"));
|
||||||
break;
|
break;
|
||||||
case 1: // sys_exit
|
case 1: // sys_exit
|
||||||
Console.WriteLine("Interrupt >>> 0x{0} num {1}, SYS_EXIT", eip.ToString("X"), intNumber.ToString("X"));
|
Console.WriteLine("[!] Interrupt 0x{0} num {1}, SYS_EXIT", eip.ToString("X"), intNumber.ToString("X"));
|
||||||
u.EmuStop();
|
u.EmuStop();
|
||||||
break;
|
break;
|
||||||
case 4: // sys_write
|
case 4: // sys_write
|
||||||
|
@ -200,7 +315,7 @@ namespace UnicornSamples
|
||||||
var content = Encoding.Default.GetString(buffer);
|
var content = Encoding.Default.GetString(buffer);
|
||||||
|
|
||||||
Console.WriteLine(
|
Console.WriteLine(
|
||||||
"Interrupt >>> 0x{0}: num {1}, SYS_WRITE. buffer = 0x{2}, size = , content = '{3}'",
|
"[!] Interrupt 0x{0}: num {1}, SYS_WRITE. buffer = 0x{2}, size = , content = '{3}'",
|
||||||
eip.ToString("X"),
|
eip.ToString("X"),
|
||||||
ecx.ToString("X"),
|
ecx.ToString("X"),
|
||||||
edx.ToString("X"),
|
edx.ToString("X"),
|
Loading…
Reference in a new issue