vfsfitvnm / frida-il2cpp-bridge

A Frida module to dump, trace or hijack any Il2Cpp application at runtime, without needing the global-metadata.dat file.
https://github.com/vfsfitvnm/frida-il2cpp-bridge/wiki
MIT License
974 stars 199 forks source link

About scheduleOnInitializerThread #247

Closed Chensem closed 1 year ago

Chensem commented 1 year ago

Recently , i develop some scaffold about hack unity-xlua game , but i encounted some problems that i can't resolve . i want to load my lua code from unity engine , offical example like below

XLua.LuaEnv luaenv = new XLua.LuaEnv();
luaenv.DoString("CS.UnityEngine.Debug.Log('hello world')");
luaenv.Dispose();

so my il2cpp-bridge code like below

const luaenv = Il2Cpp.Domain.assembly("Assembly-CSharp").image.class("XLua.LuaEnv").alloc()
luaenv.method('.ctor').invoke()
luaenv.method('DoString').overload("System.String", "System.String", "XLua.LuaTable").invoke(Il2Cpp.String.from(`print(CS.UnityEngine.GameObject)`) ,  
    Il2Cpp.String.from("chunk"),
    ptr('0x0'))
luaenv.method('Dispose').invoke()

it behaviour ok

[2023/1/11 14:47:58] [LUA] table: 0x7288db4080

look the offical example , it use CS.UnityEngine.Debug.Log to replace print , and the lua scripts also use CS.UnityEngine.Debug.Log to print log , i intercept the log function via snippets like below image image let't try to spwan the application from the frida console , logs like below image

it verify the lua scripts logs via CS.UnityEngine.Debug.Log so i also want to use CS.UnityEngine.Debug.Log to logs to console , but i failed , so let's debug the snippets , first , i try to print the CS.UnityEngine.Debug.Log in the lua environment , snippets like below

Lua.perform((engine)=>{
  engine.openLuaLog()
  Il2Cpp.perform(()=>{
    const luaenv = Il2Cpp.Domain.assembly("Assembly-CSharp").image.class("XLua.LuaEnv").alloc()
    luaenv.method('.ctor').invoke()
    luaenv.method('DoString').overload("System.String", "System.String", "XLua.LuaTable").invoke(Il2Cpp.String.from(`print(CS.UnityEngine.Debug.Log)`) ,  
    Il2Cpp.String.from("chunk"),
    ptr('0x0'))
    luaenv.method('Dispose').invoke()
  })
})

and the result like below image

try to change the lua snippets print(CS.UnityEngine.Debug.Log) to print(print) the result like below image so we believe the CS.UnityEngine.Debug.Log exists indeed , and it perform like print so let's try to invoke CS.UnityEngine.Debug.Log('test log') to perform log action image we failed , there is not log into console image

so i guess it is thread problem , so let's verify it , i add console.log(this.threadId) in openLog function , so we can know the CS.UnityEngine.Debug.Log runs on which thread , now we spawn another application for inspect , our snippets like below

Lua.perform((engine)=>{
  engine.openLuaLog()
})

image we can see the thread id is 1846 , the thread id as same as Il2Cpp.attachedThreads[0] , so we can say it called from unity main thread ? image

so , we can try use Il2Cpp.scheduleOnInitializerThread to perform codes on UnityMain thread , snippets like below image but , but there also no log on console , very sad . now change CS.UnityEngine.Debug.Log to print , then have a try . snippets like below

Lua.perform((engine)=>{
  engine.openLuaLog()
  Il2Cpp.scheduleOnInitializerThread(()=>{
    const luaenv = Il2Cpp.Domain.assembly("Assembly-CSharp").image.class("XLua.LuaEnv").alloc()
    luaenv.method('.ctor').invoke()
    luaenv.method('DoString').overload("System.String", "System.String", "XLua.LuaTable").invoke(Il2Cpp.String.from(`CS.UnityEngine.Debug.Log('hello from chensem')`) ,  
    Il2Cpp.String.from("chunk"),
    ptr('0x0'))
    luaenv.method('Dispose').invoke()
  })
})

oh my god , no logs again image

let's intercept the DoString function

Lua.perform((engine)=>{
  engine.openLuaLog()
  Interceptor.attach(Il2Cpp.Domain.assembly("Assembly-CSharp").image.class("XLua.LuaEnv").method("DoString").virtualAddress , {
    onEnter(args) {
      console.log(`onEnter`)
      console.log(`perform on ${this.threadId}`)
    }
  })

  Il2Cpp.perform(()=>{
    const luaenv = Il2Cpp.Domain.assembly("Assembly-CSharp").image.class("XLua.LuaEnv").alloc()
    luaenv.method('.ctor').invoke()
    luaenv.method('DoString').overload("System.String", "System.String", "XLua.LuaTable").invoke(Il2Cpp.String.from(`print('hello from chensem')`) ,  
    Il2Cpp.String.from("chunk"),
    ptr('0x0'))
    luaenv.method('Dispose').invoke()
  })
})

