android unity 拦截 w/ Frida

逆向工程 安卓 。网 弗里达
2021-06-24 09:30:54

我正在尝试从 Android Unity 游戏中提取符号以检测与游戏相关的内容,我可以拦截和替换变量。

以下代码将从 libmain.so、libmono.so 和 libunity.so 枚举符号,但不会枚举 UnityEngine.*.dll.so,它们似乎没有获得地址(retval 等于 0x0),我假设它们还没有加载(那是没有地址的方式),加载它们时负责什么功能我可以钩住它而不是枚举符号吗?有什么帮助吗?

Interceptor.attach(Module.findExportByName(null, 'dlopen'), {
    onEnter: function(args) {
        this.lib = Memory.readUtf8String(args[0]);
    },
    onLeave: function(retval) {
        console.log(
            this.lib.substr(this.lib.lastIndexOf('/') + 1, this.lib.length) +
            ' [ ' + retval + ' ] \n' +
            Module.enumerateExportsSync(this.lib).map(function(x) {
                return x.name
            })
        );
    }
});

输出:

libmain.so [ 0xac651e74 ] 
__aeabi_unwind_cpp_pr0,JNI_OnLoad,__aeabi_unwind_cpp_pr1,__aeabi_unwind_cpp_pr2,__gnu_Unwind_Restore_VFP_D,__gnu_Unwind_Restore_VFP,__gnu_Unwind_Restore_VFP_D_16_to_31,__gnu_Unwind_Restore_WMMXD,__gnu_Unwind_Restore_WMMXC,restore_core_regs,_Unwind_GetCFA,__gnu_Unwind_RaiseException,__gnu_Unwind_ForcedUnwind,__gnu_Unwind_Resume,__gnu_Unwind_Resume_or_Rethrow,_Unwind_Complete,_Unwind_DeleteException,_Unwind_VRS_Get,_Unwind_VRS_Set,__gnu_Unwind_Backtrace,__gnu_unwind_execute,_Unwind_VRS_Pop,__gnu_Unwind_Save_VFP_D,__gnu_Unwind_Save_VFP,__gnu_Unwind_Save_VFP_D_16_to_31,__gnu_Unwind_Save_WMMXD,__gnu_Unwind_Save_WMMXC,__restore_core_regs,___Unwind_RaiseException,_Unwind_RaiseException,___Unwind_Resume,_Unwind_Resume,___Unwind_Resume_or_Rethrow,_Unwind_Resume_or_Rethrow,___Unwind_ForcedUnwind,_Unwind_ForcedUnwind,___Unwind_Backtrace,_Unwind_Backtrace,__gnu_unwind_frame,_Unwind_GetRegionStart,_Unwind_GetLanguageSpecificData,_Unwind_GetDataRelBase,_Unwind_GetTextRelBase
libunity.so [ 0xb399b004 ] 
_ZNSt6vectorIcSaIcEE17_M_default_appendEj,_ZNSt6vectorIjSaIjEE17_M_default_appendEj,_ZdaPv,_ZdlPv,_Znaj,_Znwj,_ZdlPvRKSt9nothrow_t,_ZnwjRKSt9nothrow_t,_ZdaPvRKSt9nothrow_t,_ZnajRKSt9nothrow_t,_ZNSt6vectorIiSaIiEE17_M_default_appendEj,_ZNSt8_Rb_treeIiiSt9_IdentityIiESt4lessIiESaIiEE12_M_erase_auxESt23_Rb_tree_const_iteratorIiES7_,_ZNSt8_Rb_treeIiiSt9_IdentityIiESt4lessIiESaIiEE16_M_insert_uniqueIRKiEESt4pairISt17_Rb_tree_iteratorIiEbEOT_,_ZNSt8_Rb_treeIiiSt9_IdentityIiESt4lessIiESaIiEE5eraseERKi,_ZNSt8_Rb_treeIiiSt9_IdentityIiESt4lessIiESaIiEE8_M_eraseEPSt13_Rb_tree_nodeIiE,_ZNSt6vectorIfSaIfEE14_M_fill_insertEN9__gnu_cxx17__normal_iteratorIPfS1_EEjRKf,_ZNSt6vectorIfSaIfEE19_M_emplace_back_auxIJfEEEvDpOT_,_ZNSt6vectorISt4pairIiiESaIS1_EE17_M_default_appendEj,_ZNSt6vectorIiSaIiEE19_M_emplace_back_auxIJRKiEEEvDpOT_,_ZNSt6vectorIiSaIiEE19_M_emplace_back_auxIJiEEEvDpOT_,_ZNSt6vectorISt4pairIijESaIS1_EE19_M_emplace_back_auxIJS1_EEEvDpOT_,_ZNSt6vectorISt4pairIijESaIS1_EEaSERKS3_,_ZSt17__rotate_adaptiveIN9__gnu_cxx17__normal_iteratorIPSt4pairIijESt6vectorIS3_SaIS3_EEEES4_iET_S9_S9_S9_T1_SA_T0_SA_,_ZSt8__rotateIN9__gnu_cxx17__normal_iteratorIPSt4pairIijESt6vectorIS3_SaIS3_EEEEEvT_S9_S9_St26random_access_iterator_tag,_ZSt8__rotateIPiEvT_S1_S1_St26random_access_iterator_tag,_ZNSt6vectorIhSaIhEE17_M_default_appendEj,_ZNSt8_Rb_treeIjjSt9_IdentityIjESt4lessIjESaIjEE16_M_insert_uniqueIRKjEESt4pairISt17_Rb_tree_iteratorIjEbEOT_,_ZNSt8_Rb_treeIjjSt9_IdentityIjESt4lessIjESaIjEE16_M_insert_uniqueISt23_Rb_tree_const_iteratorIjEEEvT_S9_,_ZNSt8_Rb_treeIjjSt9_IdentityIjESt4lessIjESaIjEE16_M_insert_uniqueIjEESt4pairISt17_Rb_tree_iteratorIjEbEOT_,_ZNSt8_Rb_treeIjjSt9_IdentityIjESt4lessIjESaIjEE29_M_get_insert_hint_unique_posESt23_Rb_tree_const_iteratorIjERKj,_ZNSt8_Rb_treeIjjSt9_IdentityIjESt4lessIjESaIjEE7_M_copyEPKSt13_Rb_tree_nodeIjEPS7_,_ZNSt8_Rb_treeIjjSt9_IdentityIjESt4lessIjESaIjEE8_M_eraseEPSt13_Rb_tree_nodeIjE,_ZNSt6vectorIS_IfSaIfEESaIS1_EE19_M_emplace_back_auxIJRKS1_EEEvDpOT_,_ZNSt8_Rb_treeIiiSt9_IdentityIiESt4lessIiESaIiEE16_M_insert_uniqueIiEESt4pairISt17_Rb_tree_iteratorIiEbEOT_,_ZNSt6vectorIjSaIjEE19_M_emplace_back_auxIJRKjEEEvDpOT_,_ZNSt6vectorIiSaIiEE13_M_insert_auxIJRKiEEEvN9__gnu_cxx17__normal_iteratorIPiS1_EEDpOT_,_ZNSt6vectorIiSaIiEE13_M_insert_auxIJiEEEvN9__gnu_cxx17__normal_iteratorIPiS1_EEDpOT_,_ZNSt8_Rb_treeIPKvS1_St9_IdentityIS1_ESt4lessIS1_ESaIS1_EE16_M_insert_uniqueIS1_EESt4pairISt17_Rb_tree_iteratorIS1_EbEOT_,_ZNSt8_Rb_treeIPKvS1_St9_IdentityIS1_ESt4lessIS1_ESaIS1_EE8_M_eraseEPSt13_Rb_tree_nodeIS1_E,_ZNSt6vectorIjSaIjEE13_M_assign_auxIN9__gnu_cxx17__normal_iteratorIPjS1_EEEEvT_S7_St20forward_iterator_tag,_ZNSt6vectorIjSaIjEE14_M_fill_insertEN9__gnu_cxx17__normal_iteratorIPjS1_EEjRKj,_ZNSt6vectorIiSaIiEE13_M_assign_auxISt23_Rb_tree_const_iteratorIiEEEvT_S5_St20forward_iterator_tag,_ZNSt8_Rb_treeIiSt4pairIKiiESt10_Select1stIS2_ESt4lessIiESaIS2_EE22_M_emplace_hint_uniqueIJRKSt21piecewise_construct_tSt5tupleIJRS1_EESD_IJEEEEESt17_Rb_tree_iteratorIS2_ESt23_Rb_tree_const_iteratorIS2_EDpOT_,_ZNSt8_Rb_treeIiSt4pairIKiiESt10_Select1stIS2_ESt4lessIiESaIS2_EE29_M_get_insert_hint_unique_posESt23_Rb_tree_const_iteratorIS2_ERS1_,_ZNSt8_Rb_treeIiSt4pairIKiiESt10_Select1stIS2_ESt4lessIiESaIS2_EE8_M_eraseEPSt13_Rb_tree_nodeIS2_E,_ZNSt6vectorIfSaIfEE17_M_default_appendEj,_ZNSt6vectorIjSaIjEE19_M_emplace_back_auxIJjEEEvDpOT_,UnitySendMessage,_ZNSt6vectorIcSaIcEE19_M_emplace_back_auxIJcEEEvDpOT_,_ZNSt6vectorIhSaIhEE15_M_range_insertIPhEEvN9__gnu_cxx17__normal_iteratorIS3_S1_EET_S7_St20forward_iterator_tag,_ZNSt6vectorIhSaIhEE19_M_emplace_back_auxIJhEEEvDpOT_,JNI_OnLoad,JNI_OnUnload,_ZNSt8_Rb_treeIiSt4pairIKi9sigactionESt10_Select1stIS3_ESt4lessIiESaIS3_EE22_M_emplace_hint_uniqueIJRKSt21piecewise_construct_tSt5tupleIJRS1_EESE_IJEEEEESt17_Rb_tree_iteratorIS3_ESt23_Rb_tree_const_iteratorIS3_EDpOT_,_ZNSt8_Rb_treeIiSt4pairIKi9sigactionESt10_Select1stIS3_ESt4lessIiESaIS3_EE29_M_get_insert_hint_unique_posESt23_Rb_tree_const_iteratorIS3_ERS1_,_ZNSt8_Rb_treeIiSt4pairIKi9sigactionESt10_Select1stIS3_ESt4lessIiESaIS3_EE8_M_eraseEPSt13_Rb_tree_nodeIS3_E,_ZNSt8_Rb_treeIiSt4pairIKiiESt10_Select1stIS2_ESt4lessIiESaIS2_EE16_M_insert_uniqueIS0_IiiEEES0_ISt17_Rb_tree_iteratorIS2_EbEOT_,_ZNSt8_Rb_treeIiSt4pairIKiiESt10_Select1stIS2_ESt4lessIiESaIS2_EE7_M_copyEPKSt13_Rb_tree_nodeIS2_EPSA_,_ZN9__gnu_cxx21_Hashtable_prime_listImE16__stl_prime_listE,_ZNSt8_Rb_treeIyySt9_IdentityIyESt4lessIyESaIyEE12_M_erase_auxESt23_Rb_tree_const_iteratorIyES7_,_ZNSt8_Rb_treeIyySt9_IdentityIyESt4lessIyESaIyEE16_M_insert_uniqueIRKyEESt4pairISt17_Rb_tree_iteratorIyEbEOT_,_ZNSt8_Rb_treeIyySt9_IdentityIyESt4lessIyESaIyEE5eraseERKy,_ZNSt8_Rb_treeIyySt9_IdentityIyESt4lessIyESaIyEE8_M_eraseEPSt13_Rb_tree_nodeIyE,_ZNSt6vectorISt4pairIS0_IttEfESaIS2_EE17_M_default_appendEj,_ZNSt6vectorIhSaIhEE19_M_emplace_back_auxIJRKhEEEvDpOT_,_ZNSt6vectorIySaIyEE19_M_emplace_back_auxIJRKyEEEvDpOT_,_ZNSt6vectorIS_IySaIyEESaIS1_EE19_M_emplace_back_auxIJRKS1_EEEvDpOT_,_ZNSt6vectorIhSaIhEE15_M_range_insertIN9__gnu_cxx17__normal_iteratorIPhS1_EEEEvS6_T_S7_St20forward_iterator_tag,_ZNSt6vectorIhSaIhEE15_M_range_insertIPKcEEvN9__gnu_cxx17__normal_iteratorIPhS1_EET_S9_St20forward_iterator_tag,_ZNSt6vectorIhSaIhEE15_M_range_insertIPKhEEvN9__gnu_cxx17__normal_iteratorIPhS1_EET_S9_St20forward_iterator_tag,_ZNSt6vectorIhSaIhEE15_M_range_insertIPcEEvN9__gnu_cxx17__normal_iteratorIPhS1_EET_S8_St20forward_iterator_tag,_ZNSt6vectorIxSaIxEE19_M_emplace_back_auxIJRKxEEEvDpOT_,_ZNSt6vectorIhSaIhEEaSERKS1_,_ZNSt6vectorItSaItEEaSERKS1_
libmono.so [ 0xb399b3f4 ] 
mono_jit_tls_id,mono_tracev,mono_jit_trace_calls,mono_break_on_exc,mono_compile_aot,mono_breakpoint_info_index,mono_aot_only,mono_build_date,mono_use_imt,mono_do_signal_chaining,mono_inject_async_exc_method,mono_break_at_bb_bb_num,mono_inject_async_exc_pos,mono_do_x86_stack_align,mono_break_at_bb_method,mono_pmip,mono_dont_free_global_codeman,mono_print_method_from_ip,mono_debug_lookup_source_location,mono_debug_free_source_location,mono_domain_get,mono_jit_info_table_find,g_free,mono_method_full_name,mono_mempool_alloc0,mono_code_manager_reserve,mono_code_manager_new,mono_type_generic_inst_is_valuetype,mono_type_get_underlying_type,mono_class_enum_basetype,mono_method_get_header,mono_jit_stats,mono_type_get_name,mono_inst_name,mono_class_from_mono_type,mono_metadata_signature_alloc,mono_mempool_alloc,mono_get_root_domain,mono_free_verify_list,mono_loader_get_last_error,mono_compile_method,mono_type_size,mono_metadata_generic_class_is_valuetype,mono_class_min_align,mono_environment_exitcode_get,mono_mempool_destroy,mono_thread_exit,mono_thread_attach,mono_domain_set,mono_jit_thread_attach,mono_thread_current,mono_debugger_thread_created,mono_thread_attach_aborted_cb,mono_debugger_thread_cleanup,mono_profiler_get_events,mono_class_vtable,mono_class_init,mono_runtime_class_init,mono_metadata_field_info,mono_lookup_internal_call,mono_image_rva_map,mono_thread_interruption_request_flag,mono_ldstr,mono_metadata_blob_heap,mono_ldtoken,mono_lookup_pinvoke_call,mono_type_get_object,mono_method_signature,mono_code_manager_new_dynamic,mono_method_get_generic_container,mono_code_manager_commit,mono_class_inflate_generic_method,mono_mempool_new,mono_debug_using_mono_debugger,mono_local_deadce,mono_runtime_invoke,mono_get_exception_execution_engine,mono_exception_from_name_msg,mono_loader_error_prepare_exception,mono_get_exception_bad_image_format,.....,GC_push_all_stack,GC_delete_thread,GC_lookup_thread,GC_start_blocking,GC_end_blocking,VER_1
mscorlib.dll.so [ 0x0 ] 

