安卓逆向基础

以下内容全部来自安卓逆向这档事

这里只是记录、学习

环境搭建

视频:https://www.bilibili.com/video/BV1wT411N7sV/?spm_id_from=333.788&vd_source=f207ef8a90cd02e940572d9dc7c00d8b

(1)安卓模拟器安装

MUMU或雷电

这里使用的是雷电9

(2)模拟器配置

开启ROOT权限 、开启磁盘可写

(3)安装面具Magisk和LSPosed

Magisk Delta :https://huskydg.github.io/magisk-files/

LSPosed: https://github.com/LSPosed/LSPosed (zygisk)

(4)核心破解(软件)

CorePatch: https://github.com/LSPosed/CorePatch

核心破解是一款基于xposed模块开发的小工具。可以用来去除系统签名校验,直接安装修改过的未签名APK,禁用apk签名验证、覆盖安装不同签名应用的功能。

(5)算法助手

com.junge.algorithmaide: https://github.com/Xposed-Modules-Repo/com.junge.algorithmaide?tab=readme-ov-file

(6)XappDebug

XappDebug :https://github.com/Palatis/XAppDebug

动态调试用

PC软件:

(1)jadx-gui:https://github.com/skylot/jadx

这个用来反编译看java代码的,MT上也能看,但是要VIP

注意:

Magisk.apk是可以直接装到模拟器,但启动Magisk后会发现不能使用,这就等于没有安装成功。
市面上常见的安卓模拟器,都是那些开发商(比如雷电、夜神、mumu等)定制的,而这些系统是没有开放boot.img和system.img给大家。
以前装Magisk是基于修补boot.img再刷入,达到安装Magisk或获取root的效果,而模拟器没有boot.img,怎么刷Magisk呢?
所以就有了Magisk的分支——Magisk Terminal Emulator,它是开发者基于Magisk开发的,但不是修补boot.img,而是把Magisk安装到system分区中。
Magisk Terminal Emulator它是一个类似命令行窗口(如上图),对小白并不友好,容易操作出错。所以开发者停更了这个版本,又开发了新版的Magisk Delta,它界面跟原版Magisk几乎一摸一样,但实际就是对原版Magisk加了个安装到system分区的方式(也许还有其它变化,)。

(4)MT管理器/NP管理器

MT: https://mt2.cn/

NP: https://github.com/githubXiaowangzi/NP-Manager

APK文件结构

apk 全称 Android Package,它相当于一个压缩文件,只要在电脑上将apk后缀改为rar即可解压。

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

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

基本上字符串都是在arsc里,建议一键汉化,然后再润色。 少量没汉化到的字符串需要定位去逐个汉化。


AndroidManifest.xml文件是整个应用程序的信息描述文件,定义了应用程序中包含的Activity,Service,Content provider和BroadcastReceiver组件信息。每个应用程序在根目录下必须包含一个AndroidManifest.xml文件,且文件名不能修改。它描述了package中暴露的组件,他们各自的实现类,各种能被处理的数据和启动位置。

属性 定义
versionCode 版本号,主要用来更新,例如:12
versionName 版本名,给用户看的,例如:1.2
package 包名,例如:com.zj.52pj.demo
uses-permission android:name=”” 应用权限,例如:android.permission.INTERNET 代表网络权限
android:label=”@string/app_name” 应用名称
android:icon=”@mipmap/ic_launcher” 应用图标路径
android:debuggable=”true” 应用是否开启debug权限

cb35179c3ff8786b19fc1d2d2ecae12e

双开及原理

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

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

安卓四大组件

组件 描述
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状态时调用状态。

d810cf812e87dd8cbbb663cf9b1247a4

