freeBuf
主站

分类

漏洞 工具 极客 Web安全 系统安全 网络安全 无线安全 设备/客户端安全 数据安全 安全管理 企业安全 工控安全

特色

头条 人物志 活动 视频 观点 招聘 报告 资讯 区块链安全 标准与合规 容器安全 公开课

官方公众号企业安全新浪微博

FreeBuf.COM网络安全行业门户,每日发布专业的安全资讯、技术剖析。

FreeBuf+小程序

FreeBuf+小程序

安卓逆向-APK保护策略深度解析
2021-09-21 23:36:15

目前

由于Java字节码的抽象级别较高,因此Android的APK较容易被反编译。如果一款应用APK被破解,那么可能会被他人植入广告或者病毒以供他人盈利或窃取用户信息;如果一款游戏APK被破解,那么这款游戏可能会从收费版变成免费版,游戏的支付系统也形同虚设。不管是哪种情况,对于开发者来说,APK被破解绝对是一场噩梦,于是就有了apk保护策略。本文章从安全的角度介绍几种常用的保护策略及如何绕过这些,

一、APK保护策略-代码混淆

此类apk混淆方式主要是用短名称混淆其余的类、字段和方法。

1、分析APK中代码混淆保护策略

下载测试app最版本apk安装包

下载完成后,使用jadx进行反编译

image-20210912135025736

可以看到很多abcd,点开里面还是abcd进行命名包名!

这就是最常见的java代码混淆,会把类名,方法名,字段等进行混淆。

写代码是不可能有这种命名方式,用androidKiller去查看下

image-20210912213708418

很明显那么多方法多是后期被处理过的,他这种混淆或者重命名,系统函数是没有被处理的,混淆知识单纯的停留在表面,主要逻辑没变

那么在逆向时看着也是不舒服的,可以借助jadx操作

image-20210912213927555

会对我们的代码进行再次混淆

image-20210912213945592

可以看到再次混淆后,前面多了字符串,这时候对于逆向的我们来说方便寻找了,长字符串相比但字符串肯定方便很多,有那么一丢丢好处

2、常用的混淆开发工具

proguard是一个能够对Java 代码进行压缩,优化,混淆,预检的工具。 proguard会检测和移除封装应用中未使用的类、字段、方法和属性。ProGuard还可优化字节码,移除未使用的代码指令,以及用短名称混淆其余的类、字段和方法。

二、APK保护策略-资源文件混淆

资源混淆是可以解决apk瘦身,主要就是压缩了资源文件及修改了文件名字及映射关系.此类混淆方法是使用android Killer等软件反编译apk,回编译时报错。

1、分析APK中资源文件混淆保护策略

android Killer反编译apk

image-20210912215051232

这时候反编译完成后,直接回编译

image-20210912215230762

结果报错

image-20210912215325159

代码也没修改,直接回编译,也是报错的,这是为什么呢,这时候如果解决呢?

这里用到MT管理器

image-20210912215500490

安装到雷电模拟器上,MT管理器可以对文件进行单独操作,我们安装在模拟器上的apk都在/data/app目录下

image-20210912223137584

找到我们需要的对用的apk目录下

image-20210912223526774

我们看到了apk安装包,双击该包

image-20210912223704648

此时的签名为V1,点击查看

image-20210912223737997

assets目录
这里面一般放的是资源,这里面的资源通常是没有编译过的,可以直接用,有图片、js、html等。

lib目录
目录放的一般是so文件,也就是本地代码,好几个文件夹一般...MIPS、x86、x64等,CPU的一些平台。

META-INF 目录
我们每次打包APK后都需要做一个签名,在系统里面是需要做验证的,不管是代码验证还是在安卓系统里面都需要签名验证,假设如果把APK文件改了,在放回去的话本身的签名和修改后放回去签名是不对应的,这时候怎么解决呢?
需要破解器破解系统核心,就算不重签名也可以放上去;或者安装在雷电等模拟器上面进行操作,模拟器支持不重签名也可以操作。

res目录
放的资源,程序的图标、样式、布局、XML等,编译之后的文件,直接查看是乱码,需要反编译的!APKtool即可反编译查看。

AndroidManifest.xml
清单文件,直接查看大部分是乱码,清单:APK需要使用系统的权限、包名是什么、APK是否支持调试等等内容。

resources.arsc
编译之后的文件,语言包、程序内容等。