UnityEngine.dll.so [ 0x0 ] 

UnityEngine.CoreModule.dll.so [ 0x0 ] 

UnityEngine.AccessibilityModule.dll.so [ 0x0 ] 

UnityEngine.ParticleSystemModule.dll.so [ 0x0 ] 

UnityEngine.PhysicsModule.dll.so [ 0x0 ] 

UnityEngine.VehiclesModule.dll.so [ 0x0 ] 

UnityEngine.ClothModule.dll.so [ 0x0 ] 

UnityEngine.AIModule.dll.so [ 0x0 ] 

UnityEngine.AnimationModule.dll.so [ 0x0 ] 

UnityEngine.TextRenderingModule.dll.so [ 0x0 ] 

UnityEngine.UIModule.dll.so [ 0x0 ] 

UnityEngine.TerrainPhysicsModule.dll.so [ 0x0 ] 

UnityEngine.IMGUIModule.dll.so [ 0x0 ] 

UnityEngine.UnityWebRequestModule.dll.so [ 0x0 ] 

UnityEngine.UnityWebRequestAudioModule.dll.so [ 0x0 ] 

UnityEngine.UnityWebRequestTextureModule.dll.so [ 0x0 ] 

UnityEngine.UnityWebRequestWWWModule.dll.so [ 0x0 ] 

