bugku-逆向-8、LoopAndLoop(阿里CTF)

LoopAndLoop(阿里CTF)

实验用到的四个代码都可以在我的Github上下载:https://github.com/OnlyLegends/Bugku–CTF-/tree/master/逆向类CTF

[ LoopAndLoop ]

The friendship between native and dex.

下载LoopAndLoop.apk在安卓模拟器中打开:

题目要求输入一串数字,随便输入字符串abc时会显示:Not a Valid Integer number。
bugku-逆向-8、LoopAndLoop(阿里CTF)_第1张图片
随便输入数字123,显示错误:Not Right!
bugku-逆向-8、LoopAndLoop(阿里CTF)_第2张图片

所以程序应该是让我们输入某串数字。

下面开始逆向LoopAndLoop(阿里CTF).apk:先解压缩,得到它的一些配置文件:
bugku-逆向-8、LoopAndLoop(阿里CTF)_第3张图片

然后用dex2jar将classes.dex反编译成jar包:d2j-dex2jar.bat classes.dex
在这里插入图片描述

再用jd-gui将jar包打开,看到主要部分代码为:

bugku-逆向-8、LoopAndLoop(阿里CTF)_第4张图片

通过对输入的字符串int i = Integer.parseInt(paramAnonymousView)转换成数字。

  1. 分析OnClick方法。

    1、try-catch

NumberFormatException为数字格式异常,当试图将一个String转换为指定的数字类型,而该字符串确不满足数字类型要求的格式时,抛出该异常。故若在apk输入有数字外的字符,程序捕获该异常,输出"Not a Valid Integer number"信息。

2、if-else

对check(i,99)==1835996258进行判断,若满足条件通过MainActivity.this.stringFromJNI2(i)函数输出flag,否则输出"Not Right!"。

所以我们在这无法去修改if跳转,因为输出的flag是与取决于输入的数字,所以我们只能找到正确的数字,那么关键就是check函数了。

2)分析check方法

check方法中调用了native层的原生函数chec,另外还有check1,check2,check3方法也调用了chec函数。

bugku-逆向-8、LoopAndLoop(阿里CTF)_第5张图片

Android架构分为5层:

bugku-逆向-8、LoopAndLoop(阿里CTF)_第6张图片

1、Applications(核心应用程序):Java应用程序基本可以理解为各个App,由Java语言实现。

2、Java API Framework(开发框架包 ):Java框架层(系统服务)就是常说的Framework,我们编写的Android代码之所以能够正常识别和动作,都要依赖这一层的支持。这一层也是由Java语言实现。

3、Android运行环境或者是Native c/c++ 核心库):这部分常见一些本地服务和一些链接库等。这一层的一个特点就是通过C和C++语言实现。比如我们现在要执行一个复杂运算,如果通过java代码去实现,那么效率会非常低,此时可以选择通过C或C++代码去实现,然后和我们上层的Java代码通信(这部分在android中称为jni机制)。又比如我们的设备需要运行,那么必然要和底层的硬件驱动交互,也要通过Native层。

4、Hardware Abstraction Layer(硬件抽象层)

5、Linux Kernel(Linux内核)

所以在这里,这个app层的apk文件调用了Native层的库函数。

在这里插入图片描述

在这里插入图片描述

对本地方法的加载通过 System.loadLibrary 方法实现,System.loadLibrary 的参数是程序员任意选取的库名,这里是"lhm"。

bugku-逆向-8、LoopAndLoop(阿里CTF)_第7张图片

之后我们在apk的配置文件lib/armeabi目录下找到了调用的库liblhm.so:
bugku-逆向-8、LoopAndLoop(阿里CTF)_第8张图片

因为它是C/C++语言编写的,所以我们可以用IDA把它打开:

bugku-逆向-8、LoopAndLoop(阿里CTF)_第9张图片

代码的关键部分:

bugku-逆向-8、LoopAndLoop(阿里CTF)_第10张图片

bugku-逆向-8、LoopAndLoop(阿里CTF)_第11张图片

上面chec(a1,a2,a3,a4)方法的前两个参数a1为JNI接口指针和a2为对对象和Java类的引用,是默认的,后两个参数a3和a4即为Java层参数i和99。

GetMethodID与CallIntMethod方法:

  1. jmethodID GetMethodID(JNIEnv *env, jclass clazz,const char *name, const char *sig); 获取一个Java方法的ID,这个函数将返回非静态类或接口实例方法的方法 ID。这里是获得了check1、check2、check3的ID。

  2. CallIntMethod:通过jmethodID调用的返回值为int的java方法。

但是这里的代码遇到了一个IDA的F5优化问题:由于Java_net_bluelotus_tomorrow_easyandroid_MainActivity_chec是一个JNI函数,我们在JNI开发的过程中知道第一个参数为JNIEnv*,第二个参数为jobject类型。JNI函数可能出现问题,导致我的IDA的F5优化后的代码为:

bugku-逆向-8、LoopAndLoop(阿里CTF)_第12张图片

而实际上网上其他人的代码为:

bugku-逆向-8、LoopAndLoop(阿里CTF)_第13张图片

这里不知是我的IDA的版本还是F5优化的问题,导致后面的v7,v8-1被IDA的F5反编译认为是没有意义的变量优化掉了。参考:

https://blog.csdn.net/feibabeibei_beibei/article/details/52733249

