学习破解一个Android程序
首先编写一个android测试程序
功能:校验用户名和注册码,成功则弹出注册成功提示
以下仅给出关键部分的代码
res/layout/activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical">
<TextView
android:id="@+id/textView1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:text="@string/info"
android:textSize="20dp" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="10dp"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/username" />
<EditText
android:id="@+id/edit_username"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginLeft="10dp"
android:layout_marginRight="10dp"
android:layout_weight="1"
android:ems="10"
android:hint="@string/hint_username"></EditText>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="10dp"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/sn" />
<EditText
android:id="@+id/edit_sn"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginLeft="10dp"
android:layout_marginRight="10dp"
android:layout_weight="1"
android:ems="10"
android:hint="@string/hint_sn"></EditText>
</LinearLayout>
<Button
android:id="@+id/button_register"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="right"
android:layout_marginRight="10dp"
android:text="@string/register" />
</LinearLayout>
java/com/example/myapplication/MainActivity.java
package com.example.myapplication;
import android.app.Activity;
import android.os.Bundle;
import android.view.Menu;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
public class MainActivity extends Activity {
private EditText edit_userName;
private EditText edit_sn;
private Button btn_register;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
setTitle(R.string.unregister); //模拟程序未注册
edit_userName = (EditText) findViewById(R.id.edit_username);
edit_sn = (EditText) findViewById(R.id.edit_sn);
btn_register = (Button) findViewById(R.id.button_register);
btn_register.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
if (!checkSN(edit_userName.getText().toString().trim(),
edit_sn.getText().toString().trim())) {
Toast.makeText(MainActivity.this, //弹出无效用户名或注册码提示
R.string.unsuccessed, Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(MainActivity.this, //弹出注册成功提示
R.string.successed, Toast.LENGTH_SHORT).show();
btn_register.setEnabled(false);
setTitle(R.string.registered); //模拟程序已注册
}
}
});
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.activity_main, menu);
return true;
}
private boolean checkSN(String userName, String sn) {
try {
if ((userName == null) || (userName.length() == 0))
return false;
if ((sn == null) || (sn.length() != 16))
return false;
MessageDigest digest = MessageDigest.getInstance("MD5");
digest.reset();
digest.update(userName.getBytes());
byte[] bytes = digest.digest(); //采用MD5对用户名进行Hash
String hexstr = toHexString(bytes, ""); //将计算结果转化成字符串
StringBuilder sb = new StringBuilder();
for (int i = 0; i < hexstr.length(); i += 2) {
sb.append(hexstr.charAt(i));
}
String userSN = sb.toString(); //计算出的SN
//Log.d("crackme", hexstr);
//Log.d("crackme", userSN);
if (!userSN.equalsIgnoreCase(sn)) //比较注册码是否正确
return false;
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
return false;
}
return true;
}
private static String toHexString(byte[] bytes, String separator) {
StringBuilder hexString = new StringBuilder();
for (byte b : bytes) {
String hex = Integer.toHexString(0xFF & b);
if (hex.length() == 1) {
hexString.append('0');
}
hexString.append(hex).append(separator);
}
return hexString.toString();
}
}

运行没有问题之后,通过AndroidStudio编译成apk文件
开始破解程序
破解 Android 程序通常的方法是将 apk 文件利用 ApkTool 反编译,生成 Smali 格式的反汇编代码,然后阅读 Smali 文件的代码来理解程序的运行机制,找到程序的突破口进行修改,最后使用 ApkTool 重新编译生成 apk 文件并签名,最后运行测试,如此循环,直至程序被成功破解。
使用apk-tool反编译apk程序
下载地址:https://down.52pojie.cn/Tools/Android_Tools/apktool_2.9.3.jar
执行
java -jar apktool_2.9.3.jar d -f app-debug.apk -o output