image it performs ok

Lua.perform((engine)=>{
  engine.openLuaLog()
  Interceptor.attach(Il2Cpp.Domain.assembly("Assembly-CSharp").image.class("XLua.LuaEnv").method("DoString").virtualAddress , {
    onEnter(args) {
      console.log(`onEnter`)
      console.log(`perform on ${this.threadId}`)
    }
  })

  Il2Cpp.scheduleOnInitializerThread(()=>{
    const luaenv = Il2Cpp.Domain.assembly("Assembly-CSharp").image.class("XLua.LuaEnv").alloc()
    luaenv.method('.ctor').invoke()
    luaenv.method('DoString').overload("System.String", "System.String", "XLua.LuaTable").invoke(Il2Cpp.String.from(`print('hello from chensem')`) ,  
    Il2Cpp.String.from("chunk"),
    ptr('0x0'))
    luaenv.method('Dispose').invoke()
  })
})

image performs failed the end , which leads me confused is why CS.UnityEngine.Debug.Log can't be invoked , if we invoke CS.UnityEngine.Debug.Log from il2cpp-bridge via construct object and invoke , it maybe success , but we want to invoke it from lua code and lua vm invoke it , but failed .

Chensem commented 1 year ago

i try invoke CS.UnityEngine.Debug.Log another way

var i = 0;
Il2Cpp.Domain.assembly("Assembly-CSharp").image.class("XLua.LuaEnv").method("DoString").overload("System.String", "System.String", "XLua.LuaTable").implementation = function(a1 , a2 , a3) {
    console.log(`invoke dostring`)

    if (i == 0) {
        setTimeout(()=>{
                console.log(`schedule ok`)
                this.method("DoString").overload("System.String", "System.String", "XLua.LuaTable").invoke(Il2Cpp.String.from(`
CS.UnityEngine.Debug.Log("hello unity engine from chensem")
local a = 3
for k,v in pairs(_G) do 
print(string.format("%s => %s",k,v))
end                          
`) ,Il2Cpp.String.from("chunk"), ptr(0x0))
            // })

        } , 2000)
        i+=1
    }

    return this.method("DoString").overload("System.String", "System.String", "XLua.LuaTable").invoke(a1 ,a2,a3)
}

this snippets perform well image use Il2Cpp.scheduleOnInitializerThread wrapper setTimeOut block failed . image

Chensem commented 1 year ago

close it , i will reopen when i have no idea about this problem

vfsfitvnm commented 1 year ago

These links might help you:

https://github.com/vfsfitvnm/frida-il2cpp-bridge#0713 https://github.com/vfsfitvnm/frida-il2cpp-bridge/issues/195#issuecomment-1168795426

Chensem commented 1 year ago

i encounter a problem image UnityEngine.Debug.Log run on UnityMain thread , so i use Interceptor.attach , Interceptor.replace to do some logic when the logic hit to do my doString logic , i think these two way are all same , but Interceptor.attach offten encounter memory error , but Interceptor.replace maybe stable than Interceptor.replace. image

Chensem commented 1 year ago

Hey , i encounter a problem that let me very confused , snippets like below

Interceptor.attach(Il2Cpp.Domain.assembly("UnityEngine.CoreModule").image.class("UnityEngine.Debug").method("Log").virtualAddress , {
    onEnter(args) {
        const logstr = new Il2Cpp.String(args[0]).toString()
        console.log(`debug log ${logstr}`)
        console.log(`this thread ${this.threadId} but ${Process.getCurrentThreadId()}`)
        if(logstr.indexOf('hello unity engine from chensem') !== -1) {
            Il2Cpp.scheduleOnInitializerThread(()=>{
                console.log(`bingo ${Process.getCurrentThreadId()}`)
                const envInstanceIl2Cpp = Il2Cpp.Domain.assembly("Assembly-CSharp").image.class("LuaGlobal").field('env').value as Il2Cpp.Object
                const doString = envInstanceIl2Cpp.method("DoString").overload("System.String", "System.String", "XLua.LuaTable")
                Interceptor.attach(doString.virtualAddress , {
                    onEnter(args) {
                        console.log(`hhhhhhhhhhhhhhhhhhhh`)
                    }
                })
                doString.invoke(Il2Cpp.String.from(`print("hello unity engine from chensem1234")`) ,Il2Cpp.String.from("chunk"), ptr(0x0))
                console.log(`invoke finish ????`)
            })

        }  
    }
})

this snippet will intercept the unity debug function , and this function is invoked frequently , then log hello unity engine from chensem in other space , so it will trigger Il2Cpp.scheduleOnInitializerThread and invoke the DoString function , so far, things maybe ok . but from the log , we will find the DoString haven't been invoked properly , logs like below . image we use Interceptor.attach to intercept function DoString before invoke it , so expect the hhhhhhhhhh will log to the console , but not , so we can guess the DoString not been invoked . which let me very confused .