首先编写一个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程序的更多相关文章

  1. Android学习笔记一之第一个Android程序

    /** *Title:总结昨天下午至今天上午的学习成果 *Author:zsg *Date:2017-8-13 / 一.了解Android 1.Android架构 Android大致可分为四层架构:L ...

  2. Android开发学习之三——第一个Android程序

    下面我们建立第一个Android程序. 打开Eclipse,开始如下步骤: 1.File ==> New ==> Android Application Project 出现如下窗口: 2 ...

  3. 分析你的第一个Android程序

    目录 分析你的第一个Android程序 Android模式的项目结构 切换项目结构模式 Project模式的项目结构 .gradle和idea app build(没有发现这个文件夹) gradle ...

  4. android开发------第一个android程序

    好吧,现在我们就一起来写第一个android程序,看它带给了我们什么.sdk的使用和虚拟机的创建我就不说了.项目创建过程先略过,不太重要. 那第一个程序我们能学到什么知识呢?一起看吧.^-^ 在IDE ...

  5. Android逆向 编写一个Android程序

    本节使用的Android Studio版本是3.0.1 首先,我们先编写一个apk,后面用这个apk来进行逆向.用Android Studio创建一个新的Android项目,命名为Jhm,一路Next ...

  6. delphi 10 Seattle 第一个Android程序

    delphi 10 Seattle 第一个Android程序 1.打开Delphi RAD Studio Seattle,如下图     2.选择black application 点击OK   3. ...

  7. 【Android实验】第一个Android程序与Activity生命周期

    目录 第一个Android程序和Activity生命周期 实验目的 实验要求 实验过程 1. 程序正常启动与关闭 2. 外来电话接入的情况 3. 外来短信接入的情况 4. 程序运行中切换到其他程序(比 ...

  8. 用代码如何检测一个android程序是否在运行

    /** * 检测一个android程序是否在运行 * @param context * @param PackageName * @return */ public static boolean is ...

  9. Android学习——第一个NDK程序

    在前面的学习中,我们已经讲解了关于NDK编程的环境搭建流程,简单的使用我们也通过官网本身自带的例子进行说明了.可是相信大家一定还存在这么的一个疑惑:“如果我要自己利用NDK编写一个Android应用, ...

  10. Android逆向 破解第一个Android程序

    这节正式开始破解编写的第一个Android工程,打开Android Killer,把第一节自己编写的Android apk拖入Android Killer. PS: 如果Android Killer不 ...

随机推荐

  1. KubeSphere Cloud 月刊|灾备支持 K8s 1.22+,轻量集群支持安装灾备和巡检组件

    功能升级 备份容灾服务支持 K8s v1.22+ 版本集群 随着 Kubernetes 近一年频繁的发版.升级,越来越多的用户开始部署并使用高版本的 Kubernetes 集群.备份容灾服务支持 Ku ...

  2. 黑神话:悟空电脑太卡?配置不够?ToDesk云电脑入门新手教程

    许多玩家在玩<黑神话:悟空>时会遭遇硬件配置不足导致的游戏卡顿.画面不流畅等问题. 其实这个难题很好解决,用ToDesk云电脑即可迎刃而解.即使你的本地电脑配置不高,也能享受到流畅的游戏体 ...

  3. .NET + 微信小程序开源多功能电商系统

    前言 推荐一款基于微信小程序.LayUI 和 .NET 平台的多功能电商系统,支持二次开发和扩展,帮助大家轻松快速搭建一个功能全面且易于管理的在线商城. 项目介绍 该项目不仅包含了微信小程序前端,还配 ...

  4. 2024Java编程思想第四版(完整中文高清pdf)

    前言 再也不用担心书荒咯~~ 目录 Java编程思想第四版完整中文高清版(免费)***

  5. css画三角形,对角 √ 勾形

    .selected{ border-color: #5FB878; } .selected:after { content: ""; position: absolute; top ...

  6. Linux进程监控系统

    目录 动态监控进程 top 基本语法 关键信息说明 第一行:系统信息 第二行:进程信息 第三行:CPU占用情况 第四行:内存信息 第五行:交换区信息 交互操作 操作选项 应用实例 监控网络状态 net ...

  7. 共享存储ISCSI

    建立共享iscsi磁盘组 资源环境 服务端:192.168.2.131 客户端:192.168.2.[110,169] 服务端磁盘: [root@centos ~]# lsblk NAME MAJ:M ...

  8. ABP —— 权限管理

    ABP入门系列目录--学习Abp框架之实操演练 源码路径:Github-LearningMpaAbp 完成了简单的增删改查和分页功能,是不是觉得少了点什么?是的,少了权限管理.既然涉及到了权限,那我们 ...

  9. AI运动小程序开发常见问题集锦一

    截止到现在写博文时,我们的AI运动识别小程序插件已经迭代了23个版本,成功应用于健身.体育.体测.AR互动等场景:为了让正在集成或者计划进行功能扩展优化的用户,少走弯路.投入更少的开发资源,我们归集了 ...

  10. 抓包工具之Charles(mac)

    下载地址:https://www.charlesproxy.com/download/ 因为软件是收费的,所以破解方式可以参考:https://www.zzzmode.com/mytools/char ...