activity的切换

        <!---声明实现应用部分可视化界面的 Activity,必须使用 AndroidManifest 中的 <activity> 元素表示所有 Activity。系统不会识别和运行任何未进行声明的Activity。----->
        <activity  
            android:label="@string/app_name"  
            android:name="com.zj.wuaipojie.ui.MainActivity"  
            android:exported="true">  <!--当前Activity是否可以被另一个Application的组件启动:true允许被启动;false不允许被启动-->
            <!---指明这个activity可以以什么样的意图(intent)启动--->
            <intent-filter>  
                <!--表示activity作为一个什么动作启动,android.intent.action.MAIN表示作为主activity启动--->
                <action  
                    android:name="android.intent.action.MAIN" />  
                <!--这是action元素的额外类别信息,android.intent.category.LAUNCHER表示这个activity为当前应用程序优先级最高的Activity-->
                <category  
                    android:name="android.intent.category.LAUNCHER" />  
            </intent-filter>  
        </activity>  
        <activity  
            android:name="com.zj.wuaipojie.ui.ChallengeFirst" />
        <activity  
            android:name="com.zj.wuaipojie.ui.ChallengeFifth"  
            android:exported="true" />  
        <activity  
            android:name="com.zj.wuaipojie.ui.ChallengeFourth"  
            android:exported="true" />  
        <activity  
            android:name="com.zj.wuaipojie.ui.ChallengeThird"  
            android:exported="false" />  
        <activity  
            android:name="com.zj.wuaipojie.ui.ChallengeSecond"  
            android:exported="false" />  
        <activity  
            android:name="com.zj.wuaipojie.ui.AdActivity" />  

smali语法

注释符 :#

关键字

名称 注释
.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 全称garden equal(a>=b),a大于或等于则跳
if-le 全称little equal(a<=b),a小于或等于则跳
goto 强制跳到指定位置
switch 分支跳转,一般会有多个分支线,并根据指令跳转到适当位置
iget 获取寄存器数据,表示获取一个int类型的字段
check-cast 表示进行类型转换检查
invoke-static 调用静态方法
move-result-object 表示将前一个方法调用的返回值存储到指定寄存器中
invoke-virtual 表示调用虚拟方法
sget-object 表示获取一个静态对象
if-lt 全称little equal(a<b),a小于则跳

其余指令可用语法工具查询

例如:

