聊聊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.<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就不多说了,说了很多遍了 。感兴趣可以看 [原创]聊聊大厂设备指纹其二&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, &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原始的签名信息,他读取到的还是原始的签名信息,签名文件不会有任何变化 。**你二次签名本质的目的都是为了通过系统的签名检测 。

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/101263.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【ElasticSearch】深入探索 DSL 查询语法,实现对文档不同程度的检索,以及对搜索结果的排序、分页和高亮操作

文章目录 前言一、Elasticsearch DSL Query 的分类二、全文检索查询2.1 match 查询2.2 multi_match 查询 三、精确查询3.1 term 查询3.2 range 查询 四、地理坐标查询4.1 geo_bounding_box 查询4.2 geo_distance 查询 五、复合查询5.1 function score 查询5.2 boolean 查询 六、…

WorkPlus一站式解决方案,助力企业构建统一门户系统

在信息爆炸的时代&#xff0c;企业管理面临着海量的数据和各类业务应用的复杂性。如何实现信息的井然有序、高效管理&#xff0c;成为企业发展的关键。WorkPlus作为领先的品牌&#xff0c;致力于打造统一门户系统&#xff0c;为企业提供全方位的服务和解决方案。本文将以知乎的…

广州华锐互动:候车室智能数字孪生系统实现交通信息可视化

随着科技的不断发展&#xff0c;数字化技术在各个领域得到了广泛的应用。智慧车站作为一种新型的交通服务模式&#xff0c;通过运用先进的数字化技术&#xff0c;为乘客提供了更加便捷、舒适的出行体验。 将智慧车站与数字孪生大屏结合&#xff0c;可以将实际现实世界的实体车站…

java 无感hook实现(修改jdk)

背景 1 工作需要&#xff0c;需要修改一个java的程序逻辑&#xff0c;之前都是用的frida修改的&#xff0c;但是现在的工作场景&#xff0c;重视效率&#xff0c;所以frida这种重工具被pass了&#xff0c;只能重新选其他工具&#xff0c;初始的时候是想用java本身的一些修改工…

纸、纸板和纸制品 有效回收组分的测定

声明 本文是学习GB-T 42944-2023 纸、纸板和纸制品 有效回收组分的测定. 而整理的学习笔记,分享出来希望更多人受益,如果存在侵权请及时联系我们 1 范围 本文件描述了纸、纸板和纸制品中有效回收组分的测定方法。 本文件适用于各种纸、纸板和纸制品&#xff0c;也适用于铝箔…

微信小程序通过 movable-area 做一个与vuedraggable相似的上下拖动排序控件

