From 955324ee59a3a157bec1a137a92fef834708a465 Mon Sep 17 00:00:00 2001 From: Ruben Vereecken Date: Wed, 8 Jan 2025 20:10:01 +0000 Subject: [PATCH 1/2] Implemented basic onEnter + onLeave --- src/structs/method.ts | 59 ++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 56 insertions(+), 3 deletions(-) diff --git a/src/structs/method.ts b/src/structs/method.ts index 48d430a6..b04a7719 100644 --- a/src/structs/method.ts +++ b/src/structs/method.ts @@ -1,5 +1,11 @@ namespace Il2Cpp { + type ImplementationCallback = (this: Il2Cpp.Class | Il2Cpp.Object | Il2Cpp.ValueType, ...parameters: Il2Cpp.Parameter.Type[]) => T; + type OnEnterCallback = (this: Il2Cpp.Class | Il2Cpp.Object | Il2Cpp.ValueType, ...parameters: Il2Cpp.Parameter.Type[]) => void; + type OnLeaveCallback = (this: Il2Cpp.Class | Il2Cpp.Object | Il2Cpp.ValueType, retval: T) => T | void; + export class Method extends NativeStruct { + + /** Gets the class in which this method is defined. */ @lazy get class(): Il2Cpp.Class { @@ -154,7 +160,7 @@ namespace Il2Cpp { const FilterTypeNameMethod = FilterTypeName.field("method").value; // prettier-ignore - const offset = FilterTypeNameMethod.offsetOf(_ => _.readPointer().equals(FilterTypeNameMethodPointer)) + const offset = FilterTypeNameMethod.offsetOf(_ => _.readPointer().equals(FilterTypeNameMethodPointer)) ?? raise("couldn't find the virtual address offset in the native method struct"); // prettier-ignore @@ -174,7 +180,7 @@ namespace Il2Cpp { } /** Replaces the body of this method. */ - set implementation(block: (this: Il2Cpp.Class | Il2Cpp.Object | Il2Cpp.ValueType, ...parameters: Il2Cpp.Parameter.Type[]) => T) { + set implementation(block: ImplementationCallback) { try { Interceptor.replace(this.virtualAddress, this.wrap(block)); } catch (e: any) { @@ -193,6 +199,18 @@ namespace Il2Cpp { } } + set onEnter(block: OnEnterCallback) { + Interceptor.attach(this.virtualAddress, { + onEnter: this.wrapOnEnter(block) + }); + } + + set onLeave(block: OnLeaveCallback) { + Interceptor.attach(this.virtualAddress, { + onLeave: this.wrapOnLeave(block) + }); + } + /** Creates a generic instance of the current generic method. */ inflate(...classes: Il2Cpp.Class[]): Il2Cpp.Method { if (!this.isGeneric) { @@ -348,7 +366,7 @@ ${this.virtualAddress.isNull() ? `` : ` // 0x${this.relativeVirtualAddress.toStr } /** @internal */ - wrap(block: (this: Il2Cpp.Class | Il2Cpp.Object | Il2Cpp.ValueType, ...parameters: Il2Cpp.Parameter.Type[]) => T): NativeCallback { + wrap(block: ImplementationCallback): NativeCallback { const startIndex = +!this.isStatic | +Il2Cpp.unityVersionIsBelow201830; return new NativeCallback( (...args: NativeCallbackArgumentValue[]): NativeCallbackReturnValue => { @@ -366,6 +384,41 @@ ${this.virtualAddress.isNull() ? `` : ` // 0x${this.relativeVirtualAddress.toStr this.fridaSignature ); } + + /** @internal */ + wrapOnEnter(block: OnEnterCallback): ((this: InvocationContext, args: InvocationArguments) => void) { + const startIndex = +!this.isStatic | +Il2Cpp.unityVersionIsBelow201830; + return (args: InvocationArguments) => { + const thisObject = this.isStatic + ? this.class + : this.class.isValueType + ? new Il2Cpp.ValueType((args[0] as NativePointer).add(Il2Cpp.Object.headerSize - maybeObjectHeaderSize()), this.class.type) + : new Il2Cpp.Object(args[0] as NativePointer); + // As opposed to `Interceptor.replace`, `Interceptor.attach` doesn't + // interpret pointers, so use `read` instead of `fromFridaValue` + const parameters = this.parameters.map((_, i) => read(args[i + startIndex], _.type)); + block.call(thisObject, ...parameters); + } + } + + /** @internal */ + wrapOnLeave(block: OnLeaveCallback): ((this: InvocationContext, retval: InvocationReturnValue) => void) { + return (retval: InvocationReturnValue) => { + // TODO grab `this` pointer during `onEnter` + const thisObject = this.class + + // `retval` is always a pointer, even if a primitive type + const returnValue = this.returnType.typeEnum != Il2Cpp.Type.enum.void ? read(retval, this.returnType) as T : undefined as T; + const newReturnValue = block.call(thisObject, returnValue); + + // If callback returned nothing, replace nothing, leave the old return value + if (newReturnValue == null) return; + + const handle = Memory.alloc(this.returnType.class.valueTypeSize); + write(handle, newReturnValue, this.returnType); + retval.replace(handle); + } + } } let maybeObjectHeaderSize = (): number => { From 45a68aed6266ff015c5736b87301e57f43029679 Mon Sep 17 00:00:00 2001 From: Ruben Vereecken Date: Thu, 9 Jan 2025 17:43:27 +0000 Subject: [PATCH 2/2] Changed to work for parameters by not dereferencing pointers --- src/memory.ts | 22 +++++++++++++--------- src/structs/method.ts | 5 +---- src/structs/type.ts | 2 ++ 3 files changed, 16 insertions(+), 13 deletions(-) diff --git a/src/memory.ts b/src/memory.ts index d7989aed..1178d07d 100644 --- a/src/memory.ts +++ b/src/memory.ts @@ -23,8 +23,12 @@ namespace Il2Cpp { return Il2Cpp.exports.free(pointer); } - /** @internal */ - export function read(pointer: NativePointer, type: Il2Cpp.Type): Il2Cpp.Field.Type { + /** + * @param dereference If a pointer, dereference before reading? Usually `true`, but `false` for parameters for example. + */ + export function read(pointer: NativePointer, type: Il2Cpp.Type, derefPointer: boolean=true): Il2Cpp.Field.Type { + const dereferenced = derefPointer ? pointer.readPointer() : pointer; + switch (type.typeEnum) { case Il2Cpp.Type.enum.boolean: return !!pointer.readS8(); @@ -52,27 +56,27 @@ namespace Il2Cpp { return pointer.readDouble(); case Il2Cpp.Type.enum.nativePointer: case Il2Cpp.Type.enum.unsignedNativePointer: - return pointer.readPointer(); + return dereferenced; case Il2Cpp.Type.enum.pointer: - return new Il2Cpp.Pointer(pointer.readPointer(), type.class.baseType!); + return new Il2Cpp.Pointer(dereferenced, type.class.baseType!); case Il2Cpp.Type.enum.valueType: + // Never needs dereferencing return new Il2Cpp.ValueType(pointer, type); case Il2Cpp.Type.enum.object: case Il2Cpp.Type.enum.class: - return new Il2Cpp.Object(pointer.readPointer()); + return new Il2Cpp.Object(dereferenced); case Il2Cpp.Type.enum.genericInstance: - return type.class.isValueType ? new Il2Cpp.ValueType(pointer, type) : new Il2Cpp.Object(pointer.readPointer()); + return type.class.isValueType ? new Il2Cpp.ValueType(pointer, type) : new Il2Cpp.Object(dereferenced); case Il2Cpp.Type.enum.string: - return new Il2Cpp.String(pointer.readPointer()); + return new Il2Cpp.String(dereferenced); case Il2Cpp.Type.enum.array: case Il2Cpp.Type.enum.multidimensionalArray: - return new Il2Cpp.Array(pointer.readPointer()); + return new Il2Cpp.Array(dereferenced); } raise(`couldn't read the value from ${pointer} using an unhandled or unknown type ${type.name} (${type.typeEnum}), please file an issue`); } - /** @internal */ export function write(pointer: NativePointer, value: any, type: Il2Cpp.Type): NativePointer { switch (type.typeEnum) { case Il2Cpp.Type.enum.boolean: diff --git a/src/structs/method.ts b/src/structs/method.ts index b04a7719..f8779de1 100644 --- a/src/structs/method.ts +++ b/src/structs/method.ts @@ -4,8 +4,6 @@ namespace Il2Cpp { type OnLeaveCallback = (this: Il2Cpp.Class | Il2Cpp.Object | Il2Cpp.ValueType, retval: T) => T | void; export class Method extends NativeStruct { - - /** Gets the class in which this method is defined. */ @lazy get class(): Il2Cpp.Class { @@ -236,7 +234,6 @@ namespace Il2Cpp { return this.invokeRaw(NULL, ...parameters); } - /** @internal */ invokeRaw(instance: NativePointerValue, ...parameters: Il2Cpp.Parameter.Type[]): T { const allocatedParameters = parameters.map(toFridaValue); @@ -396,7 +393,7 @@ ${this.virtualAddress.isNull() ? `` : ` // 0x${this.relativeVirtualAddress.toStr : new Il2Cpp.Object(args[0] as NativePointer); // As opposed to `Interceptor.replace`, `Interceptor.attach` doesn't // interpret pointers, so use `read` instead of `fromFridaValue` - const parameters = this.parameters.map((_, i) => read(args[i + startIndex], _.type)); + const parameters = this.parameters.map((_, i) => read(args[i + startIndex], _.type, false)); block.call(thisObject, ...parameters); } } diff --git a/src/structs/type.ts b/src/structs/type.ts index bd2c8561..93ee0ed4 100644 --- a/src/structs/type.ts +++ b/src/structs/type.ts @@ -18,6 +18,8 @@ namespace Il2Cpp { unsignedInt: _("System.UInt32"), long: _("System.Int64"), unsignedLong: _("System.UInt64"), + + nativePointer: _("System.IntPtr"), unsignedNativePointer: _("System.UIntPtr"), float: _("System.Single"),