首页
社区
课程
招聘
聊聊Android签名检测7种核心检测方案详解
2023-9-24 11:13 61024

聊聊Android签名检测7种核心检测方案详解

2023-9-24 11:13
61024

聊聊Android签名检测总结与反思

背景:

这篇文章只讲Android端签名检测,安卓发展到现在,因为国内环境没有谷歌市场,所以很多官方推荐的Api没法使用 ,所以国内的签名检测方式也是“千奇百怪” 。发展至今每种方法都有一些绕过或者对抗手段,这些方法很难说就一定准 ,但是我们能做的就是取尽可能的提高攻击者的成本,提升Apk的签名检测能力,防止灰黑产进行攻击 。基础的什么Java获取签名信息这种基础方案,这里暂时跳过 ,不在过多叙述 。这篇文章主要分为Java和Native两部分 ,分别从不同的视角取检测签名,包括如何对抗等 , 其中包含一些大厂和企业壳的核心检测签名思路 。

Java层检测签名:

1、IPC协议获取签名信息 :

首先讲这块之前需要先简单讲一下IPC协议的实现 。

安卓基础架构就是CS架构。每个App都是客户端,服务端只有一个 ,客户端和服务端是不同的进程 。

这样做的,客户端一旦发生崩溃不影响服务端 ,服务端也可以根据不同的uid实现不同的鉴权操作 。当我们获取一些apk信息的时候都是客户端发送IPC协议 。

服务端接收以后进行处理,利用binder进行通讯,然后把数据写到客户端内部,客户端拿到 。这块设计很多技术点 ,比如动态代理,IPC协议 ,Binder通讯等

上面说起来可能很难懂 ,首先我们去Hook PackageInfo的构造方法 ,根据栈回溯看看如果想要生产一个PackageInfo到底需要哪些流程 。

然后讲一下,为什么推荐使用IPC协议去获取签名。由点到面中间可能会存在哪些问题 。大家在测试的时候最好在谷歌原生的系统上测试,如果是国产厂商可能会添加自己的一些拦截器 。测试版本Android 11 。从上往下看 ,一步一步讲一下 每一步到底做什么的

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
//触发hook回调,命中PackageInfo构造方法       
19、at RuntimeClazz.constructor(Unknown Source:11)
//PackageInfo真正的构造方法
18、at android.content.pm.PackageInfo.<init>(PackageInfo.java:29)
17、at java.lang.reflect.Method.invoke(Native Method)
16、at com.runtime.api.rposed.RposedBridge.invokeMethod(RposedBridge.java:314)
15、at com.runtime.api.rposed.RposedBridge.-$$Nest$sminvokeMethod(Unknown Source:0)
14、at com.runtime.api.rposed.RposedBridge$HookInfo.callback(RposedBridge.java:378)
//hook拦截回调
13、at RuntimeClazz.constructor(Unknown Source:14)
12、at android.content.pm.PackageInfo$1.createFromParcel(PackageInfo.java:500)
 
//这块相当于是对服务端的数据去解析,把Parcel装载的数据转换成PackageInfo,再则里面进行PackageInfo的构造。
11、at android.content.pm.PackageInfo$1.createFromParcel(PackageInfo.java:497)
//读取数据类型,开始去解析服务端发送过来的数据 。
//android.os.Parcel 这个对象大家可以理解成“小盒子”,里面存放了数据包,不管是请求服务端发送协议的数据包
//还是服务端返回结果的数据包都是通过Parcel进行装载 。
10、android.os.Parcel.readTypedObject(Parcel.java:3982)
//android.content.pm.IPackageManager$Stub$Proxy 是PackageManager内部的一个代理人,也就是服务端的代理人
//代理设计模式的好处就是,代理人只有其本地的部分功能,不同的功能需要的权限也不一样 ,方便处理。
//安卓端每个服务,内部都存在一个服务端的代理人,所谓的动态代理就是通过反射的方式强制把代理人给替换成自己的实现对象 。
//伪造服务端代理人,实现非注入式hook ,比如一般的沙箱为了稳定性,不会采用类似lsplant的方式去修改函数地址的hook 。
//所以检测服务端代理人的class名称,是否包含proxy或者检测服务端代理人的classloader是否是系统的classloader。
//都是很常见的检测沙箱的方式,这个方法是PackageManager本地逻辑处理完,把请求交给服务端代理人IPackageManager
//这个方法就是核心逻辑所在,IPC协议的拼接,底层调用的binder去通讯,通讯完毕去解析 。都是在这个方法里面处理的 。
9、at android.content.pm.IPackageManager$Stub$Proxy.getPackageInfo(IPackageManager.java:4764)
//未找到cache
8、at android.content.pm.PackageManager.getPackageInfoAsUserUncached(PackageManager.java:8224)
7、at android.content.pm.PackageManager.access$100(PackageManager.java:96)
6、at android.content.pm.PackageManager$2.recompute(PackageManager.java:8236)
5、at android.content.pm.PackageManager$2.recompute(PackageManager.java:8233)
4、at android.app.PropertyInvalidatedCache.query(PropertyInvalidatedCache.java:374)
//根据uid去查询cache
3、at android.content.pm.PackageManager.getPackageInfoAsUserCached(PackageManager.java:8251)
//getPackageInfo的一个包装,根据uid再次查询
2、at android.app.ApplicationPackageManager.getPackageInfoAsUser(ApplicationPackageManager.java:202)
//当调用获取getPackageInfo时候的调用栈
1、at android.app.ApplicationPackageManager.getPackageInfo(ApplicationPackageManager.java:179)

