简单学习一下Android之后,这里笔者准备在看一些课程,就比如吾爱破解的

课程:吾爱破解安卓逆向入门教程《安卓逆向这档事》二、初识APK文件结构、双开、汉化、基础修改_哔哩哔哩_bilibili

文章:《安卓逆向这档事》二、初识APK文件结构、双开、汉化、基础修 - 吾爱破解 - 52pojie.cn

怎么说呢?这个就打算写的杂一些

初始APK文结构

没啥说的,在笔者前面一篇文章中有写到过

文件 注释
assets目录 存放APK的静态资源文件,比如视频,音频,图片等
lib 目录 armeabi-v7a基本通用所有android设备,arm64-v8a只适用于64位的android设备,x86常见用于android模拟器,其目录下的.so文件是c或c++编译的动态链接库文件
META-INF目录 保存应用的签名信息,签名信息可以验证APK文件的完整性,相当于APK的身份证(验证文件是否又被修改)
res目录 res目录存放资源文件,包括图片,字符串等等,APK的脸蛋由他的layout文件设计
AndroidManifest.xml文件 APK的应用清单信息,它描述了应用的名字,版本,权限,引用的库文件等等信息
classes.dex文件 classes.dex是java源码编译后生成的java字节码文件,APK运行的主要逻辑
resources.arsc文件 resources.arsc是编译后的二进制资源文件,它是一个映射表,映射着资源和id,通过R文件中的id就可以找到对应的资源

双开及原理

双开:简单来说,就是手机同时运行两个或多个相同的应用,例如运行两个微信

原理 解释
修改包名 让手机系统认为这是2个APP,这样的话就能生成2个数据存储路径,此时的多开就等于你打开了两个互不干扰的APP
修改Framework 对于有系统修改权限的厂商,可以修改Framework来实现双开的目的,例如:小米自带多开
通过虚拟化技术实现 虚拟Framework层、虚拟文件系统、模拟Android对组件的管理、虚拟应用进程管理 等一整套虚拟技术,将APK复制一份到虚拟空间中运行,例如:平行空间
以插件机制运行 利用反射替换,动态代{过}{滤}理,hook了系统的大部分与system—server进程通讯的函数,以此作为“欺上瞒下”的目的,欺骗系统“以为”只有一个apk在运行,瞒过插件让其“认为”自己已经安装。例如:VirtualApp

这里up主推荐实操第一个,因为后几个比较难。

简单修改应用包名实现应用双开,当我们修改包名之后,进行安装的时候,手机系统会认为它是两个应用(APP)

MT和NP管理器网上下一下即可

image-20250525171122145

双开就是修改上面的包名即可,

  • 安装包提取就不说了
  • 功能实现APK共存

image-20250525171542478

  • 安装成功

image-20250525185317136

然后你就能看见两个相同的应用了

MT管理器和NP管理器均在登录下可使用APK共存,不涉及到VIP业务

游戏汉化

汉化:使用专门的工具对外文版的软件资源进行读取、翻译、修改、回写等一系列处理,使软件的菜单、对话框、提示等用户界面显示为中文,而程序的内核和功能保持不变,这个过程即为软件汉化

image-20250523131613905

大概看一眼,更具这个流程图简单看下

  • 汉化

主要是通过搜索和反编译来进行修改

image-20250525190426843

image-20250525190659256

然后就可以正常修改了,其他大差不差。

什么是JVM、Dalvik、ART?

JVM是JAVA虚拟机,运行JAVA字节码程序

Dalvik是Google专门为安卓设计的一个虚拟机,Dalvik有专属的文件执行格式dex(Dalvik executable)

Art(Android Runtime)相当于Dalvik的升级版,本质与Dalvik无异

smali及其语法

简单来说smali就是Dalvik的寄存器语言,smali代码是dex反编译而来的。

关键字

名称 注释
.class 类名
.super 父类名,继承的上级类名名称
.source 源名
.field 变量
.method 方法名
.register 寄存器
.end method 方法名的结束
public 公有
protected 半公开,只有同一家人才能用
private 私有,只能自己使用
.parameter 方法参数
.prologue 方法开始
.line xxx 位于第xxx行

数据类型对应