classes.dex
dex文件,运行在Dalivk虚拟机上的文件,是smali代码也就是源代码,需要反编译转换为smali代码,还可以把smali代码转换为java代码或者直接des转换为jar包也可以。
大型的apk里面会有好几个classes,本身是可以合成为一个的,为什么分开放,是因为文件大小是有限制的。

接下来双击classes.dex

image-20210912224126594

使用Dex编辑器打开,进行反编译

image-20210912224156743

随便打开一个类

image-20210912224335085

image-20210912224343053

image-20210912224352381

image-20210912224402950

然后进入后,随意修改文本信息,加入个空格测试,并退出

image-20210912224521406

退出Dex编辑器,保存

image-20210912224706587

勾选自动签名,保存更新文件

image-20210912224752981

我们看到classes.dex变绿了

image-20210912224851599

后退一步APK也变绿了

image-20210912224949824

这时候点击apk,我们看到签名状态变了,点击安装

image-20210912225058752

image-20210912225118643

安装完成

三、APK保护策略-签名验证

1、分析APK中签名验证保护策略

开始详细讲解签名校验!先来看看签名验证的原理

我们首先分析一个网上的源码

image-20210913114722874

LoginActivity.java

首先来看一看LoginActivity.java,我们看到LoginActivity继承了BaseActivity

image-20210913114802076

继续看看,这里发现了几个变量和重写了onCreate方法

image-20210913142519824

BaseActivity.java

看一下BaseActivity.java下面,BaseActivity继承了Activity主类

image-20210913114921878

看一看getSignature()方法,此方法是获取签名信息,

image-20210913142857500

往下还有个hashcode然后获取他的hash值,sig给他一个返回

image-20210913143429182

会跟原来的sig值1774763026进行对比,如果不相等执行Collector.finishAll();如果等于提示程序正常运行。

PackageManager和getPackageManager是包管理器和获取包管理器的意思

getPackageInfo :获取包信息给pi

Collector.java

image-20210913203851487

大框中都是在获取签名信息,当获取完签名信息后回进行判断,说这个签名信息是不是自己的,equals判断的是hashcode两个对象的hash

2、案例分析

使用Android Killer对apk重新签名

image-20210913204823527

在雷电模拟器安装后,无法运行,因为存在签名验证!这里能分析到签名校验执行较早,在apk刚刚启动时就进行判断。

android killer 反编译

image-20210913205249849

点击入口,进入MainActivity.smali文件,找到onCreate方法

image-20210913205449579

然后点击咖啡杯,看java代码分析

image-20210913205543086

首先MainActivity继承Activity这个界面,就时一些弹窗界面等信息,继续往下看有那些重点

image-20210913211231406

找到onCreate、onClick,下面有个run方法,调用了一个bug方法,来查看下this.bug这个方法(点击进入)

image-20210913211813612

image-20210913211558167

目前找到这个bug就是签名AK反编译入口点smali里面的bug,他是对native修饰的方法,根据前面学习ndk开发的时候就知道,如果一个方法被native修饰就说明实现的逻辑在so里面

这儿就是最简单的java层分析到so层的一个逻辑思路!那么接下来用到IDA分析下apk中的so文件

image-20210913212129134

image-20210913212139254

查看so库文件第一步就是看有没有jni动态库,如果没有就查一下java_

image-20210913212331132

双击进入后,抓住我们的目的,如果一个程序有签名校验,那么去弄清楚他的签名校验的逻辑所在处

image-20210913212505302

这里我们看到getSigHashCode关键函数,就说明在这指令进行着hash校验,关键函数,使用TAB键切换为伪c代码

image-20210913212952866

双击getSignHashCode方法

image-20210913213059880

从上往下看

image-20210913213244709

getPackageManager:获取包名

getPackageInfo:获取包信息

signatures:签名

还有一个getPackageName,他和第一个、第二个即使签名的jni方法,需要出现着三个名称,就是和签名有关

找到了地方接下来就要进行修改参数,先隐藏类型,Hide casts

image-20210913213711251

image-20210913213807890

如何修改参数了,按Y键

静态注册的第一个函数时JNIEnv *

image-20210913213935290

这时候jni接口中的函数就被识别出来了

image-20210913214703876

修改第二个参数时jobject obj

image-20210913214859738

修改完参数之后,这些jni接口函数都被识别出来了

我们看最下边,这里有个result返回值,如果result不等于字符串(随机的),就执行exit(0)

image-20210913215117385

那么这个字符串哪里获取的呢

image-20210913215611327

