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

聊聊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.(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就不多说了,说了很多遍了 。感兴趣可以看 [原创]聊聊大厂设备指纹其二&Hunter环境检测思路详解!-Android安全-看雪-安全社区|安全招聘|kanxue.com

我之前再看雪发的文章 。

这种方式主要是直接在方法里面内敛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, ¢ral_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*>(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*>(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 , 页面如下 。聊聊Android签名检测7种核心检测方案详解_第1张图片

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

 

你可能感兴趣的:(系统,android)