I. Introduction

At present, there are many business modules that provide Deeplink services. In simple terms, Deeplink is to provide entrances to external applications.

For different types of jumps, apps may choose to provide inconsistent services. At this time, it is necessary to distinguish the applications that jumped into from outside. Generally speaking, we will use reflection to call the mReferrer field in Acticity to get the package name of the jump source.

The specific code is as follows;

/**
 * 通过反射获取referrer
 * @return
 */
private String reflectGetReferrer() {
    try {
        Field referrerField =
        Activity.class.getDeclaredField("mReferrer");
        referrerField.setAccessible(true);
        return (String) referrerField.get(this);
    } catch (NoSuchFieldException e) {
        e.printStackTrace();
    } catch (IllegalAccessException e) {
        e.printStackTrace();
    }
    return "";
}

But is it possible for mReferrer to be forged?

Once mReferrer is forged, the business logic will be faulty and the economic loss will be caused. In this case, is there a way to find a safer source?

This requires an analysis of the source of mReferrer. Let's perform an alternative source code analysis of mReferrer source. The reason why it is alternative is that this time I will use a lot of debugging methods to reverse the source code analysis.

2. Where does mReferrer come from

2.1 Search mReferrer, trace back the source

Use the search function to search for mReferrer in the Activity class; use the Find Usages function to find the mReferrer field.

Assign a value to mReferrer in the Attach method of Activity.

2.2 Use breakpoint debugging to trace the call stack

We add a breakpoint on the Attach method, and track the call of Attach through the breakpoint;

The red box is the call path of Attach. The call stack is executed in the main thread; it can be seen from the call stack that Attach is called by ActivityThread.performLaunchActivity.

When performLaunchActivity calls Attach, the referrer parameter of r is passed in, and r is an ActivityClientRecord object.

We further find the place where referrer is assigned in ActivityClientRecord, which is the constructor of ActivityClientRecord.

Add a breakpoint in the constructor to view the call stack;

It is found that ActivityClientRecord is instantiated in the execute of LaunchActivityItem, and the mReferrer of LaunchActivityItem is passed in.

The mReferrer of LaunchActivityItem is assigned in the setValues method. We need to debug to see who called setValues. When we use conventional breakpoints to view the caller of setValues, we will find such a situation.

Explain that LaunchActivityItem is an object generated by deserialization after serialization in the local process.

In Activity, serialized object transmission is usually done using binder, and the server of the binder is in the System process. Deserialization is implemented here, so there must be a serialization process in the remote binder service. We can debug this breakpoint in the System process, which should be the process of serialization.

2.3 Breakpoint debugging

The way to debug the System process is also relatively simple;

  • Step1: Download and install the X86 emulator that comes with Android (note that the google api version must be installed, the play version does not support debugging system processes).
  • step2: Select the System process when debugging.

Through debugging, we found the assignment stack (note that the process shown on the stack here is already a Binder process).

We follow this stack's instructions step by step. Here we need to pay attention. When we look at the debug stack, we only need to pay attention to the class name and method name, and we don’t have to pay attention to the line number in the stack, because the line number Not necessarily accurate. If you find that the difference is too large during debugging, you can try to change to an emulator version.

Follow up here to the realStartActivityLoacked method of ActivityStackSupervisor.

In ActivityStackSupervisor, we found that this parameter is from r.LaunchedFromPackage, this r is ActivityRecord, look for the assignment of LaunchedFromPackage, and finally find the initialization method of ActivityRecord.

2.4 Object instantiation process

Add breakpoints in the initialization method for stack debugging;

Follow the stack to see step by step, into the execute method of ActivityStarter, here you can see that the source of the package is mRequest.callingPackage.

By searching for the Vaule write of the callingPackage object pair of Request, the source of mRequest.callingPackage is the setCallingPackage method of ActivityStarter, and the setCallingPackage method must be called to realize the injection of callingPackage content.

Looking at the stack in the previous step, this method is called by the startActivity method of ActivityTaskManagerService; startActivity uses setCallingPackage to pass in the package during construction. It is consistent with our previous guess.

The analysis here is close to the truth.

2.5 Analysis of remote service Binder call

We all know that ActivityTaskManagerService is a remote service, which can be seen from the process in which it works, and it is a binder process. Because ActivityTaskManagerService extends IActivityTaskManager.Stub, then we have to find the place where IActivityTaskManager.Stub is called remotely.

To find the place of his remote call, we must first find out how IActivityTaskManager.Stub was obtained by the caller.

Search globally for IActivityTaskManager.Stub or search for IActivityTaskManager.Stub.asInterface, here for the convenience of using the online Android source code search platform.

We found the following code in ActivityTaskManager;

@TestApi
@SystemService(Context.ACTIVITY_TASK_SERVICE)
public class ActivityTaskManager {
 
    ActivityTaskManager(Context context, Handler handler) {
    }
 
    /** @hide */
    public static IActivityTaskManager getService() {
        return IActivityTaskManagerSingleton.get();
    }
 
    @UnsupportedAppUsage(trackingBug = 129726065)
    private static final Singleton<IActivityTaskManager> IActivityTaskManagerSingleton =
            new Singleton<IActivityTaskManager>() {
                @Override
                protected IActivityTaskManager create() {
                    final IBinder b = ServiceManager.getService(Context.ACTIVITY_TASK_SERVICE);
                    //这里生成了远程调用对象
                    return IActivityTaskManager.Stub.asInterface(b);
                }
            };
 
}