smali类型 java类型 注释
V void 无返回值
Z boolean 布尔值类型,返回0或1
B byte 字节类型,返回字节
S short 短整数类型,返回数字
C char 字符类型,返回字符
I int 整数类型,返回数字
J long (64位 需要2个寄存器存储) 长整数类型,返回数字
F float 单浮点类型,返回数字
D double (64位 需要2个寄存器存储) 双浮点类型,返回数字
string String 文本类型,返回字符串
Lxxx/xxx/xxx object 对象类型,返回对象

常用指令

关键字 注释
const 重写整数属性,真假属性内容,只能是数字类型
const-string 重写字符串内容
const-wide 重写长整数类型,多用于修改到期时间。
return 返回指令
if-eq 全称equal(a=b),比较寄存器ab内容,相同则跳
if-ne 全称not equal(a!=b),ab内容不相同则跳
if-eqz 全称equal zero(a=0),z即是0的标记,a等于0则跳
if-nez 全称not equal zero(a!=0),a不等于0则跳
if-ge 全称greater equal(a>=b),a大于或等于则跳
if-le 全称little equal(a<=b),a小于或等于则跳
goto 强制跳到指定位置
switch 分支跳转,一般会有多个分支线,并根据指令跳转到适当位置
iget 获取寄存器数据

广告类型

启动广告 弹窗&更新广告 横幅广告

启动广告的流程:

启动Activity-->广告Activity-->主页Activity

修改方法:

1.修改加载时间

2.Activity切换定位,修改Intent的Activity类名

弹窗定位&堆栈分析

修改方法:
1.修改xml中的versiocode
2.Hook弹窗(推荐算法助手开启弹窗定位)
3.修改dex弹窗代码
4.抓包修改响应体(也可以路由器拦截)

实操一下还是比较麻烦的。

下载算法助手之后会发现我们缺少Xposed模块。

并且我们的算法助手是强制更新的,然后呢我们可以学以致用,直接给改就好。

直接去看第一期安装环境那里,安装Magisk Delta之后即可解锁LSPosed模块是一样的

但是注意有可能安装失败,报错是没有权限的原因的话,修改下图即可

image-20250601170956109

然后我们成功安装之后,可以下载一个下面的软件管理,也可以使用up主的方法

image-20250601171023725

安装包在

image-20250601171131556

具体文章看这块算法助手模块激活教程 - 吾爱破解 - 52pojie.cn

注意勾选我们需要的软件即可。

image-20250602120550181

四大组件

没啥说的

组件 描述
Activity(活动) 在应用中的一个Activity可以用来表示一个界面,意思可以理解为“活动”,即一个活动开始,代表 Activity组件启动,活动结束,代表一个Activity的生命周期结束。一个Android应用必须通过Activity来运行和启动,Activity的生命周期交给系统统一管理。
Service(服务) Service它可以在后台执行长时间运行操作而没有用户界面的应用组件,不依赖任何用户界面,例如后台播放音乐,后台下载文件等。
Broadcast Receiver(广播接收器) 一个用于接收广播信息,并做出对应处理的组件。比如我们常见的系统广播:通知时区改变、电量低、用户改变了语言选项等。
Content Provider(内容提供者) 作为应用程序之间唯一的共享数据的途径,Content Provider主要的功能就是存储并检索数据以及向其他应用程序提供访问数据的接口。Android内置的许多数据都是使用Content Provider形式,供开发者调用的(如视频,音频,图片,通讯录等)

Activity的生命周期

函数名称 描述
onCreate() 一个Activity启动后第一个被调用的函数,常用来在此方法中进行Activity的一些初始化操作。例如创建View,绑定数据,注册监听,加载参数等。
onStart() 当Activity显示在屏幕上时,此方法被调用但此时还无法进行与用户的交互操作。
onResume() 这个方法在onStart()之后调用,也就是在Activity准备好与用户进行交互的时候调用,此时的Activity一定位于Activity栈顶,处于运行状态。
onPause() 这个方法是在系统准备去启动或者恢复另外一个Activity的时候调用,通常在这个方法中执行一些释放资源的方法,以及保存一些关键数据。
onStop() 这个方法是在Activity完全不可见的时候调用的。
onDestroy() 这个方法在Activity销毁之前调用,之后Activity的状态为销毁状态。
onRestart() 当Activity从停止stop状态恢进入start状态时调用状态。

image-20250601144359273

JEB动态调试、log插桩

配置JAVA环境

略(不会的,call back)

JEB安装

看笔者的另一篇文章

Android逆向 | Bosh’s Blog

动态调试步骤

1.修改Debug权限

方法一:

在AndroidMainifest.xml里添加可调式的信息

下面进行简单的实操一下

  • 安装包提取
  • 定位功能之后,反编译xml文件
  • 在applocation中下敲入
1
android:debuggable="true"

image-20250611172012332

  • 然后退出保持安装即可

然后把模拟器进入开发者模式,并且系统开启USB调试

方法二:

XappDebug中hook对应的app

  • LSPosed启动板块,勾选相应的模块

image-20250611183219933

image-20250611183352373

  • XAppDebug中勾选即可

image-20250611183539002

  • 重启模拟器

方法三:

面具(MagisK)命令:

1
2
3
4
5
6
7
1.  adb shell #adb进入命令行模式

2. su #切换至超级用户

3. magisk resetprop ro.debuggable 1

4. stop;start; #一定要通过该方式重启

方法四:

在刷入MagiskHide Props Config模块…….

image-20250611183812990

笔者没有进行尝试

2.端口转发以及开启adb权限

使用雷电模拟器,忽略这一步

版本号点击七次开启开发者模式并开启adb调试权限

夜神模拟器:adb connect 127.0.0.1:62001

3.下段点

JEB简单的使用

1
2
3
4
5
6
7
ctrl+b下断点

快捷键:
^F6进入方法
F6跳过方法
F7从方法中跳出来
R运行到光标处
  • 定位函数

image-20250611180123135

  • 右键反编译,找的核心逻辑进行下断点
  • 调试

image-20250611181012440

4.debug模式启动

1
adb shell am start -D -n com.zj.wuaipojie/.ui.MainActivity

adb shell am start -D -n
adb shell am start -D -n 包名/类名
am start -n 表示启动一个activity
am start -D 表示将应用设置为可调试模式

image-20250611180337515

image-20250611180441620

image-20250611180529026

看到这个即为成功,然后JEB进行调试

调试和ida调试差不多,都需要触发走到段点出

image-20250611182442668

得到字符串

1
5byS5bCkOBUSFRIMBgE=

简单验证即可

image-20250611182752131

总结:

和ida调试的时候差不多

LOG插桩

定义:log插桩指的是反编译APK文件时,在对应的smali文件里,添加相应的smali代码,将程序中的关键信息,以log日志的形式进行输出。

照着做就行,不难

调用命令

1
invoke-static {v0}, Lcom/mtools/LogUtils;->v(Ljava/lang/Object;)V

什么是校验?

开发者在数据传送时采用的一种校验方式

常见的校验有:签名校验(最常见)、dexcrc校验、apk完整性校验、路径文件校验等

什么事APK签名?

通过对 Apk 进行签名,开发者可以证明对 Apk 的所有权和控制权,可用于安装和更新其应用。而在 Android 设备上的安装 Apk ,如果是一个没有被签名的 Apk,则会被拒绝安装。在安装 Apk 的时候,软件包管理器也会验证 Apk 是否已经被正确签名,并且通过签名证书和数据摘要验证是否合法没有被篡改。只有确认安全无篡改的情况下,才允许安装在设备上。

简单来说,APK 的签名主要作用有两个:

  • 证明 APK 的所有者。

  • 允许 Android 市场和设备校验 APK 的正确性。

Android 目前支持以下四种应用签名方案:
v1 方案:基于 JAR 签名。
v2 方案:APK 签名方案 v2(在 Android 7.0 中引入)
v3 方案:APK 签名方案 v3(在 Android 9 中引入)
v4 方案:APK 签名方案 v4(在 Android 11 中引入)

V1 签名的机制主要就在 META-INF 目录下的三个文件,MANIFEST.MF,ANDROID.SF,ANDROID.RSA,他们都是 V1 签名的产物。

(1)MANIFEST.MF:这是摘要文件。程序遍历Apk包中的所有文件(entry),对非文件夹非签名文件的文件,逐个用SHA1(安全哈希算法)生成摘要信息,再用Base64进行编码。如果你改变了apk包中的文件,那么在apk安装校验时,改变后的文件摘要信息与MANIFEST.MF的检验信息不同,于是程序就不能成功安装。

image-20250618094730288

(2)ANDROID.SF:这是对摘要的签名文件。对前一步生成的MANIFEST.MF,使用SHA1-RSA算法,用开发者的私钥进行签名。在安装时只能使用公钥才能解密它。解密之后,将它与未加密的摘要信息(即,MANIFEST.MF文件)进行对比,如果相符,则表明内容没有被异常修改。