smail目录下存放了程序的所有反汇编代码,res目录下则是程序的所有资源文件
先通过res\values\string.xml定位程序的错误信息
<resources>
...
<string name="unsuccessed">无效用户名或注册码</string>
...
</resources>
再通过同目录下的public.xml找到name="unsuccessed"的id
<public type="string" name="unsuccessed" id="0x7f1000a5" />
通过该id可以到smail目录搜索,在output\smali_classes3\com\example\myapplication\MainActivity$1.smali搜索到一处结果
91 iget-object v0, p0, Lcom/example/myapplication/MainActivity$1;->this$0:Lcom/example/myapplication/MainActivity;
92
93: const v2, 0x7f1000a5
94
95 invoke-static {v0, v2, v1}, Landroid/widget/Toast;->makeText(Landroid/content/Context;II)Landroid/widget/Toast;
onclick方法
# virtual methods
.method public onClick(Landroid/view/View;)V
.locals 3
.param p1, "v" # Landroid/view/View;
.line 31
iget-object v0, p0, Lcom/example/myapplication/MainActivity$1;->this$0:Lcom/example/myapplication/MainActivity;
invoke-static {v0}, Lcom/example/myapplication/MainActivity;->access$000(Lcom/example/myapplication/MainActivity;)Landroid/widget/EditText;
move-result-object v1
invoke-virtual {v1}, Landroid/widget/EditText;->getText()Landroid/text/Editable;
move-result-object v1
invoke-virtual {v1}, Ljava/lang/Object;->toString()Ljava/lang/String;
move-result-object v1
invoke-virtual {v1}, Ljava/lang/String;->trim()Ljava/lang/String;
move-result-object v1
iget-object v2, p0, Lcom/example/myapplication/MainActivity$1;->this$0:Lcom/example/myapplication/MainActivity;
.line 32
invoke-static {v2}, Lcom/example/myapplication/MainActivity;->access$100(Lcom/example/myapplication/MainActivity;)Landroid/widget/EditText;
move-result-object v2
invoke-virtual {v2}, Landroid/widget/EditText;->getText()Landroid/text/Editable;
move-result-object v2
invoke-virtual {v2}, Ljava/lang/Object;->toString()Ljava/lang/String;
move-result-object v2
invoke-virtual {v2}, Ljava/lang/String;->trim()Ljava/lang/String;
move-result-object v2
.line 31
invoke-static {v0, v1, v2}, Lcom/example/myapplication/MainActivity;->access$200(Lcom/example/myapplication/MainActivity;Ljava/lang/String;Ljava/lang/String;)Z
move-result v0
const/4 v1, 0x0
if-nez v0, :cond_0 # 关键条件判断
.line 33
iget-object v0, p0, Lcom/example/myapplication/MainActivity$1;->this$0:Lcom/example/myapplication/MainActivity;
const v2, 0x7f1000a5
invoke-static {v0, v2, v1}, Landroid/widget/Toast;->makeText(Landroid/content/Context;II)Landroid/widget/Toast;
move-result-object v0
.line 34
invoke-virtual {v0}, Landroid/widget/Toast;->show()V
goto :goto_0
.line 36
:cond_0
iget-object v0, p0, Lcom/example/myapplication/MainActivity$1;->this$0:Lcom/example/myapplication/MainActivity;
const v2, 0x7f1000a2
invoke-static {v0, v2, v1}, Landroid/widget/Toast;->makeText(Landroid/content/Context;II)Landroid/widget/Toast;
move-result-object v0
.line 37
invoke-virtual {v0}, Landroid/widget/Toast;->show()V
.line 38
iget-object v0, p0, Lcom/example/myapplication/MainActivity$1;->this$0:Lcom/example/myapplication/MainActivity;
invoke-static {v0}, Lcom/example/myapplication/MainActivity;->access$300(Lcom/example/myapplication/MainActivity;)Landroid/widget/Button;
move-result-object v0
invoke-virtual {v0, v1}, Landroid/widget/Button;->setEnabled(Z)V
.line 39
iget-object v0, p0, Lcom/example/myapplication/MainActivity$1;->this$0:Lcom/example/myapplication/MainActivity;
const v1, 0x7f100099
invoke-virtual {v0, v1}, Lcom/example/myapplication/MainActivity;->setTitle(I)V
.line 41
:goto_0
return-void
.end method
经过分析,if-nez v0, :cond_0判断决定了校验是否通过,这句代码的意思是:如果v0不为0则跳转到cond_0,也就是注册失败的分支。
破解方法:将if-nez改为if-eqz也就是等于则为真
修改后保存,执行如下代码重新编译
java -jar apktool_2.9.3.jar b output
编译后需要对apk进行签名,这里我本地生成了一个测试签名
keytool -genkey -alias testalias -keyalg RSA -keysize 2048 -validity 36500 -keystore test.keystore
然后用360加固助手的工具包进行签名。
接下来可以通过adb命令安装和启动
adb install app-debug_sign.apk
adb shell am start -n com.example.myapplication/.MainActivity

至此破解就算完成了。
使用IDA pro破解
因为使用apktool每次都需要重新编译,很花时间,idapro提供了快速测试的方法。
下载地址:https://down.52pojie.cn/Tools/Disassemblers/IDA_Pro_v8.3_Portable.zip
用压缩包工具打开app-debug.apk,提取出classes.dex文件,通过ida pro打开
注意:如果
classes.dex没有可能在classes3.dex
按照先找错误信息的思路,按alt+t打开文本搜索功能,搜索:0x7f1000a5

定位到关键代码(按空格可以切换视图)

可以看到分支判断位于CODE:000006BE,将光标放在if-nez处,点击hex-view,修改if-nez的字节码
39 00 0F 00改为38 00 0F 00
然后关闭ida pro,不需要保存到database
用c32asm打开classes3.dex定位到000006BE,将39改为38,保存退出

这里由于修改了dex文件,会导致dex文件在验证计算checksum会失败,从而导致程序安装失败,因此需要重新计算checksum值
用Dexfixer将classes3.dex文件checksum值修复

将修复好的classes3.dex,重新拉入apk
aapt r app-debug.apk classes3.dex
aapt a app-debug.apk classes3.dex
删除META-INT(可使用winrar工具),并重新签名apk即可破解成功。

