序言
鱼沉雁杳天涯路,始信人间别离苦。
老博客归档。
如何创建一个类
之前看ASM创建一个类,很实费劲,需要直接写字节码口令,这次尝试用Soot API写一下最简单的Hello World。
加载java.lang.Object
和库类
我们知道所有对象的父类都是Object类,那么上来第一步,先添加两个依赖类:
1 2 3
| Scene.v().loadClassAndSupport("java.lang.Object"); Scene.v().loadClassAndSupport("java.lang.System");
|
这里,教程上说,当你loadClassAndSupport时候,相关类的所有内容都会被加载进来,一招拿下!
创建一个新SootClass
对象
接下来创建我们创建的这个类就叫做HelloWorld。在Soot里面需要用SootClass
来封装,并将其父类设置为java.lang.Object
。
1
| sClass = new SootClass("HelloWorld", Modifier.PUBLIC);
|
1
| sClass.setSuperclass(Scene.v().getSootClass("java.lang.Object"));
|
这会将新创建的类的父类设置为的SootClass
对象java.lang.Object
。注意使用工具的方法getSootClass
上Scene
。
1
| Scene.v().addClass(sClass);
|
这会将新创建的HelloWorld
类添加到中Scene
。Scene
一旦创建,所有类都应该属于它们。
向SootClass
添加方法
想向Soot类添加一个sout方法,打印helloworld,首先需要main方法。
目前Helloworld类的main()
方法还是空的。
现在有了SootClass
,我们需要向其中添加方法。
1 2 3
| method = new SootMethod("main", Arrays.asList(new Type[] {ArrayType.v(RefType.v("java.lang.String"), 1)}), VoidType.v(), Modifier.PUBLIC | Modifier.STATIC);
|
解读:
在这里需要首先写出一个main方法,他是public&static的,并且main方法接受一个java.lang.String
的参数数组,并且返回void。
SootClass:
每个SootClass代表一个Java对象,我们可以实例化该类,也可以为它指定类型。
如果想要获得java.lang.String
的类型,我们可以用RefType.v("java.lang.String")
。
如果现在已经有了一个SootClass对象sc,我们可以用sc.getType()
获取对应的类型。
1
| sClass.addMethod(method);
|
此代码将main方法添加到其所属类,也就是sClass。
向方法添加代码
如果方法不包含任何代码,则它是无用的。我们继续向该main
方法添加一些代码。为此,我们必须为代码选择一个中间表示形式。
创建JimpleBody
Jimple是soot四个中间表示形式之一,也是最受欢迎的。
在Soot中,通常这一步是将一个Body附加在SootMethod对象上,也就是附加到之前我们声明的method对象上。
每个Body知道自己属于哪个SootMethod,但是每个SootMethod每次只能有一个activeBody:
1
| SootMethod.getActiveBody()
|
更准确滴说,每个Body有三个特征组件:
Local
Body体内的局部变量;
Trap
哪些代码用来捕获哪些异常;
Unit
Unit表示语句本身;
所以接下来,我们需要给main方法添加一个Jimple主体,再像主体添加代码语句;
1 2
| JimpleBody body = Jimple.v().newBody(method); method.setActiveBody(body);
|
我们将Jimple单例对象称为JimpleBody
与我们的方法相关联的新对象,并使它成为方法的活动主体。
这里先看一下Jimple中间代码:
接下来下面就需要按照目标Jimple一步步来做。
添加本地变量
添加本地变量需要用到arg,所以直接写;
1 2
| arg = Jimple.v().newLocal("l0", ArrayType.v(RefType.v("java.lang.String"), 1)); body.getLocals().add(arg);
|
这里是为啥呢,我的理解;
对于sout(“Helloworld!”)来说,我们需要有一个字符串变量,最后打印的也是他,不难理解;
这里局部变量还有一个就是打印对象,在Java里面,所有东西都是对象;
这里第二个就是java.io.PrintStream对象,打印流对象;
1 2 3
| tmpRef = Jimple.v().newLocal("tmpRef", RefType.v("java.io.PrintStream")); body.getLocals().add(tmpRef);
|
添加方法代码
添加方法就需要unit了,直接写:
1 2 3 4
| units.add(Jimple.v().newIdentityStmt(arg, Jimple.v().newParameterRef(ArrayType.v (RefType.v("java.lang.String"), 1), 0)));
|
理解:
就在上面一步,arg是我们的字符串变量,交给JVM时候,他就是字符串变量l0。
继续写:
1 2 3
| units.add(Jimple.v().newAssignStmt(tmpRef, Jimple.v().newStaticFieldRef( Scene.v().getField("<java.lang.System: java.io.PrintStream out>").makeRef())));
|
理解:
这里其实就是把上面的tmpRef给到System.out对象,这里仍有存疑,之后会慢慢看。
最后一步,赋值:
1 2 3 4 5
| { 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
文件的首选方法是使用ASM后端。
1 2 3 4 5 6
| int java_version = Options.v().java_version(); String fileName = SourceLocator.v().getFileNameFor(sClass, Options.output_format_class); OutputStream streamOut = new FileOutputStream(fileName); BafASMBackend backend = new BafASMBackend(sClass, java_version); backend.generateClassFile(streamOut); streamOut.close();
|
也可以使用过时的Jasmin后端。我们首先构造输出流,该流将使用Jasmin源并输出.class
文件。我们可以手动指定文件名,也可以让Soot确定正确的文件名。我们在这里做后者。
1 2 3 4 5 6 7
| String fileName = SourceLocator.v().getFileNameFor(sClass, Options.output_format_class); OutputStream streamOut = new JasminOutputStream(new FileOutputStream(fileName)); PrintWriter writerOut = new PrintWriter(new OutputStreamWriter(streamOut)); JasminClass jasminClass = new soot.jimple.JasminClass(sClass); jasminClass.print(writerOut); writerOut.flush(); streamOut.close();
|
如果我们希望输出简单的源代码而不是.class
文件,则可以使用以下代码:
1 2 3 4 5 6
| 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();
|
我们省略了JasminOutputStream
,并在上调用printTo
方法Printer
。
结果
源代码环节
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62
| public class Test { public static void main(String[] args) throws IOException { Scene.v().loadClassAndSupport("java.lang.Object"); Scene.v().loadClassAndSupport("java.lang.System");
SootClass sClass = new SootClass("HelloWorld", Modifier.PUBLIC); sClass.setSuperclass(Scene.v().getSootClass("java.lang.Object")); Scene.v().addClass(sClass);
SootMethod 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);
{
JimpleBody body = Jimple.v().newBody(method); method.setActiveBody(body);
Chain units = body.getUnits(); Local arg,tmpRef;
arg = Jimple.v().newLocal("l0", ArrayType.v(RefType.v("java.lang.String"), 1)); body.getLocals().add(arg);
tmpRef = Jimple.v().newLocal("tmpRef", RefType.v("java.io.PrintStream")); body.getLocals().add(tmpRef);
units.add(Jimple.v().newIdentityStmt(arg, Jimple.v().newParameterRef(ArrayType.v (RefType.v("java.lang.String"), 1), 0)));
units.add(Jimple.v().newAssignStmt(tmpRef, Jimple.v().newStaticFieldRef( Scene.v().getField("<java.lang.System: java.io.PrintStream out>").makeRef())));
{ 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!")))); }
units.add(Jimple.v().newReturnVoidStmt());
} String fileName = SourceLocator.v().getFileNameFor(sClass, Options.output_format_class); OutputStream streamOut = new JasminOutputStream(new FileOutputStream(fileName)); PrintWriter writerOut = new PrintWriter(new OutputStreamWriter(streamOut)); JasminClass jasminClass = new soot.jimple.JasminClass(sClass); jasminClass.print(writerOut); writerOut.flush(); streamOut.close(); } }
|
#