总结:

客户端IPC协议流程图简述:

发起人App调用获取PackageInfo-> PackageManager收到请求,判断是否存在cache,如果不存在 ->服务端代理人 ->

发起Binder协议->服务端接收 ,处理完毕写入->客户端接收。

这块我们发现,很多逻辑都是没用的,都是客户端去为了各种方便封装了很多方法,但是最终目的都是为了发送IPC协议和服务端通讯 ,

但是其中,中间每一层级都可能导致我们的程序被Hook导致我们拿到的数据是不安全的 ,比如攻击者是直接hook PackageManager 或者直接hook PackageManager代理人,然后修改对应的数据内容。导致我们的数据不安全 ,被替换 。

所以得出结论:

我们可以直接模拟IPC协议直接和服务端通讯,因为客户端这些都是不安全的 ,直接通讯绕过这些复杂的处理逻辑,但是弊端就是需要兼容不同的android版本 , 否则可能会导致解析失败。

实现代码如下:

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
try {
            PackageManager packageManager = getBaseContext().getPackageManager();
 
            Object IPC_PM_Obj = RposedHelpers.getObjectField(packageManager, "mPM");
            //取binder
            IBinder mRemote = (IBinder) RposedHelpers.getObjectField(IPC_PM_Obj, "mRemote");
            Parcel _data = Parcel.obtain();
            Parcel _reply = Parcel.obtain();
            _data.writeInterfaceToken("android.content.pm.IPackageManager");
            _data.writeString(getPackageName());
            _data.writeLong(PackageManager.GET_SIGNATURES);
            _data.writeInt(android.os.Process.myUid());
            //自定义flag
            boolean _status = mRemote.transact(TransactCase.TRANSACTION_getPackageInfo(), _data, _reply, 0);
            _reply.readException();
            PackageInfo packageInfo = _reply.readTypedObject(PackageInfo.CREATOR);
            _data.recycle();
            _reply.recycle();
            CLog.e("ipc sign info -> : "+packageInfo.signatures[0].toCharsString());
        } catch (Throwable e) {
            CLog.i("IPC_TEST_getPackageInfo error "+e);
        }
         
public static int TRANSACTION_getPackageInfo() {
        if(TRANSACTION_getPackageInfo == -1) {
            try {
                Field field = null;
                try {
                    Class<?> pkmIPCClazz = Class.forName("android.content.pm.IPackageManager$Stub");
                    field = pkmIPCClazz.getDeclaredField("TRANSACTION_getPackageInfo");
                } catch (Throwable e) {
                    CLog.e(">>>>>>>>>> getTranscationId forName error " + e.getMessage());
                }
                assert field != null;
                field.setAccessible(true);
                TRANSACTION_getPackageInfo = field.getInt(null);
            } catch (Throwable e) {
                e.printStackTrace();
                CLog.e(">>>>>>>>>> getTranscationId error " + e.getMessage());
            }
        }
        return TRANSACTION_getPackageInfo;
    }

反思:

这种方式一定是安全的么?

不是的,如果这块有个细节就是 我们本质上还是需要通过binder驱动进行通讯 ,和PackageInfo.CREATOR解析

