rovo89 / XposedInstaller

3.89k stars 1.51k forks source link

System Services #51

Open dbergloev opened 11 years ago

dbergloev commented 11 years ago

Okay, I said that I would look into adding Services via Xposed, and this is what I have come up with so far.

package android.os;

/*
 * The AIDL Interface
 */

/** {@hide} */
interface IXAService {
    String getText();
}
package com.android.server;

import android.content.Context;
import android.os.IBinder;
import android.os.IXAService;
import android.os.RemoteException;

/*
 * The actual service that will be registered with Android.
 * This instance will be the one getting parsed as an IBinder instance when 
 * calling ServiceManager.getService("xa.service")
 */
public class XAService extends IXAService.Stub {

   /*
    * The hook that will get the service registered in the ServiceManager
    */
    public static void inject() {
        XposedTools.hookMethods(
            XposedTools.findClass("com.android.server.am.ActivityManagerService"), 
            "main", 
            new XC_MethodHook() {
                @Override
                protected final void afterHookedMethod(final MethodHookParam param) {
                    Context context = (Context) param.getResult();

                    XposedHelpers.callMethod(
                            XposedHelpers.findClass("android.os.ServiceManager", null), 
                            "addService", 
                            new Class[]{String.class, IBinder.class}, 
                            "xa.service", 
                            new XAService(context)
                    );
                }
            }
        );
    }

    private final Context mContext;

    public XAService(Context context) {
        mContext = context;
    }

    @Override
    public String getText() throws RemoteException {
        return "This is a test";
    }
}
package com.my.package;

import com.android.server.XAService;

/*
 * You made it, so you know what this is :)
 */
public final class XposedHook implements IXposedHookZygoteInit {
    @Override
    public void initZygote(IXposedHookZygoteInit.StartupParam startupParam) throws Throwable {
        XAService.inject();
    }
}
package android.os;

/*
 * This is so that regular application can use the wrapper XAServiceManager. 
 * For some reason XposedHelpers.getClass() will fail when using an application ClassLoader
 */
public class ServiceManager {
    public static IBinder getService(String name) { return null; }
}
package android.os;

/*
 * This is a wrapper for XAService that is accessible to all. 
 * Based on how all Android services work.
 */
public final class XAServiceManager {

    private static XAServiceManager oInstance;

    private IXAService mService;

    public static XAServiceManager getService() {
        if (oInstance == null) {
            oInstance = new XAServiceManager();
        }

        return oInstance;
    }

    private XAServiceManager() {
        mService = IXAService.Stub.asInterface(
                ServiceManager.getService("xa.service")
        );
    }

    public String getText() {
        try {
            return mService.getText();

        } catch (RemoteException e) { e.printStackTrace(); }

        return null;
    }
}
package com.my.package;

/*
 * Just a test activity
 */
public class MyActivity extends Activty {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        XAServiceManager manager = XAServiceManager.getService();
        Toast.makeText(this, manager.getText(), Toast.LENGTH_LONG).show();
    }
}

Normally, all system services is registered from com.android.server.SystemServer, however they are registered within a Thread where you will have no access to the context no mater if you hook before or after. Instead I hooked it in com.android.server.am.ActivityManagerService.main() which is the first call made by the SystemServer thread to get the system context, right before all of the services are created and registered.

After this, you can use ServiceManager.getService(String name) like with any other system service. However, this is only available to Android, so like Android does it, I made a wrapper for applications (XAServiceManager) which is available to all. And if one should fallow the true Android syntax, you could also hook android.os.ContextImpl and add it to Context.getSystemService() if you don't like to use XAServiceManager.getService().

Either way, this will create a service that can be used to do anything. And the context in XAService.mContext is the System Context, so it should have access to anything. And it can be used to share data between multiple processes as XAService will be the same no mater where you use it from. And you can decide which part of it should be accessible globally by just excluding/including it in the wrapper XAServiceManager, just like Android does.