参考:《Android软件安全与逆向分析》
学习破解一个Android程序的更多相关文章
- Android学习笔记一之第一个Android程序
/** *Title:总结昨天下午至今天上午的学习成果 *Author:zsg *Date:2017-8-13 / 一.了解Android 1.Android架构 Android大致可分为四层架构:L ...
- Android开发学习之三——第一个Android程序
下面我们建立第一个Android程序. 打开Eclipse,开始如下步骤: 1.File ==> New ==> Android Application Project 出现如下窗口: 2 ...
- 分析你的第一个Android程序
目录 分析你的第一个Android程序 Android模式的项目结构 切换项目结构模式 Project模式的项目结构 .gradle和idea app build(没有发现这个文件夹) gradle ...
- android开发------第一个android程序
好吧,现在我们就一起来写第一个android程序,看它带给了我们什么.sdk的使用和虚拟机的创建我就不说了.项目创建过程先略过,不太重要. 那第一个程序我们能学到什么知识呢?一起看吧.^-^ 在IDE ...
- Android逆向 编写一个Android程序
本节使用的Android Studio版本是3.0.1 首先,我们先编写一个apk,后面用这个apk来进行逆向.用Android Studio创建一个新的Android项目,命名为Jhm,一路Next ...
- delphi 10 Seattle 第一个Android程序
delphi 10 Seattle 第一个Android程序 1.打开Delphi RAD Studio Seattle,如下图 2.选择black application 点击OK 3. ...
- 【Android实验】第一个Android程序与Activity生命周期
目录 第一个Android程序和Activity生命周期 实验目的 实验要求 实验过程 1. 程序正常启动与关闭 2. 外来电话接入的情况 3. 外来短信接入的情况 4. 程序运行中切换到其他程序(比 ...
- 用代码如何检测一个android程序是否在运行
/** * 检测一个android程序是否在运行 * @param context * @param PackageName * @return */ public static boolean is ...
- Android学习——第一个NDK程序
在前面的学习中,我们已经讲解了关于NDK编程的环境搭建流程,简单的使用我们也通过官网本身自带的例子进行说明了.可是相信大家一定还存在这么的一个疑惑:“如果我要自己利用NDK编写一个Android应用, ...
- Android逆向 破解第一个Android程序
这节正式开始破解编写的第一个Android工程,打开Android Killer,把第一节自己编写的Android apk拖入Android Killer. PS: 如果Android Killer不 ...
随机推荐
- KubeSphere Cloud 月刊|灾备支持 K8s 1.22+,轻量集群支持安装灾备和巡检组件
功能升级 备份容灾服务支持 K8s v1.22+ 版本集群 随着 Kubernetes 近一年频繁的发版.升级,越来越多的用户开始部署并使用高版本的 Kubernetes 集群.备份容灾服务支持 Ku ...
- 黑神话:悟空电脑太卡?配置不够?ToDesk云电脑入门新手教程
许多玩家在玩<黑神话:悟空>时会遭遇硬件配置不足导致的游戏卡顿.画面不流畅等问题. 其实这个难题很好解决,用ToDesk云电脑即可迎刃而解.即使你的本地电脑配置不高,也能享受到流畅的游戏体 ...
- .NET + 微信小程序开源多功能电商系统
前言 推荐一款基于微信小程序.LayUI 和 .NET 平台的多功能电商系统,支持二次开发和扩展,帮助大家轻松快速搭建一个功能全面且易于管理的在线商城. 项目介绍 该项目不仅包含了微信小程序前端,还配 ...
- 2024Java编程思想第四版(完整中文高清pdf)
前言 再也不用担心书荒咯~~ 目录 Java编程思想第四版完整中文高清版(免费)***
- css画三角形,对角 √ 勾形
.selected{ border-color: #5FB878; } .selected:after { content: ""; position: absolute; top ...
- Linux进程监控系统
目录 动态监控进程 top 基本语法 关键信息说明 第一行:系统信息 第二行:进程信息 第三行:CPU占用情况 第四行:内存信息 第五行:交换区信息 交互操作 操作选项 应用实例 监控网络状态 net ...
- 共享存储ISCSI
建立共享iscsi磁盘组 资源环境 服务端:192.168.2.131 客户端:192.168.2.[110,169] 服务端磁盘: [root@centos ~]# lsblk NAME MAJ:M ...
- ABP —— 权限管理
ABP入门系列目录--学习Abp框架之实操演练 源码路径:Github-LearningMpaAbp 完成了简单的增删改查和分页功能,是不是觉得少了点什么?是的,少了权限管理.既然涉及到了权限,那我们 ...
- AI运动小程序开发常见问题集锦一
截止到现在写博文时,我们的AI运动识别小程序插件已经迭代了23个版本,成功应用于健身.体育.体测.AR互动等场景:为了让正在集成或者计划进行功能扩展优化的用户,少走弯路.投入更少的开发资源,我们归集了 ...
- 抓包工具之Charles(mac)
下载地址:https://www.charlesproxy.com/download/ 因为软件是收费的,所以破解方式可以参考:https://www.zzzmode.com/mytools/char ...