破解Android程序通常的方法是将apk文件利用ApkTool反编译,生成Smali格式的反汇编代码,然后阅读Smali文件的代码来理解程序的运行机制,找到程序的突破口进行修改,最后使用ApkTool重新编译生成apk文件并签名,最后运行测试,如此循环,直至程序被成功破解。

1. 反编译APK文件

ApkTool是跨平台的工具,可以在windows平台与linux平台下直接使用。使用前到:http://code.google.com/p/android-apktool/  下载ApkTool,目前最新版本为1.4.3,Windows平台需要下载apktool1.4.3.tar.bz2与apktool-install-windows-r04-brut1.tar.bz2两个压缩包,如果是linux系统则需要下载apktool1.4.3.tar.bz2与apktool-install-linux-r04-brut1.tar.bz2,将下载后的文件解压到同一目录下。进入到命令行的解压目录下,执行apktool命令会列出程序的用法:

反编译apk文件的命令为: apktool  d[ecode]  [OPTS]  <file.apk>  [<dir>]

编译apk文件的命令为: apktool  b[uild]  [OPTS]  [<app_path>]  [<out_file>]

那么在命令行下进入到apktool工具目录,输入命令:

1
$ ./apktool  d  /home/fuhd/apk/gnapk/nice/com.nice.main.apk  outdir

稍等片刻,程序就会反编译完成,如图:

2. 分析APK文件

如上例,反编译apk文件成功后,会在当前的outdir目录下生成一系列目录与文件。其中smali目录下存放了程序所有的反汇编代码,res目录则是程序中所有的资源文件,这些目录的子目录和文件与开发时的源码目录组织结构是一致的。

如何寻找突破口是分析一个程序的关键。对于一般Android来说,错误提示信息通常是指引关键代码的风向标。以书中的注册示例为例,在错误提示附近一般是程序的核心验证代码,分析人员需要阅读这些代码来理解软件的注册流程。

错误提示是Android程序中的字符串资源,开发Android程序时,这些字符串可能硬编码到源码中,也可能引用 自“res/values”目录下的strings.xml文件,apk文件在打包时,strings.xml中的字符串被加密存储为“resources.arsc”文件保存到apk程序包中,apk被成功反编译后这个文件也被解密出来了。

以书中2.1.2节运行程序时的错误提示,在软件注册失败时会Toast弹出“无效用户名或注册码”,我们以此为线索来寻找关键代码。打开“res/values/strings.xml”文件,内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?xml version="1.0" encoding="utf-8" ?>
<resources>
    <string name="app_name">Crackme0201</string>
    <string name="hello_world">Hello world!<string>
    <string name="menu_settings">Settings</string>
    <string name="title_activity_main">crackme02</string>
    <string name="info">Android程序破解演示实例</string>
    <string name="username">用户名:</string>
    <string name="sn">注册码:</string>
    <string name="register">注册</string>
    <string name="hint_username">请输入用户名</string>
    <string name="hint_sn">请输入16位的注册码</string>
    <string name="unregister">程序未注册</string>
    <string name="registered">程序已注册</string>
    <string name="unsuccessed">无效用户名或注册码</string>    <!-- 就是这一行 -->
    <string name="successed">恭喜您!注册成功</string>
</resources>

开发Android程序时,strings.xml文件中的所有字符串资源都在“gen/<packagename>/R.java”文件的String类中被标识,每个字符串都有唯一的int类型索引值,使用Apktool反编译apk文件后,所有的索引值保存在strings.xml文件同目录下的public.xml文件中。

从上面列表中找到“无效用户名或注册码”的字符串名称unsuccessed。打开public.xml文件,它的内容如下:

1
2
3
4
5
6
7
8
9
10
11
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <public type="drawable" name="ic_launcher" id="0x7f020001" />
    <public type="drawable" name="ic_action_search" id="0x7f020000" />
    .......
    <public type="string" name="unsuccessed" id="0x7f05000c"/>     <!-- 这是这一行 -->
    .......
    <public type="id" name="edit_sn" id="0x7f080002" />
    <public type="id" name="button_register" id="0x7f080003" />
    <public type="id" name="menu_settings" id="0x7f080004" />