A lot of fun stuff can be created with this, but my first thought, which is why I looked into it in the first place, was to share data between processes without having to write files or database entries. So maybe this could be used to create some standard Xposed communication API to share things like property values and such. This could be small configs between settings and module (Since a static class will do no good here) or just data sharing between two separate Android processes. The biggest issue is properly the cleanup part if devs forget to clear some of the old unused data.

Also, normal applications is not able to create this type of Service. This type is only meant for the parts of Android that contains the same Context throughout the lifetime of the OS. So it will need to be added into ServiceManager via a Zygote hook.

rovo89 commented 11 years ago

Sweet. :) Looks like a good step into the direction of providing an IPC API. Possibly Xposed could register such a service by default. There are some Android concepts like Handler which could be used for some generic messaging system where apps can add their callback for a certain message type and send such messages via an API. What I'm just wondering is: Would you be able to push data to a certain app with this? Or would you need to pull for config changes?

dbergloev commented 11 years ago

The service operates just like any normal class instances. The difference is that it is the same instance on all processes using it and everything is done using the outer most system context. So you can for an example set a property from one process and collect the value from another.

Using this, you can make whatever features you like. You can merge permissions, threads, handlers, broadcasts etc into one large Xposed Service providing all kinds of different tools. Using this it will also be possible to make tools to interact with parts of the system without first adding hooks.

And to make it two way, like calling one application by a trigger from another, you can make some register methods where applications can parse certain abstract classes or interfaces, like when registering a Broadcast Receiver.

But I have not yet played with this, I only made it work. For starters I will try to make a shared cache for preferences which can be updated from the settings part and then used in the module without having to send a broadcast and have the module reload the sharedPreferences every time. I might also see if the system context has enough permissions to actually change the applications sharedPreferences file. If so, you could add your XSharedPreferences to such a service where it would also be able to store values and not just read them. And we might even be able to skip all of this Global Readable which is currently needed by XSharedPreferences to gain access.

dbergloev commented 11 years ago

B.t.w, in your XSharedPreferences class, have you considered using Context.createPackageContext().getSharedPreferences() instead of the more manual XML parsing?

rovo89 commented 11 years ago

