1

I am trying to instrument branches using dexlib2. However, since certain instructions only allow to use the local registers v0-v15 and my instrumentation requires one additional register, it is necessary to save the value of v0, use v0 for the actual instrumentation and after restore the original value of v0. This is done by two move instructions, e.g.

move vNew, v0  
... // actual instrumentation code using v0  
move v0, vNew

However, it is necessary to use the correct move instruction. In particular, we need to distinguish between move, move-wide and move-object depending on type of v0 (the content of it). Fortunately, dexlib2 provides some MethodAnalyzer, which performs this kind of analysis, but the MethodAnalyzer called as follows:

analyzer = new MethodAnalyzer(new ClassPath(Lists.newArrayList(new DexClassProvider(dexFile)),true, ClassPath.NOT_ART), method, null, true);

fails with certain classes like java/lang/StringBuilder. For instance, the following stack-trace is produced:

org.jf.dexlib2.analysis.UnresolvedClassException: Could not resolve class Ljava/lang/StringBuilder;
    at org.jf.dexlib2.analysis.ClassPath.getClassDef(ClassPath.java:155)
    at org.jf.dexlib2.analysis.ClassProto$1.get(ClassProto.java:93)
    at org.jf.dexlib2.analysis.ClassProto$1.get(ClassProto.java:91)
    at com.google.common.base.Suppliers$MemoizingSupplier.get(Suppliers.java:125)
    at org.jf.dexlib2.analysis.ClassProto.getClassDef(ClassProto.java:87)
    at org.jf.dexlib2.analysis.ClassProto.getSuperclass(ClassProto.java:326)
    at org.jf.dexlib2.analysis.MethodAnalyzer.normalizeMethodReference(MethodAnalyzer.java:1987)
    at org.jf.dexlib2.analysis.MethodAnalyzer.analyzeInvokeVirtual(MethodAnalyzer.java:1756)
    at org.jf.dexlib2.analysis.MethodAnalyzer.analyzeInstruction(MethodAnalyzer.java:798)
    at org.jf.dexlib2.analysis.MethodAnalyzer.analyze(MethodAnalyzer.java:201)
    at org.jf.dexlib2.analysis.MethodAnalyzer.<init>(MethodAnalyzer.java:131)
    at BranchCoverage.main(BranchCoverage.java:578)

Update:

The MethodAnalyzer seems to work now. However, my instrumentation still ends up with some verify errors. In particular, the following stack trace is produced:

12-30 09:45:55.415 3486-3486/ws.xsoh.etar E/AndroidRuntime: FATAL EXCEPTION: main
                                                            Process: ws.xsoh.etar, PID: 3486
                                                            java.lang.VerifyError: Verifier rejected class com.android.calendar.AllInOneActivity: void com.android.calendar.AllInOneActivity.setMainPane(android.app.FragmentTransaction, int, int, long, boolean) failed to verify: void com.android.calendar.AllInOneActivity.setMainPane(android.app.FragmentTransaction, int, int, long, boolean): [0xA9] Rejecting invocation, long or double parameter at index 1 is not a pair: 15 + 0.
                                                             void com.android.calendar.AllInOneActivity.updateSecondaryTitleFields(long) failed to verify: void com.android.calendar.AllInOneActivity.updateSecondaryTitleFields(long): [0x1D3] Expected category1 register type not 'Long (Low Half)' (declaration of 'com.android.calendar.AllInOneActivity' appears in /data/app/ws.xsoh.etar-1/base.apk)
                                                                at java.lang.Class.newInstance(Native Method)
                                                                at android.app.Instrumentation.newActivity(Instrumentation.java:1078)
                                                                at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2557)
                                                                at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2726)
                                                                at android.app.ActivityThread.-wrap12(ActivityThread.java)
                                                                at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1477)
                                                                at android.os.Handler.dispatchMessage(Handler.java:102)
                                                                at android.os.Looper.loop(Looper.java:154)
                                                                at android.app.ActivityThread.main(ActivityThread.java:6119)
                                                                at java.lang.reflect.Method.invoke(Native Method)
                                                                at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:886)
                                                                at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:776) 

It seems like I am still using the wrong move instruction. I am assigning

move to BOOLEAN,CHAR,INTEGER,FLOAT,SHORT
move-wide to DOUBLE,LONG
move-object to the remaining types.

Is this assignment correct? What move instruction is appropriate for UNINIT, CONFLICTED, UNINIT_THIS, etc?

auermich
  • 130
  • 10

1 Answers1

1

The MethodAnalyzer has 2 modes of operation - in one case, you provide it with the full classpath from an actual device, and it uses that information to compute the full register types.

In the other mode, it can run without full classpath info, and it basically assumes that any Reference object with an unknown type is a subclass of Object. In this mode, you won't necessarily get 100% accurate reference type info, but it should still be able to accurately track the register type - i.e. normal primitives vs long primitives vs reference types.

Some operations do require the full classpath though, and normalizing method references is one of them. If you pass false to the normalizeVirtualMethods parameter in the MethodAnalyzer constructor, it should have a much better chance of working without a full classpath.

In order to run with a full classpath, you have to pull a /system/framework directory from a device. You then create a ClassPathResolver object that points to that directory. The ClassPathResolver loads the various needed framework files and generates a set of ClassPathProviders, which you use to create a ClassPath object, which gets passed to the MethodAnalyzer.

roughly:

classPathResolver = new ClassPathResolver(new ArrayList<>("/tmp/framework"), new ArrayList<>(), dexFileToBeAnalyzed);
ClassPath classPath = new ClassPath(classPathResolver.getResolvedClassProviders);
MethodAnalyzer methodAnalyzer = new MethodAnalyzer(classPath, methodToAnalyze, null, false);
JesusFreke
  • 19,784
  • 5
  • 65
  • 68
  • Well, this resolved the issue as far as I can see. Thank you. How do I need to alter the invocation of the MethodAnalyzer in order to run it with the full classpath info? – auermich Dec 21 '18 at 11:37
  • Added to answer – JesusFreke Dec 22 '18 at 01:48
  • you may have a look at the updated question. There is still some issue with the assignment of register types to the correct move instruction. – auermich Dec 30 '18 at 10:04
  • For verification errors, you want to look at the logcat log from when the apk is installed, not when it is run. ART will print some logs with specific information about exactly what is failing verification. – JesusFreke Dec 30 '18 at 11:30
  • Also, please create another question. A single question on SO isn't really meant to be used as a thread, where you keep updating the question and answers with new problems. – JesusFreke Dec 30 '18 at 11:35