That is to say, the remote call handle of IActivityTaskManager.Stub can be obtained through the ActivityTaskManager.getService() method.

So the startActivity method call of ActivityTaskManagerService should be written as ActivityTaskManager.getService().startActivity. The next step is to find the place where this method is called.

2.6 Omnipotent search is not universal

According to the normal thinking, we will use the search function to search for ActivityTaskManager.getService().startActivity on this online source code website.

Can't search? It must be noted here, because there are many parameters in the startActivity method, it is possible that the code is wrapped. Once the line is wrapped, searching for ActivityTaskManager.getService().startActivity will not find it.

Search is not a panacea, we still consider adding breakpoints to try.

So where should the breakpoint be added? Can we add breakpoints to the startActivity of ActivityTaskManagerService?

The answer is no, if you try to add a breakpoint on a method called by a binder process (remote service call). Then you will only get the following call stack.

Obviously, the call stack points directly to the remote end of the binder, which is not the call stack we want. We know that the source code for calling startActivity must be ActivityTaskManager.getService().startActivity.

And this line of code must be called in the App process, which belongs to the client of the binder, so we try to add a breakpoint on getService(). Pay attention after adding a breakpoint here, because startActivity at this time should be called by the attacker, that is, called by the Deeplink application.

so. We need to debug the initiator of Deeplink. We can write a Demo for debugging.

Click the button to initiate Deeplink, and then breakpoint, this time you can find the following stack.

Clicking on the next step (Step Over) happens to be the method call of ActivityTaskManager.getService().startActivity.

So we get the following call stack;

ContextImpl.startActivty()
Instrumentation.execStartActivity()
ActivityTaskManager.getService()
                    .startActivity(whoThread, who.getBasePackageName(), intent,
                     intent.resolveTypeIfNeeded(who.getContentResolver()),
                     token, target != null ? target.mEmbeddedID : null,
                     requestCode, 0, null, options);

I found it here and you can see that callingPackage is implemented using the getBasePackageName method. Who is the context, which is our Activity.

At this point, you can confirm that mReferrer is actually implemented using context's getBasePackageName().

Three, how to avoid the package name being forged

3.1 Pay attention to PID and Uid

How to prevent PackageName from being forged?

When we debug ActivityRecord, we found that there are PID and Uid in the properties of ActivityRecord;

As long as we get this Uid, we can call the packageManager method according to the Uid to get the registration of the corresponding Uid.

3.2 Investigate whether Uid is likely to be forged

The following is to verify whether the Uid may be forged. Debug to find the source of Uid, and check the source of callingUid at the interruption point of the initialization method of ActivityRecord.

We found that this Uid is actually obtained using Binder.getCallingUid in ActivityStarter. The Binder process is not something that can be interfered at the application level. We can safely use this Uid without worrying about being forged. The rest is how to use the Uid to get the PackageName.

3.3 Replace PackageName with Uid

We searched the code and found that ActivityTaskManagerService just provided a method to get Uid.

So we need to get the ActivityTaskManagerService reference and search for IActivityTaskManager.Stub.

ActivityTaskManager cannot be referenced at the app layer (it is a hide class, but in fact there are ways, you can explore it yourself).

We continue to find;

Eventually found that ActivityManager provides such a method to get ActivityTaskManagerService, but unfortunately, getTaskService is a blacklisted method and is forbidden to call.

Finally, we found that the getLaunchedFromUid method of ActivityTaskManagerService was actually wrapped by ActivityManageService.

public class ActivityManagerService extends IActivityManager.Stub
        implements Watchdog.Monitor, BatteryStatsImpl.BatteryCallback {
 
 
    @VisibleForTesting
    public ActivityTaskManagerService mActivityTaskManager;
 
 
    @Override
    public boolean updateConfiguration(Configuration values) {
        return mActivityTaskManager.updateConfiguration(values);
    }
 
    @Override
    public int getLaunchedFromUid(IBinder activityToken) {
        return mActivityTaskManager.getLaunchedFromUid(activityToken);
    }
 
    public String getLaunchedFromPackage(IBinder activityToken) {
        return mActivityTaskManager.getLaunchedFromPackage(activityToken);
    }
 
}

So you can use ActivityManageService to call it, the code is as follows (note that the code may be different for different system versions).

private String reRealPackage() {
    try {
        Method getServiceMethod = ActivityManager.class.getMethod("getService");
        Object sIActivityManager = getServiceMethod.invoke(null);
        Method sGetLaunchedFromUidMethod = sIActivityManager.getClass().getMethod("getLaunchedFromUid", IBinder.class);
        Method sGetActivityTokenMethod = Activity.class.getMethod("getActivityToken");
        IBinder binder = (IBinder) sGetActivityTokenMethod.invoke(this);
        int uid = (int) sGetLaunchedFromUidMethod.invoke(sIActivityManager, binder);
        return getPackageManager().getPackagesForUid(uid)[0];
    } catch (Exception e) {
        e.printStackTrace();
    }
    return "null";
}

Is it foolproof to replace PackageName with Uid? Is there any other mystery in this? Let's sell a key here, friends can discuss it in the comment area.

Four, summary

mReferrer can easily be forged by overriding the context's getBasePackageName(), so be careful when using it. Uid obtained through ActivityManageService cannot be forged, you can consider using Uid to convert PackageName.

Author: vivo internet client team-Chen Long

vivo互联网技术
3.3k 声望10.2k 粉丝