</resources>

unsuccessed的id值为0x7f05000c,在smali目录中搜索含有内容为0x7f05000c的文件,最后发现只有MainActivity$1.smali文件一处调用,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# virtual methods
.method public onClick(Landroid/view/View;)V
    .locals 4
    .parameter "v"
    .prologue
    const/4 v3, 0x0
    ......
    .line 32
    #calls:
    Locm/droider/crackme0201/MainActivity;->checkSN(Ljava/lang/String;Ljava/lang/String;)Z
    invoke-static {v0,v1,v2}, Lcom/droider/crackme0201/MainActivity;->
        #检查注册码是否合法
        access$2(Lcom/droider/crackme0201/MainActivity;Ljava/lang/string;Ljava/lang/String;)Z    
    move-result v0
    if-nez v0, :cond_0  #如果结果不为0,就跳转到cond_0标号处
    .line 34
    iget-object v0, p0, Lcom/droider/crackme0201/MainActivity$1;->
        this$0:Lcom/droider/crackme0201/MainActivity;
    .line 35
    const v1, 0x7f05000c  #unsuccessed字符串,就是这一句
    .line 34
    invoke-static {v0, v1, v3}, Landroid/widget/Toast;->
        makeText(Landroid/content/Context;II) Landroid/widget/Toast;
    move-result-object v0
    .............
    .............(略)
    .............
.end method

Smali代码中添加的注释使用“#”号开头,".line 32"行调用了checkSN()函数进行注册码的合法检查,接着下面有如下两行代码:

1
2
move-result v0
if-nez v0,  :cond_0

checkSN()函数返回Boolean类型的值。这里的第一行代码将返回的结果保存到v0寄存器中,第二行代码对v0进行判断,如果v0的值不为零,即条件为真的情况下,跳转到cond_0标号处,反之,程序顺序向下执行。

如果代码不跳转,会执行如下几行代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
.line 34
iget-object v0, p0, Lcom/droider/crackme0201/MainActivity$1;->
    this$0:Lcom/droider/crackme0201/MainActivity;
.line 35
const v1, 0x7f05000c        #unsuccessed字符串
.line 34
invoke-static {v0, v1, v3}, Landroid/widget/Toast;->
    makeText(Landroid/content/Context;II)Landroid/widget/Toast;
move-result-object v0
.line 35
invoke-virtual {v0}, Landroid/widget/Toast;->show()V
.line 42
:goto_0
return-void

“.line 34”行使用iget-object指令获取MainActivity实例的引用。代码中的->this$0是内部类MainActivity$1中的一个synthetic字段,存储 的是父类MainActivity的引用,这是Java语言的一个特性,类似的还有->access$0,这一类代码会在后面进行详细介绍。“.line 35”行将v1寄存器传入unsuccessed字符串的id值,接着调用Toast;->makeText()创建字符串,然后调用Toast;->show()V方法弹出提示,最后.line 40行调用return-void函数返回。

如果代码跳转,会执行如下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
:cond_0
iget-object v0, p0, Lcom/droider/crackme0201/MainActivity$1;->
    this$0:Lcom/droider/crackme0201/MainActivity;
.line 38
const v1, 0x7f05000d         #successed字符串
.line 37
invoke-static {v0, v1, v3}, Landroid/widget/Toast;->
    makeText(Landroid/content/Context;II)Landroid/widget/Toast;
move-result-object v0
.line 38
invoke-virtual {v0}, Landroid/widget/Toast;->show()V
.line 39
iget-object v0, p0, Lcom/droider/crackme0201/MainActivity$1;->
    this$0:Lcom/droider/crackme0201/MainActivity;