如果攻击者 直接hook了binder驱动 ,或者hook解析方法还是可以绕过 。比如binder也是会有一些入口,还是可能被Hook劫持 。

  • binder的入口android.os.BinderProxy->transact方法 。

或者直接hook签名解析的方法,因为在getPackageInfo也是需要去解析签名信息的,可以直接hook签名信息解析这块逻辑 。

  • android.content.pm.PackageParser->generatePackageInfo 系统解析签名方法
  • PackageInfo.CREATOR PackageInfo Parcel 解析签名方法都是可以的 。

还可以继续增强么?

可以根据安卓源码去自实现binder驱动和解析过程 ,然后连本地的binder也不信任,直接和服务端通讯也是可以的 。

Java层还有比较好的签名检测手段么?

目前推荐的只有这么一种,大部分都是SO层做的检测,Java层还可以做一些代理人的动态代理,包括一些代理人的classloader,或者

PackageInfo.CREATOR的classloader之类的去做对抗 。还有一个tee检测也很不错 ,用于检测各种证书,也是容易被hook,这块不多说了 ,重点放在So层 。

So层的基础svc拦截就可以bypass掉很多小白 。

Native层检测签名:

1、基础SVC读取签名:

这块什么是svc就不多说了,说了很多遍了 。感兴趣可以看 https://bbs.kanxue.com/thread-277402.htm

我之前再看雪发的文章 。

这种方式主要是直接在方法里面内敛svc的方式去openat打开已安装的apk ,然后去解析证书文件 。

如果svc层不做处理 ,很难绕过检测 。解析apk签名过程如下 ,代码来自Magisk ,调用 checkSign 对返回的字符串进行判断即可 。

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
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
#define APK_SIGNING_BLOCK_MAGIC     "APK Sig Block 42"
#define SIGNATURE_SCHEME_V2_MAGIC   0x7109871a
#define EOCD_MAGIC                  0x6054b50
 
// Top-level block container
struct signing_block {
    uint64_t block_sz;
 
    struct id_value_pair {
        uint64_t len;
        struct /* v2_signature */ {
            uint32_t id;
            uint8_t value[0];  // size = (len - 4)
        };
    } id_value_pair_sequence[0];
 
    uint64_t block_sz_;   // *MUST* be same as block_sz
    char magic[16];       // "APK Sig Block 42"
};
 
struct len_prefixed {
    uint32_t len;
};
 
// Generic length prefixed raw data
struct len_prefixed_value : public len_prefixed {
    uint8_t value[0];
};
 
// V2 Signature Block
struct v2_signature {
    uint32_t id// 0x7109871a
    uint32_t signer_sequence_len;
    struct signer : public len_prefixed {
        struct signed_data : public len_prefixed {
            uint32_t digest_sequence_len;
            struct : public len_prefixed {
                uint32_t algorithm;
                len_prefixed_value digest;
            } digest_sequence[0];
 
            uint32_t certificate_sequence_len;
            len_prefixed_value certificate_sequence[0];
 
            uint32_t attribute_sequence_len;
            struct attribute : public len_prefixed {
                uint32_t id;
                uint8_t value[0];  // size = (len - 4)
            } attribute_sequence[0];
        } signed_data;
 
        uint32_t signature_sequence_len;
        struct : public len_prefixed {
            uint32_t id;
            len_prefixed_value signature;
        } signature_sequence[0];
 
        len_prefixed_value public_key;
    } signer_sequence[0];
};
 
// End of central directory record
struct EOCD {
    uint32_t magic;            // 0x6054b50
    uint8_t pad[8];            // 8 bytes of irrelevant data
    uint32_t central_dir_sz;   // size of central directory
    uint32_t central_dir_off;  // offset of central directory
    uint16_t comment_sz;       // size of comment
    char comment[0];
} __attribute__((packed));
/*
 * A v2/v3 signed APK has the format as following
 *
 * +---------------+
 * | zip content   |
 * +---------------+
 * | signing block |
 * +---------------+
 * | central dir   |
 * +---------------+
 * | EOCD          |
 * +---------------+
 *
 * Scan from end of file to find EOCD, and figure our way back to the
 * offset of the signing block. Next, directly extract the certificate
 * from the v2 signature block.
 *
 * All structures above are mostly just for documentation purpose.
 *
 * This method extracts the first certificate of the first signer
 * within the APK v2 signature block.
 */