image-20250618094801149

(3)ANDROID.RSA文件中保存了公钥、所采用的加密算法等信息。

image-20250618094817593

在某些情况下,直接对apk进行v1签名可以绕过apk的签名校验

v2方案会将 APK 文件视为 blob,并对整个文件进行签名检查。对 APK 进行的任何修改(包括对 ZIP 元数据进行的修改)都会使 APK 签名作废。这种形式的 APK 验证不仅速度要快得多,而且能够发现更多种未经授权的修改

什么是签名校验?

怎么检验是否存在签名校验?

不做任何修改,直接签名安装,应用闪退则说明大概率有签名校验。

一般来说,普通的签名校验会导致软件的闪退,黑屏,卡启动页等
当然,以上都算是比较好的,有一些比较狠的作者,则会直接rm -rf /,把基带都格掉的一键变砖。

1
2
3
4
5
kill/killProcess-----kill/KillProcess()可以杀死当前应用活动的进程,这一操作将会把所有该进程内的资源(包括线程全部清理掉).当然,由于ActivityManager时刻监听着进程,一旦发现进程被非正常Kill,它将会试图去重启这个进程。这就是为什么,有时候当我们试图这样去结束掉应用时,发现它又自动重新启动的原因.

system.exit-----杀死了整个进程,这时候活动所占的资源也会被释放。

finish----------仅仅针对Activity,当调用finish()时,只是将活动推向后台,并没有立即释放内存,活动的资源并没有被清理

在我个人见过最恶心的签名校验中,当属三角校验(低调大佬教的)最烦人。
所谓三角校验,就是so检测dex,动态加载的dex(在软件运行时会解压释放一段dex文件,检测完后就删除)检测so,dex检测动态加载的dex。

这里试一下作者的demo

使用算法助手

这里我们直接使用MT管理器对其进行签名之后,我们打开之后会发现会白屏。

image-20250618100957156

然后这里打开算法助手

image-20250618101054128

勾选这几个重新打开即可

image-20250618101150017

然后我们就可以看见是正常打开了

注释闪退代码

看一下算法助手的日志

找到闪退的方法

image-20250618103054782

1
com.zj.wuaipojie.ui.ChallengeFifth.onCreate

image-20250618103617451

注释之后重新安装,就会发现我们是正常打开了

修改验证字符串

直接查看日志

image-20250618102117277

找到验证的方法checksign,这里不知道笔者为什么日志找不到那个方法

image-20250618192147380

🫵🫵🫵🫵🫵🫵🫵👊👊👊👊👊👊👊🫵🫵🫵🫵🫵🫵🫵🤌🤌🤌🤌🤌

image-20250618140626378

这块跟着作者试了一下找到验证的字符串h99Ici0iopj8tC6NkfqyJgs73ss

安装签名后的apk、然后看日志记得勾选

image-20250618150029183

image-20250618145950560

然后更具JADX中的字符串在MT中找到相应地方进行替换即可image-20250618150328543

然后退出保存即可

image-20250618150515225

即可看见成功

修改判断条件

安装原包,打开

修改验证条件即可

image-20250618140917629

以上就是最简单的获取普通校验签名的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
private boolean SignCheck() {
String trueSignMD5 = "d0add9987c7c84aeb7198c3ff26ca152";
String nowSignMD5 = "";
try {
// 得到签名的MD5
PackageInfo packageInfo = getPackageManager().getPackageInfo(getPackageName(),PackageManager.GET_SIGNATURES);
Signature[] signs = packageInfo.signatures;
String signBase64 = Base64Util.encodeToString(signs[0].toByteArray());
nowSignMD5 = MD5Utils.MD5(signBase64);
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
}
return trueSignMD5.equals(nowSignMD5);
}

系统将应用的签名信息封装在 PackageInfo 中,调用 PackageManager 的 getPackageInfo(String packageName, int flags) 即可获取指定包名的签名信息。

签名校验对抗

**方法一:**核心破解插件,不签名安装应用

**方法二:**一键过签名工具,例如MT、NP、ARMPro、CNFIX、Modex的去除签名校验功能

**方法三:**具体分析签名校验逻辑(手撕签名校验)

**方法四:**io重定向–VA&SVC:ptrace+seccomp SVC的TraceHook沙箱的实现&无痕Hook实现思路

**方法五:**去作者家严刑拷打拿到.jks文件和密码