前面的V21哈希,把V21和V19给传入V2中,V2会有一个结果,然后比较,如果不等于直接exit退出,不然就返回

result = ((*v2)->CallIntMethod)(v2, v19, v21);

分析到这里就知道整改的思路了,开始在killer反编译找到apk入口点进去,分析onCreate,分析java层代码找到native后掉调用了bug方法,然后知道逻辑在so层,并在so层分析逻辑代码,现在知道了逻辑后,我们就根据前几课的知识,直接将条件修改为nez条件就可

拓展知识:

如果在IDA修改了SO库文件后,如何保存呢

image-20210913224257378

image-20210913224439289

四、APK检测模拟器运行原理分析

例如有些apk应用有活动,邀请好友就送东西,这时候肯定是模拟器非常好用的,但是APK就会有模拟器检测,发现如果是迷你其就无法运行,怎么通过逆向找到逻辑点,过掉呢?

首先要知道APK如何检测是否是模拟器、蓝牙、wifi、电量、CPU、配置等,模拟器wifi是无的,蓝牙也是无,电量一直是百分百

1、检测通道

image-20210914202344006

"/dev/socket/qemud","/dev/qemu_pipe"

首先回定义string[]字符串数组来检测两个通道,进行一个for循环,如果有就回返回true,就是不行

2、检测驱动文件内容

image-20210914202649410

读取/proc/tty/drivers文件内容,然后检测已知的QEmu的驱动程序的列表

代码先new了一个file,它会判断这个文件(/proc/tty/drivers)存不存在,如果存在并且可以写(cnaRend)就执行下面代码:

image-20210914203219242

上面这些是java中的IO流

3、检测模拟器上的特有的几个文件

image-20210914203619993

会检测三个文件

1、/system/lib/libc_malloc_debug_qemu.so
2、/sys/qemu_trace
3、/system/bin/qemu-props

这三个文件时模拟器上特有的文件

4、检测模拟器默认的电话号码

image-20210914203818907

会监测默认的电话号码,这里都是155开头的号码,当然在实际情况下,会通过正则之类的比较多。

5、监测设备IDS序列号

image-20210914204119316

监测IDS是不是这串数值即可,如果是就被认为时模拟器

6、检测imsi号

image-20210914204653600

检测imsi是不是这串数值,如果是就被认为时模拟器

7、检测手机上的一些硬件信息

image-20210914204835380

检测蓝牙等硬件信息,如果没有就被认为时模拟器

8、检测手机运营商家

image-20210914205001056

检测设备,是android就认为是模拟器

9、基于模拟器cpu信息检测

image-20210914205226364

1、基于模拟器cpu信息的检测,cpu信息检测主要是在cpu信息看看是否包含intel、amd等字段,很多模拟器目前对于cpu信息还无法进行模拟。
2、模拟器的cpu和真机的cpu有所不同 一般而言模拟器读取的是电脑上的cpu,电脑是Inter或者AMD,那么模拟器的cpu也是Inter或者AMD,而手机一般是ARM。
3、要读取cpu信息,可以用java代码或者c代码,用c代码就要使用jni。

10、关键路径检测特定模拟器检测

image-20210914205536748

对上面特定的目录或者文件进行检测,如果存在就是模拟器

五、实战详解-绕过无法在模拟器运行APK

拿一个APK在雷电模拟器上测试一下

image-20210914211028498

image-20210914211043284

会出现这几种情况,都是在模拟器上运行使用导致无法登录

还没进入程序的界面,在程序的入口点就进行检测的

使用androidkiller对APK进行反编译分析

image-20210914212043033

反编译完成后,进来第一个入口点就是检测创建蓝牙连接,而模拟器是没有蓝牙的

可以删除相应的权限内容,这样是可以防止检测的,但是这样比较暴力,会导致程序崩溃现象

image-20210914212402304

还有进行对一些隐私进行检测

工程搜索:应用在模拟器上运行

image-20210914212605701

没有查到东西,转换成unicode

image-20210914212659675

可以看到转换unicode后查询到了

image-20210914212837424

我们来分析smali代码,他把字符串给了V1寄存器

image-20210914213105139

image-20210915103550248

那么V0是p0的上下文信息context,v0、v1、v5对应上下文,字符串、0x0。再往后看Toast指向makeText,后面是一个上下文、字符串、int(string.int),前面我用过Toast函数,这里的意识就是调用了Toast弹窗和停留时间。

image-20210915103859869

观察下,都在一个代码smali里面,第一个是212行、第二个是321行

