Android逆向之旅—动态方式破解apk前奏篇(Eclipse动态调试smail源码)

Android技术篇 尼古拉斯.赵四 50944℃ 0评论

一、前言

今天我们开始apk破解的另外一种方式:动态代码调试破解,之前说的主要采用的是静态方式,步骤也很简单,首先使用apktool来反编译apk,得到smail源码,然后分析smail代码,采用代码注入技术来跟踪代码,然后找到关键方法进行修改,进而破解,同时还可以使用一些开源的hook框架,比如:Xposed和Cydia Substrate,来进行关键方法的hook。所以这里我们可以看到我们破解的第一步是使用apktool来进行成功的反编译,然后是需要了解smali语法,不过关于smali语法其实很简单,网上有很多教程。

 

二、知识概要分析

那么今天我们就用另外一种方式来破解apk:动态方式,关于动态方式其实很广义的,因为动态方式相对于静态方式来说,难度大一点,但是他比静态方式高效点,能够针对更过的破解范围。当然动态方式很多,所以这里就分为三篇文章来讲解这块:

1、动态方式破解apk前奏篇(Eclipse动态调试smail源码)

2、动态方式破解apk升级篇(IDA动态调试so源码)

3、动态方式破解apk终极篇(应对加固的apk破解方法)

从这三篇文章能够让我们破解一般的apk没有任何问题,不过不能代表能够破解所有的apk,因为没有绝对的安全,也是没有绝对的破解,两方都在进步,我们只能具体问题具体分析。好了,下面我们就来看第一篇文章,也是今天的重点:Eclipse动态调试smali源码,首先需要解释一下,这里为什么说是调试smali源码,不是Java源码,因为我们弄过反编译的人知道,使用apktool反编译apk之后,会有一个smali文件夹,这里就存放了apk对应的smali源码,关于smali源码这里不解释了,网上有介绍。

 

三、案例分析

因为这一篇是一个教程篇,所以不能光说,那样会很枯燥的,所以这里用一个例子来介绍一下,我们就用阿里2014年安全挑战赛的第一题:AliCrack_one.apk

看到这张图了,阿里还挺会制造氛围的,那么其实很简单,我们输入密码就可以破解了,下面我们就来看看如何获取这个密码。

第一步:使用apktool来破解apk

java -jar apktool_2.0.0rc4.jar d -d AliCraceme_1.apk -o out

这里的命令不做解释了。

但是有一个参数必须带上,那就是:-d

因为这个参数代表我们反编译得到的smali是java文件,这里说的文件是后缀名是java,如果不带这个参数的话,后缀名是smali的,但是Eclipse中是不会识别smali的,而是识别java文件的,所以这里一定要记得加上这个参数。

反编译成功之后,我们得到了一个out目录,如下:

源码都放在smali文件夹中,我们进入查看一下文件:

看到了,这里全是Java文件的,其实只是后缀名为java了,内容还是smali的:

2、修改AndroidManifest.xml中的debug属性和在入口代码中添加waitDebug

上面我们反编译成功了,下面我们为了后续的调试工作,所以还是需要做两件事:

1》修改AndroidManifest.xml中的android:debuggable=”true”

关于这个属性,我们前面介绍run-as命令的时候,也提到了,他标识这个应用是否是debug版本,这个将会影响到这个应用是否可以被调试,所以这里必须设置成true。

2》在入口处添加waitForDebugger代码进行调试等待。

这里说的入口处,就是程序启动的地方,就是我们一般的入口Activity,查找这个Activity的话,方法太多了,比如我们这里直接从上面得到的AndroidManifest.xml中找到,因为入口Activity的action和category是固定的。

当然还有其他方式,比如aapt查看apk的内容方式,或者是安装apk之后用

adb dumpsys activity top 命令查看都是可以的。

找到入口Activity之后,我们直接在他的onCreate方法的第一行加上waitForDebugger代码即可,找到对应的MainActivity的smali源码:然后添加一行代码:

invoke-static {}, Landroid/os/Debug;->waitForDebugger()V

这个是smali语法的,其实对应的Java代码就是:android.os.Debug.waitForDebugger();

这里把Java语言翻译成smali语法的,不难,网上有smali的语法解析,这里不想再解释了。

第三步:回编译apk并且进行签名安装

java -jar apktool_2.0.0rc4.jar b -d out -o debug.apk

还是使用apktool进行回编译

