0%

编写JEB的python脚本

逆向apk时会经常遇到一些字符串加解密函数。加解密函数写的简单或者用的次数不算很多的时候我们可以手动进行解密或者加些log来获取解密后的字符串。但如果加解密函数很复杂或者被大量使用的时候这样做就有些不太方便了。JEB提供了运行jython脚本的机制,本文将讲述如何编写脚本对字符串进行解密。

JEB的官方教程很简洁,需要我们多看下其给出的例子和参考接口文档

下面是官方给给出的简单示例

1
2
3
4
5
6
7
from com.pnfsoftware.jeb.client.api import IScript

class JEB2SampleScript(IScript):
def run(self, ctx):
print('Hello, JEB version %s' % ctx.getSoftwareVersion())
print('- Arguments: %s' % ctx.getArguments())
print('- Base directory: %s' % ctx.getBaseDirectory())

我们的脚本需要实现IScript接口。run方法便是我们脚本执行的入口。这里有一点需要注意,实现IScript的类的类名必须和文件名一致。比如这里示例的类名是JEB2SampleScript,那我们的python文件名字必须是JEB2SampleScript.py。否则运行时便会报import Error: cannot import name XXX的错误。run方法会传入一个参数ctx,ctx是IClientContext或者其它继承的类。这里我们只关注IClientContext。每个类提供的方法我们可以去接口文档中查询。

