0%

使用soot创建类文件

序言

鱼沉雁杳天涯路,始信人间别离苦。

老博客归档。

如何创建一个类

之前看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。注意使用工具的方法getSootClassScene

1
Scene.v().addClass(sClass);

这会将新创建的HelloWorld类添加到中SceneScene一旦创建,所有类都应该属于它们。

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有三个特征组件:

  1. Local

    Body体内的局部变量;

  2. Trap

    哪些代码用来捕获哪些异常;

  3. Unit

    Unit表示语句本身;

所以接下来,我们需要给main方法添加一个Jimple主体,再像主体添加代码语句;

1
2
JimpleBody body = Jimple.v().newBody(method);
method.setActiveBody(body);

我们将Jimple单例对象称为JimpleBody与我们的方法相关联的新对象,并使它成为方法的活动主体。

这里先看一下Jimple中间代码:

image-20200718212825394

接下来下面就需要按照目标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
//加入局部变量,java.io.PrintStream tmpRef
tmpRef = Jimple.v().newLocal("tmpRef", RefType.v("java.io.PrintStream"));
body.getLocals().add(tmpRef);

添加方法代码

添加方法就需要unit了,直接写:

1
2
3
4
//组成链,l0 = @parameter0
units.add(Jimple.v().newIdentityStmt(arg,
Jimple.v().newParameterRef(ArrayType.v
(RefType.v("java.lang.String"), 1), 0)));

理解:

就在上面一步,arg是我们的字符串变量,交给JVM时候,他就是字符串变量l0。

继续写:

1
2
3
//继续组成链,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())));

理解:

这里其实就是把上面的tmpRef给到System.out对象,这里仍有存疑,之后会慢慢看。

最后一步,赋值:

1
2
3
4
5
// 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文件的首选方法是使用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

结果

image-20200718202705757

源代码环节

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");

//声明目标类HelloWorld,他的父类当然是Object
SootClass sClass = new SootClass("HelloWorld", Modifier.PUBLIC);
sClass.setSuperclass(Scene.v().getSootClass("java.lang.Object"));
Scene.v().addClass(sClass);

//创建main方法,public static void main
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);

//创建main方法内容,这里需要用到Jimple
{

JimpleBody body = Jimple.v().newBody(method);
method.setActiveBody(body);

//工具组件
Chain units = body.getUnits();
Local arg,tmpRef;

//加入局部变量,java.lang.String l0
arg = Jimple.v().newLocal("l0", ArrayType.v(RefType.v("java.lang.String"), 1));
body.getLocals().add(arg);

//加入局部变量,java.io.PrintStream tmpRef
tmpRef = Jimple.v().newLocal("tmpRef", RefType.v("java.io.PrintStream"));
body.getLocals().add(tmpRef);

//组成链,l0 = @parameter0
units.add(Jimple.v().newIdentityStmt(arg,
Jimple.v().newParameterRef(ArrayType.v
(RefType.v("java.lang.String"), 1), 0)));

//继续组成链,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());

}
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();
}
}

#