编译完成之后,将得到debug.apk文件,但是这个apk是没有签名的,所以是不能安装的,那么下面我们需要在进行签名,这里我们使用Android中的测试程序的签名文件和sign.jar工具进行签名:

java -jar .\sign\signapk.jar .\sign\testkey.x509.pem .\sign\testkey.pk8 debug.apk debug.sig.apk

签名之后,我们就可以进行安装了。

第四步:在Eclipse中新建一个Java工程,导入smali源码

这里我们新建一个Java工程,记住不是Android工程,因为我们最后调试其实是借助于Java的调试器,然后勾选掉Use default location选项,选择我们的smali源码目录,也就是我们上面反编译之后的out目录,点击完成

我们导入源码之后的项目工程结构:

主要看MainActivity类:

第五步:找到关键点,然后打断点

这一步我们看到,其实说的比较广义了,这个要具体问题具体分析了,比如这个例子中,我们知道当我们输入密码之后,肯定要点击按钮,然后触发密码的校验过程,那么这里我们知道找到这个button的定义的地方,然后进入他的点击事件中就可以了。这里分为三步走:

1》使用Eclipse自带的View分析工具找到Button的ResId

点击之后,需要等待一会,分析View之后的结果:

看到了,这里我们能够看到整个当前的页面的全部布局,已经每个控件的属性值,我们需要找到button的resource-id

这里我们看到定义是@+id/button这个值。

2》我们得到这个resId之后,能否在smali工程中全局搜索这个值,就可以定位到这个button的定义的地方呢?

然后我们看看搜到的结果:

这时候我们其实是在资源文件中搜到了这个id的定义,这个id值对应的是0x7F05003E。

当然除了这种方式,我们还有一种方式能快速找到这个id对应的整型值,那就是在反编译之后的values/public.xml文件中:

这个文件很有用的,他是真个apk中所有资源文件定义的映射内容,比如drawable/string/anim/attr/id 等这些资源文件定义的值,名字和整型值对应的地方:

这个文件很重要,是我们在寻找突破口的重要关键,比如我们有时候需要通过字符串内容来定位到关键点,这里就可以通过string的定义来找到对应的整型值即可。

当我们找到了button对应的id值了之后,我们就可以用这个id值在一次全局搜索一下,因为我们知道,Android中编译之后的apk,在代码中用到的resId都是用一个整型值代替的,这个整型值就是在R文件中做了定义,将资源的id和一个值对应起来,然后代码里面一般使用R.id.button这样的值,在编译出apk的时候,这个值就会被替换成对应的整型值,所以在全局搜索0x7F05003E

搜索的结果如下:

看到了,这里就定位到了代码中用到的这个button,我们进入代码看看:

在这里,看到了,使用了findViewById的方式定义Button,我们在往下面简单分析一下smali语法,下面是给button添加一个按钮事件,这里用的是内部类MainActivity$1,我们到这个类看看,他肯定实现了OnClickListener接口,那么直接搜onClick方法:

在这里我们就可以下个断点了,这里就是触发密码校验过程。

第六步:运行程序,设置远程调试工程

在第五步中,我们找到了关键点,然后打上断点,下面我们就来运行程序,然后在Eclipse中设置远程调试的工程

首先我们运行程序,因为我们加入了waitForDebug的代码,所以启动的时候会出现一个Wait debug的对话框。不过,我测试的时候,我的手机没有出现这个对话框,而是一个白屏,不过这个不影响,程序运行起来之后,我们看看如何在Eclipse中设置远程调试工程,首先我们找到需要调试的程序对应远程调试服务端对应的端口:

这里我们看到有几个点:

1》在程序等待远程调试服务器的时候,前面会出现一个红色的小蜘蛛

2》在调试服务端这里我们会看到两个端口号:8600/8700,这里需要解释一下,为什么会有两个端口号呢?

首先在这里的端口号,代表的是,远程调试服务器端的端口,下面在简单来看一下,Java中的调试系统:

这里我们看到,这里有三个角色:

111》JDB Client端(被调试的客户端),这里我们可以认为我们需要破解的程序就是客户端,如果一个程序可以被调试,当启动的时候,会有一个jdwp线程用来和远程调试服务端进行通信

这里我们看到,我们需要破解的程序启动了JDWP线程,注意这个线程也只有当程序是debug模式下才有的,也就是AndroidManifest.xml中的debug属性值必须是true的时候,也就是一开始为什么我们要修改这个值的原因。