I'm still not sure if that would save the module from reloading the settings every time. It would have to call the service instead, right? That's why I was asking if it is possible to push values to certain processes (I can't think of a way right now, except for sending an intent to it and have them implement a dynamic BroadcastReceiver).

Also an important thing to consider: Pretty much every solution could be used by any app (e.g. using reflection). So that could open up some security gaps. So that's something that needs to be considered before publishing an API or recommending a best practice.

Regarding XSharedPreferences: This is strictly a read-only implementation to avoid that a file with wrong permissions is created, e.g. if you load the settings in initZygote() or a different app than your own. That's one reason for not using the existing preference classes. Another one is that your example needs a Context, which often doesn't exist when the preferences are needed.

dbergloev commented 11 years ago

No reloading, that is the point. The service makes sure that one instance is shared across processes. So if the settings part makes a change, the module part will get that change right away.

The below is the service that I am making for my module. It is of cause made specifically for that module, but it still gives an idea.

public class XAService extends IXAService.Stub {

    private static XAService oInstance;

    private Context mSystemContext;
    private Context mApplicationContext;

    private Map<String, Object> mPreferences = new HashMap<String, Object>();

    public static void inject() {
        final Class<?> ActivityManagerServiceClazz = XposedTools.findClass("com.android.server.am.ActivityManagerService");

        XposedTools.hookMethods(
            ActivityManagerServiceClazz, 
            "main", 
            new XC_MethodHook() {
                @Override
                protected final void afterHookedMethod(final MethodHookParam param) {
                    Context context = (Context) param.getResult();

                    oInstance = new XAService(context);

                    XposedTools.callMethod(
                            XposedTools.findClass("android.os.ServiceManager"), 
                            "addService", 
                            new Class[]{String.class, IBinder.class}, 
                            "xa.service", 
                            oInstance
                    );
                }
            }
        );

        XposedTools.hookMethods(
            ActivityManagerServiceClazz, 
            "systemReady", 
            new XC_MethodHook() {
                @Override
                protected final void afterHookedMethod(final MethodHookParam param) {
                    oInstance.systemReady();
                }
            }
        );

        File appDir = new File(Environment.getDataDirectory(), "data/" + Common.PACKAGE_NAME);

        if (appDir.exists()) {
            File packageList = new File(Environment.getDataDirectory(), "system/packages.list");
            File preferencePath = new File(appDir.getPath(), "shared_prefs");
            File preferenceFile = new File(preferencePath.getPath(), "test_preference.xml");
            int uid = 1000;
            int gid = 1000;

            if (packageList.exists()) {
                try {
                    BufferedReader buffer = new BufferedReader(new FileReader(packageList));
                    String line;

                    while ((line = buffer.readLine()) != null) {
                        if (line.startsWith(Common.PACKAGE_NAME + " ")) {
                            String[] parts = line.trim().split(" ");

                            uid = Integer.parseInt(parts[1]);

                            break;
                        }
                    }

                    buffer.close();

                } catch (Throwable e) { e.printStackTrace(); }
            }

            if (!preferencePath.exists()) {
                preferencePath.mkdir();
            }

            if (!preferenceFile.exists()) {
                try {
                    FileOutputStream stream = new FileOutputStream(preferenceFile);

                    XposedTools.callMethod(
                            XposedTools.findClass("com.android.internal.util.XmlUtils"),
                            "writeMapXml",
                            new HashMap<String, Object>(), 
                            stream
                    );

                    XposedTools.callMethod(
                            XposedTools.findClass("android.os.FileUtils"),
                            "sync",
                            stream
                    );

                    try {
                        stream.close();

                    } catch (IOException e) {}

                } catch (FileNotFoundException e) { e.printStackTrace(); }
            }

            FileUtils.setPermissions(preferencePath.getPath(), 0771, uid, gid);
            FileUtils.setPermissions(preferenceFile.getPath(), 0660, uid, gid);
        }
    }

    public XAService(Context context) {
        mSystemContext = context;
    }

    @SuppressWarnings("unchecked")
    private void systemReady() {
        try {
            mApplicationContext = mSystemContext.createPackageContext(Common.PACKAGE_NAME, Context.CONTEXT_RESTRICTED);

        } catch (NameNotFoundException e) { e.printStackTrace(); }

        File preferenceFile = new File(Environment.getDataDirectory(), "data/" + Common.PACKAGE_NAME + "/shared_prefs/test_preference.xml");

        if (preferenceFile.exists()) {
            try {
                BufferedInputStream stream = new BufferedInputStream(new FileInputStream(preferenceFile), 16*1024);

                Map<String, Object> map = (Map<String, Object>) XposedTools.callMethod(
                        XposedTools.findClass("com.android.internal.util.XmlUtils"),
                        "readMapXml",
                        stream
                );

                if (map != null) {
                    mPreferences = map;
                }

            } catch (FileNotFoundException e) { e.printStackTrace(); }
        }
    }

    @Override
    public String getStringPreference(String name, String defaultValue) throws RemoteException {
        return (String) mPreferences.get(name);
    }

    @Override
    public void putStringPreference(String name, String value) throws RemoteException {
        mPreferences.put(name, value);
    }

    @Override
    public void applyPreferenceChanges() {
        // Write changes to file
    }
}

If the Settings part of a module app calls putStringPreference, then every part of the module will get that new value the next time they call getStringPreference because the change is done to the property in the service class instance.

new XC_MethodHook() {
    XAService mService;

    @Override
    protected final void afterHookedMethod(final MethodHookParam param) {
        if (mService == null) {
            mService = IXAService.Stub.asInterface( ServiceManager.getService("xa.service") );
        }

        if (mService.getStringPreference("key", "default").equals("something")) {
            ...
        }
    }
}

But yes, there is of cause the security issue. Android controls these things using permissions. What you need for Xposed is similar, only it should instead check whether or not the caller (application) is enabled in XposedInstaller. But like I said, I have only just started playing with this, so I have not yet gotten to this part, but it should not be impossible to implement this sort of security.

And about XSharedPreferences, try taking a look in inject() in the top example. That method actually creates a better protection. It creates the preference file with the apps uid but using the systems gid. Then it only allows access to user and group, which means that only the app itself and Android can read and write to the file. However, the regular SharedPreference class still can't write to it from the system context, don't know why. But the regular file tools can, so it just requires to add a custom write implementation and your service can read and write to the preference files without allowing global access to it, and without the need for the application context, which b.t.w is always possible from the service, as you have the system context.

rovo89 commented 11 years ago

Ahhh, I think the key point is that the call to get the settings goes via Binder/IPC to the system_server process every time. Well, I'm actually not sure whether this is faster than reloading the file every time (which is usually just one or two stats, it will only reload if necessary). Also it will only work after the system server has been initialized, i.e. not in initZygote(). Still it might be an alternative in some cases, especially if it's something more complex and not called too often.

Permissions: That concept works based on UIDs. However, any Xposed code kind of "melts" with the application code, so it has the same UID, same PID, same permissions, ... You can't really differentiate between the two, that's the problem with any solution. And even if there was a check in the Xposed API code based on a classloader or something, what would stop the application from using the same communication channel? Anyway, that's not something for the first step, but we have to remember it.

Regarding XSharedPreferences: As I said, it's read-only and it will stay like this. It's just too dangerous otherwise. Only root can change the owner of a file, so if it's created by an application process, it will be stuck at that process' user. I also want to keep my hands off that file structure, I think the system can handle it best.

One additional hint: I used to use /data/system/packages.list, but it turned out to be unreliable (e.g. empty) on some ROMs.

dbergloev commented 11 years ago

If packages.list is empty, one could fall-back on packages.xml, it's just that packages.list is faster to check.

The protection part of the IPC is a mater of protecting disabled modules and none-xposed apps from accessing it. So it does not mater if Xposed modules UID's melts together with the system processes as none-exposed apps and disabled modules will not have any active hooks that can melt with them anyways. So you would only have to check application uid's and skip system id's.

About speed, I don't think that is something to worry about. The point of the System Services is that they can be used as regular instances. That is also how Android uses them, and by looking in Android's internal code, it does not seam that Google is all that worry about the speed of this. Even their Key Event query makes multiple service calls on each key down event. It might be called a "connection", but it is local. There is no external linking with a network in between. Android also connects to it differently than with regular binded services, which is also why you can connect and use it right away. You don't have to wait for a connection to establish because there is no real connection. All it really does, is parse your call from one Java method through a Native part to another Java method. But is that not how all JVM calls work? Is that not what XposedBridge exploits to add it's features?

I am almost done with my service. Once I get it added to the rest of the module, I will test the speed difference between a local IPC call and a regular instance call.

rovo89 commented 11 years ago

Well, why not call getPackageManager(),getApplicationInfo() and use the "uid" field of the returned object? That would be a very clean way. As the service won't run before the system is ready, that package manager should be available at least when the first call is made.

I don't understand what you say about UIDs. Let's imagine there is some API which can send data to the system process. Let's say you hook SomeApp and want to retrieve your settings from there. So you hook something like Activity.onCreate() in SomeApp, then use the API to load the data. The system process just sees that the call comes from SomeApp's UID and process, it cannot know that the call came from the code that your module has injected. Obviously, SomeApp isn't an active Xposed module, but the call should work anyway to make any sense. So calling UID and process can't be criteria to differentiate between allowed and not allowed calls. But simply allowing any call isn't an option either. SomeApp could have some "sleeping" code which checks for the XposedBridge class via reflection. I think that is possible already, theoretically any app should be able to hook itself if Xposed is active (even if no modules are enabled). That's not really a problem because it can't escape its own process. However, with an IPC API, it actually could. If that API supports more than just reading some settings, it could send malicous things to the system process, even without being enabled. And the system process wouldn't even know, because all it sees is the UID and the PID as far as I know.

I would love to see some speed comparisons. It's entirely possible that it's not an issue. Just one comment on what you said: "All it really does, is parse your call from one Java method through a Native part to another Java method." It at least has to speak "somehow" with a different process. So it's more than just calling a few methods. It might be fast anyway.

I hope my comments don't demotivate you to much, I'm just trying to think of as many difficulties as possible. I had lots of great discussions with Tungstwenty, and I think that led to a stable API.

dbergloev commented 11 years ago

Demotivate? Why, I don't mind being proved wrong, you learn from it. Besides you do have a few points. For one, I did not think about injecting regular apps, so you are right about the security checks, the uid's is of no help here. But do note that Android's own services is not all that protected either. So, for starters one could start thinking about service features that would do no harm, and would not require to much security. For an example forcing a refresh on some UI part or what not, not quite sure as I have not yet encountered any needs for something like that. Maybe someone else has with their modules. The point anyway is that the service can be used for anything, not just to implement dangerous injection features or controlling shared preferences, but also smaller and more simple but yet useful things.

The reason that I don't use Package Manager, is because the file, ownership and permissions is done from Zygote as I need root. I don't think the package manager is ready at this time (At least no services has yet been started) and I would also need a Context which I don't have yet, right?

And yes, the IPC calls it passed between processes (the whole point of it). But the processes is manages by Zygote, so I guess (not sure yet) that calls is just passed through there. But still, not much different than how it works in general. Data is constantly being passed through multiple processes, the OS would not work otherwise, so a few additional pass troughs can hardly make a big difference. Jellybean has almost double amount of services since gingerbread, and a lot more calls are made between them. Still JB runs much faster (Compared on an old HTC Legend), so it does not seam that services is what makes up the difference.

rovo89 commented 11 years ago

Yes, there are definitely use-cases which wouldn't be a security risk, so it wouldn't matter too much if some app calls them. So it's really a question what functionality exactly a module or the framework exposes via IPC. That kind of excludes very generic methods. For normal system services, every app can call them, but the service itself denies access if it was called from a UID without the required permission.

Right, if you create the file from Zygote, the package manager doesn't exist yet. You could check the owner of /data/data/the.package.name/ though, pretty sure it's always owned by the app.

dbergloev commented 11 years ago

hmm, well I take some of the above back. Just did some testing, and there is not only a difference, but a huge difference.

I had a method connect to the IPC instance of my service and also have it create an instance of a local test class. I then made two equal methods in both the and had a loop run through the IPC and the local instance 100 times.

IPC Instance = 58258057ns (About 45ms) Local Instance = 427246ns (About 3ms)

I had hoped on a lot less, but it's good to know.

rovo89 commented 9 years ago

@dk-zero-cool Did you happen to investigate further about this topic, especially about security aspects? I have written much low-level code to work around SELinux, and a native Binder service could become a piece of the puzzle. The question is mainly: How can we make sure that this API (or some parts of it) is only used by module code, but not the hooked app itself (via reflection or something)?

dbergloev commented 9 years ago

Yes I did make this work, and I wrote a small article about it.

I did not however work the security aspects. However, the way it normally works, is by creating an instance of your service, and save it to the service manager. From here you can easily get that instance from anywhere, by collecting it from the service manager and use the interface to communicate with it. What if you skip that part? Save the instance of an Xposed Service somewhere else. Then create a caller for your existing param object.

new XC_MethodHook() {
    @Override
    protected final void afterHookedMethod(final MethodHookParam param) {
        XposedService service = param.getService();
    }
}

The service will not be available from anywhere, except from hooked instances. And there is no need to access it from anywhere else. Well, maybe from a module control app, but not sure how to handle that part yet. It really depends on what you want to use it for.

EDIT:

If you want to allow normal apps, like module config/control apps to access this service also, you can always add something like XposedBridge.getService(). Since these are not hooked classes, you can do a normal access check on each call, and make sure that this app is enabled in Xposed. So you can have the param object for hooked instances and the other for regular app instances.

M66B commented 9 years ago

I am using a similar service in XPrivacy, because XML settings files have proven to be problematic and appeared to be the cause of many bootloops due to locking. With tight SELinux restrictions there will even be new problems with XML settings files.

Performance on modern devices doesn't seem a problem, but I have to mention XPrivacy does cache data on both the calling and called side of the service to maximize performance (restrictions do not change that often).

It is of course possible to query the calling uid from within the service, but I guess this is not enough to determine if the call is allowed in all cases. To prevent spamming the usage log of XPrivacy by malicious apps, XPrivacy does generate a secret in initZygote, which is being passed to the hook classes (a small wrapper class around all hooks), which passes the secret in the calls to the privacy service, which checks the secret against the original secret. Maybe a similar approach can be used for Xposed.

rovo89 commented 9 years ago

From here you can easily get that instance from anywhere, by collecting it from the service manager and use the interface to communicate with it. What if you skip that part? Save the instance of an Xposed Service somewhere else.

My initial idea went into this direction. I had read that Binders are identified by handles, so I thought I could simply get such a handle in Zygote and pass it on to the apps. Then I researched in detail how Binders work (especially the kernel code is interesting) and I don't think this is possible:

Binder handles are small integers. There doesn't seem to be a way to create a handle for an object "manually". It wouldn't be useful anyway, as each process seems to start with handle 1, so the same handle refers to different objects in other processes. Handles are created by the driver when necessary. Process A writes a Binder object to a Parcel and calls a transaction on a service that it has a reference to. If this service lives in process B, it automatically creates a new handle (valid only for process B) and passes it on instead of the pointer to the object. When process B reads the Binder from the Parcel, it receives a Proxy instead of the actual object. In case it talks back to Process A, the handles are automatically converted back to the original pointers.

What we need in each process is such a Proxy, which we only get when we get a handle, which will get a) if someone calls a method in our process or b) as the result of a call we make. For a), the other process would need a proxy for an object in our process, and how should it get one? For b), we would need a proxy for an object in the other process, which is the problem we're trying to solve. The only proxy we can get without knowing anything is the one for the service manager, because it has the magic binder handle 0 and can always be resolved.