static string read_certificate(int fd) {
    uint32_t size4;
    uint64_t size8;
 
    // Find EOCD
    for (int i = 0;; i++) {
        // i is the absolute offset to end of file
        uint16_t comment_sz = 0;
        lseek(fd, -((off_t) sizeof(comment_sz)) - i, SEEK_END);
        read(fd, &comment_sz, sizeof(comment_sz));
        if (comment_sz == i) {
            // Double check if we actually found the structure
            lseek(fd, -((off_t) sizeof(EOCD)), SEEK_CUR);
            uint32_t magic = 0;
            read(fd, &magic, sizeof(magic));
            if (magic == EOCD_MAGIC) {
                break;
            }
        }
        if (i == 0xffff) {
            // Comments cannot be longer than 0xffff (overflow), abort
            return {};
        }
    }
 
    // We are now at EOCD + sizeof(magic)
    // Seek and read central_dir_off to find start of central directory
    uint32_t central_dir_off = 0;
    {
        constexpr off_t off = offsetof(EOCD, central_dir_off) - sizeof(EOCD::magic);
        lseek(fd, off, SEEK_CUR);
    }
    read(fd, &central_dir_off, sizeof(central_dir_off));
 
    // Next, find the start of the APK signing block
    {
        constexpr int off = sizeof(signing_block::block_sz_) + sizeof(signing_block::magic);
        lseek(fd, (off_t) (central_dir_off - off), SEEK_SET);
    }
    read(fd, &size8, sizeof(size8));  // size8 = block_sz_
    char magic[sizeof(signing_block::magic)] = {0};
    read(fd, magic, sizeof(magic));
    if (memcmp(magic, APK_SIGNING_BLOCK_MAGIC, sizeof(magic)) != 0) {
        // Invalid signing block magic, abort
        return {};
    }
    uint64_t signing_blk_sz = 0;
    lseek(fd, (off_t) (central_dir_off - size8 - sizeof(signing_blk_sz)), SEEK_SET);
    read(fd, &signing_blk_sz, sizeof(signing_blk_sz));
    if (signing_blk_sz != size8) {
        // block_sz != block_sz_, invalid signing block format, abort
        return {};
    }
 
    // Finally, we are now at the beginning of the id-value pair sequence
 
    for (;;) {
        read(fd, &size8, sizeof(size8)); // id-value pair length
        if (size8 == signing_blk_sz) {
            // Outside of the id-value pair sequence; actually reading block_sz_
            break;
        }
 
        uint32_t id;
        read(fd, &id, sizeof(id));
        if (id == SIGNATURE_SCHEME_V2_MAGIC) {
            read(fd, &size4, sizeof(size4)); // signer sequence length
 
            read(fd, &size4, sizeof(size4)); // signer length
            read(fd, &size4, sizeof(size4)); // signed data length
 
            read(fd, &size4, sizeof(size4)); // digest sequence length
            lseek(fd, (off_t) (size4), SEEK_CUR); // skip all digests
 
            read(fd, &size4, sizeof(size4)); // cert sequence length
            read(fd, &size4, sizeof(size4)); // cert length
 
            string cert;
            cert.resize(size4);
            read(fd, (void *) cert.data(), size4);
 
            return cert;
        } else {
            // Skip this id-value pair
            lseek(fd, (off_t) (size8 - sizeof(id)), SEEK_CUR);
        }
    }
    return {};
}
 
string checkSign(JNIEnv * env,const char* apkPath){
 
    int fd1 = static_cast<int >(raw_syscall(__NR_openat, AT_FDCWD,
                         reinterpret_cast<const char *>(apkPath),
                         O_RDONLY | O_CLOEXEC,
                         0640
                     ));
    if(fd1==-1){
        return {};
    }
 
    std::string md5Result = Base64Utils::VTEncode(read_certificate(fd1));
    close(fd1);
    return md5Result;
}
 
string checkSign(JNIEnv * env,int fd1){
    if(fd1 == -1){
        return {};
    }
    std::string md5Result = Base64Utils::VTEncode(read_certificate(fd1));
    return md5Result;
}

2、文件权限:

当我们打开fd的时候,文件很有可能会被IO重定向,重定向到一个新的文件,但是这个文件不可能是系统的,因为没权限。

