最近在测试一些需求时感觉额外的费力,例如快速构建非重复性请求+统计结果。通过手动操作应用的方式触发请求,不但需要修改代码,还得配置相应的测试环境。使用抓包软件修改请求又需要重新计算签名,否则会导致校验失败。综上种种,需要开发一款定制的工具来辅助进行测试。
项目是以Android客户端的请求为基础,所以设计的思路是用java写一个简单的GUI并处理数据,这样更方便修改请求。native层负责签名,最后将项目打包成jar直接使用。
主要步骤如下(本文环境均在macOS Mojave系统):
1、Intellij IDEA通过GUI Form创建图形界面
2、Java层处理数据
3、C实现加密算法并编译成动态库
4、项目打成jar包
5、jar包调用动态库
6、Obfuscator-LLVM(ollvm)混淆
一、开发简单的GUI交互界面
页面布局极为简单,这里用Intellij IDEA通过GUI Form创建图形界面的方式实现。
1、创建新的GUI Form
项目中File -> New -> GUI Form
2、绘制布局
在右侧点击控件,然后再点击想要放置的位置,就可以添加控件了,控件的属性可以在左侧的窗口中设置,例如preferredSize等等。
3、跳转到绑定的class
选中一个控件,使用快捷键 fn + F4 跳转到Form绑定的class类。
4、初始化界面
选中panel,快捷键⌘N键Generate,点击上图中的Form main(),即可创建main() 并且初始化界面。
5、运行
Run一下Main()就可以看到我们创建的GUI了。接下来就是设置各种监听事件和逻辑处理。
二、处理数据
1、Java工程导入JSONObject
处理服务端返回的数据时需要用到json库的相关方法,因此需要导入库。
这里直接从Git下载https://github.com/stleary/JSON-java,在工程中新建package -> org.json,然后把所有文件拷贝org.json目录下,就可以使用了。
2、Java调用CURL命令
原始数据的采集是curl格式,偷点懒代码中也是直接调用curl命令。
开始的时候,直接把整个curl命令字符串传了进去,但是总是不能顺利执行。后面查了下资料,需要把curl命令切割成数组,再执行才能成功。
# 错误的演示
String curl = "curl xxxxx";
Process process = Runtime.getRuntime().exec(curl);
# 正确的演示
String[] cmd = {"curl", "-H", "Host: www.xxx.com"};
Process process = Runtime.getRuntime().exec(cmd);
3、巧用正则表达式处理数据
上面说到要把原始的curl命令拆成数组,这里就需要正则上场了。
仔细观察curl命令,发现可以按照空格来切割字符串。唯一需要注意的是单引号内的空格需要保留,以免破坏某些诸如设备信息等数据的格式。这里有个取巧的方法,既然需要按照空格拆分又不能拆分单引号里面的内容,那么可以把单引号之外的空格全部替换为某个字符,然后按照这个特定字符再切割curl命令就可以了。
匹配单引号之外的所有空格
# 伪代码
regex = " (?=([^']*['][^']*['])*[^']*$)";
command.replaceAll(regex, "awsomeReg");
String[] curl = command.split("awsomeReg");
三、Java调用动态库
1、调用动态库
在Java中调用动态库也很单,使用System.load(file name)或者System.loadLibrary(lib-name)。但是这两种方法都不够灵活,后面还会再讲到。
2、编译动态库文件
加密算法是在Android native实现的,动态库也是基于arm的,所以不能直接在mac上使用,需要手动重新编译一下。得益于mac系统的便利性,省去了很多环境配置的麻烦。
首先生成.h文件,接下来可以用gcc来进行编译,编译成功后会生成libxxx.jnilib文件,这个就是我们能够通过java直接调用的动态库了。
但是这样编译的话第一次会报缺少jni_md.h的错误。不过不用担心,这个问题很好解决,把对应的.h文件拷贝到jdk的include目录就好了。
四、Intellij Idea将Java项目打成jar包
总体上只需要四步就能完成打包了:
1、菜单:File->project stucture
2、在弹窗最左侧选中Artifacts->"+",选jar,选择from modules with dependencies,然后会有配置窗口出现,配置完成后,勾选Build on make >ok保存
3、然后菜单:Build->make project
4、最后在项目目录下去找输出的jar包(路径在你添加Artifacts的时候设置的路径)
五、jar包调用动态库
这里有很多坑,运行jar的时候会发现,无法直接加载Jar包中的动态库。
这与JVM的一个系统变量有关,可以通过打印System.getProperty("java.library.path")查看。我们在任意一个目录执行jar文件的时候,系统实际上是找不到这个libxxx.jnilib(so,dll等等)动态库文件的。为了方便,我们又不可能同时提供一个jar文件和一个动态库,这样太傻了。
回过头来看System.load(String filename)这个方法,它是从本地文件系统中以指定的文件名加载代码文件,并且文件名参数必须是完整的路径名。所以我们可以在执行jar的时候,通过流的形式写出动态库到本地,然后再加载调用。
static {
try {
InputStream in = Your.class.getClassLoader().getResourceAsStream("libxxx.jnilib");
File ffile = new File("");
String filePath = null;
filePath = ffile.getAbsolutePath() + File.separator
+ "libxxx.jnilib";
File jnilib = new File(filePath);
FileOutputStream out = new FileOutputStream(jnilib);
int i;
byte[] buf = new byte[1024];
try {
while ((i = in.read(buf)) != -1) {
out.write(buf, 0, i);
}
} finally {
in.close();
out.close();
}
System.load(jnilib.getAbsolutePath());
jnilib.deleteOnExit();
} catch (Throwable e) {
try {
} catch (Throwable ee) {
throw ee;
}
}
}
这样,我们只需要一个jar文件,就可以搞定所有事情了。运行一下jar包,在json中把version改为0.1,然后提交。可以成功的看到服务端的返回数据,说明修改的请求已经通过服务端的校验。
通过这个小测试工具,可以脱离客户端做一些实时的网络请求;也可以针对特定的接口做边界值的验证;还可以做一些安全和渗透测试等等。
对于批量甚至是组合的请求,虽然GUI不能满足,但是利用工程本身却非常容易实现。例如我需要测试一个推荐策略(100页不能有重复内容),那么至少得刷上百次带翻页接口,手动完成该任务并记录结果显然不太现实。但是利用工程就容易的多,修改请求分页数据的参数,再记录返回值并对比有无重复就可以了。
所以从这里我们看出,最关键的地方不在于数据怎么组织,而是如何进行校验,也就是所谓的sign值。Java由于其容易被反编译,现在的应用已经几乎没有再把签名算法放在java层了,都统一交给了native层去做。但是需要注意的是,native代码如果不进行混淆加密,也是非常容易被反编译的。接下来就聊聊native代码的保护。
六、Obfuscator-LLVM(ollvm)混淆
我们都知道java代码容易被反编译,为了让反编译后的代码难以读懂,通常采用混淆的方式。但即使这样,一些关键的代码也难以隐藏。例如之前分析的某app协议,通过反编译工具能够清晰的看到被还原的代码。
同样的,native代码也可以被反编译。如下图所示,虽然易读性要差一些,但关键的逻辑方法都能看到,甚至加密的key也能读到。懂一点点汇编语言就能摸清程序的执行流程,会动态调式的更加毫无阻拦。由此可见native代码的安全性也不容忽视。
Obfuscator-LLVM就是这样一个项目,它旨在提供一套开源的针对LLVM的代码混淆工具,以增加对逆向工程的难度。虽然官方的版本只更新到4.0版本 LLVM-4.0,但是一些大神在此基础上做了更新和移植。这里我们采用一个移植好的版本https://github.com/heroims/obfuscator,该作者fork原版后又加入了llvm5.0,6.0,7.0以及swift-llvm5.0的版本,并扩展了一些功能。这些已经足够满足当前项目的需要了。
七、macOS配置ollvm环境
介绍完ollvm,接下来就讲下如何在macOS配置ollvm环境。
1、下载LLVM
2、编译
解压缩zip后,进入目录新建一个build目录,然后按照下面的命令编译。
mkdir build
cd build
#如果不想跑测试用例加上-DLLVM_INCLUDE_TESTS=OFF
cmake -DCMAKE_BUILD_TYPE=Release -DLLVM_CREATE_XCODE_TOOLCHAIN=ON ../obfuscator/
make -j7
3、使用
原版ollvm中提供了3种混淆方式,分别是:
-mllvm -fla:控制流扁平化
-mllvm -sub:指令替换
-mllvm -bcf:虚假控制流程
大神移植的时候又扩展了一个字符串混淆方式,-mllvm -sobf 字符串加密,可以丰富我们的使用场景。
直接使用编译的二进制文件
build/bin/clang test.c -o test -mllvm -sub -mllvm -fla -mllvm -bcf
也可以重新编译
/build/bin/clang -shared -fPIC you.c you2.c -o libxxx.so -mllvm -sub -mllvm -sobf -mllvm -fla -mllvm -bcf
最后我们看下效果,混淆前:程序的流程非常清晰明了。
混淆后:流程加入了大量干扰的模块,并且字符串也是加密的,极大的提高了静态分析的难度。
至此,这个小工具就算完成了。