BruceFan's Blog

Stay hungry, stay foolish

0%

Android逆向之脱壳

本文主要介绍一下APK简单加固的破解方法,以及一些其他的逆向技巧和知识。现在市场中加固APK的方式一般有两种:一种是对源APK整体做一个加固,放到指定位置,运行的时候再解密动态加载;另一种是对so进行加固,在so加载内存的时候进行解密释放。本文主要针对第一种方式。

题目介绍

用一个案例来讲解,阿里CTF2014的第三题:

题目要求:要求自己构造一个网页,并把网页对应的URL输入到URL输入框控件,然后,点击”进入”按钮,jscrack会打开webview浏览你的网页,如果jscrack能弹出一个Toast,就证明已经成功破解,同时Toast显示的内容就是这个题目的flag。
题目的大致思路:输入的url会传递给一个WebView控件,进行网页展示,应该是网页中的JS会调用本地的一个Java方法,然后弹出相应的提示。

初步分析

按照破解的一般步骤:
(1)用JEB打开APK文件,对Java代码进行分析:

这里只有一个Application类,所以这个APK应该是被加固了。加固一个APK,外面一定得套一个壳,这个壳必须是自定义的Application类(初始化操作)。因为没有其他类了,无处下手,只能从其他地方再分析。
(2)对AndroidManifest.xml文件进行分析:

不管APK如何加固,即使代码中看不到四大组件的定义,但是肯定会在AndroidManifest.xml中声明,因为不声明运行会报错。
当发现APK中主要的类都没有了,肯定是APK被加固了,加固的源程序肯定是在本地,一般会有这么几个地方需要注意的:

  • 应用程序的assert目录,我们知道这个目录是不参与apk的资源编译过程的,所以很多加固的应用喜欢把加密之后的源apk放到这里
  • 把源apk加密放到壳的dex文件的尾部,这个肯定不是我们这里的案例,但是也有这样的加固方式,这种加固方式会发现使用dex2jar工具解析dex是失败的,我们这时候就知道了,肯定对dex做了手脚
  • 把源apk加密放到so文件中,这个就比较难了,一般都是把源apk进行拆分,存到so文件中,分析难度会加大的。

在assert/目录中发现了cls.jarfak.jar两个jar文件,JD-GUI打开失败,猜想这个jar是加密过的,很有可能是存放源APK的地方。
刚刚分析过DEX文件,第二种方式肯定不可能。
在lib/目录中发现了libhack.solibmobisec.solibtranslate.so三个so文件,Application中加载的只有libmobisec.so,其他两个so文件可能存放了拆分的apk文件。
通过上面的分析之后,我们大致知道了两个地方很有可能是源apk的藏身地方,一个是assert目录,一个是libs目录,那么分析完了之后,我们发现现在面临两个问题:
第一个问题:assert/目录中的jar文件被处理了,打不开,也不知道处理逻辑;
第二个问题:lib/目录中的三个so文件,唯一加载了libmobisec.so文件,因为没有上层代码,没法分析。

@JavascriptInterface这个注解是在SDK17加上的,也就是Android4.2版本中,那么在之前的版本中没有这个注解,任何public的方法都可以在JS代码中访问,而Java对象继承关系会导致很多public的方法都可以在JS中访问,其中一个重要的方法就是getClass()。然后JS可以通过反射来访问其他一些内容。这个也算是WebView的一个漏洞了。所以通过引入@JavascriptInterface注解,则在JS中只能访问@JavascriptInterface注解的函数。这样就可以增强安全性。

dump内存中的DEX内容

不管源程序如何加固,放到哪里,最终都是需要被加载到内存中,只要从内存中把这部分数据dump出来就可以了。在libdvm.so中的函数:

1
int dvmDexFileOpenPartial(const void *addr, int len, DvmDex **ppDvmDex);

函数的前两个参数就给出了dex文件在内存中的起始地址和大小,在这个函数下断点用IDC脚本dump内存即可。
dump过程:
(1)在设备中运行android_server,主机上进行adb forward端口转发:

1
2
3
root@hammerhead:/data/local/tmp # ./android_server
IDA Android 32-bit remote debug server(ST) v1.19. Hex-Rays (c) 2004-2015
Listening on port #23946...
1
$ adb forward tcp:23946 tcp:23946

(2)打开Monitor

1
$ monitor

(3)使用adb命令以Debug模式启动APK

1
2
3
4
$ adb shell am start -D -n com.ali.tg.testapp/.MainActivity
WARNING: linker: app_process has text relocations. This is wasting memory and is a security risk. Please fix.
WARNING: linker: app_process has text relocations. This is wasting memory and is a security risk. Please fix.
Starting: Intent { cmp=com.ali.tg.testapp/.MainActivity }

(4)打开IDA:Debugger->Attach->Remote ARMLinux/Android debugger附加到进程

(5)使用jdb命令启动连接attach调试器

1
$ jdb -connect com.sun.jdi.SocketAttach:hostname=127.0.0.1,port=8700

(6)在IDA的Modules窗口中找到libdvm.so模块,双击打开,在模块中找到dvmDexFileOpenPartial函数并右键下断点:

(7)点击运行,Cancel掉弹出的窗口,程序就会运行到dvmDexFileOpenPartial()函数
(8)R0~R4寄存器一般是用来存放一个函数的参数值的,dvmDexFileOpenPartial()的第一个参数是dex内存起始地址,第二个参数是dex大小:

下面就使用IDC脚本进行内存dump:

1
2
3
4
5
6
7
8
static main(void)
{
auto fp, dex_addr, end_addr;
fp = fopen("/User/fan/dump.dex", "wb");
end_addr = r0 + r1;
for (dex_addr = r0; dex_addr < end_addr; dex_addr++)
fputc(Byte(dex_addr), fp);
}

这是dump内存中DEX的通用代码,在IDA的File->Script command…界面中运行这段代码,等一段时间就可以得到dump.dex文件了。

分析正确的DEX文件

用JEB打开dump出来的DEX文件,首先分析MainActivity,找到button的点击事件:

这里把输入的url内容传递给WebViewActivity,但是这里的intent中的key是通过native方法decrypt_native加密了的,内容是dV.
接着分析WebViewActivity类:

其中有一个JavaScriptInterface类,下面会通过addJavascriptInterface方法,添加Javascript接口:

addJavascriptInterface方法一般用法:
mWebView.addJavascriptInterface(new JavaScriptObject(this), “objName”);
第一个参数是本地的Java对象,第二个参数是给Js中使用的对象的名称。然后js得到这个对象的名称就可以调用本地的Java对象中的方法了。
将js中的名称(objName)进行混淆加密,是为了防止恶意的网站来拦截url,然后调用我们本地的Java中的方法。

比如,现在有一个程序有一个获取设备重要信息的方法,比如获取设备的imei,如果没有名称混淆的话,破解者得到这个JS名称和方法名,然后伪造一个恶意url来调用我们程序中的这个方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
public class JavaScriptObject {
Context mContxt;
public JavaScriptObject(Context mContxt) {
this.mContxt = mContxt;
}
/* sdk17以上加注解,JS中能够访问设置了这个注解的方法,
* 没有这个注解的本地Java方法JS是访问不了的。*/
@JavascriptInterface
public String getimei() {
TelephonyManager tm = (TelephonyManager) getSystemService(TELEPHONY_SERVICE);
return tm.getDeviceId();
}
}

然后再设置js名称:

1
mWebView.addJavascriptInterface(new JavaScriptObject(this), "objName");

下面就可以伪造一个恶意的url页面来访问这个方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<script type="text/javascript">
function crackali()
{
return objName.getimei();
}
</script>
</head>
<body>
Crack...
<script type="text/javascript">
var imei = crackali();
alert("I am cracked..." + imei);
</script>
</body>
</html>

但是由于兼容性和安全性问题,基本上我们不会再用Android系统提供的addJavascriptInterface方法或@JavascriptInterface注解了,不多介绍,可以查JSBridge。

到这里就分析完了apk的逻辑了,下面来整理一下:
1、在MainActivity中输入一个页面的url,跳转到WebViewActivity进行展示;
2、WebViewActivity有Js交互,需要调用本地Java对象中的showToast方法展示消息。
问题:
因为这里的js对象名称进行了加密,所以这里我们自己编写一个网页,但是不知道这个js对象名称,无法完成showToast方法的调用。

破解的方法

破解的方法有以下几种:
1.修改smali源码,将上面的js对象名称改成任意其他值,如”objName”,然后用smali工具编译成DEX文件,重打包,再在自己编写的页面中调用:objName.showToast方法即可。
2.利用Android4.2中的WebView漏洞,直接使用如下Js代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<script>
function findobj() {
for (var obj in window) {
if ("getClass" in window[obj]) {
return window[obj]
}
}
}
</script>
</head>
<body>
hello world!
<script type="text/javascript">
var obj = findobj()
obj.showToast()
</script>
</body>
</html>

3.自己写一个程序,调用那个加密方法,就能得到正确的js对象名称。
这里使用第三种方法:

可以看到加密方法是translate库中的,因此新建一个Android项目,将translate库放到项目中,写一段代码调用native方法:

1
2
String val = ListViewAutoScrollHelpern.decrypt_native("BQ1$*[w6G_", 2);
Log.i("objName", "val:"+val);

运行会报错,原因是少了两个类,根据错误提示构造这两个类,再运行:

1
objName: val:SmokeyBear

解密之后的JS对象的名称是SmokeyBear,下面构造一个url页面,直接调用:SmokeyBear.showToast即可。

如果知道了Java层的native方法的定义,就可以调用这个native方法了,这是很不安全的,可以在so中的native函数做一个应用的签名校验,只有属于自己的签名应用才能调用,否则直接退出。

开始破解

构造页面:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<script type="text/javascript">
function alicrack() {
SmokeyBear.showToast()
}
</script>
</head>
<body>
Crack...
<script type="text/javascript">
alicrack();
</script>
</body>
</html>

构造好的页面可以放到服务器上或存到本地,WebView的loadUrl方法是可以加载本地页面的,但不能存到SD卡上,因为这个应用没有读取SD卡的权限。放到/data/data/com.ali.tg.testapp/目录下即可。WebView加载本地页面的格式是:

1
file:///data/data/com.ali.tg.testapp/crack.html

在手机上输入这么多内容比较麻烦,这里有一个技巧,用命令行输入:

1
$ adb shell input text "content to input"

点击按钮运行,成功!
reference
Android逆向之旅—动态方式破解apk终极篇(应对加固apk破解方式)