首先编写一个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. 双通道MIL-STD-1553B总线通讯模块

    * 双通道MIL-STD-1553B总线通讯模块 * 32bi,33 MHz  CPCI/PCI/总线* 每个通道为A.B双冗余总线* 单功能可设置BC/RT/BM一种工作模式* 数据传输率: 4Mb ...

  2. KubeSphere 社区双周报|2024.06.21-07.04

    KubeSphere 社区双周报主要整理展示新增的贡献者名单和证书.新增的讲师证书以及两周内提交过 commit 的贡献者,并对近期重要的 PR 进行解析,同时还包含了线上/线下活动和布道推广等一系列 ...

  3. 修复 KubeSphere 内置 Jenkins 的 Apache Log4j2 漏洞

    作者:老Z,中电信数智科技有限公司山东分公司运维架构师,云原生爱好者,目前专注于云原生运维,云原生领域技术栈涉及 Kubernetes.KubeSphere.DevOps.OpenStack.Ansi ...

  4. OGRE 渲染引擎 Windows 平台构建及编译

    0 OGRE 机器人领域常见的Rviz和Gazebo可视化的渲染后端. 1 无数的坑 这东西真的可恶,官方教程文档以及项目构建的方式是真的繁琐,在Windows上. CMake 的构建必须要使用 CM ...

  5. Java新特性--方法引用

    常见方法引用 方法引用可以让你重复使用现有的方法定义,并像Lambda一样传递它们. 方法引用可以看做是仅仅调用特定方法的Lambda表达式的一种便捷写法.类似于Lambda表达式,方法引用不能独立存 ...

  6. 学习JavaScript第六天

    文章目录 1. JavaScript 中的垃圾回收机制(GC) 1.1 垃圾回收相关概念 ① 什么是垃圾 ② 什么是垃圾回收 ③ 垃圾没有及时回收的后果 ④ JavaScript 垃圾回收的常见算法 ...

  7. TrueNAS关闭5357端口

    sockstat -4l 查看监听5357端口进程的ID. 用kill -9 PID 干掉进程.

  8. React 防抖(Debounce)Hook

    防抖动 (debounce)是常见的需求,那么如何搭配 React Hooks 实现防抖动呢,下面演示两种方案. 方案一 副作用防抖 /** * * @param {React.EffectCallb ...

  9. spring需要在https下进行开发配置方法

    keytool -genkey -alias tomcathttps -keyalg RSA -keysize 2048 -keystore sang.p12 -validity 365 genkey ...

  10. linux基本指令总结

    拖了好久的linux学习,终于开始啦 环境终于没问题了 边学边总结 一.常用指令 1.1 关机与开机 poweroff 马上关机 reboot 马上重启 1.2 目录文件操作命令 cd / 切换到根目 ...