了解最基本的知识后,我们就开始着手编写字符串解密脚本。首先让我们先看看待分析的样本。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//字符串解密
public final class b {
public static final String a;
public static final String b;
public static final String c;
public static final String d;
public static final String e;
public static final String f;
public static final String g;

static {
b.a = e.b("A3f+WdyBhMfBkPRYkPmXA3/ao3yXLYbSRbjniVjViabNbc==");
b.b = e.b("A3f+WdyBhMfBkPRYkPmXA3/ao3yXL+jGSIRnmay/iu4Vbubu");
b.c = e.b("A3f+WdyBhMfBkPRYkPmXA3/ao3yXL+jebVRViYYnmaleS+sVRc==");
b.d = e.b("A3f+WdyBhMfBkPRYkPmXA3/ao3yXLYRjSibnbuY78C==");
b.e = e.b("A3f+WdyBhMfBkPRYkPmXA3/ao3yXLYVb8i/Lm+yGbVymSIsVi+yN");
b.f = e.b("A3f+WdyBhMfBkPRYkPmXA3/ao3yXLYeemaUeRabnmiRuRim=");
b.g = e.b("A3f+WdyBhMfBkPRYkPmXA3/ao3yXLYeemaUeRabni+b/SIhVRc==");
}
}
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
//解密函数片段
public static String b(String arg8) {
Exception v1_1;
String v0_4;
Exception v7;
Object v0_2;
String v1 = "";
try {
if(TextUtils.isEmpty(((CharSequence)arg8))) {
return v1;
}

char[] v3 = arg8.toCharArray();
if(v3 != null && v3.length > 0) {
char[] v4 = new char[v3.length];
int v2;
for(v2 = 0; v2 < v3.length; ++v2) {
char v0_1 = v3[v2];
if(e.e.containsKey(Character.valueOf(v0_1))) {
v0_2 = e.e.get(Character.valueOf(v0_1));
}
else {
Character v0_3 = Character.valueOf(v0_1);
}

v0_1 = ((Character)v0_2).charValue();
v4[v2] = v0_1;
}

v1 = new String(v4);
}
}
catch(Exception v0) {
v7 = v0;
v0_4 = v1;
v1_1 = v7;
goto label_39;
}
......

当我们遇到复杂的解密函数时候,如果看懂解密函数原理在实现相应的jython版本可能需要花费大量的时间。这时我们可以将java的解密代码抠出来,重新编译成一个jar然后通过jython直接调用这个jar来进行解密。但是上面这个例子扣代码出来是行不通的,因为JEB反编译出来的java代码中包含goto语句,但是java目前并没有实现goto语句。这里我们可以通过dex2jar将整个dex转换成jar。解决了goto的问题,我们需要面对另外一个问题,那就是解密函数用到了android定义的方法TextUtils.isEmpty。直接在java虚拟下运行android定义的方法会遇到诸如java.lang.RuntimeException: Stub!等问题。这个例子中的用到的TextUtils.isEmpty是无关痛痒的,我们选择去掉。具体过程不详述了,主要是用apktool重打包一下。修改好之后,我们使用dex2jar生成jar包。

1
$ d2j-dex2jar -f ~/path/to/apk_to_decompile.apk

然后我们就会得到一个jar包,这里我们把jar包重命名为Dec.jar。然后我们在编写jython代码试下解密函数是否正确。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#-*-coding: utf-8 -*-
from com.pnfsoftware.jeb.client.api import IScript
import sys
from java.lang import String
import java.lang

# 将Dec.jar包加入到sys.path中
sys.path.append(r"D:\tmp\Dec.jar")

from com.fire.api.e import e

class JEB2MyJebDecr(IScript):
def run(self, ctx):
print self.dec("A3f+WdyBhMfBkPRYkPmXA3/ao3yXLYbSRbjniVjViabNbc==")

def dec(self, target):
return e.b(target)

现在我们目录下就有三个文件了,测试用的apk, 转换的jar,解密脚本。

1
2
3
4
5
6
7
$ tree
.
├── Dec.jar
├── demo.apk
└── JEB2MyJebDecr.py

0 directories, 3 files

通过JEB运行JEB2MyJebDecr.py后,从log中得到android.intent.action.USER_PRESENT的输出。解密函数搞定了,下面就是要把JEB相关的加密字符串替换掉,从而方便我们分析样本。替换思路也很简单,找到解密函数的调用处,取出解密函数传入的字符串,进行解密后将整个解密函数替换掉。代码如下:

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
#-*-coding: utf-8 -*-

import sys

from com.pnfsoftware.jeb.client.api import IScript, IGraphicalClientContext
from com.pnfsoftware.jeb.core import RuntimeProjectUtil
from com.pnfsoftware.jeb.core.actions import Actions, ActionContext, ActionXrefsData
from com.pnfsoftware.jeb.core.events import JebEvent, J
from com.pnfsoftware.jeb.core.output import AbstractUnitRepresentation, UnitRepresentationAdapter
from com.pnfsoftware.jeb.core.units.code import ICodeUnit, ICodeItem
from com.pnfsoftware.jeb.core.units.code.java import IJavaSourceUnit, IJavaCall, IJavaAssignment, IJavaConstant

#将jar包加入到sys.path中
sys.path.append(r"D:\tmp\Dec.jar")
from com.fire.api.e import e

class JEB2MyJebDecr(IScript):
def run(self, ctx):
self.decr_method = "Lcom/fire/api/e/e;->b(Ljava/lang/String;)Ljava/lang/String;"

engctx = ctx.getEnginesContext()
if not engctx:
print('Back-end engines not initialized')
return

projects = engctx.getProjects()
if not projects:
print('There is no opened project')
return

project = projects[0] # Get current project(IRuntimeProject)
#获取所有的java类
units = RuntimeProjectUtil.findUnitsByType(project, IJavaSourceUnit, False)
for unit in units:
cstbuilder = unit.getFactories().getConstantFactory()
class_ = unit.getClassElement()
#遍历每个类的方法
for method in class_.getMethods():
#print "method:", method.getName()
body = method.getBody()
#遍历方法中的每行语句
for i in range(body.size()):
part = body.get(i)
#如果是赋值语句,取右操作数
if isinstance(part, IJavaAssignment):
right = part.getRight()
if isinstance(right, IJavaCall):
#如果右操作数是函数调用且调用了解密函数
if right.getMethod().getSignature() == self.decr_method:
for arg in right.getArguments():
if isinstance(arg, IJavaConstant):
print arg.getString()
part.replaceSubElement(
right,
cstbuilder.createString(
self.decrypt(arg.getString())
)
)


def decrypt(self, target):
return e.b(target)

运行后,按F5刷新下JEB,结果如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public final class b {
public static final String a;
public static final String b;
public static final String c;
public static final String d;
public static final String e;
public static final String f;
public static final String g;

static {
b.a = "android.intent.action.USER_PRESENT";
b.b = "android.intent.action.BOOT_COMPLETED";
b.c = "android.intent.action.BATTERY_CHANGED";
b.d = "android.intent.action.TIME_TICK";
b.e = "android.intent.action.QUICKBOOT_POWERON";
b.f = "android.intent.action.PACKAGE_ADDED";
b.g = "android.intent.action.PACKAGE_REMOVED";
}
}