分析Android程序之破解第一个程序
破解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 v0if-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 34iget-object v0, p0, Lcom/droider/crackme0201/MainActivity$1;-> this$0:Lcom/droider/crackme0201/MainActivity;.line 35const v1, 0x7f05000c #unsuccessed字符串.line 34invoke-static {v0, v1, v3}, Landroid/widget/Toast;-> makeText(Landroid/content/Context;II)Landroid/widget/Toast;move-result-object v0.line 35invoke-virtual {v0}, Landroid/widget/Toast;->show()V.line 42:goto_0return-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_0iget-object v0, p0, Lcom/droider/crackme0201/MainActivity$1;-> this$0:Lcom/droider/crackme0201/MainActivity;.line 38const v1, 0x7f05000d #successed字符串.line 37invoke-static {v0, v1, v3}, Landroid/widget/Toast;-> makeText(Landroid/content/Context;II)Landroid/widget/Toast;move-result-object v0.line 38invoke-virtual {v0}, Landroid/widget/Toast;->show()V.line 39iget-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 v0invoke-virtual {v0, v3}, Landroid/widget/Button;->setEnabled(Z)V #设置注册按钮不可用.line 40iget-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)Vgoto :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程序之破解第一个程序的更多相关文章
- 破解第一个程序----分析APK文件
反编译APK成功后,在outdir目录下会生成一系列目录与文件. smali:程序所有的反汇编代码: res:程序中所有的资源文件: 如何寻找突破口是分析程序的关键.错误提示一般是指引关键代码的风向标 ...
- android学习三---创建第一个程序
1.创建一个Helloworld程序 1.1 new-android application 点击file-new-android application出现如下界面 填上应用名,项目名,包名,选择所 ...
- Android使用AndEngine创建第一个程序
首先要把andengine.jar复制到libs文件夹里 01 package com.hu.anden; 02 03 import org.anddev.andengine.engine.Eng ...
- 激活前一个程序(注册全局消息,使用Mutex探测,如果已经占用就广播消息通知第一个程序,然后第一个程序做出响应)
unit MultInst; interface const MI_QUERYWINDOWHANDLE = ; MI_RESPONDWINDOWHANDLE = ; MI_ERROR_NONE = ; ...
- Chapter2——如何分析Android程序
前几天买了<Android软件安全与逆向分析>这本书,决定在这里记一些笔记. 第一章介绍了如何搭建环境,此处略去:第二章开始讲分析Android程序. 下面按顺序记录关键内容. ----- ...
- (转)Android 系统 root 破解原理分析
现在Android系统的root破解基本上成为大家的必备技能!网上也有很多中一键破解的软件,使root破解越来越容易.但是你思考过root破解的 原理吗?root破解的本质是什么呢?难道是利用了Lin ...
- 在Eclipse中使用MAT分析Android程序内存使用状况(转)
对于Android这种手持设备来说,通常不会带有太大的内存,而且一般用户都是长时间不重启手机,所以编写程序的时候必须要非常小心的使用内存,尽量避免有内存泄露的问题出现.通常分析程序中潜在内存泄露的问题 ...
- Android开发-之第一个程序:HelloWorld!
小编觉得不管学习什么编程的时候,第一个程序都是要求打印输出一个"HelloWorld!",那就从最简单的HelloWorld开始吧!哈哈~~~~ 一.创建一个Android工程 1 ...
- Android的第一个程序
摘要:对于安卓的历史和安卓需要学习哪些东西以及怎么安卓环境,我就不在这里多说了,网上一大推. 我这里主要说的就是代码.一些基础的安卓知识.在接下来的每个月里我都会不定期写一些博客给初学者学习,我会尽量 ...
随机推荐
- SQL中DATE和DATETIME类型不能直接作比较
如题,今天纠结了一天的问题. 在存储过程中定义了两个datetime类型的时间,然后把这个两个时间作为where条件中一个date字段between的两个时间段,结果无论如何都不执行... 就像 u ...
- 如何使用C#去灰度化一幅图像
灰度化一幅图像就是将图像的色彩信息全部丢掉,将24位的位图信息,用8位来表示,灰度图共有256级灰度等级,也就是将24位位图的一点如(255,255,255)转换成255,所以R,G,B三个值所乘的系 ...
- javascript --学习this
this 在一般的强类型语言中,this指向的是这个对象本身,可在javascript中 this的取值是执行上下文环境的一部分 其实这个this并不是很难立即,只要记住二点就可以了 那就是谁call ...
- 根据条件自定义 cxGrid 的单元格样式
当指定的单元格需要指定样式(如字体颜色设置为红色,背景色设置为黄色)时,可按如下步骤进行: 1.添加 csStyleRepository 控件,并新建 Style,设置前景(TextColor).背景 ...
- 错误内存【读书笔记】C程序中常见的内存操作有关的典型编程错误
题记:写这篇博客要主是加深自己对错误内存的认识和总结实现算法时的一些验经和训教,如果有错误请指出,万分感谢. 对C/C++程序员来讲,内存管理是个不小的挑战,绝对值得慎之又慎,否则让由上万行代码构成的 ...
- 用 Swift 制作一个漂亮的汉堡按钮过渡动画
汉堡按钮在界面设计中已经是老生常谈了,但是当我在dribbble看到这个漂亮的过渡动画时,我决定试试用代码实现它. 这是 CreativeDash team 的原型图: 你可能已经注意到了,汉堡顶 ...
- iOS开发——UI篇OC篇&layoutSubviews和drawRect
layoutSubviews和drawRect 首先两个方法都是异步执行.layoutSubviews方便数据计算,drawRect方便视图重绘. layoutSubviews在以下情况下 ...
- Shell脚本调试工具set
可以使用set命令的x选项,显示所有命令执行及变量值的变化过程等. 具体使用方法:首先使用set -x开启调试模式,最后使用命令set +x关闭调试模式. 一个简单示例演示如何使用set命令进行脚本调 ...
- Computer Science Theory for the Information Age-6: 学习理论——VC定理的证明
VC定理的证明 本文讨论VC理论的证明,其主要内容就是证明VC理论的两个定理,所以内容非常的枯燥,但对于充实一下自己的理论知识也是有帮助的.另外,VC理论属于比较难也比较抽象的知识,所以我总结的这些证 ...
- MySQL大批量插入数据
MySQL大批量插入数据 1. 对于Myisam类型的表,可以通过以下方式快速的导入大量的数据. ALTER TABLE tblname DISABLE KEYS; loading the ...