UnityEngine.UNETModule.dll.so [ 0x0 ] 

UnityEngine.DirectorModule.dll.so [ 0x0 ] 

UnityEngine.UnityAnalyticsModule.dll.so [ 0x0 ] 

UnityEngine.CrashReportingModule.dll.so [ 0x0 ] 

UnityEngine.PerformanceReportingModule.dll.so [ 0x0 ] 

UnityEngine.UnityConnectModule.dll.so [ 0x0 ] 

UnityEngine.WebModule.dll.so [ 0x0 ] 

UnityEngine.ARModule.dll.so [ 0x0 ] 

UnityEngine.VRModule.dll.so [ 0x0 ] 

UnityEngine.UIElementsModule.dll.so [ 0x0 ] 

UnityEngine.StyleSheetsModule.dll.so [ 0x0 ] 

UnityEngine.AudioModule.dll.so [ 0x0 ] 

UnityEngine.GameCenterModule.dll.so [ 0x0 ] 

UnityEngine.GridModule.dll.so [ 0x0 ] 

UnityEngine.ImageConversionModule.dll.so [ 0x0 ] 

UnityEngine.InputModule.dll.so [ 0x0 ] 

UnityEngine.JSONSerializeModule.dll.so [ 0x0 ] 

UnityEngine.ParticlesLegacyModule.dll.so [ 0x0 ] 

