[TOC]
了解了编译、打包、签名、安装apk文件后,正式开始逆向的基础,静态分析
apk包内的dex文件是dalvik虚拟机可识别的可执行文件,我们主要也是对dex文件进行逆向,分析其代码逻辑、更改其逻辑做一些分析、破解之类的行为
androidkiller
这里我们用一个去广告的例子,简单的过一下流程
java -jar apktool.jar d xxx.apk -o out
定位关键代码的方法很多,这里我用的是一个开源的小工具,原理是注入+栈追踪
,我们可以先用字符串大法先随意拼接以下ad字符串大致定位一下,可能会在哪个文件夹中,挑选出下面包含类所在文件夹批量注入日志打印方法
python3 inject_log.py -r .\out\smali\com\youdo
由于我们的目的是去广告,所以我们需要打开一个视频,查看日志中,广告开始播放时调用了哪些方法,这里我们就这样不断的去缩小我们过滤的范围,这里不做太多的赘述,最终确定到parsead方法,我们在返回的地方加一条指令const/4 v0, 0x0
,让返回始终为null
const/4 v0, 0x0
return-object v0
首先针对不同逆向,我们需要清楚逆向的目的,在crakeme赛题中,我们主要目的是找到flag,具体需要我们找到打印输出的地方,在其周围找到判断逻辑,然后就是最主要的一部分逆向算法,算出flag
1.反编译
2.定位关键代码
3.逆向算法
反编译后,查看AndroidManifest.xml找到入口 点
<activity android:label="@h/a" android:name="ctf.bobbydylan.M"> <intent-filter> <action android:name="android.intent.action.MAIN"/> <category android:name="android.intent.category.LAUNCHER"/> </intent-filter>
进入这个Activity类中,我们根据执行逻辑,如果有static{}和构造方法,我们可以看一眼里面除了初始化是否还有别的东西,这里并没有这两个调用,我们直接去onCreate方法中,这个Acitivity创建时调用的方法
分析代码:主要有个开启服务的方法,进入这个P服务类,扫一眼生命周期中用到的方法,并没有值得留意的东西,直接看onStartCommand方法,服务启动时执行的方法,这里是循环播放res/raw目录下的音频文件bodylan,这个类分析到这,不是什么重要的类,接着往下看
public void onCreate(Bundle bundle) { super.onCreate(bundle); setContentView(R.layout.main); startService(new Intent(this, P.class)); ((Button) findViewById(R.id.button)).setOnClickListener(new a(this, (TextView) findViewById(R.id.et))); }
public int onStartCommand(Intent intent, int i, int i2) { try { if (this.a == null) { this.a = MediaPlayer.create(this, R.raw.bobdylan); this.a.start(); this.a.setLooping(true); } } catch (Exception e) { } return super.onStartCommand(intent, i, i2); }
按钮控件设置监听方法,在Jadx-Gui中右键->Find Usage找到方法定义的地方,可以从中文字符看出我们找到了主要逻辑,我们的目的就是打印出正确二字,从这个逻辑中可以看出,我们必须保证this.b.check
不能抛出异常才行,下面才是crakeme真正开始的地方,分析这个check方法
a(M m, TextView textView) { this.b = m; this.a = textView; } public void onClick(View view) { try { this.b.check(this.a.getText().toString()); new Builder(this.b).setMessage("正确").setNeutralButton("OK", null).create().show(); } catch (Exception e) { new Builder(this.b).setMessage("错误").setNeutralButton("OK", null).create().show(); } }
我们必须保证不抛出异常
满足下面注释的两个条件即可
public void check(String str) { int i = 0; //条件1:输入字符串长度必须等于16 if (str.length() != 16) { throw new RuntimeException(); } String str2 = ""; try { str2 = getKey(); } catch (Exception e) { str2 = getKey(); System.arraycopy(str2, 0, str, 5, 5); } int[] iArr = new int[16]; iArr[0] = 0; iArr[12] = 14; iArr[10] = 7; iArr[14] = 15; iArr[15] = 42; try { iArr[1] = 3; iArr[5] = 5; System.out.println(); } catch (Exception e2) { iArr[5] = 37; iArr[1] = 85; } iArr[6] = 15; iArr[2] = 13; iArr[3] = 19; iArr[11] = 68; iArr[4] = 85; iArr[13] = 5; iArr[9] = 7; iArr[7] = 78; iArr[8] = 22; //条件2:iArr数组&255必须和后面这串计算相等 while (i < str.length()) { if ((iArr[i] & 255) != ((str.charAt(i) ^ str2.charAt(i % str2.length())) & 255)) { throw new RuntimeException(); } i++; } } public String getKey() { return "bobbydylan"; }
根据面的分析写出第一版算不出结果的解密脚本,然后我用androidkiller打开这个apk文件,看汇编代码发现这个getKey不是调用M类中的方法,因为是个乱码,实际上是调用了父类中继承的方法
iArr = [0, 3, 13, 19, 85, 5, 15, 78, 22, 7, 7, 68, 14, 5, 15, 42] chArr = ['b', 'o', 'b', 'b', 'y', 'd', 'y', 'l', 'a', 'n'] for i in iArr: number = 0 while(True): if iArr[i] == number ^ ord(chArr[i%len('bobbydylan')]): print(chr(number)) if i == len(iArr): print("end") exit(0) break number += 1
解密算法,结果blow,in the winD
iArr = [0, 3, 13, 19, 85, 5, 15, 78, 22, 7, 7, 68, 14, 5, 15, 42] chArr = ['b', 'o', 'b', 'd', 'y', 'l', 'a', 'n'] for i in range(len(iArr)): number = 0 while(True): if iArr[i] == number ^ ord(chArr[i%len('bobdylan')]): print(chr(number)) if i == len(iArr): print("end") exit(0) break number += 1
【1】当java层代码不能给我们正确答案是,可以阅读smali代码
根据这几篇文章的逆向流程来分析病毒即可
【1】zoopark https://www.freebuf.com/articles/system/184286.html
【2】锁屏病毒 https://www.freebuf.com/articles/others-articles/199515.html
上面从具体逆向的一些分支的实现中来了解一下java层的具体实现,难度其实没那么大,但是需要对Android开发一些基础知识有一定的掌握
【1】批量注入栈跟踪小工具
【2】crakeme赛题 https://bbs.pediy.com/thread-251787-1.htm