首先编写一个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. 容器化部署nacos 1.4.6报错caused: The specified key byte array is 0 bits which is not secure enough for any JWT

    nacos2.0+ 与nacos 1.x区别 nacos在2.0+版本开始使用grpc与客户端通信,并且通过非8848端口通信 主要是有两个端口 端口 与主端口的偏移量 描述 9848 1000 客户 ...

  2. KubeKey 升级 KubeSphere 和 Kubernetes 补丁版本实战指南

    作者:运维有术 前言 知识点 定级:入门级 KubeKey 如何升级 KubeSphere 补丁版本 KubeKey 如何升级 Kubernetes 补丁版本 KubeSphere 和 Kuberne ...

  3. 云原生周刊:Kubernetes v1.28 正式发布 | 2023.8.21

    开源项目推荐 kurt 一个 Kubernetes 插件,可提供 Kubernetes 集群中重启内容的上下文信息. Kubean Kubean 是一个基于 kubespray 的 Kubernete ...

  4. NOIP2024模拟12:孤帆远影

    NOIP2024模拟12:孤帆远影 听了机房同学的讨论,于是T1死磕冒泡和逆序对做法.最后只得了40pts. 思想对了,但不是自己的做法. 还是要坚持自己想,坚持自己可以想出来,不要被任何人带偏. T ...

  5. Windows通过修改注册表设置系统默认浏览器

    前段时间有个程序要求获取系统的默认浏览器,baidu.Google了好久,后又结合procmon.exe跟踪浏览器打开web页面的注册表操作信息,找到了最终的位置,这里做一个总结.亲测win10多个浏 ...

  6. git reset 之后切换到原来的commit

    git reset的语法: git reset [--hard|soft|mixed|merge|keep] [<commit>或HEAD] 作用:将当前分支reset到指定的commit ...

  7. begin-预览,不行啊还是太弱了

    方便管理,主要是想熟悉下git的操作 先创建并且切换到一个新的分支: git commit --allow-empty -am "before starting PA1" git ...

  8. 在vue中使用XLSX导出表格

    安装依赖 npm install file-saver xlsx -S 然后在需要的页面中引入依赖包 import FileSaver from 'file-saver'; import XLSX f ...

  9. 使用Acme.sh免费签发SSL证书

    github:https://github.com/acmesh-official/acme.sh 概述一个纯粹用Shell(Unix shell)语言编写的ACME协议客户端.完整的ACME协议实施 ...

  10. Educational Codeforces Round 77 (Rated for Div2)

    B - Obtain Two Zeroes 给定两个整数\(a,b\),你可以执行以下操作任意次:每次操作选择一个正整数\(x\),使得\(a:=a-x,b:=b-2x\)或者\(a:=a-2x,b: ...