UnityEngine.Physics2DModule.dll.so [ 0x0 ] 

UnityEngine.ScreenCaptureModule.dll.so [ 0x0 ] 

UnityEngine.SpriteMaskModule.dll.so [ 0x0 ] 

UnityEngine.TerrainModule.dll.so [ 0x0 ] 

UnityEngine.TilemapModule.dll.so [ 0x0 ] 

UnityEngine.VideoModule.dll.so [ 0x0 ] 

UnityEngine.WindModule.dll.so [ 0x0 ] 

System.dll.so [ 0x0 ] 

Assembly-CSharp-firstpass.dll.so [ 0x0 ] 

Assembly-CSharp.dll.so [ 0x0 ] 

UnityEngine.UI.dll.so [ 0x0 ] 

UnityEngine.Networking.dll.so [ 0x0 ] 

UnityEngine.Timeline.dll.so [ 0x0 ] 

UnityEngine.SpatialTracking.dll.so [ 0x0 ] 

UnityEngine.Advertisements.Android.dll.so [ 0x0 ] 

Facebook.Unity.dll.so [ 0x0 ] 

Facebook.Unity.Settings.dll.so [ 0x0 ] 

Facebook.Unity.Android.dll.so [ 0x0 ] 

Facebook.Unity.IOS.dll.so [ 0x0 ] 

