首先编写一个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. 揭秘 FineVideo 数据集构建的背后的秘密

    开放视频数据集稀缺,因此减缓了开源视频 AI 的发展.为此,我们构建了 FineVideo,这是一个包含 43,000 个视频的数据集,总时长为 3,400 小时,并带有丰富的描述.叙事细节.场景分割 ...

  2. Linux系统管理-yum源配置

    一.本地光盘yum源配置 1.创建挂载点 [root@localhost ~]# mkdir /mnt/cdrom 2.配置自动挂载本地光盘 [root@localhost ~]# vim /etc/ ...

  3. dotnet6.0安装

    解压到目录 sudo mkdir -p /usr/share/dotnet && sudo tar zxf dotnet-sdk-6.0.423-linux-x64.tar.gz -C ...

  4. Power BI 网关无法添加My SQL数据集

    今天第一次发布数据类型为MySQL的数据集到Power BI报表服务器,desktop的连接正常,但是发布到web端后,添加网关时却提示以下错误,如下图所示: 错误信息: 无法创建连接,原因如下: 无 ...

  5. 2款使用.NET开发的数据库系统

    前言 今天大姚给大家分享2款使用.NET开发且开源的数据库系统. Garnet Garnet是一款由微软研究院基于.NET开源的高性能.跨平台的分布式缓存存储数据库,该项目提供强大的性能(吞吐量和延迟 ...

  6. Xshell终端连接服务器慢,问题解决方法

    Xshell连接慢的话,先在xshell中设置中点隧道然后把转发xshell的勾取消 /etc/ssh/sshd_config 里边修改第115行UseDNS把注释取消然后yes改为no 然后重启ss ...

  7. MMdetection 问题报错 mmdet/evaluation/metrics/coco_metric.py data[‘category_id’] = self.cat_ids[label] IndexError: list index out of range

    方案一:有人说 在自己定义的 conifg文件中增加 metainfo = { 'classes': ('class1','class2', 'class2',), 'palette': [ (220 ...

  8. 开源 PHP 商城项目 CRMEB 二次开发和部署教程

    上篇文章给大家介绍了如何使用 Sealos 应用商店一键部署 CRMEB 开源商城系统,那速度真叫一个快啊,比宝塔快多了! 但是有些读者还不满足于此,问我能不能边运行边改代码,而且还得用 Cursor ...

  9. PSD.See 隐私政策声明

    PSD.See will not collect any user privacy data. PSD.See 不会收集任何用户隐私数据.

  10. VMware使用及CentOS 7安装配置

    本文是在VMware虚拟机下安装CentOS 7的,并不是在物理机下直接安装.无论是在学习上还是工作上都建议在虚拟机安装Linux系统来进行实验.这样就算实验失败,或者系统崩溃.只需要使用虚拟机的快照 ...