The service will not be available from anywhere, except from hooked instances. I'm not so sure about it. Think about reflection and stuff. If XposedBridge should pass the service to the methods, it has to store it somewhere in the very same process. We have to make it as hard as possible to access this information from the rest of the app. Also, the app could try to hook itself to get the reference to the service. Normally this wouldn't be a problem because it could only modify its own process, but the whole intention of this idea is being able to influence other processes.

It is of course possible to query the calling uid from within the service, but I guess this is not enough to determine if the call is allowed in all cases. Correct, this would only work to allow a complete app to access the service. Both the app and the modules that hook it have the same UID.

To prevent spamming the usage log of XPrivacy by malicious apps, XPrivacy does generate a secret in initZygote, which is being passed to the hook classes (a small wrapper class around all hooks), which passes the secret in the calls to the privacy service, which checks the secret against the original secret. Maybe a similar approach can be used for Xposed.

I think we have talked about this before, and it's basically what I would like to use. But like I mentioned above, we have to make sure that the secret cannot be accessed via reflection etc., all while being transparent for the module. Maybe I could implement a substitute for the transact() call natively and add the secret there.

dbergloev commented 9 years ago

I am not very familiar with IPC, but here is how I see it, maybe I am wrong.

Everything in Android is running in multiple processes, and each process has it's own little sandbox (closed section in RAM). Anything created and/or changed within a particular process, will only affect that specific process. Even static classes cannot share data between processes, since each one will get their own instance of the class. System services are just normal class instances created within the android process, but since this process is consistent during the uptime of the OS, these instances will never be destroyed.