RTL.dll.so [ 0x0 ] 

System.Core.dll.so [ 0x0 ] 
2个回答

嘿,这是某种尸检,但为什么不呢?

好吧,unity3d 在后台使用 2 个变体:mono,加载 C# dll 和 il2cpp,在构建之前将其转换为 C++。对于第二种情况,您可以使用unity_metadata_loader或类似的产品,它采用 C# 元数据(主要是类的描述)及其地址偏移量。

基本上你可能需要等待libil2cpp.so加载,然后获取它的基地址,然后通过添加基地址来计算内存中的偏移量。我为此使用类似以下内容:

 var awaitForCondition = function (callback) {
     var int = setInterval(function () {
         var addr = Module.findBaseAddress('libil2cpp.so');
         if (addr) {
             console.log("Address found:", addr);
             clearInterval(int);
             callback(+addr);
             return;
         }
     }, 0);
 }
awaitForCondition((baseAddr)=>{
    var realAddr = ptr(baseAddr+offset);
});

如果是单声道(我认为是您的情况),您可以尝试阅读这篇 文章(俄语,但带有代码示例)。mono_image_open_full如果我没记错的话,基本上你需要挂钩 mono's

顺便说一句,我不确定,Assembly-CSharp.dll.so 是什么,因为它只是 C# dll,而不是二进制文件。

