作者:郭嘉
邮箱:allenwells@163.com
博客:http://blog.csdn.net/allenwells
github:https://github.com/AllenWells
【Android SDK程序逆向分析与破解系列】章节索引
【Android SDK程序逆向分析与破解系列】之一:Android安装程序APK分析
【Android SDK程序逆向分析与破解系列】之二:Android可执行程序DEX分析(一)
【Android SDK程序逆向分析与破解系列】之三:Android可执行程序DEX分析(二)
【Android SDK程序逆向分析与破解系列】之四:Android可执行程序ODEX分析
【Android SDK程序逆向分析与破解系列】之五:Android APK的静态分析
什么是静态分析?
静态分析(Static Analysis)是指在不运行代码的情况下,采用词法分析、语法分析等技术手段对程序文件进行扫描而生成程序的反汇编代码,然会阅读反汇编代码来掌握程序功能的一种技术。
一 如何快速定位Android程序的关键代码
APK程序反编译后生成的文件中包含一个AndroidManifest.xml文件,该文件包含了软件的包名,运行系统的版本,使用的组件是破解的关键点。
关注MainActivity
每个APK只有一个主Activity,它的定义如下图所示:
- android:label:Activity的标题
- android:name:Activity的包名+类名
- intent-filter:Activity的启动意图
找到主Activity后,查看该类的onCreate()函数的反汇编代码,这就是程序的入口,从这里一直往下看,追踪软件的执行流程。
关注Application
Application类主要负责在程序的组件之间传递全局变量,或者在Activity启动之前做一些初始化工作,比如商业软件的授权等等Application类在application标签中用包名+类名来注册。
APK反编译的后代码量一般非常庞大,那么定位关键代码就需要有技巧,有方法,常见的定位关键代码的方法如下所示:
1.1 信息反馈法
信息反馈法是先运行目标程序,然后根据程序运行的反馈信息定位关键代码。比如商业软件需要注册,在输入错误的饿注册码时,程序会反馈“无效的注册码”等信息,类似这样的字符串一般会存储在string.xml文件中或硬编码到程序代码中,对于前者会以id的形式访问,直接在反汇编的代码中搜索字符串的id即可,对于后者,在反汇编代码中直接搜索字符串即可。
1.2 特征函数法
特征函数法与信息反馈法类似,消息的提示一般最终都是由Android的API来完成,比如Toast、AlertDialog等。
1.3 顺序查看法
顺序查看法是从软件的启动代码开始,逐行的向下分析,掌握软件的执行流程。这种方法在病毒分析经常用到,
1.4 代码注入法
代码注入法属于动态调试方法,它的原理是手动修改APK的反汇编代码,加入Log输出,配合LogCat查看程序执行到定点的状态数据,这种方法在解密程序时经常使用。
1.5 栈跟踪法
栈跟踪法属于动态调试法,然后查看栈上函数调用序列来理解方法的执行流程。
1.6 方法剖析
方法剖析(Method Profiling)属于动态调试方法,它主要用于热点分析和性能优化,该功能可以记录每个函数占用的CPU时间,还能追踪所有的函数调用关系,并提供比栈跟踪法更详细的函数调用序列报告。
二 Smail语法
2.1 类信息
Smail文件的头三行描述了当前类的信息
.class <访问权限>[修饰关键字]<类名>
.super <父类名>
.source <源文件名>
注意:经过混淆的DEX文件,反编译出来的Smail代码可能没有源文件信息,即.source可能为空。
2.2 主体部分
一个类由多个字段或方法组成。
2.2.1 字段
字段的声明使用.field指令。
- 静态字段:field<访问权限>staic[修饰关键字]<字段名>:<字段类型>
- 实例字段:field<访问权限>[修饰关键字]<字段名>:<字段类型>
2.2.2 方法
方法的声明使用.method指令。
- 直接方法:.method<访问权限>[静态关键字]<方法原型>,起始处会注释# direct methods
- 虚方法:.method<访问权限>[静态关键字]<方法原型>,起始处会注释#virtual methods
2.2.3 类
内部类
Android的内部类分为成员内部类、静态嵌套类、方法内部类和匿名内部类。
class Outer
{
class Inner()
{
}
}
内部类会被反编译成两个文件Outer.smail和Inner.smail
监听器
监听器的本质是接口
btn.setOnClickListener(new OnClickListener()
{
@Override
public void onClick(View v)
{
}
});
.implements<接口名>,起始处会注释#interfaces。
注释类
.annotation[注释属性]<注释类名> [注释字段 = 值]
.end annotation,起始处会注释#annotations。
自动生成的类
R类
R类主要描述APK的资源信息,其中包含内部类,R.smail、R
BuildConfg类
BuildConfg类中只有一个boolean类型的DEBUG字段,用来标识程序发布的类型,true表示已调试版本发布,false表示以非调试版本发布。
注释类
如果在代码中使用了SuppresLint或TargetApi等注释,程序将会自动生成对应的类,反编译后会在smail\android\annotation目录下生成相应的Smail文件。
2.2.4 循环语句
循环语句主要有两种:
for循环
for(<对象><对象名>; <对象名范围>; <对象递增/递减步长>)
{
[处理单个对象的代码体]
}
for循环对应的Smail代码如下所示:
.local v1, i:I #初始化v1为0
:goto_0 #迭代循环开始
if-1t v1, v5 :cond_0 #如果v1<v5,则跳转到cond_0标号处。
...
...
...
:cond_0
invoke-interface {v0, v1}, Ljava/util/List;->get(I)Ljava/lang/Object; #单个循环项
...
...
...
add-int/lit8 v1, v1, 0x1 #下一个索引
goto :goto_0 #跳转到循环开始处
while循环
while(<迭代器>hasNext())
{
<对象><对象名> = <迭代器>.next()
[处理单个对象的代码体]
}
:goto_0 #迭代循环开始
invoke-interface {v4}, Ljava/util/Iterator;->hasNext()Z #开始迭代
...
...
...
invoke-interface {v4}, Ljava/util/Iterator;->next()Ljava/lang/Object #循环获取每一项
...
...
...
goto :goto_0 #跳转到循环开始处
两种循环生成的Smail代码非常相似,总的说来,迭代器循环有如下特点:
- 迭代器循环会调用迭代器的hasNext()方法检测循环条件是否满足。
- 迭代器循环调用迭代器的next()方法获取单个对象。
- 循环中使用goto指令来控制代码的流程。
- for形式的迭代器循环展开后即为while形式的迭代器循环。
2.2.5 分支语句
switch分支语句
switch(判断项)
{
case 判断项值1:
break;
case 判断项值1:
break;
case 判断项值1:
break;
default:
break;
}
对应的Smail代码如下所示:
packed_switch p1, :pswitch_data_0 #packed_switch分支,pswitch_data_0指定case区域
...
...
...
:pswitch_data_0 #case区域,从0开始
2.2.6 异常捕捉语句
try/catch语句
try
{
}
catch(Exception e)
{
}
对应的Smail语句
:try_start_0 #第一个try开始
...
...
...
:try_end_0 #第一个try结束
...
...
...
:try_start_1 #第二个try开始
...
...
...
:try_end_1 #第二个try结束
...
...
...
:goto_0
...
...
...
:catch_0
...
...
...
:try_start_2 #第三个try开始
...
...
...
:try_end_2 #第三个try结束
...
...
...
:catch_1