222》JDWP协议(用于传输调试信息的,比如调试的行号,当前的局部变量的信息等),这个就可以说明,为什么我们在一开始的时候,反编译成java文件,因为为了Eclipse导入能够识别的Java文件,然后为什么能够调试呢?因为smali文件中有代码的行号和局部变量等信息,所以可以进行调试的。

333》JDB Server端(远程调试的服务端,一般是有JVM端),就是开启一个JVM程序来监听调试端,这里就可以认为是本地的PC机,当然这里必须有端口用来监听,那么上面的8600端口就是这个作用,而且这里端口是从8600开始,后续的程序端口后都是依次加1的,比如其他调试程序:

那么有了8600端口,为什么还有一个8700端口呢?他是干什么的?其实他的作用就是远程调试端备用的基本端口,也就是说比如这里的破解程序,我们用8600端口可以连接调试,8700也是可以的,但是其他程序,比如demo.systemapi他的8607端口可以连接调试,8700也是可以的:

所以呀,可以把8700端口想象成大家都可以用于连接调试的一个端口,不过,在实际过程中,还是建议使用程序独有的端口号8600,我们可以查看8600和8700端口在远程调试端(本地pc机)的占用情况:

看到了,这里的8600端口和8700端口号都是对应的javaw程序,其实javaw程序就是启动一个JVM来进行监听的。好了,到这里我们就弄清楚了,Java中的调试系统以及远程调试的端口号。

注意:

其实我们可以使用adb jdwp命令查看,当前设备中可以被调试的程序的进程号信息:

下面继续,我们知道了远程调试服务端的端口:8600,以及ip地址,这里就是本地ip:localhost/127.0.0.1

我们可以在Eclipse中新建一个远程调试项目,将我们的smali源码工程和设备中需要调试的程序关联起来:

右击被调试的项目=》选择Debug Configurations:

然后开始设置调试项目

选择Romote Java Application,在Project中选择被调试的smali项目,在Connection Type中选择SocketAttach方式,其实还有一种方式是Listener的,关于这两种方式其实很好理解:

#Listner方式:是调试客户端启动就准备好一个端口,当调试服务端准备好了,就连接这个端口进行调试

#Attach方式:是调试服务端开始就启动一个端口,等待调试端来连接这个端口

我们一般都是选择Attach方式来进行操作的。

好了,我们设置完远程调试的工程之后,开始运行,擦发现,设备上的程序还是白屏,这是为什么呢?看看DDMS中调试程序的状态:

擦,关联到了这个进程,原因也很简单,我们是上面使用的是8700端口号,这时候我们选中了这个进程,所以就把smali调试工程关联到了这个进程,所以破解的进程没反应了,我们立马改一下,用8600端口:

好了,这下成功了,我们看到红色的小蜘蛛变成绿色的了,说明调试端已经连接上远程调试服务端了。

注意:

我们在设置远程调试项目的时候,一定要注意端口号的设置,不然没有将调试项目源码和调试程序关联起来,是没有任何效果的

第七步:开始运行调试程序,进入调试

下面我们就开始操作了,在程序的文本框中输入:gggg内容,点击开始:

好了,到这里我们看到期待已久的调试界面出来了,到了我们开始的时候加的断点处,这时候我们就可以开始调试了,使用F6单步调试,F5单步跳入,F7单步跳出进行操作:

看到了,这里使用v3变量保存了我们输入的内容

这里有一个关键的地方,就是调用MainActivity的getTableFromPic方法,获取一个String字符串,从变量的值来看,貌似不是规则的字符串内容,这里先不用管了,继续往下走:

这里又遇到一个重要的方法:getPwdFromPic,从字面意义上看,应该是获取正确的密码,用于后面的密码字符串比对。

查看一下密码的内容,貌似也是一个不规则的字符串,但是我们可以看到和上面获取的table字符串内容格式很像,接着往下走:

这里还有一个信息就是,调用了系统的Log打印,log的tag就是v6保存的值:lil

这时候,我们看到v3是保存的我们输入的密码内容,这里使用utf-8获取他的字节数组,然后传递给access$0方法,我们使用F5进入这个方法:

在这个方法中,还有一个bytesToAliSmsCode方法,使用F5进入:

那么这个方法其实看上去还是很简单的,就是把传递进来的字节数组,循环遍历,取出字节值,然后转化成int类型,然后在调用上面获取到的table字符串的chatAt来获取指定的字符,使用StringBuilder进行拼接,然后返回即可。

按F7跳出,查看,我们返回来加密的内容是:日日日日,也就是说gggg=>日日日日

最后再往下走,可以看到是进行代码比对的工作了。

那么上面我们就分析完了所有的代码逻辑,还不算复杂,我们来梳理一下流程:

A>调用MainActivity中的getTableFromPic方法,获取一个table字符串

我们可以进入看看这个方法的实现:

这里可以大体了解了,他是读取asset目录下的一个logo.png图片,然后获取图片的字节码,在进行操作,得到一个字符串,那么我们从上面的分析可以知道,其实这里的table字符串类似于一个密钥库。

B>通过MainActivity中的getPwdFromPic方法,获取正确的密码内容

C>获取我们输入内容的utf-8的字节码,然后调用access$0方法,获取加密之后的内容

D>access$0方法中在调用bytesToAliSmsCode方法,获取加密之后的内容

这个方法是最核心的,我们通过分析知道,他的逻辑是,通过传递进来的字节数组,循环遍历数组,拿到字节转化成int类型,然后在调用密钥库字符串table的charAt得到字符,使用StringBuilder进行拼接。

通过上面的分析之后,我们知道获取加密之后的输入内容和正确的密码内容做比较,那么我们现在有的资源是:密钥库字符串和正确的加密之后的密码,以及加密的逻辑

那么我们的破解思路其实很简单了,相当于,我们知道了密钥库字符串,也知道了,加密之后的字符组成的字符串,那么可以通过遍历加密之后的字符串,循环遍历,获取字符,然后再去密钥库找到指定的index,然后在转成byte,保存到字节数组,然后用utf-8获取一个字符串,那么这个字符串就是我们要的密码。

下面我们就用代码来实现这个功能:

代码逻辑,很简单吧,其实这个函数相当于上面加密函数的bytesToAliSmsCode的反向实现,运行结果:

OK,得到了正确的密码,下面来验证一下:

哈哈,不要太激动,成功啦啦~~。破解成功。

补充:

刚刚我们在断点调试的时候,看到了代码中用了Log来打印日志,tag是lil,那么我们可以打印这个log看看结果:

看到了,这里table是密钥库,pw是正确的加密之后的密码,enPassword是我们输入之后加密的密码。所以从这里可以看到,这个例子,其实我们在破解apk的时候,有时候日志也是一个非常重要的信息。

四、思路整理

1、我们通过apktool工具进行apk的反编译,得到smali源码和AndroidManifest.xml,然后修改AndroidManifest.xml中的debug属性为true,同时在入口处加上waitForDebug代码,进行debug等待,一般入口都是先找到入口Activity,然后在onCreate方法中的第一行这里需要注意的是:apktool工具一定要加上-d参数,这样反编译得到的文件是java文件,这样才能够被Eclipse识别,进行调试。

2、修改完成AndroidManifest.xml和添加waitForDebug之后,我们需要在使用apktool进行回编译,回编译之后得到的是一个没有签名的apk,我们还需要使用signapk.jar来进行签名,签名文件直接使用测试程序的签名文件就可以,最后在进行安装。

3、然后我们将反编译之后的smali源码导入到Eclipse工程中,找到关键点,进行下断点,这里的关键点,一般是我们先大致了解程序运行的结构,然后找到我们需要破解的地方,使用View分析工具,或者是使用jd-gui工具直接查看apk源码(使用dex2jar将dex文件转化成jar文件,然后用jd-gui进行查看),找到代码的大体位置。然后下断点,这里我们可以借助Eclipse的DDMS自带的View分析工具找到对应控件的resid,然后在全局搜索这个控件的resid,或者直接在values/public.xml中查找,最终定位到这个控件位置,在查看他的点击事件即可。

4、设置远程调试工程,首先运行需要调试程序,然后在DDMS中找到对应的调试服务端的端口号,然后在Debug Configurations中设置远程调试项目,设置对应的调试端口和ip地址(一般都是本机pc,那就是localhost),然后红色小蜘蛛变成绿色的,表示我们的远程调试项目连接关联上了调试程序,这里需要注意的是,一定需要关联正确,不然是没有任何效果的,关联成功之后,就可以进行操作。