回答

    // Constants
    const kIgnoreArg = '-';

    // Utils
    function pmalloc() {
        return Memory.alloc(Process.pointerSize);
    }
    function debug() {
        send({ event: 'DEBUG', data: Array.prototype.slice.call(arguments).join(' ') });
    }

    // Globals
    var Metadata = {}; // < className, { pointer, methods < methodName, { pointer, args[], returnType } >, fields } >
    var Global = {}; // save global variables across hooks
    var MonoApi = {
        mono_image_get_table_rows: ['int', ['MonoImage*', 'int'/*table_id*/]],
        mono_class_get: ['MonoClass*', ['MonoImage*', 'int'/*type_token*/]],
        mono_class_get_parent: ['MonoClass*', ['MonoClass*']],
        mono_class_get_name: ['char*', ['MonoClass*']],
        mono_method_get_name: ['char*', ['MonoMethod*']],
        mono_class_get_methods: ['MonoMethod*', ['MonoClass*', 'iter*']],
        mono_class_get_fields: ['MonoClassField*', ['MonoClass*', 'iter*']],
        mono_signature_get_params: ['MonoType*', ['MonoMethod*', 'iter*']],
        mono_field_full_name: ['char*', ['MonoField*']],
        mono_class_get_namespace: ['char*', ['MonoClass*']],
        mono_type_full_name: ['char*', ['MonoType*']],
        mono_signature_get_return_type: ['MonoType*', ['MonoMethodSignature*']],
        mono_class_get_method_from_name: ['MonoMethod*', ['MonoClass*', 'name*', 'int'/*number of params. -1 for any*/]],
        mono_method_signature: ['MonoMethodSignature*', ['MonoMethod*']],
        /** gpointer mono_compile_method (MonoMethod *method)
         * http://docs.go-mono.com/index.aspx?link=xhtml%3Adeploy%2Fmono-api-unsorted.html */
        mono_compile_method: ['gpointer*'/* pointer to the native code produced.*/, ['MonoMethod*']],
        /**
         * char* mono_string_to_utf8 (MonoString *s)
         * @param    s  a System.String
         * @Description
         # TODO mono_free
         *       Returns the UTF8 representation for s. The resulting buffer needs to be freed with mono_free().
         *       deprecated Use mono_string_to_utf8_checked to avoid having an exception arbritraly raised.
         */
        mono_string_to_utf8: ['char*', ['System.String*']],
        getClassMethods: function (klass) {
            var method, methods = {}, iter = pmalloc();

            while ( !(method = MonoApi.mono_class_get_methods(klass, iter)).isNull() ) {
                var methodName = MonoApi.mono_method_get_name(method).readUtf8String();
                if (!methodName.startsWith('<') /*|| methodName.startsWith('.')*/) {
                    var methodRef = MonoApi.mono_class_get_method_from_name(klass, Memory.allocUtf8String(methodName), -1);
                    var monoSignature = MonoApi.mono_method_signature(methodRef);
                    var retType = MonoApi.mono_type_full_name(MonoApi.mono_signature_get_return_type(monoSignature)).readUtf8String();
                    var args = MonoApi.getSignatureParams(monoSignature);
                    methods[methodName] = { ref: methodRef, args: args, ret: retType };
                }
            }

            return methods;
        },
        getSignatureParams: function (monoSignature) {
            var params, fields = [], iter = pmalloc();

            while ( !(params = MonoApi.mono_signature_get_params(monoSignature, iter)).isNull() )
                fields.push( MonoApi.mono_type_full_name(params).readUtf8String() );

            return fields;
        },
        getClassFields: function (monoClass) {
            var field, fields = [], iter = pmalloc();

            while ( !(field = MonoApi.mono_class_get_fields(monoClass, iter)).isNull() )
                fields.push( 
                    MonoApi.mono_field_full_name(field).readUtf8String().split(':')[1] );

            return fields;
        },
        init: function() {
            var monoModule = Process.findModuleByName('mono.dll');
            debug("Process.findModuleByName('mono.dll') ? " + monoModule);
            if (!monoModule) {
                var monoThreadAttach = Module.findExportByName(null, 'mono_thread_attach');
                debug("monoThreadAttach ? " + monoThreadAttach);
                if (monoThreadAttach)
                    monoModule = Process.findModuleByAddress(monoThreadAttach);
            }
            if (!monoModule) throw new Error('Mono.dll not found');

            Object.keys(MonoApi).map(function(exportName) {
                var monoApiIter = MonoApi[exportName];
                if (typeof monoApiIter === 'object') {
                    var returnValue = monoApiIter[0].endsWith('*') ? 'pointer' : monoApiIter[0];
                    var argumentTypes = monoApiIter[1].map(function(t) { return t.endsWith('*') ? 'pointer' : t });
                    var exportAddress = Module.findExportByName(monoModule.name, exportName);
                    MonoApi[exportName] = new NativeFunction(exportAddress, returnValue, argumentTypes);
                }
            });
        }
    };

    function intercept(op) {
        var nothingSetSoJustLogMethodArguments = !op.argumentsKeys && !op.onEnterCallback && !op.onLeaveCallback;
        var method = Metadata[op.className].methods[op.methodName];
        debug('Intercepting', op.className + '#' + op.methodName, JSON.stringify(method));
        // TODO assert re compile is necessary
        var monoCompileMethod = MonoApi.mono_compile_method(method.ref);
        Interceptor.attach(monoCompileMethod, {
            onEnter: function (args) {
                var argsValues = {};
                for (var i = 0, l = method.args.length; i < l; i++) {
                    var key = op.argumentsKeys ? op.argumentsKeys[i] : i;
                    if (key === kIgnoreArg)
                        continue;
                    var j = i + 1;
                    switch (method.args[i]) {
                        case 'string':
                            argsValues[key] = MonoApi.mono_string_to_utf8(args[j]).readUtf8String();
                            break;
                        case 'long':
                        case 'int':
                            argsValues[key] = parseInt(args[j]);
                            break;
                        default:
                            argsValues[key] = args[j];
                            break;
                    }
                }

                if (nothingSetSoJustLogMethodArguments)
                    debug(op.className + '#' + op.methodName, JSON.stringify(argsValues, null, 2));

                if (op.onEnterCallback)
                    op.onEnterCallback(argsValues);
            },
            onLeave: function (retval) {
                if (op.onLeaveCallback)
                    op.onLeaveCallback(retval);
            }
        });
    }

    function getMetadata(monoImage) {
        // MONO_TABLE_TYPEDEF = 0x2; // https://github.com/mono/mono/blob/master/mono/metadata/blob.h#L56
        for (var i = 1, l = MonoApi.mono_image_get_table_rows(monoImage, 0x2); i < l; ++i) {
            // MONO_TOKEN_TYPE_DEF = 0x2000000 // https://github.com/mono/mono/blob/master/mono/metadata/tokentype.h#L16
            var mClass = MonoApi.mono_class_get(monoImage, 0x2000000 | i);
            var className = MonoApi.mono_class_get_name(mClass).readUtf8String();
            var classNameSpace = MonoApi.mono_class_get_namespace(mClass).readUtf8String();
            try {
                var parentClassName = MonoApi.mono_class_get_name( MonoApi.mono_class_get_parent(mClass) ).readUtf8String();
                if (parentClassName === 'MonoBehaviour' && classNameSpace === '') {
                    Metadata[className] = {
                        // namespace: classNameSpace,
                        ref: mClass,
                        methods: MonoApi.getClassMethods(mClass),
                        fields: MonoApi.getClassFields(mClass)
                    };
                }
            } catch (e) {
                debug("Error @ getMetadata/mono_class_get_parent", e);
            }
        }
        send({ event: 'METADATA', data: Metadata });
    }

    function hookMonoLoad() {
        // hooking the method in charge of loading the DLL files
        Interceptor.attach(Module.findExportByName(null, 'mono_assembly_load_from_full'), {
            onEnter: function (args) {
                // passing variables to onLeave scope using 'this'
                this._args = {
                    image: args[0], // MonoImage* Image to load the assembly from
                    fname: args[1].readUtf8String() // const char* assembly name to associate with the assembly
                    // status: args[2], // MonoImageOpenStatus* returns the status condition
                    // refonly: args[3] // gboolean Whether this assembly is being opened in "reflection-only" mode.
                };
            },
            onLeave: function (_retval) {
                // Return value: A valid pointer to a MonoAssembly* on success and the status will be set to MONO_IMAGE_OK
                //               or NULL on error.
                if (this._args.fname.endsWith('Assembly-CSharp.dll')) {
                    MonoApi.init();
                    getMetadata(this._args.image);
                    /*placeholder*/
                }
            }
        });
    }

    function awaitForCondition(func) {
        // From MDN: If this parameter is less than 10, a value of 10 is used. Note that the actual delay may be longer;
        var delay = 10; // Fight for CPU
        var intervalPointer = setInterval(function() {
            // The condition that asserts Mono's required resources can be hooked
            // TODO switch with intercepting dlopen wait for mono.dll ?
            // FIXME use Module.ensureInitialized(name)
            if (Module.findExportByName(null, 'mono_get_root_domain')) {
                clearInterval(intervalPointer);
                func(); // Executing the passed function
            }
        }, delay);
    }

    // Main
    Java.perform(awaitForCondition(hookMonoLoad));