首页
社区
课程
招聘
[原创]《安卓逆向这档事》六、校验的N次方-签名校验对抗、PM代理、IO重定向
发表于: 2023-7-30 22:13 7516

[原创]《安卓逆向这档事》六、校验的N次方-签名校验对抗、PM代理、IO重定向

2023-7-30 22:13
7516

|250
# 一、课程目标

1.了解APK文件签名
2.了解APK常见校验及校验对抗方法
3.了解PM代{过}{滤}理和IO重定向

二、工具


1.教程Demo(更新)
2.MT管理器/NP管理器
3.雷电模拟器
4.Jadx-gui(第三课课件里有)
5.算法助手(第四课课件里有)

三、课程内容


1.什么是校验

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

2.什么是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的检验信息不同,于是程序就不能成功安装。

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

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

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

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

3.什么是签名校验

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

一般来说,普通的签名校验会导致软件的闪退,黑屏,卡启动页等
当然,以上都算是比较好的,有一些比较狠的作者,则会直接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
300

普通获取签名校验代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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) 即可获取指定包名的签名信息

4.签名校验对抗

方法一:核心破解插件,不签名安装应用
方法二:一键过签名工具,例如MT、NP、ARMPro、CNFIX、Modex的去除签名校验功能
方法三:具体分析签名校验逻辑(手撕签名校验)
方法四:io重定向--VA&SVC:ptrace+seccomp
SVC的TraceHook沙箱的实现&无痕Hook实现思路
方法五:去作者家严刑拷打拿到.jks文件和密码

5.手动实现PM代{过}{滤}理

1.什么是PMS

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

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

2.实现方法以及原理解析

HOOK PMS代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
package com.zj.hookpms;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
 
import android.content.Context;
import android.content.pm.PackageManager;
import android.util.Log;
 
public class ServiceManagerWraper {
 
    public final static String ZJ = "ZJ595";
 
    public static void hookPMS(Context context, String signed, String appPkgName, int hashCode) {
        try {
            // 获取全局的ActivityThread对象
            Class<?> activityThreadClass = Class.forName("android.app.ActivityThread");
            Method currentActivityThreadMethod =
                    activityThreadClass.getDeclaredMethod("currentActivityThread");
            Object currentActivityThread = currentActivityThreadMethod.invoke(null);
            // 获取ActivityThread里面原始的sPackageManager
            Field sPackageManagerField = activityThreadClass.getDeclaredField("sPackageManager");
            sPackageManagerField.setAccessible(true);
            Object sPackageManager = sPackageManagerField.get(currentActivityThread);
            // 准备好代{过}{滤}理对象, 用来替换原始的对象
            Class<?> iPackageManagerInterface = Class.forName("android.content.pm.IPackageManager");
            Object proxy = Proxy.newProxyInstance(
                    iPackageManagerInterface.getClassLoader(),
                    new Class<?>[]{iPackageManagerInterface},
                    new PmsHookBinderInvocationHandler(sPackageManager, signed, appPkgName, 0));
            // 1. 替换掉ActivityThread里面的 sPackageManager 字段
            sPackageManagerField.set(currentActivityThread, proxy);
            // 2. 替换 ApplicationPackageManager里面的 mPM对象
            PackageManager pm = context.getPackageManager();
            Field mPmField = pm.getClass().getDeclaredField("mPM");
            mPmField.setAccessible(true);
            mPmField.set(pm, proxy);
        } catch (Exception e) {
            Log.d(ZJ, "hook pms error:" + Log.getStackTraceString(e));
        }
    }
 
    public static void hookPMS(Context context) {
        String Sign = "原包的签名信息";
        hookPMS(context, Sign, "com.zj.hookpms", 0);
    }
}

ActivityThread的静态变量sPackageManager
ApplicationPackageManager对象里面的mPM变量

6.IO重定向

什么是IO重定向?

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

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

IO重定向可以干嘛?

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

2,禁止访问文件

3,路径替换

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
using namespace std; 
string packname; 
string origpath; 
string fakepath; 
   
int (*orig_open)(const char *pathname, int flags, ...); 
int (*orig_openat)(int,const char *pathname, int flags, ...); 
FILE *(*orig_fopen)(const char *filename, const char *mode); 
static long (*orig_syscall)(long number, ...); 
int (*orig__NR_openat)(int,const char *pathname, int flags, ...); 
   
void* (*orig_dlopen_CI)(const char *filename, int flag); 
void* (*orig_dlopen_CIV)(const char *filename, int flag, const void *extinfo); 
void* (*orig_dlopen_CIVV)(const char *name, int flags, const void *extinfo, void *caller_addr); 
   