所以当我们得到这个fd的时候可以,再次通过fstat去获取已经打开的这个fd 是不是一个系统文件,系统文件的gid和uid都是1000 。

确认我们打开的fd是系统文件 。因为/data/app/包名/base.apk是一个系统文件 。

对抗的话可以直接svc fstat 退出阶段,对返回结果进行bypass即可 。

3、Readlinkat反查路径:

当我们打开fd的时候,文件很有可能会被IO重定向,重定向到一个新的文件,我们根据fd通过readlinkat 反查fd路径,判断和传入的路径是否相等,

如果相等则认为正确 。也是在SVC Readlinkat推出阶段对查询的buff进行二次覆盖 。

4、Readlinkat返回值截断:

这个思路主要是根据 Readlinkat的返回返回值进行检测,在创建数组的时候 数组大小采用Readlinkat返回值的大小 。

Readlinkat返回值 返回值返回的是一个查询路径结果的长度,如果攻击者只改了Readlinkat的参数,改了路径,但是返回值忘记修改,这样他的返回值就会被阶段,也就是大小不匹配,也可以检测出来 路径和传入的原始路径是否相等 。

5、Inode:

inode是linux的文件或者目录的唯一标识符,每个文件都是独一无二的 ,如果文件被关闭,再次打开一个新的文件可能会被占用,不过一些系统apk 不会被删除和关闭 。

在maps里面 每个item类似如下 :

7041e02000-7041e4e000 r--p 00000000 fc:01 25131 /apex/com.android.conscrypt/lib64/libc++.so

这里面的倒数25131 第二项就是inode,我们当得到一个fd的时候可以获取文件的fd 信息 ,然后再去maps里面获取已经打开的文件fd信息 。

判断打开文件的inode和内存里面的inode是否相等 。

对抗的话可以直接svc fstat 退出阶段,对返回结果进行bypass即可 。

6、对已经打开的fd进行签名获取:

这个方法过于激进,很少部分人知道 ,很多手机在刚启动的时候会自动打开 /data/app/包名/base.apk

比如 我们可以直接遍历已经打开的fd ,然后对这个readlinkat反查路径 , 如果是base.apk的fd直接对fd进行签名解析 。相当于对已经打开的文件进行签名解析 。

因为这个apk是系统启动阶段就被打开了 ,所以不会走IO重定向的逻辑 ,相对安全 。

经过测试,有的手机App在开机启动阶段不会打开 ,只适合Apk再启动阶段打开了base.apk ,直接对已经打开的fd解析即可 。可以逃逸IO重定向 。

对抗的话 也很简单3种方案

  • 如果有人通过已经打开的文件去遍历fd , 在readlinkat 结果处理 ,如果是base.apk直接隐藏掉 。

  • 直接处理getdents64和getdents ,在遍历阶段直接找不到对应的fd即可

  • 直接close,但是有的版本在close会报 fdsan异常,需要通过 android_fdsan_set_error_level 设置禁用

总结:

上述这些方案基本覆盖大多数手段 ,Hunter的核心检测思路也是上面这几种 。签名Native部分代码如下 。

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
jobject getApkSign(JNIEnv *env,
                   [[maybe_unused]] jclass engine,
                   jobject context) {
 
    const char *path = getApkPath(env, context);
    if(path == nullptr){
        return getItemData(env, "APK签名验证失败",
                           "getApkPath == null", true, RISK_LEAVE_DEADLY, TAG_REPACKAGE);
    }
    int fd = my_openat(AT_FDCWD, reinterpret_cast<const char *>(path),
                       O_RDONLY | O_CLOEXEC,
                       0640);
    if(fd < 0){
        return getItemData(env, "APK签名验证失败",
                           string ("getApkSign open error \n")+path, true, RISK_LEAVE_DEADLY, TAG_REPACKAGE);
    }
#define TEMP_APK_SIGN_STR "xxxxxx"
    //GET svc apk sign
    auto svc_apk_sign = Base64Utils::VTEncode(read_certificate(fd)).substr(0, 10);
    LOG(INFO) << "getApkSign apk sign  " << svc_apk_sign;
    //1、check svc get sign match
    if (svc_apk_sign == Base64Utils::VTDecode(TEMP_APK_SIGN_STR)) {
        //2、check apk sign for open fd
        auto base_fd_list = getBaseApkFd(path);
        if(!base_fd_list.empty()){
            //disabled fdsan
            change_fdsan_error_level();
            for(int temp_fd:base_fd_list){
                auto apk_sign_fd = checkSign(env, temp_fd).substr(0, 10);
                LOG(INFO) << "get open fd apk sign  " << apk_sign_fd;
                if(svc_apk_sign != Base64Utils::VTDecode(TEMP_APK_SIGN_STR)){
                    if(fd>0){
                        close(fd);
                    }
                    return getItemData(env, "APK签名验证失败",
                                       "open fd cache  error", false,
                                       RISK_LEAVE_DEADLY, TAG_REPACKAGE);
                }
            }
        }
        //3、check apk path
        char buff[PATH_MAX] = {0};
        std::string fdPath("/proc/");
        fdPath.append(to_string(getpid())).append("/fd/").append(to_string(fd));
        long len = raw_syscall(__NR_readlinkat, AT_FDCWD, fdPath.c_str(), buff, PATH_MAX);
        if (len < 0) {
            if(fd>0){
                close(fd);
            }
            return getItemData(env, "APK签名验证失败",
                               "readlinkat error", true,
                               RISK_LEAVE_DEADLY, TAG_REPACKAGE);
        }
        //4、截断,如果攻击者hook了readlinkat,只修改了参数,没修改返回值也可以检测出来。
        buff[len] = '\0';
        LOG(INFO) << "check apk sign path " << buff;
        if (my_strcmp(path, buff) == 0) {
            LOG(INFO) << "check apk sign path success ";
            //start check memory&location inode
            struct stat statBuff = {0};
            long stat = raw_syscall(__NR_fstat, fd, &statBuff);
            if (stat < 0) {
                if(fd>0){
                    close(fd);
                }
                LOG(ERROR) << "check apk sign path fail __NR_fstat<0";
                return getItemData(env, "APK签名验证失败",
                                   "fstat error", true, RISK_LEAVE_DEADLY, TAG_REPACKAGE);
            }
            //5、check uid&gid (1000 = system group)
            if (statBuff.st_uid != 1000 && statBuff.st_gid != 1000) {
                if(fd>0){
                    close(fd);
                }
                LOG(ERROR) << "check apk sign gid&uid fail ";
                return getItemData(env, "APK签名验证失败",
                                   nullptr, true, RISK_LEAVE_DEADLY, TAG_REPACKAGE);
            }
            //6、check Inode
            size_t inode = getFileInMapsInode(path);
            if (statBuff.st_ino != inode) {
                if(fd>0){
                    close(fd);
                }
                LOG(ERROR) << "check apk sign inode fail " << statBuff.st_ino << " maps ->"
                           << inode;
                return getItemData(env, "APK签名验证失败",
                                   nullptr, true, RISK_LEAVE_DEADLY, TAG_REPACKAGE);
            }
            LOG(ERROR) << ">>>>>>>>>> check apk sign success! uid-> " << statBuff.st_uid
                       << " gid-> "
                       << statBuff.st_gid;
        } else {
            if(fd>0){
                close(fd);
            }
            LOG(ERROR) << "check apk sign path fail ";
            return getItemData(env, "APK签名验证失败",
                               nullptr, true, RISK_LEAVE_DEADLY, TAG_REPACKAGE);
 
        }
        LOG(INFO) << "check apk sign success";
        if(fd>0){
            close(fd);
        }
        return nullptr;
    }
    else {
        if(fd>0){
            close(fd);
        }
        LOG(ERROR) << "check apk sign fail   " << svc_apk_sign;
        //check sign fail
        return getItemData(env, "APK签名验证失败",
                           nullptr, true, RISK_LEAVE_DEADLY, TAG_REPACKAGE);
    }
}

有没有一种方式可以轻轻松松绕过上面全部的检测呢?

经常有人问我,签名检测太难,不知道应该怎么绕过 ,有没有一种可以快速绕过全部检测的方案呢?并且我可以随便修改apk信息不被发现 。

其实很简单,在root手机上直接使用核心破解或者小米的三方lsp插件 Cemiuiler , 页面如下 。

图片描述

直接禁用系统的签名验证,直接安装,也就是在破坏了原始签名的情况下 ,未签名,直接安装 。
**因为你没有修改apk原始的签名信息,他读取到的还是原始的签名信息,签名文件不会有任何变化 。**你二次签名本质的目的都是为了通过系统的签名检测 。