//一个私有、静态、不可变的方法   方法名
.method private static final onCreate$lambda-2(Lkotlin/jvm/internal/Ref$IntRef;Lcom/zj/wuaipojie/ui/ChallengeSecond;Landroid/widget/ImageView;Landroid/widget/ImageView;Landroid/widget/ImageView;Landroid/view/View;)Z //(这里面是方法的参数)这里是方法返回值类型,表示布尔值类型,返回假或真
    .registers 7  //寄存器数量

    .line 33  //代码所在的行数
    iget p0, p0, Lkotlin/jvm/internal/Ref$IntRef;->element:I  //读取p0(第一个参数,参考寄存器知识)中element的值赋值给p0

    const/4 p5, 0x1  //p5赋值1

    const/16 v0, 0xa //v0赋值10,在16进制里a表示10 ,16指位数

    if-ge p0, v0, :cond_15  //判断p0的值是否大于或等于v0的值(即p0的值是否大于或等于10),如果大于或等于则跳转到:cond_15

    .line 34  //以下是常见的Toast弹窗代码
    check-cast p1, Landroid/content/Context; //检查Context对象引用

    const-string p0, "请先获取10个硬币哦" //弹窗文本信息,把""里的字符串数据赋值给p0

    check-cast p0, Ljava/lang/CharSequence; //检查CharSequence对象引用

    invoke-static {p1, p0, p5}, Landroid/widget/Toast;->makeText(Landroid/content/Context;Ljava/lang/CharSequence;I)Landroid/widget/Toast; 
    //将弹窗文本、显示时间等信息传给p1

    move-result-object p0  //结果传递给p0

    invoke-virtual {p0}, Landroid/widget/Toast;->show()V  //当看到这个Toast;->show你就应该反应过来这里是弹窗代码

    goto :goto_31  //跳转到:goto_31

    :cond_15 //跳转的一个地址
    
    invoke-virtual {p1}, Lcom/zj/wuaipojie/ui/ChallengeSecond;->isvip()Z  //判断isvip方法的返回值是否为真(即结果是否为1)
  
    move-result p0  //结果赋值给p0
  
    if-eqz p0, :cond_43 //如果结果为0则跳转cond_43地址
    
    const p0, 0x7f0d0018  //在arsc中的id索引,这个值可以进行查询

    .line 37
    invoke-virtual {p2, p0}, Landroid/widget/ImageView;->setImageResource(I)V //设置图片资源

    const p0, 0x7f0d0008

    .line 38
    invoke-virtual {p3, p0}, Landroid/widget/ImageView;->setImageResource(I)V

    const p0, 0x7f0d000a

    .line 39
    invoke-virtual {p4, p0}, Landroid/widget/ImageView;->setImageResource(I)V

    .line 40
    sget-object p0, Lcom/zj/wuaipojie/util/SPUtils;->INSTANCE:Lcom/zj/wuaipojie/util/SPUtils; 

    check-cast p1, Landroid/content/Context;

    const/4 p2, 0x2 //p2赋值2

    const-string p3, "level" //sp的索引

    invoke-virtual {p0, p1, p3, p2}, Lcom/zj/wuaipojie/util/SPUtils;->saveInt(Landroid/content/Context;Ljava/lang/String;I)V //写入数据

    goto :goto_50 //跳转地址
    
    :cond_43
  
    check-cast p1, Landroid/content/Context;
  
  
    const-string p0, "\u8bf7\u5148\u5145\u503c\u5927\u4f1a\u5458\u54e6\uff01" //请先充值大会员哦!
  
  
    check-cast p0, Ljava/lang/CharSequence;
  
  
    invoke-static {p1, p0, p5}, Landroid/widget/Toast;->makeText(Landroid/content/Context;Ljava/lang/CharSequence;I)Landroid/widget/Toast;
  
  
    move-result-object p0
  
    invoke-virtual {p0}, Landroid/widget/Toast;->show()V
  
  
    :goto_50
    return p5  //返回p5的值
.end method //方法结束


//判断是否是大会员的方法
.method public final isvip()Z
    .registers 2
    
    const/4 v0, 0x0 //v0赋值0
    
    return v0 //返回v0的值
    
.end method

静态修改

fe7852e13a354ab54af29ff3a3361b3f

修改判断:例如大于改成小于等于

强制跳转: goto

寄存器

在smali里的所有操作都必须经过寄存器来进行:本地寄存器用v开头数字结尾的符号来表示,如v0、 v1、v2。 参数寄存器则使用p开头数字结尾的符号来表示,如p0、p1、p2。特别注意的是,p0不一定是函数中的第一个参数,在非static函数中,p0代指“this”,p1表示函数的第一个 参数,p2代表函数中的第二个参数。而在static函数中p0才对应第一个参数(因为Java的static方法中没有this方法)

去广告/弹窗

广告

启动广告流程: 启动Activity->广告Activity->主页Activity

修改方法: 1.修改加载时间 2.Acitivity切换定位,修改Intent的Activity类名

弹窗

修改方法:

1.修改xml中的versiocode(对于升级弹窗)

2.Hook弹窗(推荐算法助手开启弹窗定位)

3.修改dex弹窗代码(定位弹窗代码,在dex把show方法注释掉)

4.抓包修改响应体(也可以路由器拦截)

//1.开发者助手抓布局 2.MT管理器xml搜索定位 3.修改xml代码

5.修改弹窗的大小,将长宽修改为0

6.关闭窗口