When we call a system service, from an app for an example, what we really get, is kind of like a network connection, just that this connection communicates across memory sections (processes) instead of across computers. And instead of working with a local instance, we retrieve and send data between our process and the original instance stored in another process, in this case the android process. This allows multiple processes to work with the same instance and the same data it contains.

Now if this is correct, then what you need, is to parse a connection to each hook. And to avoid reflection security problems, you need to establish and store these connections natively. You don't need a new connection per hook, only per process. And since multiple processes cannot share the same class data, you should be able to create/extend a native class to create one, if one does not already exist. Then it is only a mater of parsing this connection to each hook in the process.

I am not much of a c++ programmer, so I cannot give you any proper examples, but I can try a more general one.

/*
 * This should be some native class that handles the hook calls for each process. 
 * This is properly in the system_process binary, but you would know more about this than I. 
 */
class someClass {
    IBinder mProxy;

    void someMethod() {
        if (mProxy == null) {
            mProxy = // Establish a connection
        }

        /*
         * Call whichever part of XposedBridge that handles hook call, and parse the IBinder object.
         * From there, parse it to each hook. 
         */
    }
}
rovo89 commented 9 years ago

What you wrote about processes and their own copies of all the classes is absolutely correct. The comparison with network connections is fine on a higher abstraction level, although the details differ significantly.