5、操作的过程中,会进入到关键的断点处,通过F6单步,F5单步进入,F7单步跳出,来进行调试,找到关键方法,然后通过分析smali语法,了解逻辑,如果逻辑复杂的,可以通过查看具体的环境变量的值来观察,这里也是最重要的,也是最复杂的,同时这里也是没有规章可寻的,这个和每个人的逻辑思维以及破解能力有关系,分析关键的加密方法是需要功底的,当然这里还需要注意一个信息,就是Log日志,有时候也是很重要的一个信息。

6、最后一般当我们知道了核心方法的逻辑,要想得到正确的密码,还是需要自己用语言去实现逻辑的,比如本文中的加密方法,我们需要手动的code一下加密的逆向方法,才能得到正确的密码。

五、遗留问题

1、使用apktool工具进行反编译有时候并不是那么顺利,比如像这样的报错:

这个一般都是apktool中解析出现了错误,其实这个都是现在apk为了抵抗apktool,做的apk加固策略,这个后面会写一篇文章如何应对这些加固策略,如何进行apk修复,其实原理就是分析apktool源码,找到指定的报错位置,进行apktool代码修复即可。

2、本文中说到了Java的调试系统,但是为了篇幅限制,没有详细的讲解了整个内容,后面会写一篇文章具体介绍Java中的调试系统以及Android的调试系统。

3、有时候我们还会遇到回编译成功了,然后遇到运行不起来的错误,这个就需要使用静态方式先去分析程序启动的逻辑,看看是不是程序做了什么运行限制,比如我们在静态分析那篇文章中,提到了应用为了防止反编译在回编译运行,在程序的入口处作了签名校验,如果校验失败,直接kill掉自己的进程,退出程序了,所以这时候我们还是需要使用静态方式去分析apk。

4、如何做到不修改AndroidManifest.xml中的debug属性就可以进行调试:

1》 修改boot.img,从而打开系统调试,这样就可以省去给app添加android:debuggable=”true”,再重打包的步骤了。
2》直接修改系统属性,使用setpropex工具在已经root的设备上修改只读的系统属性。使用此工具来修改ro.secure和ro.debuggable的值。

本文的目的只有一个就是学习更多的逆向技巧和思路,如果有人利用本文技术去进行非法商业获取利益带来的法律责任都是操作者自己承担,和本文以及作者没关系,本文涉及到的代码项目可以去编码美丽小密圈自取,欢迎加入小密圈一起学习探讨技术

六、总结

这篇文章我们就介绍了如何使用Eclipse去动态调试反编译之后的smali源码,这种方式比静态方式高效很多的,比如本文中的这个例子,其实我们也可以使用静态方式进行破解的,但是肯定效率没有动态方式高效,所以以后我们又学会了一个技能,就是动态的调试smali源码来跟踪程序的核心点,但是现在市场上的大部分应用没有这么简单就破解了,比如核心的加密算法放到了native层去做,那么这时候就需要我们去动态调试so文件跟踪,这个是我们下一篇文章的内容,也有的时候,apk进行加固了,直接在apktool进行反编译就失败了,这时候我们就需要先进行apk修复,然后才能后续的操作,这个是我们下下篇的文章,如何应对apk的加固策略。通过这篇文章我们可以看到动态方式破解比静态方式高效的多,但是有时候我们还需要使用静态方式先做一些准备工作,所以在破解apk的时候,动静结合,才能做到完美的破解。

《Android应用安全防护和逆向分析》

点击立即购买:京东  天猫  

更多内容:点击这里

关注微信公众号,最新技术干货实时推送


转载请注明:尼古拉斯.赵四 » Android逆向之旅—动态方式破解apk前奏篇(Eclipse动态调试smail源码)

喜欢 (29)or分享 (0)
发表我的评论
取消评论

表情

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址
(6)个小伙伴在吐槽
  1. 第五步的第一张图是不是错了呀?View分析是在哪儿哦?谢谢~
    Annoymous2016-06-13 11:02 回复
  2. 不好意思刚开始学习,Eclipse还没搞太明白。。我再研究下怎么安装SDK插件。。
    Annoymous2016-06-13 11:37 回复
  3. 做事太靠谱了!赞
    aaa2016-07-29 12:33 回复
  4. -d 参数在 2.1.0 被去掉了
    Lynn2016-09-20 11:06 回复
  5. test
    lucy2016-10-08 18:25 回复
  6. 大神,请接收我的膝盖
    android初学者2016-10-18 09:11 回复