static inline bool needs_mode(int flags) { 
    return ((flags & O_CREAT) == O_CREAT) || ((flags & O_TMPFILE) == O_TMPFILE); 
bool startsWith(string str, string sub){ 
    return str.find(sub)==0
   
bool endsWith(string s,string sub){ 
    return s.rfind(sub)==(s.length()-sub.length()); 
bool isOrigAPK(string  path){ 
   
    if(path==origpath){ 
        return true; 
    
    return false; 
//该函数的功能是在打开一个文件时进行拦截,并在满足特定条件时将文件路径替换为另一个路径 
   
//fake_open 函数有三个参数: 
//pathname:一个字符串,表示要打开的文件的路径。 
//flags:一个整数,表示打开文件的方式,例如只读、只写、读写等。 
//mode(可选参数):一个整数,表示打开文件时应用的权限模式。 
int fake_open(const char *pathname, int flags, ...) { 
    mode_t mode = 0
    if (needs_mode(flags)) { 
        va_list args; 
        va_start(args, flags); 
        mode = static_cast<mode_t>(va_arg(args, int)); 
        va_end(args); 
    
    //LOGI("open,  path: %s, flags: %d, mode: %d",pathname, flags ,mode); 
    string cpp_path= pathname; 
    if(isOrigAPK(cpp_path)){ 
        LOGI("libc_open, redirect: %s, --->: %s",pathname, fakepath.data()); 
        return orig_open("/data/user/0/com.zj.wuaipojie/files/base.apk", flags, mode); 
    
    return  orig_open(pathname, flags, mode); 
   
   
   
//该函数的功能是在打开一个文件时进行拦截,并在满足特定条件时将文件路径替换为另一个路径 
   
//fake_openat 函数有四个参数: 
//fd:一个整数,表示要打开的文件的文件描述符。 
//pathname:一个字符串,表示要打开的文件的路径。 
//flags:一个整数,表示打开文件的方式,例如只读、只写、读写等。 
//mode(可选参数):一个整数,表示打开文件时应用的权限模式。 
//openat 函数的作用类似于 open 函数,但是它使用文件描述符来指定文件路径,而不是使用文件路径本身。这样,就可以在打开文件时使用相对路径,而不必提供完整的文件路径。 
//例如,如果要打开相对于当前目录的文件,可以使用 openat 函数,而不是 open 函数,因为 open 函数只能使用绝对路径。 
// 
int fake_openat(int fd, const char *pathname, int flags, ...) { 
    mode_t mode = 0
    if (needs_mode(flags)) { 
        va_list args; 
        va_start(args, flags); 
        mode = static_cast<mode_t>(va_arg(args, int)); 
        va_end(args); 
    
    LOGI("openat, fd: %d, path: %s, flags: %d, mode: %d",fd ,pathname, flags ,mode); 
    string cpp_path= pathname; 
    if(isOrigAPK(cpp_path)){ 
        LOGI("libc_openat, redirect: %s, --->: %s",pathname, fakepath.data()); 
        return  orig_openat(fd,fakepath.data(), flags, mode); 
    
    return orig_openat(fd,pathname, flags, mode); 
   
FILE *fake_fopen(const char *filename, const char *mode) { 
   
    string cpp_path= filename; 
    if(isOrigAPK(cpp_path)){ 
        return  orig_fopen(fakepath.data(), mode); 
    
    return orig_fopen(filename, mode); 
//该函数的功能是在执行系统调用时进行拦截,并在满足特定条件时修改系统调用的参数。 
//syscall 函数是一个系统调用,是程序访问内核功能的方法之一。使用 syscall 函数可以调用大量的系统调用,它们用于实现操作系统的各种功能,例如打开文件、创建进程、分配内存等。 
// 
static long fake_syscall(long number, ...) { 
    void *arg[7]; 
    va_list list
   
    va_start(list, number); 
    for (int i = 0; i < 7; ++i) { 
        arg[i] = va_arg(list, void *); 
    
    va_end(list); 
    if (number == __NR_openat){ 
        const char *cpp_path = static_cast<const char *>(arg[1]); 
        LOGI("syscall __NR_openat, fd: %d, path: %s, flags: %d, mode: %d",arg[0] ,arg[1], arg[2], arg[3]); 
        if (isOrigAPK(cpp_path)){ 
            LOGI("syscall __NR_openat, redirect: %s, --->: %s",arg[1], fakepath.data()); 
            return orig_syscall(number,arg[0], fakepath.data() ,arg[2],arg[3]); 
        
    
    return orig_syscall(number, arg[0], arg[1], arg[2], arg[3], arg[4], arg[5], arg[6]); 
   
   
//函数的功能是获取当前应用的包名、APK 文件路径以及库文件路径,并将这些信息保存在全局变量中 
//函数调用 GetObjectClass 和 GetMethodID 函数来获取 context 对象的类型以及 getPackageName 方法的 ID。然后,函数调用 CallObjectMethod 函数来调用 getPackageName 方法,获取当前应用的包名。最后,函数使用 GetStringUTFChars 函数将包名转换为 C 字符串,并将包名保存在 packname 全局变量中 
//接着,函数使用 fakepath 全局变量保存了 /data/user/0/<packname>/files/base.apk 这样的路径,其中 <packname> 是当前应用的包名。 
//然后,函数再次调用 GetObjectClass 和 GetMethodID 函数来获取 context 对象的类型以及 getApplicationInfo 方法的 ID。然后,函数调用 CallObjectMethod 函数来调用 getApplicationInfo 方法,获取当前应用的 ApplicationInfo 对象。 
//它先调用 GetObjectClass 函数获取 ApplicationInfo 对象的类型,然后调用 GetFieldID 函数获取 sourceDir 字段的 ID。接着,函数使用 GetObjectField 函数获取 sourceDir 字段的值,并使用 GetStringUTFChars 函数将其转换为 C 字符串。最后,函数将 C 字符串保存在 origpath 全局变量中,表示当前应用的 APK 文件路径。 
//最后,函数使用 GetFieldID 和 GetObjectField 函数获取 nativeLibraryDir 字段的值,并使用 GetStringUTFChars 函数将其转换为 C 字符串。函数最后调用 LOGI 函数打印库文件路径,但是并没有将其保存在全局变量中。 
   
extern "C" JNIEXPORT void JNICALL 
Java_com_zj_wuaipojie_util_SecurityUtil_hook(JNIEnv *env, jclass clazz, jobject context) { 
    jclass conext_class = env->GetObjectClass(context); 
    jmethodID methodId_pack = env->GetMethodID(conext_class, "getPackageName"
                                               "()Ljava/lang/String;"); 
    auto packname_js = reinterpret_cast<jstring>(env->CallObjectMethod(context, methodId_pack)); 
    const char *pn = env->GetStringUTFChars(packname_js, 0); 
    packname = string(pn); 
   
   
    env->ReleaseStringUTFChars(packname_js, pn); 
    //LOGI("packname: %s", packname.data()); 
    fakepath= "/data/user/0/"+ packname +"/files/base.apk"
   
    jclass conext_class2 = env->GetObjectClass(context); 
    jmethodID methodId_pack2 = env->GetMethodID(conext_class2,"getApplicationInfo","()Landroid/content/pm/ApplicationInfo;"); 
    jobject application_info = env->CallObjectMethod(context,methodId_pack2); 
    jclass pm_clazz = env->GetObjectClass(application_info); 
   
   
    jfieldID package_info_id = env->GetFieldID(pm_clazz,"sourceDir","Ljava/lang/String;"); 
    auto sourceDir_js = reinterpret_cast<jstring>(env->GetObjectField(application_info,package_info_id)); 
    const char *sourceDir = env->GetStringUTFChars(sourceDir_js, 0); 
    origpath = string(sourceDir); 
    LOGI("sourceDir: %s", sourceDir); 
   
    jfieldID package_info_id2 = env->GetFieldID(pm_clazz,"nativeLibraryDir","Ljava/lang/String;"); 
    auto nativeLibraryDir_js = reinterpret_cast<jstring>(env->GetObjectField(application_info,package_info_id2)); 
    const char *nativeLibraryDir = env->GetStringUTFChars(nativeLibraryDir_js, 0); 
    LOGI("nativeLibraryDir: %s", nativeLibraryDir); 
    //LOGI("%s", "Start Hook"); 
   
    //启动hook 
    void *handle = dlopen("libc.so",RTLD_NOW); 
    auto pagesize = sysconf(_SC_PAGE_SIZE); 
    auto addr = ((uintptr_t)dlsym(handle,"open") & (-pagesize)); 
    auto addr2 = ((uintptr_t)dlsym(handle,"openat") & (-pagesize)); 
    auto addr3 = ((uintptr_t)fopen) & (-pagesize); 
    auto addr4 = ((uintptr_t)syscall) & (-pagesize); 
   
    //解除部分机型open被保护 
    mprotect((void*)addr, pagesize, PROT_READ | PROT_WRITE | PROT_EXEC); 
    mprotect((void*)addr2, pagesize, PROT_READ | PROT_WRITE | PROT_EXEC); 
    mprotect((void*)addr3, pagesize, PROT_READ | PROT_WRITE | PROT_EXEC); 
    mprotect((void*)addr4, pagesize, PROT_READ | PROT_WRITE | PROT_EXEC); 
   
    DobbyHook((void *)dlsym(handle,"open"), (void *)fake_open, (void **)&orig_open); 
    DobbyHook((void *)dlsym(handle,"openat"), (void *)fake_openat, (void **)&orig_openat); 
    DobbyHook((void *)fopen, (void *)fake_fopen, (void**)&orig_fopen); 
    DobbyHook((void *)syscall, (void *)fake_syscall, (void **)&orig_syscall); 
}
1
2
3
4
5
6
7
    sget-object p10, Lcom/zj/wuaipojie/util/ContextUtils;->INSTANCE:Lcom/zj/wuaipojie/util/ContextUtils; 
 
invoke-virtual {p10}, Lcom/zj/wuaipojie/util/ContextUtils;->getContext()Landroid/content/Context; 
 
move-result-object p10 
 
invoke-static {p10}, Lcom/zj/wuaipojie/util/SecurityUtil;->hook(Landroid/content/Context;)V

7.其他常见校验

root检测:

反制手段
1.算法助手、对话框取消等插件一键hook
2.分析具体的检测代码
3.利用IO重定向使文件不可读
4.修改Andoird源码,去除常见指纹

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
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。

模拟器检测

1
2
3
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"

模拟器检测对抗

反调试检测

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

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

debuggable属性

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

ptrace检测

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

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

调试进程名检测

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
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检测手段

9.反思

签名的博弈日新月异,善用工具,拥抱开源!!!

通过系统自带的api去获取签名很容易被伪造,不妨试试通过SVC的方式去获取(参考MT开源的方法)
隐式签名校验
有一些则比较隐晦,在发现apk被修改后,会偷偷修改apk的部分功能,例如在某些多开定位软件中,会暗改ip的经纬网等,跟实际产生一定的偏差。
PS:推荐学习芽衣大神的手撕签名校验教程

点击学习

四、课后小作业


1.手动实现PM代{过}{滤}理
2.移植我的重定向代码(要学会偷代码!)

3.完成作业demo的校验
点击下载
作业反馈贴
# 五、答疑

待更新

六、视频及课件地址


百度云
阿里云
哔哩哔哩
PS:解压密码都是52pj,阿里云由于不能分享压缩包,所以下载exe文件,双击自解压

七、其他章节


《安卓逆向这档事》一、模拟器环境搭建
《安卓逆向这档事》二、初识APK文件结构、双开、汉化、基础修改
《安卓逆向这档事》三、初识smail,vip终结者
《安卓逆向这档事》四、恭喜你获得广告&弹窗静默卡
《安卓逆向这档事》五、1000-7=?&动态调试&Log插桩
《安卓逆向这档事》六、校验的N次方-签名校验对抗、PM代{过}{滤}理、IO重定向
《安卓逆向这档事》七、Sorry,会Hook真的可以为所欲为-Xposed快速上手(上)模块编写,常用Api
《安卓逆向这档事》八、Sorry,会Hook真的可以为所欲为-xposed快速上手(下)快速hook
《安卓逆向这档事》九、密码学基础、算法自吐、非标准加密对抗
《安卓逆向这档事》十、不是我说,有了IDA还要什么女朋友?
《安卓逆向这档事》十二、大佬帮我分析一下
《安卓逆向这档事》番外实战篇1-某电影视全家桶

八、参考文档


APK 签名:v1 v2 v3 v4
如何把签名校验做到极致
Android PMS HOOK
[实战破解]白描-动态代{过}{滤}理Hook签名校验
[原创]对安卓反调试和校验检测的一些实践与结论
新版MT去签及对抗
【小白教程】正则匹配的写法 多行匹配 批量赋值 smali逆向 简单实用


[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)

最后于 2023-7-30 22:50 被正己编辑 ,原因:
收藏
免费 5
支持
分享
最新回复 (3)
雪    币: 2767
活跃值: (2068)
能力值: ( LV5,RANK:60 )
在线值:
发帖
回帖
粉丝
2
小小搬运一下,看雪的佬轻喷
2023-7-30 22:20
0
雪    币: 2354
活跃值: (3224)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
3
TQL
2023-7-30 22:49
0
雪    币: 3525
活跃值: (31011)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
4
mark
2023-7-31 09:07
1
游客
登录 | 注册 方可回帖
返回
//