android:visibility="gone"  //视图不可见
广告关键词 厂商 文档
com.qq.e.ads 腾讯优量汇广告 https://developers.adnet.qq.com/doc/android/union/union_splash
CSJAD、TTAdSdk、bytedance、pangolin 穿山甲广告 https://www.csjplatform.com/supportcenter/5395
ADMob、google.ads 谷歌广告 https://developers.google.com/admob/android/app-open?hl=zh-cn#extend
TorchAd 360广告 https://easydoc.soft.360.cn/doc?project=186589faed863b0a24f15f9bcbafd5c7&doc=2cbbbe19c5cb90f5e7a41c7037b0029a&config=title_menu_toc
kwad 快手广告 https://u.kuaishou.com/home/help/detail/1334/1370/1310
baidu.mobads 百度广告 http://bce.ssp.baidu.com/mssp/sdk/BaiduMobAds_MSSP_bd_SDK_android_v5.1.pdf
MimoSdk 米盟广告 https://t5.a.market.xiaomi.com/download/AdCenter/0d3a369516ee146e8a9d5c290985939da4624fe0a/AdCenter0d3a369516ee146e8a9d5c290985939da4624fe0a.html
sigmob.sdk sigmob广告 https://doc.sigmob.com/#/Sigmob%E4%BD%BF%E7%94%A8%E6%8C%87%E5%8D%97/SDK%E9%9B%86%E6%88%90%E8%AF%B4%E6%98%8E/Android/SDK%E6%8E%A5%E5%85%A5%E9%85%8D%E7%BD%AE/
TradPlus TradPlus聚合广告 https://service.cocos.com/document/zh/tradplusad.html
通过免广告关键字来实现部分广告的去除

第三方广告sdk汇总:https://static.52mvp.com/policy_other_sdk.html

查看厂商的官方文档找到关闭广告的方法

从广告SDK初始化的地方下手

App动态调试

动态调试是指自带的调试器跟踪自己软件的运行,可以在调试的过程中知道参数或者局部变量的值以及履清代码运行的先后顺序。多用于爆破注册码(CTF必备技能)

动态调试步骤:

(一)修改debug权限:

总的来说就是修改ro.debuggable的值为1

  • 方法一:在AndroidManifest.xml里添加可调试权限 遇到签名校验时不可用
android:debuggable="true"
  • 方法二:XappDebug模块hook对应的app (建议使用这个)
  • 方法三:Magisk命令(重启会失效)
 adb shell #adb进入命令行模式
    
 su #切换至超级用户
    
 magisk resetprop ro.debuggable 1
    
 stop;start; #一定要通过该方式重启
  • 方法四: 刷入MagiskHide Props Config模块(永久有效)

一般来说,在4选项中如果有ro.debuggable那就直接修改 没有的话就选5

image-20240316222356144

(二) 端口转发以及开启adb权限

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

adb connect xxxx:xxx

(三)下段点

ctrl+b下断点

(四)debug模式启动

adb shell am start -D -n com.zj.wuaipojie/.ui.MainActivity
//adb shell am start -D -n 包名/类名

am start -n 表示启动一个activity

am start -D 表示将应用设置为可调试模式

(五)Jeb附加调试进程

(六)Log插桩

有签名校验时不可用

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

首先将日志插壮.dex改名为为classes2.dex并丢进apk里面然后安装运行

然后再需要获取值的地方插入调用命令:

invoke-static {对应寄存器}, Lcom/mtools/LogUtils;->v(Ljava/lang/Object;)V

使用算法助手监听日志输出(选择对应app,打开应用总开关,打开Log捕获)

校验

校验是开发者在数据传送时采用的一种校正数据的一种方式 常见的校验有:签名校验(最常见)、dexcrc校验、apk完整性校验、路径文件校验等

APK签名

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

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

  1. 证明 APK 的所有者。
  2. 允许 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的检验信息不同,于是程序就不能成功安装。

img

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

img

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

img

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

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

签名校验

如何判断是否有签名校验?

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

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

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

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

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

三角校验: 所谓三角校验,就是so检测dex,动态加载的dex(在软件运行时会解压释放一段dex文件,检测完后就删除)检测so,dex检测动态加载的dex

|300

普通获取签名校验代码:

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文件(签名密钥文件)和密码

PM代理

(已过时)