#getter for:Lcom/droider/crackme0201/MainActivity;->btn_register:Landroid/widger/Button;
invoke-static {v0}; Lcom/droider/crackme0201/MainActivity;->
    access$3(Lcom/droider/crackme0201/MainActivity;)Landroid/widget/Button;
move-result-object v0
invoke-virtual {v0, v3}, Landroid/widget/Button;->setEnabled(Z)V    #设置注册按钮不可用
.line 40
iget-object v0, p0, Lcom/droider/crackme0201/MainActivity$1;->
    this$0:Lcom/droider/crackme0201/MainActivity;
const v1, 0x7f05000b   # registered字符串,模拟注册成功 
invoke-virtual {v0, v1}, Lcom/droider/crackme0201/MainActivity;->setTitle(I)V
goto :goto_0

这段代码的功能是弹出注册成功提示,也就是说,上面的跳转如果成功意味着程序会成功注册。

3. 修改Smali文件代码

经过上一小节的分析可以发现,“.line 32”行的代码“if-nez v0,:cond_0”是程序的破解点。if-nez是Dalvik指令集中的一个条件跳转指令。类似的还有if-eqz,if-gez,if-lez等。这些指令会在后面blog中进行介绍,在这里只需要知道,与if-nez指令功能相反的指令为if-eqz,表示比较结果为0或相等时进行跳转。

用任意一款文本编辑器打开MainActivity$1.smali文件,将“.line 32”行的代码“if-nez v0,:cond_0”修改为“if-eqz v0,:cond_0”,保存后退出,代码就算修改完成了。

4. 重新编译APK文件并签名

修改完Smali文件代码后,需要将修改后的文件重新进行编译打包成apk文件。编译apk文件的命令格式为:

apktool  b[uild]  [OPTS]  [<app_path>]  [<out_file>],打开命令行进入到apktool工具的目录,执行以下命令:

1
$ ./apktool  b  /home/fuhd/apk/gw/outdir/

不出意外的话,程序就会编译成功。编译成功 后会在outdir目录下生成dist目录,里面存放着编译成功的apk文件。编译生成的crackme02.apk没有签名,还不能安装测试,接下来需要使用signapk.jar工具对apk文件进行签名。signapk.jar是Android源码包中的一个签名工具。代码位于Android源码目录下的/build/tools/signapk/SignApk.java文件中,源码编译后可以在/out/host/linux-x86/framework目录中找到它。使用signapk.jar签名时需要提供签名文件,我们在此可以使用Android源码中提供的签名文件 testkey.pk8与testkey.x509.pem,它们位于Android源码的build/target/product/security目录。将signapk.jar,testkey.x509.pem,testkey.pk8,3个文件放到同一目录,然后在命令提示符下输入如下命令对APK文件进行签名:

1
2
$ java -jar signapk.jar testkey.x509.pem testkey.pk8 
        /home/fuhd/apk/gw/outdir/crackme02.apk  crackme02Sign.apk

签名成功后会在同目录下生成crackme02sign.apk文件。

4. 安装测试

现在是时候测试修改后的成果了。启动一个Android AVD,或者使用数据线连接手机与电脑,然后在命令提示符下执行以下命令安装破解后的程序:

1
$ adb install crackme02sign.apk



?

版权声明:本文为博主原创文章,未经博主允许不得转载。