Now if this is correct, then what you need, is to parse a connection to each hook.

I assume you mean "pass" instead of "parse"? Anyway, I'm afraid that even leaving all security considerations aside, creating a connect won't be possible. Using your example with network connections, you obviously need to know the IP and port representing the server to connect to. But now imagine that a server doesn't have one IP address that all clients can connect to. Each client needs to use a different IP to reach the same server, and the same IP could refer to different servers if you connect from different clients. That's basically my understanding of Binders, with the actual objects as servers and the Binder handles as IP addresses.

Obviously, this means that the server can't determine or store its IP address somewhere - there is no such thing as the IP address, there are many possibilities. And the clients need a way to determine their personal IP address for the server. Either they have to ask a central registry (= the service manager) for it, or somebody (the server or some other client) needs to know that the client's own IP address and call it actively, submitting the server's IP address in the parameters. I don't see how the second option could work, as the sender would have the same issues figuring out their IP address for the client. And option one is known to be working, as the service manager has the only fixed IP (= binder handle 0) in the system, but obviously everyone can ask it for the server's IP address.

dbergloev commented 9 years ago

Yes I see your point. You have no way to access the original instance, without storing it in the Service Manager. Where is the Service Manager controlled from? Since you are already adding/changing native files, could you not add your own handle for your service in the same way as done in the Service Manager?

