上篇介绍了在apk中插入System.out.println(“HELLO”)句子。但是,利用soot插桩java/apk,其实都可以插入类、方法等。soot的分析/插桩等都是在Jimple中间语言上进行的,因此,本文介绍利用soot生成一个完整类的过程,希望能够从中受到启发。
首先,我们需要创建一个将methods放入的class,下述步骤是整个创建类文件的必须过程。
加载java.lang.Object和库类
加载java.lang.Object,它是Java class层次的根结点。
在扩展soot框架的代码中,这一步是不需要的;在扩展soot框架时,当用户代码被调用时,类文件的加载已经完成了。
Scene.v().loadClassAndSupport(“java.lang.Object”);
这行代码将使Soot加载java.lang.Object类,并且创建合适的SootClass对象,为它的字段创建SootMethod和SootFields。当然,其他对象都是继承于java.lang.Object。loadClassAndSupprt将加载这个特殊类的传递包,以至于所有需要java.lang.Object类型的类能够自己加载。
这个过程就是Resolution.
因为我们知道HelloWorld程序将使用到标准类库,因此我们必须resolve:
Scene.v().loadClassAndSupport(“java.lang.System”);这行引用了Scene.v()。Scene是一个程序中所有SootClass的容器,并且提供了很多有用的方法。通过Scene.v()可以得到一个Scene对象。
注释:Soot加载类一般从classfiles/.Jimple两种输入文件,当是前一种时,Soot将加载每个类文件中常量池引用的所有类,当从.Jimple文件中加载时,soot将只加载需要的类型。
创建一个新的SootClass对象
创建HelloWorldSootClass,并且设置它的父类为java.lang.Object
sClass = newSootClass(“HelloWorld”,Modifier.PUBLIC);这行代码表示创建一个SootClass对象,代表一个public类HelloWorld。
sClass.setSuperClass(Scene.v().getSootClass(“java.lang.Object”));
这是设置新创建的SootClass对象的父类为java.lang.Object。注意:在Scene中getSootClass方法的使用(译者加:getSootClass将String表示的类转换为SootClass)。
Scene.v().addClass(sClass);这是将新创建的HelloWorld类加到Scene中。一旦类创建它都应该属于Scene。
向SootClasses中添加方法:
给HelloWorld穿件一个main()方法
现在,我们已经有一个SootClass,我们需要向SootClass中添加方法。
method=new SootMethod("main",Arrays.asList(newType[]{ArrayType.v(RefType.v("java.lang.String"), 1)}),VoidType.v(),Modifier.PUBLIC|Modifier.STATIC);我们创建了一个新的publicstatic方法mian,并且它的参数是java.lang.String对象,返回类型为void。
SootMethod的构造携带一个list,因此,我们利用Java方法Arrays.asList去创建一个列表,这个列表来自于利用new Type[]生成的一元数组。在list中,我们put一个数组,并且数组类型是java.lang.String的一元数组类型。其中RefType的调用是返回与java.lang.String类对应的类型。
Types:每个SootClass代表一个Java对象,我们可以通过一个给定类型的对象去初始化class,type和class是紧密相关却又独立的。如果要通过java.lang.String类名得到它的类型,可以通过RefType.v(“java.lang.String”),如果给定一个SootClass对象sc,也可以通过调用sc.getType()得到匹配的类型。
sClass.addMethod(method);
这行代码向类中添加方法。
向方法中添加代码:
译者加:上篇就是直接向方法中添加System.out.println(“HELLO”)。如果向某个apk/java中添加类,就需要从本篇的《创建一个新的SootClass对象》开始;如果向某个apk/java中添加方法,则应该从本篇的《向SootClasses中添加方法》开始。
如果一个方法没有包含任何的代码,那么它就是无用的。我们将向main方法中添加一些代码。为了达到目的,我们必须选择一种中间语言(Jimple、Baf、Shample等)添加到代码中。
创建JimpleBody:
在soot中,我们将Body依附于一个SootMethod去关联代码和方法。每个Body它与哪个SootMethod对应,但是一个SootMethod仅有一个active Body对应(可以通过SootMethod.getActiveBody()来获取)。多种Intermediate representations(中间语言)提供了Body的不同类型;Soot有JimpleBody、ShimpleBody、BafBody和GrimpBody。
一个Body有三个重要的属性:Local S链、Trap S链和Unit S链,链就相当于列表结构,提供O(1)的复杂度来插入和删除元素。Locals是Body中的局部变量;Trap表示哪个units捕捉哪种异常;Unit S表示句子本身。
注意:Unit在Jimple表示statemetns,而在Baf中表示instructions。
给main类创建一个Jimple Body,并给Body增加locals/instructions。
JimpleBody body = Jimple.v().newBody(method); Method.setActiveBody(body);
增加Local:
units.add(Jimple.v().newIdentityStmt(arg,Jimple.v().newParameterRef(ArrayType.v(RefType.v(“java.lang.String”),1),0)));
SootMethod声明了它的参数,but these arenot bound to the Local S of the Body.IdentityStmt将第一个参数的值赋给arg,第一个参数类型为string数组。
//insert “tmpRef.println(“Hello world!”)” { SootMethod toCall = Scene.v().getMethod(“<java.io.PrintStream: void println(java.lang.String)>”); units.add(Jimple.v().newInvokeStmt( Jimple.v().newVirtualInvokeExpr(tmpRef,toCall.makeRef(),StringConstant.v(“Hello World!”)))); }
我们通过方法签名<java.io.PrintStream:void println(java.lang.String)>(方法名为println,属于类PrintStream,返回类型为void,并且携带一个String参数,这些数据足以识别一个方法)得到该方法,并且调用它,参数为StringConstant “Hello World!”。
写类文件:
为了使程序输出为.class文件,方法的body必须从Jimple转换成Jasmin,并且汇编成字节码。通过JasminOutputStream汇编成字节码。
我们先构造输出流(outputstream),将携带Jasmin源码并输出.class文件。我们可以自定义文件名,也可以让soot自己生成正确的文件名。
String fileName = SourceLocator.v().getFileNameFor(sClass,Options.output_format_class); OutputStream streamOut = new JasminOutputStream(new FileOutputStream(fileName)); PrintWriter writerOut = new PrintWriter(new OutputStreamWriter(streamOut));
我们先将Jimple转化为Jasmin,并且将Jasmin类输到outputstream。
JasminClass jasminClass = new soot.jimple.JasminClass(sClass); jasminClass.print(writerOut); writerOut.flush(); streamOut.close();
如果希望输出类型为jimple,应该使用下面的代码:
String fileName = SourceLocator.v().getFileNameFor(sClass,Options.output_format_jimple); OutputStream streamOut = new FileOutputStream(fileName); PrintPriter writerOut = new PrintWriter(new OutputStreamWriter(streamOut)); Printer.v().printTo(sClss,writerOut); writerOut.flush(); streamOut.close();我们省略了JaminOutputStream,并且调用了方法的printTo。
下面是完整源码:
/* Soot - a J*va Optimization Framework * Copyright (C) 2008 Eric Bodden * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 59 Temple Place - Suite 330, * Boston, MA 02111-1307, USA. */ import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.PrintWriter; import java.lang.reflect.Modifier; import java.util.Arrays; import java.util.Map; import soot.ArrayType; import soot.Body; import soot.BodyTransformer; import soot.G; import soot.Local; import soot.PackManager; import soot.Printer; import soot.RefType; import soot.Scene; import soot.SootClass; import soot.SootMethod; import soot.SourceLocator; import soot.Transform; import soot.Type; import soot.VoidType; import soot.jimple.JasminClass; import soot.jimple.Jimple; import soot.jimple.JimpleBody; import soot.jimple.StringConstant; import soot.options.Options; import soot.toolkits.graph.ExceptionalUnitGraph; import soot.util.Chain; import soot.util.JasminOutputStream; public class MyMain { public static void main(String[] args) { SootClass sClass; SootMethod method; //Resolve dependencies Scene.v().loadClassAndSupport("java.lang.Object"); Scene.v().loadClassAndSupport("java.lang.System"); //Declare 'public class HelloWorld' sClass = new SootClass("HelloWorld", Modifier.PUBLIC); //extends Object sClass.setSuperclass(Scene.v().getSootClass("java.lang.Object")); Scene.v().addClass(sClass); //Create the method,public static void main(String[]) method=new SootMethod("main",Arrays.asList(new Type[]{ArrayType.v(RefType.v("java.lang.String"), 1)}), VoidType.v(),Modifier.PUBLIC|Modifier.STATIC); sClass.addMethod(method); //Create the method body { //create emtpy body JimpleBody body = Jimple.v().newBody(method); method.setActiveBody(body); Chain units = body.getUnits(); Local arg,tmpRef; //Add some locals,java.lang.String l0 arg = Jimple.v().newLocal("l0", ArrayType.v(RefType.v("java.lang.String"), 1)); body.getLocals().add(arg); //Add locals,java.io.printStream tmpRef tmpRef = Jimple.v().newLocal("tmpRef", RefType.v("java.io.PrintStream")); body.getLocals().add(tmpRef); //add "l0=@parameter0" units.add(Jimple.v().newIdentityStmt(arg, Jimple.v().newParameterRef(ArrayType.v(RefType.v("java.lang.String"), 1), 0))); //add "tmpRef = java.lang.System.out" units.add(Jimple.v().newAssignStmt(tmpRef, Jimple.v().newStaticFieldRef( Scene.v().getField("<java.lang.System: java.io.PrintStream out>").makeRef()))); //insert "tmpRef.println("Hello world!")" { SootMethod toCall = Scene.v().getMethod("<java.io.PrintStream: void println(java.lang.String)>"); units.add(Jimple.v().newInvokeStmt(Jimple.v().newVirtualInvokeExpr(tmpRef, toCall.makeRef(),StringConstant.v("Hello World!")))); } //insert "return" units.add(Jimple.v().newReturnVoidStmt()); } try { String fileName = SourceLocator.v().getFileNameFor(sClass, Options.output_format_jimple); OutputStream streamOut = new FileOutputStream(fileName); PrintWriter writerOut = new PrintWriter(new OutputStreamWriter(streamOut)); Printer.v().printTo(sClass, writerOut); writerOut.flush(); streamOut.close(); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } } }
public class HelloWorld extends java.lang.Object { public static void main(java.lang.String[]) { java.lang.String[] l0; java.io.PrintStream tmpRef; l0 := @parameter0: java.lang.String[]; tmpRef = <java.lang.System: java.io.PrintStream out>; virtualinvoke tmpRef.<java.io.PrintStream: void println(java.lang.String)>("Hello World!"); return; } }
下篇博客将利用soot实现Android Apps插桩(一)(二)中的内容,实现一个较为复杂的插桩过程。