思路源自:Android中Hook 应用签名方法

PackageManagerService(简称PMS),是Android系统核心服务之一,处理包管理相关的工作,常见的比如安装、卸载应用等。

IO重定向(✔)

什么是IO重定向?

例:在读A文件的时候指向B文件

平头哥的核心代码 Virtual Engine for Android(Support 12.0 in business version)

IO重定向可以干嘛?

1,可以让文件只读,不可写

2,禁止访问文件

3,路径替换

具体实现: 过签名检测(读取原包) 风控对抗(例:一个文件记录App启动的次数) 过Root检测,Xposed检测(文件不可取)

各种检测

root检测:

反制手段 1.算法助手、对话框取消等插件一键hook

2.分析具体的检测代码

3.利用IO重定向使文件不可读

4.修改Andoird源码,去除常见指纹

fun isDeviceRooted(): Boolean {
    return checkRootMethod1() || checkRootMethod2() || checkRootMethod3()
}

fun checkRootMethod1(): Boolean {
    val buildTags = android.os.Build.TAGS
    return buildTags != null && buildTags.contains("test-keys")
}

fun checkRootMethod2(): Boolean {
    val paths = arrayOf("/system/app/Superuser.apk", "/sbin/su", "/system/bin/su", "/system/xbin/su", "/data/local/xbin/su", "/data/local/bin/su", "/system/sd/xbin/su",
            "/system/bin/failsafe/su", "/data/local/su", "/su/bin/su")
    for (path in paths) {
        if (File(path).exists()) return true
    }
    return false
}

fun checkRootMethod3(): Boolean {
    var process: Process? = null
    return try {
        process = Runtime.getRuntime().exec(arrayOf("/system/xbin/which", "su"))
        val bufferedReader = BufferedReader(InputStreamReader(process.inputStream))
        bufferedReader.readLine() != null
    } catch (t: Throwable) {
        false
    } finally {
        process?.destroy()
    }
}

定义了一个 isDeviceRooted() 函数,该函数调用了三个检测 root 的方法:checkRootMethod1()checkRootMethod2()checkRootMethod3()

checkRootMethod1() 方法检查设备的 build tags 是否包含 test-keys。这通常是用于测试的设备,因此如果检测到这个标记,则可以认为设备已被 root。

checkRootMethod2() 方法检查设备是否存在一些特定的文件,这些文件通常被用于执行 root 操作。如果检测到这些文件,则可以认为设备已被 root。

checkRootMethod3() 方法使用 Runtime.exec() 方法来执行 which su 命令,然后检查命令的输出是否不为空。如果输出不为空,则可以认为设备已被 root。

模拟器检测

fun isEmulator(): Boolean { 
    return Build.FINGERPRINT.startsWith("generic") || Build.FINGERPRINT.startsWith("unknown") || Build.MODEL.contains("google_sdk") Build.MODEL.contains("Emulator") || Build.MODEL.contains("Android SDK built for x86") || Build.MANUFACTURER.contains("Genymotion") || Build.HOST.startsWith("Build") || Build.PRODUCT == "google_sdk" 
    }

通过检测系统的 Build 对象来判断当前设备是否为模拟器。具体方法是检测 Build.FINGERPRINT 属性是否包含字符串 "generic"

模拟器检测对抗

反调试检测

安卓系统自带调试检测函数

fun checkForDebugger() {  
    if (Debug.isDebuggerConnected()) {  
        // 如果调试器已连接,则终止应用程序  
        System.exit(0)  
    }  
}

debuggable属性