https://www.cnblogs.com/gm-201705/p/9863956.html

之后开始修改CallIntMethod的参数个数,先单击进入CallIntMethod函数查看:

bugku-逆向-8、LoopAndLoop(阿里CTF)_第14张图片

发现它原本就是有四个参数的,而之前F5反编译出来的代码之后三个,再返回原chec函数查看:
bugku-逆向-8、LoopAndLoop(阿里CTF)_第15张图片

我们只是查看了一下CallIntMethod函数再返回,它竟然就又反编译出了第四个参数v7。。。可能IDA的F5反编译优化在反编译JNI函数时真的可能出现问题。

加上第五个参数的方法:可以右击CallIntMethod函数选择Set item type或按快捷键Y即可编辑函数名及其参数:

bugku-逆向-8、LoopAndLoop(阿里CTF)_第16张图片

点击OK之后第五个参数也出现了:

bugku-逆向-8、LoopAndLoop(阿里CTF)_第17张图片

再查看CallIntMethod函数:

bugku-逆向-8、LoopAndLoop(阿里CTF)_第18张图片

的确有了int a5这个参数。

那我们给它加个a6会怎样呢?

bugku-逆向-8、LoopAndLoop(阿里CTF)_第19张图片

bugku-逆向-8、LoopAndLoop(阿里CTF)_第20张图片

依旧显示出来了,但是代码变量名发生了一些变化,除此之外对函数没有什么影响了。后面再加a7也是没有逻辑算法上的影响了。所以CallIntMethod函数应该就是在5个参数的时候有意义。

另外:其实GetMethodID函数只有一个参数,但它开始反编译的结果确有四个参数:

bugku-逆向-8、LoopAndLoop(阿里CTF)_第21张图片

bugku-逆向-8、LoopAndLoop(阿里CTF)_第22张图片

再返回去看,有变成了一个参数:

bugku-逆向-8、LoopAndLoop(阿里CTF)_第23张图片

看来,这里的IDA的F5反编译优化真的有点问题。

0x03

程序算法分析

言归正传,我们继续逆向,其实代码到了这,我们已经能看懂chec函数的主要算法了:

bugku-逆向-8、LoopAndLoop(阿里CTF)_第24张图片

可以看到具体流程就是:根据CallIntMethod函数的第三个个参数*(&v10 + 2 * v8 % 3)的值来选择回调哪个Java函数:check(1、2、3)。

满足check(i,99)==1835996258即可得到flag,i为apk中输入的数值大小,check为native层的原生函数,功能是根据传入check的第二个参数k*2%3值选择回调Java层三个方法中的一个,check1,check2,check3都是对i值进行改变,分析知check最后返回的值也是i,满足最终的i等于1835996258即可,传入的99在check中每次会减1,check函数的终止条件即为减到2。

所以我们有了两种思路,1是进行算法求逆,将所有函数逆向,从最后的结果1835996258算出开始输入的数字串。2是进行暴力破解,从0逐个开始带入算法计算,直到某个数字算出的结果等于1835996258。

流程图如下:

bugku-逆向-8、LoopAndLoop(阿里CTF)_第25张图片

0x04

解题脚本

方法1、进行算法求逆:

写脚本需要注意的细节是在对check2方法的求逆的时候,if的判断条件为(num-1)%2==0,不要忘记减去1,因为native层check函数传给Java层方法的第二个参数减去1了。

源代码:
LoopAndLoop.py

运行结果截图:

bugku-逆向-8、LoopAndLoop(阿里CTF)_第26张图片

算出最初输入的数字串为:236492408。将它输入开始的Android应用:

bugku-逆向-8、LoopAndLoop(阿里CTF)_第27张图片

Flag为:alictf{Jan6N100p3r}

方法2、进行暴力破解

到这里就已经很简单了,仅需对该程序进行爆破就好,先说说我刚开始踩的一个坑,一开始我为了移植方便,直接用Java写的爆破程序,代码如下:
LoopAndLoop.java

但是由于Java的速度不高,而且主要原因是这段代码的时间复杂度本身就非常大,Java不支持代码优化,所以半个小时都没跑完一轮。

bugku-逆向-8、LoopAndLoop(阿里CTF)_第28张图片

之后我就用C语言重新写了一遍:

LoopAndLoop.c

代码优化很重要,不开启的话即使是C语言也快不到哪里去,不开启在Windows下跑,依旧跑不出来:

bugku-逆向-8、LoopAndLoop(阿里CTF)_第29张图片

在Linux下运行并开启了代码优化,正是因为代码优化所以编译出来的程序时间复杂度就缩小了很多,我用gcc编译的,编译的时候开启了-O3(最高优化),结果如下:

bugku-逆向-8、LoopAndLoop(阿里CTF)_第30张图片

用了54秒跑出了结果,就是236492408,输入结果即可得到flag。

为了更加快速的进行的爆破了,我还写了一个多线程版本的,兼容Windows和Linux,还要一件事,具体的线程数还要看看自己的机器,代码如下:

LoopAndLoop_thread.c

bugku-逆向-8、LoopAndLoop(阿里CTF)_第31张图片
加了10条线程基本可以在20多秒跑出结果。

实验用到的四个代码都可以在我的Github上下载:https://github.com/OnlyLegends/Bugku–CTF-/tree/master/逆向类CTF

你可能感兴趣的:(#,#,Android逆向,java,安全)