因为只是做个小案例 我就直接代码写page页面里了 其实很简单 组件稍微改一下就好了 wxss /* 设置movable-area的宽度 */ .area{width: 100%; }/* a b c 每条元素的样式 */ movable-view {width: 100%;background-color: red;height: 40px;line-height: 40px;color: #FFFFFF;tex…

day

#include <iostream> using namespace std; class Per {//算术运算符friend const Per operator(const Per &k1,const Per &k2);friend const Per operator-(const Per &k1,const Per &k2);friend const Per operator*(const Per &k1,const Per &…

3ds Max渲染太慢?创意云“一键云渲染”提升3ds Max渲染体验

&#xfeff;在数字艺术设计领域&#xff0c;3ds Max是广泛使用的三维建模和渲染软件之一。然而&#xff0c;许多用户都面临着一个共同的问题&#xff1a;渲染速度太慢。渲染一帧画面需要耗费数小时&#xff0c;让人无法忍受。除了之前给大家介绍的几种解决方法外&#xff1a; …

Vue3中reactive, onMounted, ref,toRaw,conmpted 使用方法

import { reactive, onMounted, ref,toRaw,conmpted } from vue; vue3中 reactive &#xff0c;ref &#xff0c; toRaw&#xff0c;watch&#xff0c;conmpted 用法 toRaw 返回原响应式对象 用法&#xff1a; const rowList toRaw(row) reactive:ref: ref和reactive都是V…

快讯|Tubi 有 Rabbit AI 啦

在每月一期的 Tubi 快讯中&#xff0c;你将全面及时地获取 Tubi 最新发展动态&#xff0c;欢迎星标关注【比图科技】微信公众号&#xff0c;一起成长变强&#xff01; Tubi 推出 Rabbit AI 帮助用户找到喜欢的视频内容 Tubi 于今年九月底推出了 Rabbit AI&#xff0c;这是一项…

有效回收组分含量

声明 本文是学习GB-T 42944-2023 纸、纸板和纸制品 有效回收组分的测定. 而整理的学习笔记,分享出来希望更多人受益,如果存在侵权请及时联系我们 1 范围 本文件描述了纸、纸板和纸制品中有效回收组分的测定方法。 本文件适用于各种纸、纸板和纸制品&#xff0c;也适用于铝箔…

【自动驾驶】PETR/PETRv2/StreamPETR论文分析

1.PETR PETR网络结构如下&#xff0c;主要包括image-backbone, 3D Coordinates Generator, 3D Position Encoder, transformer Decoder 1.1 Images Backbone 采用resnet 或者 vovNet,下面的x表示concatenate 1.2 3D Coordinates Generator 坐标生成跟lss类似&#xff0c;假…

如何对比github中不同commits的区别

有时候想要对比跨度几十个commits之前的代码区别&#xff0c;想直接使用github的用户界面。可以直接在官网操作。 示例 首先要创建一个旧commit的branch。进入该旧的commit&#xff0c;然后输入branch名字即可。 然后在项目网址后面加上compare即可对比旧的branch和新的bran…

【轻松玩转MacOS】故障排除篇

引言 在使用 MacOS 时&#xff0c;遇到故障是在所难免的。不要担心&#xff0c;这篇文章将为您提供一些常见的故障排除步骤&#xff0c;并介绍如何联系苹果的支持团队寻求帮助。让我们一起来看看吧&#xff01; 一、常见的故障排除步骤 1.1 网络连接问题 如果你发现你的Mac…

初识华为云数据库GaussDB for openGauss

01 前言 GaussDB是华为自主创新研发的分布式关系型数据库。该产品具备企业级复杂事务混合负载能力&#xff0c;同时支持分布式事务&#xff0c;同城跨AZ部署&#xff0c;数据0丢失&#xff0c;支持1000的扩展能力&#xff0c;PB级海量存储。同时拥有云上高可用&#xff0c;高可…

【postgresql】ERROR: integer out of range

查询文章都类似&#xff0c;只是类型没有对应上&#xff0c;根据实际情况处理。 前情 使用postgresql数据库数据库ID类型int4实体类代码private Long id; 异常 ### Cause: org.postgresql.util.PSQLException: ERROR: integer out of range ; ERROR: integer out of range;…

Go 语言切片扩容规则是扩容2倍?1.25倍?到底几倍

本次主要来聊聊关于切片的扩容是如何扩的&#xff0c;还请大佬们不吝赐教 切片&#xff0c;相信大家用了 Go 语言那么久这这种数据类型并不陌生&#xff0c;但是平日里聊到关于切片是如何扩容的&#xff0c;很多人可能会张口就来&#xff0c;切片扩容的时候&#xff0c;如果老…

unity中绑定动画的行为系统

主要代码逻辑是创建一个action队列,当动画播放结束时就移除队头,执行后面的事件 public class Enemy : MonoBehaviour {public event Action E_AnimatorFin;//当动画播放完毕时public Action DefaultAction;//默认事件public Dictionary<Action, string> EventAnimator n…

基于 chinese-roberta-wwm-ext 微调训练中文命名实体识别任务

一、模型和数据集介绍 1.1 预训练模型 chinese-roberta-wwm-ext 是基于 RoBERTa 架构下开发&#xff0c;其中 wwm 代表 Whole Word Masking&#xff0c;即对整个词进行掩码处理&#xff0c;通过这种方式&#xff0c;模型能够更好地理解上下文和语义关联&#xff0c;提高中文文…

振弦传感器和振弦采集仪应用隧道安全监测的解决方案

振弦传感器和振弦采集仪应用隧道安全监测的解决方案 现代隧道越来越复杂&#xff0c;对于隧道安全的监测也变得越来越重要。振弦传感器和振弦采集仪已经成为了一种广泛应用的技术&#xff0c;用于隧道结构的监测和评估。它们可以提供更精确的测量结果&#xff0c;并且可以在实…