5.2.2 程序的主Activity
我们知道,一个Android 程序由一个或多个Activity 以及其它组件组成,每个Activity
都是相同级别的,不同的Activity 实现不同的功能。每个Activity 都是Android 程序的一个
显示“页面”,主要负责数据的处理及展示工作,在Android 程序的开发过程中,程序员很
多时候是在编写用户与Activity 之间的交互代码。
每个Android 程序有且只有一个主Activity(隐藏程序除外,它没有主Activity),它是
程序启动的第一个Activity。打开crackme0502 文件夹下的AndroidManifest.xml 文件,其中
有如下片断的代码。
<activity android:label="@string/title_activity_main" android:name=".
MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
在程序中使用到的Activity 都需要在AndroidManifest.xml 文件中手动声明,声明Activity
使用activity 标签,其中android:label 指定Activity 的标题,android:name 指定具体的Activity
类,“.MainActivity”前面省略了程序的包名,完整类名应该为com.droider.crackme0502.
MainActivity,intent-filter 指定了Activity 的启动意图,android.intent.action.MAIN 表示这个
Activity 是程序的主Activity。android.intent.category.LAUNCHER 表示这个Activity 可以通过
LAUNCHER 来启动。如果AndroidMenifest.xml 中, 所有的Activity 都没有添加
android.intent.category.LAUNCHER,那么该程序安装到Android 设备上后,在程序列表中是
不可见的,同样的,如果没有指定android.intent.action.MAIN,Android 系统的LAUNCHER
就无法匹配程序的主Activity,因此该程序也不会有图标出现。
在反编译出的AndroidManifest.xml 中找到主Activity 后,可以直接去查看其所在类的
OnCreate()方法的反汇编代码,对于大多数软件来说,这里就是程序的代码入口处,所有的
功能都从这里开始得到执行,我们可以沿着这里一直向下查看,追踪软件的执行流程。
5.2.3 需重点关注的Application 类
如果需要在程序的组件之间传递全局变量,或者在Activity 启动之前做一些初始化工作,
就可以考虑使用Application 类了。使用Application 时需要在程序中添加一个类继承自
android.app.Application,然后重写它的OnCreate()方法,在该方法中初始化的全局变量可以
在Android 其它组件中访问,当然前提条件是这些变量具有public 属性。最后还需要在
AndroidManifest.xml 文件的Application 标签中添加“android:name”属性,取值为继承自
android.app.Application 的类名。
鉴于Application 类比程序中其它的类启动得都要早,一些商业软件将授权验证的代码都转
移到了该类中。例如,在OnCreate()方法中检测软件的购买状态,如果状态异常则拒绝程序继
续运行。因此,在分析Android 程序过程中,我们需要先查看该程序是否具有Application 类,
如果有,就要看看它的OnCreate()方法中是否做了一些影响到逆向分析的初始化工作。
5.2.4 如何定位关键代码——六种方法
一个完整的Android 程序反编译后的代码量可能非常庞大,要想在这浩如烟海的代码中
找到程序的关键代码,是需要很多经验与技巧的。笔者经过长时间的探索,总结了以下几种
定位代码的方法。
信息反馈法
所谓信息反馈法,是指先运行目标程序,然后根据程序运行时给出的反馈信息作为
突破口寻找关键代码。在第2 章中,我们运行目标程序并输入错误的注册码时,会弹出
提示“无效用户名或注册码”,这就是程序反馈给我们的信息。通常情况下,程序中用
到的字符串会存储在String.xml 文件或者硬编码到程序代码中,如果是前者的话,字符
串在程序中会以id 的形式访问,只需在反汇编代码中搜索字符串的id 值即可找到调用
代码处;如果是后者的话,在反汇编代码中直接搜索字符串即可。
特征函数法
这种定位代码的方法与信息反馈法类似。在信息反馈法中,无论程序给出什么样的
反馈信息,终究是需要调用Android SDK 中提供的相关API 函数来完成的。比如弹出
注册码错误的提示信息就需要调用Toast.MakeText().Show()方法,在反汇编代码中直接
搜索Toast 应该很快就能定位到调用代码,如果Toast 在程序中有多处的话,可能需要
分析人员逐个甄别。
顺序查看法
顺序查看法是指从软件的启动代码开始,逐行的向下分析,掌握软件的执行流程,
这种分析方法在病毒分析时经常用到。
代码注入法
代码注入法属于动态调试方法,它的原理是手动修改apk 文件的反汇编代码,加入
Log 输出,配合LogCat 查看程序执行到特定点时的状态数据。这种方法在解密程序数
据时经常使用,详细的内容会在本书的第8 章介绍。
栈跟踪法
栈跟踪法属于动态调试方法,它的原理是输出运行时的栈跟踪信息,然后查看栈上
的函数调用序列来理解方法的执行流程,这种方法的详细内容会在本书的第8 章介绍。
Method Profiling
Method Profiling(方法剖析)属于动态调试方法,它主要用于热点分析和性能优化。
该功能除了可以记录每个函数占用的CPU 时间外,还能够跟踪所有的函数调用关系,
并提供比栈跟踪法更详细的函数调用序列报告,这种方法在实践中可帮助分析人员节省
很多时间,也被广泛使用,详细的内容会在本书的第8 章介绍。
转载于丰生强《Android软件安全与逆向分析》