[培训]《安卓高级研修班(网课)》月薪三万计划,掌握调试、分析还原ollvm、vmp的方法,定制art虚拟机自动化脱壳的方法

最后于 2023-9-24 11:34 被珍惜Any编辑 ,原因:
收藏
点赞22
打赏
分享
最新回复 (36)
雪    币: 2944
活跃值: (3931)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
0xEA 2023-9-25 14:22
2
0
tql
雪    币: 2944
活跃值: (3931)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
0xEA 2023-9-25 14:23
3
0
这么好的帖子怎么能没人回复呢
雪    币: 565
活跃值: (1406)
能力值: ( LV2,RANK:15 )
在线值:
发帖
回帖
粉丝
ogli324 2023-9-25 14:23
4
0
珍惜哥一如既往的好文~ 
雪    币: 1026
活跃值: (494)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
yikuaiyingbi 2023-9-25 14:24
5
0
火钳刘明
雪    币: 519
活跃值: (3752)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
龙飞雪 2023-9-25 15:10
6
0
谢谢大佬的分享~
雪    币:
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
CC小白p 2023-9-25 15:37
7
0
感谢
雪    币:
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
CC小白p 2023-9-25 15:40
8
0
大佬学了多久才到达这个地步呢
雪    币: 1929
活跃值: (12850)
能力值: ( LV9,RANK:190 )
在线值:
发帖
回帖
粉丝
珍惜Any 2 2023-9-25 17:14
9
0
CC小白p 大佬学了多久才到达这个地步呢
要么充值,要么自己慢慢扣 ,没别的办法 
雪    币: 1113
活跃值: (1639)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
mb_lpcoesnt 2023-9-26 09:43
10
0
tql
雪    币: 2944
活跃值: (3931)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
0xEA 2023-9-27 10:52
11
0
ogli324 珍惜哥一如既往的好文~ [em_71][em_71][em_71]
幡动分子的认可
雪    币: 1226
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
mb_sbsievro 2023-9-28 10:01
12
0
检测package代理人类名容易误判,正常手机是android.content.pm.IPackageManager$Stub$Proxy,但某些手机是com.zte.ZTESecurity.ZTEPackageManager
雪    币: 1929
活跃值: (12850)
能力值: ( LV9,RANK:190 )
在线值:
发帖
回帖
粉丝
珍惜Any 2 2023-9-28 10:36
13
0
mb_sbsievro 检测package代理人类名容易误判,正常手机是android.content.pm.IPackageManager$Stub$Proxy,但某些手机是com.zte.ZTESecurity.ZTEP ...
嗯对的,一些厂商会魔改这块 ,这块可以取去动态反射获取,代码仅仅是给个参考 
雪    币: 498
活跃值: (3776)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
哆啦噩梦 2023-10-7 11:19
14
0
太强了
雪    币:
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
王杰克 2023-10-11 17:44
15
0
安卓逆向的爹
雪    币: 263
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
军哥i 2023-10-12 09:25
16
0
惜佬yyds
雪    币: 19389
活跃值: (29037)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
秋狝 2023-10-12 09:31
17
1
感谢分享
雪    币: 789
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
wyao888 2023-10-12 10:51
18
0
学习了,研究下
雪    币: 25
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
wx_靛芒 2023-10-14 13:18
19
0
大佬这些知识都是在哪学的,自己研究还是国外论坛
雪    币: 1929
活跃值: (12850)
能力值: ( LV9,RANK:190 )
在线值:
发帖
回帖
粉丝
珍惜Any 2 2023-10-14 22:09
20
0
wx_靛芒 大佬这些知识都是在哪学的,自己研究还是国外论坛
分析别人的apk
雪    币: 258
活跃值: (451)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
hacker521 2023-10-29 03:32
21
0
珍惜大佬太强了
雪    币: 242
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
BetrayPity 2023-10-30 21:43
22
0
谢谢分享
雪    币: 1929
活跃值: (12850)
能力值: ( LV9,RANK:190 )
在线值:
发帖
回帖
粉丝
珍惜Any 2 2023-11-7 17:04
23
0
hacker521 珍惜大佬太强了
雪    币:
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
mb_ludqooda 2023-11-7 20:31
24
0
学习学习
雪    币: 84
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
ch1 2023-12-2 10:37
25
0
马上实践一遍
游客
登录 | 注册 方可回帖
返回