image-20210915104034173

并且都在run方法中

image-20210915104113127

在第二条分析查看到if-nez v0,:cond_6, v0是bluetoolthAddress(蓝牙地址)静态方法返回值,回去判断不等于就跳到cond_6,如果等于就说“应用在模拟器上运行”

image-20210916201147130

跳到cond_6直接就return-void!这逻辑就很简单分析到了!继续分析入口界面

image-20210916201840015

提示文件已丢失,丢失怎么办呢?去找到配置清单

image-20210916202011612

入口点在com.vdog.VDogApplication

image-20210916202724775

找到入口点后查看方法,发现有onCreate进入分析

image-20210916202829730

public onCreate()V 其中的V,意识是viod,所以返回值为空

.locals 2表示有两个局部变量寄存器

.prologue表示从此开始执行程序

invoke-super表示调用了一个父类的方法onCreate

iget-object v0,p0 表示get是读,他会把p0的applicationName对象放到v0里面

if-eqz v0,:cond_0 表示对V0进行一个判断,如果等于1就跳过cond_0,,如果等于0就接着执行,什么时候会等于0?就是字段applicationName什么都没有获取就等于0。如果获取到了条件就不成立的。不成立就执行:

image-20210916205253498

不成立就执行下面的逻辑

iget-object v0,p0 .... 表示获取一个对象,就是p0的mApplication的对象放在v0中

iget-object v1,p0 ... 表示获取一个对象,就是p0指向的mApplication的对象放在v0中

invoke-virtual {v1} ... 表示把v1作为一个对象调用getBaseContext方法,在java层的写法mApplication.getBaseContext(); mApplication就是V1

move-result-object v1 表示把结果放在v1中

invoke-static{v0,v1} 表示之前v1获取的getBaseContext方法作为参数通过实例方法(静态方法)传入v0,有两个方法Application和Context

image-20210916210705641

刚好把这两个参数attach传入,javac层代码Application.getBaseContext()

attach包含进去后:attach(mApplication.Application.getBaseContext())第一个参数+第二个参数,把参数给到attach传进去

iget-obect v0,p0 表示同样的对象p0给到v0

invoke-virtual {v0} 表示把v0作为一个对象调用getBaseContext方法,上面是v1 getBaseContext,这里v0 getBaseContext

move-result-object v0 把结果放入v0里面,java层:v0=v0.getBaseContext();

iget-object v1,p0 : 表示获取一个对象,就是p0的mApplication方法的对象放入v1中

invoke-static {v0,v1} 表示把v0和v1作为一个参数给initInnerSdk,括号后面两个类型刚好是v0和v1,还原java层代码:initInnerSdk(v0,v1);

invoke-virtual {v0} ; v0是什么意思呢?

image-20210916213119074

此时v0是一个对象调用了onCreate,java层代码:v0.onCreate();,v0又是getBaseContext,所以getBaseContext.onCreate();

分析完smlie,也重新复现了一边smlie代码后就,发现主要逻辑什么都没有,没有任何反调试逻辑的一个代码。回到前面搜索的第一条,点击咖啡杯,分析java层代码

image-20210916213529417

image-20210916213600212

首先InitAntiEmul$1实现了一个Runnable,接下来两个参数和run()方法,如果InitAntiEmul.access$2() > 8000执行InitAntiEmul.access$3(1);

image-20210916213934312

if条件后检测一个模拟器,当检测到模拟器后,弹出会提示“应用在模拟器上运行”,执行Process.killProcess(Process.myPid());,进行一个程序杀死然后结束进程,那么有回到了改smali代码前面写出的知识,先找到哪里调用了run方法找到包名,可以注释修改源代码。

六、总结

本章重点详解解读了常见的APK保护策略(代码混淆、资源文件混淆、签名验证等)并通过一个案例来进行实战绕过问题。之前的篇幅中讲解了部分APK无法在模拟器上运行只能在手机上运行的原因及原理,并通过实战进行对绕过APK无法在模拟器进行测试,如果你想学习APK保护策略的知识可以进行参考,如有不对之处,欢迎大家指正,谢谢。
# Android # APK逆向分析 # Java代码审计 # apk反编译
本文为 独立观点,未经允许不得转载,授权请联系FreeBuf客服小蜜蜂,微信:freebee2022
被以下专辑收录,发现更多精彩内容
+ 收入我的专辑
+ 加入我的收藏
相关推荐
  • 0 文章数
  • 0 关注者
文章目录