Otherwise we are back at secrets. Create a random validation secret of some sort, and have that being passed around during hook calls. Then store your service in the Service Manager, and pass that secret on each call to a service method. Alternatively, create a Service relay class that does this automatically, to avoid having users doing it during each call.

new XC_MethodHook() {
    @Override
    protected final void afterHookedMethod(final MethodHookParam param) {
        /*
         * getService() returns a new instance of the XposedService relay class, passing param.secret along. 
         * The relay class instance then gets the binder from the service manager. 
         * The relay class contains all of the options that the service has, and just relays all calls to a 
         * service method of the same name, passing the internal secret along with it.  
         */
        XposedService service = param.getService();

        /*
         * This will call XposedService.mService.testMethod( XposedService.mSecret )
         */
        service.testMethod();

        /*
         * This will call XposedService.mService.testAddMethod( XposedService.mSecret, value )
         */
        service.testAddMethod(string value);
    }
}

The secret should be stored within a native variable/property, and of course be randomly created, for an example during boot.

rovo89 commented 9 years ago

Where is the Service Manager controlled from? Since you are already adding/changing native files, could you not add your own handle for your service in the same way as done in the Service Manager?

The service manager is a separate executable. I can add my service to it with some tricks (to work around SELinux restrictions). Creating a "magic" handle like 0 for the service manager is not possible, that logic is included in the kernel. Even if that was possible, it would mean that every process could use it, so it would no longer be secret.

So yes, it seems that I have to use a random secret. I think a good point to add the secret would be right after Parcel.obtain() in the proxy implementation... well, let's see.

sorgelig commented 9 years ago

It seams on Lollipop with SELinux (especially in enforcing mode) no longer possible to call addService. Is there a way to overcome this without modifying /file_context?