分析Android程序之破解第一个程序的更多相关文章

  1. 破解第一个程序----分析APK文件

    反编译APK成功后,在outdir目录下会生成一系列目录与文件. smali:程序所有的反汇编代码: res:程序中所有的资源文件: 如何寻找突破口是分析程序的关键.错误提示一般是指引关键代码的风向标 ...

  2. android学习三---创建第一个程序

    1.创建一个Helloworld程序 1.1 new-android application 点击file-new-android application出现如下界面 填上应用名,项目名,包名,选择所 ...

  3. Android使用AndEngine创建第一个程序

    首先要把andengine.jar复制到libs文件夹里 01 package com.hu.anden; 02   03 import org.anddev.andengine.engine.Eng ...

  4. 激活前一个程序(注册全局消息,使用Mutex探测,如果已经占用就广播消息通知第一个程序,然后第一个程序做出响应)

    unit MultInst; interface const MI_QUERYWINDOWHANDLE = ; MI_RESPONDWINDOWHANDLE = ; MI_ERROR_NONE = ; ...

  5. Chapter2——如何分析Android程序

    前几天买了<Android软件安全与逆向分析>这本书,决定在这里记一些笔记. 第一章介绍了如何搭建环境,此处略去:第二章开始讲分析Android程序. 下面按顺序记录关键内容. ----- ...

  6. (转)Android 系统 root 破解原理分析

    现在Android系统的root破解基本上成为大家的必备技能!网上也有很多中一键破解的软件,使root破解越来越容易.但是你思考过root破解的 原理吗?root破解的本质是什么呢?难道是利用了Lin ...

  7. 在Eclipse中使用MAT分析Android程序内存使用状况(转)

    对于Android这种手持设备来说,通常不会带有太大的内存,而且一般用户都是长时间不重启手机,所以编写程序的时候必须要非常小心的使用内存,尽量避免有内存泄露的问题出现.通常分析程序中潜在内存泄露的问题 ...

  8. Android开发-之第一个程序:HelloWorld!

    小编觉得不管学习什么编程的时候,第一个程序都是要求打印输出一个"HelloWorld!",那就从最简单的HelloWorld开始吧!哈哈~~~~ 一.创建一个Android工程 1 ...

  9. Android的第一个程序

    摘要:对于安卓的历史和安卓需要学习哪些东西以及怎么安卓环境,我就不在这里多说了,网上一大推. 我这里主要说的就是代码.一些基础的安卓知识.在接下来的每个月里我都会不定期写一些博客给初学者学习,我会尽量 ...

随机推荐

  1. 001_bytearray

    bytearray([source [, encoding [, errors]]]) 中文说明: bytearray([source [, encoding [, errors]]])返回一个byt ...

  2. plsql developer导入导出数据库方法

    导出步骤: 1 tools ->export user object 选择选项,导出.sql文件 2 tools ->export tables-> Oracle Export 选择 ...

  3. Python requests模块

    import requests 下面就可以使用神奇的requests模块了! 1.向网页发送数据 >>> payload = {'key1': 'value1', 'key2': [ ...

  4. strutx.xml中配置文件的讲解

    Struts2框架的核心就是struts.xml文件了,该文件主要负责管理Struts的2的业务控制组件的核心内容.为了避免struts.xml的文件国 语庞大和臃肿,我们可以通过把一个struts. ...

  5. Hadoop 2.4.0全然分布式平台搭建、配置、安装

    一:系统安装与配置 虚拟机软件:Virtualbox 4.3.10(已安装对应扩展包) 虚拟机:Ubuntu 13.04 LTS 32位(至于为什么选择13.04,是由于最新的版本号装上后开机会出现错 ...

  6. 数据字符集mysql主从数据库,分库分表等笔记

    文章结束给大家来个程序员笑话:[M] 1.mysql的目录:在rpm或者yum安装时:/var/lib/mysql  在编译安装时默许目录:/usr/local/mysql 2.用rpm包安装的MyS ...

  7. DynamoDB Local for Desktop Development

    Would you like to be able to write and test code that uses the Amazon DynamoDB API even if you have ...

  8. MaterialViewPager

    https://github.com/florent37/MaterialViewPager

  9. [转载]Android开发常用调试技术记录

    ANDROID 调试技术: 1)Ps 指令 ls –l /proc/27/ cat /proc/27/cmdline       #cmdline文件表示了这个进程所在的命令行. cat /proc/ ...

  10. Subsequence

    Description A sequence of N positive integers (10 < N < 100 000), each of them less than or eq ...