public boolean getAppCanDebug(Context context)//上下文对象为xxActivity.this
{
    boolean isDebug = context.getApplicationInfo() != null &&
            (context.getApplicationInfo().flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0;
    return isDebug;
}

ptrace检测

int ptrace_protect()//ptrace附加自身线程 会导致此进程TracerPid 变为父进程的TracerPid 即zygote
{
    return ptrace(PTRACE_TRACEME,0,0,0);;//返回-1即为已经被调试
}

每个进程同时刻只能被1个调试进程ptrace ,主动ptrace本进程可以使得其他调试器无法调试

调试进程名检测

int SearchObjProcess()
{
    FILE* pfile=NULL;
    char buf[0x1000]={0};
 
    pfile=popen("ps","r");
    if(NULL==pfile)
    {
        //LOGA("SearchObjProcess popen打开命令失败!\n");
        return -1;
    }
    // 获取结果
    //LOGA("popen方案:\n");
    while(fgets(buf,sizeof(buf),pfile))
    {
 
        char* strA=NULL;
        char* strB=NULL;
        char* strC=NULL;
        char* strD=NULL;
        strA=strstr(buf,"android_server");//通过查找匹配子串判断
        strB=strstr(buf,"gdbserver");
        strC=strstr(buf,"gdb");
        strD=strstr(buf,"fuwu");
        if(strA || strB ||strC || strD)
        {
            return 1;
            // 执行到这里,判定为调试状态
 
        }
    }
    pclose(pfile);
    return 0;
}

[原创]对安卓反调试和校验检测的一些实践与结论

frida检测

一些Frida检测手段

Xposed

Xposed是一款可以在不修改APK的情况下影响程序运行的框架,基于它可以制作出许多功能强大的模块,且在功能不冲突的情况下同时运作。在这个框架下,我们可以编写并加载自己编写的插件APP,实现对目标apk的注入拦截等。

原理

用自己实现的app_process替换掉了系统原本提供的app_process,加载一个额外的jar包,入口从原来的: **com.android.internal.osZygoteInit.main()被替换成了: de.robv.android.xposed.XposedBridge.main()**,
创建的Zygote进程就变成Hook的Zygote进程了,从而完成对zygote进程及其创建的Dalvik/ART虚拟机的劫持(zytoge注入)

image-20240317180905810

Xposed的发展及免root框架

名称 地址 支持版本 是否免root
xposed https://github.com/rovo89/Xposed 2.3-8.1
EDXposed https://github.com/ElderDrivers/EdXposed 8.0-10
LSPosed https://github.com/LSPosed/LSPosed 8.1-13
VirtualXposed https://github.com/android-hacker/VirtualXposed 5.0-10.0
太极 https://www.coolapk.com/apk/me.weishu.exp 5.0-10.0
两仪 https://www.coolapk.com/apk/io.twoyi 8.1-12
天鉴 https://github.com/Katana-Official/SPatch-Update 6-10

多阅读他人代码,学习

2022 最好的Xposed模块: GravityBox, Pixelify, XPrivacyLua

基于Xposed的抖音爬虫,抖音风控后自动一键新机,模拟一个全新的运行环境

基于xposed的frida持久化方案

A Xposed Module for Android Penetration Test, with NanoHttpd.

GravityBox

Xposed-Modules-Repo]

一个旨在使QQ变得更好用的开源Xposed模块

杜比大喇叭

知乎去广告Xposed模块

哔哩漫游

曲境

自动化创建Xposed模块及钩子,让Xposed模块编写时只需关注钩子实现

编写Xposed需要的api

https://github.com/bywhat/XposedBridgeApi

下载后将需要的版本添加到依赖

XPosed模块编写查看文章:
https://todis21.github.io/2024/03/18/Xposed%E6%A8%A1%E5%9D%97%E7%BC%96%E5%86%99%E5%AD%A6%E4%B9%A0/

Xposed模块patch

LSPatch 可以理解为无需root的 LSPosed框架

快速Hook

SimpleHook

jshook

密码学相关

学这个是为了算法还原、协议分析、对抗等。

编码:

Base64编码、Hex编码、Unicode编码、Byte

加密算法:

MD5加密、AES 、DEA 、RSA 、RC4

….(头皮发麻.jpg)

遇到非标准加密算法:

方法一:主动调用 方法二:扣算法 方法三:ChatGPT

END