rovo89 / XposedBridge

The Java part of the Xposed framework.
3.89k stars 1.1k forks source link

Updating a value in a hook #242

Closed voider1 closed 6 years ago

voider1 commented 6 years ago

While writing my Xposed module I wanted it to communicate with my app so I can update some values. The communication is done with two Broadcastreceiver, and this works fine. Updating the value in the hook didn't work. I tried multiple things.

The first thing I tried was making a class-wide variable which got updated when my BroadcastReceiver got a certain Intent. This variable was referenced inside the beforeHookedMethod. This seems to work until you send an intent which updates the value, it's like the hook never gets the updated value- almost as if it's a hardcoded value, example:

companion object {
    var index = 0
}

findAndHookMethod("foo.bar.baz", lpparam.classLoader, "a", Int::class.java, object : XC_MethodHook() {
            override fun beforeHookedMethod(param: MethodHookParam?) {
                param?.args?.set(0, index);
            }
        })

Imagine it also has some code which updates this periodically on a certain condition. When I update the value, it still sets 0 for some reason.

After that I wrote the value to the shared preferences of the app and instructed the beforeHookedMethod to read the shared preferences and replace the passed argument with the value retrieved from the shared preferences, which also didn't work and made the hooked app crash. (The reading of the preferences part, I reckon). I got a seemingly unrelated null pointer exception.

Any ideas on how I could tackle this?

voider1 commented 6 years ago

I've also tried to just create a new file in the app's internal storage and just on the /sdcard/ and read from that, but I get the same bogus null pointer exception as when I try to read the shared preferences. Writing is no problem, weirdly enough.

voider1 commented 6 years ago

I just tried to use setAdditionalStaticField to attach the value to a class and then use getStaticIntField to retrieve the value inside of the beforeHookedMethod, but that also didn't work out for me and had now effect whatsoever and sometimes induced a crash.

voider1 commented 6 years ago

Also, I'm on Android 7.0 if that makes any difference.

voider1 commented 6 years ago

I just tried letting my application in which I select the option make a SharedPreferences file, hooking the security mechanism and make it do nothing, then tried to read from that with XSharedPreferences, but that also didn't really work out for me.

voider1 commented 6 years ago

findAndHookMethod returns an Unhook object, I've tried saving this globally. When I receive an intent in my broadcast receiver I call the unhook method and I recreate the hook with a different value, this also doesn't work, it doesn't unhook and it doesn't set a new hook.

voider1 commented 6 years ago

I downgraded to Android 6.0 and tried the XSharedPreferences method again, but it seems that it still doesn't work. But something interesting I saw was are the logs. I added more logging to the module and before the method I'm hooking gets invoked I see the logging message a couple of times in logcat. After that I never see the logging message again. No null pointer exceptions anymore.

rovo89 commented 6 years ago

I guess most of the stuff you tried doesn't work because the code gets executed in different processes/classloaders. Say you have a class called MyClass in my_app.apk. Then Xposed will create a new classloader for my_app.apk when the system starts and load MyClass with it. That happens in the "Zygote" process (app_process). Whenever apps are started, the Zygote is forked - and all loaded classes and their memory with it. Any changes you do in App A are neither reflected in App B nor in Zygote, even though it seems to be the same class. Only changes done during startup will be inherited by all apps. And if your own app is started, the system will create another classloader, so you'll end up with two different MyClass classes, both with their own memory.

Also keep in mind that your hooks run in the context of whatever app you're hooking. So you might not have the same permissions that you declared in your own manifest. The UID will also be different, so you probably can't access anything in your app's directory.

voider1 commented 6 years ago

Is there any way to do what I want to do? I have the right permissions to read and write external storage, reading also doesn't give me null pointer exceptions anymore, but it also doesn't affect the value in my hook for one reason or another.

rovo89 commented 6 years ago

And what exactly do you want to do? I don't mean how you want to do it, but what your ultimate goal is.

Obviously, you need to make sure that your BroadcastReceiver or your file reading is done in the same process as where your hook is executed, otherwise the updated values won't have any effect.

voider1 commented 6 years ago

I want to choose a value in the UI of my own app and then make sure my hook receives the value and replaces the argument of a method with that value. That's my ultimate goal. My broadcastreceiver is started like this:


private var context: Context? = null
        set(value) {
            if (context == null) {
                value?.let {
                    setupBroadcastReceiver(it)
                    field = it
                    log("Retrieved context")
                }
            }
        }

override fun handleLoadPackage(lpparam: XC_LoadPackage.LoadPackageParam?) {
        if (lpparam?.packageName != packageName) {
            return
        }
        Main.lpparam = lpparam

        log("Hooked")

        setCouponIndex(null)

        findAndHookMethod("android.app.Application", lpparam.classLoader, "onCreate", object : XC_MethodHook() {
            override fun beforeHookedMethod(param: MethodHookParam?) {
                val app = param?.thisObject as Application
                context = app.applicationContext
            }
        })
}

So when the hook sets the context to a value, a method gets called which sets up the entire broadcastreceiver. I did the file reading inside of the hook, so that should be alright.

voider1 commented 6 years ago

@rovo89 Ping. Sorry, I'm really lost on this one.

rovo89 commented 6 years ago

So your BroadcastReceiver should run within the process of the hooked app, good. To verify that, I suggest you do some logging and compare the PIDs. If you change a static variable from that receiver, it should be reflected in the hook running in the same process. Do you mind posting the relevant code of the receiver and the hook? I'm not familar with Kotlin, so no idea what that "companion object" in your first comment is.

voider1 commented 6 years ago

I confirmed that the PID of the BroadcastReceiver and the method hook are the same, so they're running in the same process indeed. The companion object is akin to Java's static modifier. For some reason it's not reflected, and what I find interesting is the log. When I start the app I know that the method I hook isn't called, it's only called the moment I click on a button. Even though that's a fact I see the log message from inside the beforeHookedMethod a couple of times (else branch of course). But when I then do click the button I get no logs and it seems the value never gets reflected.

    companion object {
        lateinit var lpparam: XC_LoadPackage.LoadPackageParam
        var couponIndex: Int? = null
    }

        findAndHookMethod(couponReadingClazz, lpparam.classLoader, staticMethodReadCouponFromResources, Resources::class.java, Int::class.java, object : XC_MethodHook() {
            override fun beforeHookedMethod(param: MethodHookParam?) {
                if (couponIndex != null) {
                    param?.args?.set(1, couponIndex)
                    log("Set second argument to: $couponIndex")
                } else {
                    log("No couponIndex set")
                }
            }
        })

       val receiver = object : BroadcastReceiver() {
            override fun onReceive(p0: Context?, intent: Intent?) {
                val action = intent?.action?.let { Action.de(it) }
                log(Action.ser(action!!))

                when (action) {
                    Action.SetCoupon -> {
                        val couponIndex = intent.getIntExtra("coupon", -1)
                        log("Got value $couponIndex from intent.")

                        if (couponIndex > -1) {
                            resetCoupon(context)
                            Main.couponIndex = couponIndex
                            log("Wrote value: $couponIndex, last value was: ${Main.couponIndex}")
                        } else {
                            log("Got an invalid coupon index.")
                        }
                    }
                }
           }

Logs:

04-02 23:19:12.857 8896-8896/nl.subway.subway I/Xposed: [Subway PWN] Hooked
04-02 23:19:13.107 8896-8896/nl.subway.subway I/Xposed: [Subway PWN] Registered BroadcastReceiver
    [Subway PWN] Retrieved context
04-02 23:19:13.857 8896-8896/nl.subway.subway I/Xposed: [Subway PWN] No couponIndex set
    [Subway PWN] No couponIndex set
    [Subway PWN] No couponIndex set
    [Subway PWN] No couponIndex set
    [Subway PWN] No couponIndex set
    [Subway PWN] No couponIndex set
04-02 23:19:13.857 8896-8896/nl.subway.subway I/Xposed: [Subway PWN] No couponIndex set
    [Subway PWN] No couponIndex set
04-02 23:20:10.647 8896-8896/nl.subway.subway I/Xposed: [Subway PWN] SETCOUPON
04-02 23:20:10.647 8896-8896/nl.subway.subway I/Xposed: [Subway PWN] Got value null from intent.
    [Subway PWN] Coupon reset
    [Subway PWN] Writing value: 3, last value was: null

After I click the button which invokes the method I hooked, nothing gets logged and it doesn't seem to have any effect whatsoever.