逆向apk时会经常遇到一些字符串加解密函数。加解密函数写的简单或者用的次数不算很多的时候我们可以手动进行解密或者加些log来获取解密后的字符串。但如果加解密函数很复杂或者被大量使用的时候这样做就有些不太方便了。JEB提供了运行jython脚本的机制,本文将讲述如何编写脚本对字符串进行解密。
JEB的官方教程 很简洁,需要我们多看下其给出的例子和参考接口文档 。
下面是官方给给出的简单示例
1 2 3 4 5 6 7 from com.pnfsoftware.jeb.client.api import IScriptclass 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 from com.pnfsoftware.jeb.client.api import IScriptimport sysfrom java.lang import Stringimport java.langsys.path.append(r"D:\tmp\Dec.jar" ) from com.fire.api.e import eclass 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 import sysfrom com.pnfsoftware.jeb.client.api import IScript, IGraphicalClientContextfrom com.pnfsoftware.jeb.core import RuntimeProjectUtilfrom com.pnfsoftware.jeb.core.actions import Actions, ActionContext, ActionXrefsDatafrom com.pnfsoftware.jeb.core.events import JebEvent, Jfrom com.pnfsoftware.jeb.core.output import AbstractUnitRepresentation, UnitRepresentationAdapterfrom com.pnfsoftware.jeb.core.units.code import ICodeUnit, ICodeItemfrom com.pnfsoftware.jeb.core.units.code.java import IJavaSourceUnit, IJavaCall, IJavaAssignment, IJavaConstantsys.path.append(r"D:\tmp\Dec.jar" ) from com.fire.api.e import eclass 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 ] units = RuntimeProjectUtil.findUnitsByType(project, IJavaSourceUnit, False ) for unit in units: cstbuilder = unit.getFactories().getConstantFactory() class_ = unit.getClassElement() for method in class_.getMethods(): 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" ; } }