关于Android 安全工具jeb的基本使用和自定义插件扩展。
1.基本使用
A.针对对象
- jar文件(利用dx工具将jar文件转换为dex文件)
- dex文件
- apk文件
B.基本操作
- 重命名(n)
- 跟踪(Enter, 双击)
- 返回(Esc)
- 前进(Ctrl + Enter)
- 帮助(H)
- 条目信息(I)
- 交叉引用(X),源码交叉引用(Ctrl + X)
- 注释(;或者 C)
- 改变进制数(B)
- 反编译(Tab)
C.参考
2.jeb插件扩展(java/python)
除了一些简单的增加分析效率的扩展(如提取资源文件,打印指定方法的交叉引用等),比较重要的扩展应用就是实现字符串解密、混淆后的apk代码的部分还原等功能。
A.API手册
可在jeb的帮助中查看其可用API,如下图。
关键API(位于jeb.jar
中):jeb.api.ast
(Abstract Syntax Tree-抽象语法树),以如下图所示的函数isZtz162
为例——
其AST结构如图所示——
可以通过如下代码来递归打印一个Method
中的各个Element
——
#!python
class test(IScript):
def run(self, j):
self.instance = j
sig = self.instance.getUI().getView(View.Type.JAVA).getCodePosition().getSignature()
currentMethod = self.instance.getDecompiledMethodTree(sig)
self.instance.print("scanning method: " + currentMethod.getSignature())
body = currentMethod.getBody()
self.instance.print(repr(body))
for i in range(body.size()):
self.viewElement(body.get(i),1)
def viewElement(self, element, depth):
self.instance.print(" "*depth+repr(element))
for sub in element.getSubElements():
self.viewElement(sub, depth+1)
B.区分脚本和插件
JEB客户端扩展(脚本)应该用Python编写;JEB后端扩展(插件)应该用Java 编写(某些类的后端插件可能用Python编写)。
(1)脚本(python)
jeb1支持java脚本和python脚本,jeb2目前只支持python脚本。
使用方法:在jeb安装目录的scripts目录下放置jython-standalone-2.7.0.jar和自定义python脚本,然后在用jeb分析apk的过程中,
File -> Scripts -> Run script
即可。
(2)插件(java)
一、jeb插件支持用java和python语言编写。
二、java插件编写环境配置:在eclipse中建立java工程,然后将javadoc路径指向jeb/doc/apidoc.zip
,添加library jeb.jar
即可。
三、插件安装:将eclipse中的java项目导出为jar,然后放到jeb安装目录的coreplugins
目录下,jeb在启动时就会加载相应的插件。
#简单的java插件示例——SampleEnginesPlugin.java
package com.pnf.plugintest;
import java.util.List;
import java.util.Map;
import com.pnfsoftware.jeb.core.IEnginesContext;
import com.pnfsoftware.jeb.core.IEnginesPlugin;
import com.pnfsoftware.jeb.core.IOptionDefinition;
import com.pnfsoftware.jeb.core.IPluginInformation;
import com.pnfsoftware.jeb.core.PluginInformation;
import com.pnfsoftware.jeb.core.Version;
import com.pnfsoftware.jeb.util.logging.GlobalLog;
import com.pnfsoftware.jeb.util.logging.ILogger;
/**
* Sample plugin.
*
* @author Nicolas Falliere
*/
public class SampleEnginesPlugin implements IEnginesPlugin {
private static final ILogger logger = GlobalLog.getLogger(SampleEnginesPlugin.class);
@Override
public IPluginInformation getPluginInformation() {
return new PluginInformation("Sample Engines Plugin", "A sample JEB back-end plugin", "PNF Software",
Version.create(1, 0, 1));
}
@Override
public void load(IEnginesContext context) {
logger.info("Sample plugin is loaded");
}
@Override
public List<? extends IOptionDefinition> getExecutionOptionDefinitions() {
return null;
}
@Override
public void execute(IEnginesContext context) {
execute(context, null);
}
@Override
public void execute(IEnginesContext engctx, Map<String, String> executionOptions) {
logger.info("Executing sample plugin");
}
@Override
public void dispose() {
}
}
B.实例
(1)打印目标方法的交叉引用
from jeb.api import IScript
class MyPlugin(IScript):
def run(self, jeb):
OpMethod = "sendTextMessage"
def foo(dex, i, num_hyphen):
l = dex.getMethodReferences(i) //获取方法i的交叉引用l
for k in l:
print '-'*num_hyphen+dex.getMethod(k).getSignature(True)
if dex.getMethodReferences(k) != None:
foo(dex, k, num_hyphen+4) //递归调用回溯上层函数调用,用'-'*num_hyphen表示调用关系
print ''
print ''
dex = jeb.getDex()
method_count = dex.getMethodCount()
print "method_count:"+str(method_count)
for i in range(method_count):
if dex.getMethod(i).getSignature(True).find(OpMethod)>=0:
print '*'+dex.getMethod(i).getName()
print dex.getMethod(i).getSignature(True)
foo(dex, i, 4)
(2)处理混淆应用
主要处理逻辑——
- 对于类:递归寻找它的父类和实现的接口。如果父类和接口包含了有意义的名字:例如SDK类Activity、不能混淆的类名MainActivity,以此为基础进行重命名。
- 对于Field:根据该Field的类型,重命名其名字。
- 对于函数:根据该函数的参数类型,重命名其名字。
思路根据被混淆item的基类信息和类型信息/参数信息对其重命名
的主要逻辑实现如下——
for clz in codeunit.getClasses():
if isObfuscated(clz):
name = determineNameFromHierchy(clz) --->1
rename(clz, name)
for field in codeUnit.getFields():
if isObfuscated(field):
name = determineNameByFieldType(field)
rename(field, name)
for mtd in codeUnit.getMethods():
if isObfuscated(mtd):
name = determineNameByArgsType(field)
rename(field, name)
例如, class IiIiIiIi是继承于class iIiIiIiI, 而iIiIiIiI又继承于Activity/实现了onClickListener, 那么我们就可以使用Activity/onClickListener作为基准重命名两个被混淆的类. 这里的关键在于一个递归获取基类的函数, 代码如下所示——
'''
clzElement is ICodeClass retrieved from ICodeUnit.getClass()
'''
def tryDetermineGodeName(self, clzElement):
javaunit = self.decomp.decompile(clzElement.getAddress())
clzElement = javaunit.getClassElement()
#now clzElement is a IJavaClass
if not isFuckingName(clzElement.getName()):
#this is a non-obfuscated name, just return it
return clzElement.getName()
ssupers = clzElement.getImplementedInterfaces()
supers = []
supers.extend(ssupers)
# do not directly append on returned list!
superSig = clzElement.getSupertype().getSignature()
supers.append(clzElement.getSupertype())
for superItem in supers:
sig = superItem.getSignature()
if sig == "Ljava/lang/Object;":
#extend from java/lang/Object gives us zero info
#so try next
continue
if not isFuckingName(sig):
#return first non-obfuscated name
return sig
resolvedType = self.targetUnit.getClass(sig)
if resolvedType:
#this is a concret class
guessedName = self.tryDetermineGoodName(resolvedType)
if guessedName:
return guessedName
else:
#this is a SDK class
return sig
#cannot determine name from its supers, return None
return None