NDK应用
NDK应用
卸载反馈 linux 层 -feedback
- fork 一个进程
- 监听自身应用的/data/data/package/目录是否存在
- 通过 linux 中的文件监听,inotify 监听 linux 中的文件状态
- 注意覆盖升级的问题
- 调用 am 命令跳转到网页
Java 层的 FileObserver 中的就是 inotify 机制
分析 AM.java
静默安装
SO 签名校验防被逆向盗用
- valid.cpp
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
#include <stdio.h>
#include <stdlib.h>
#include <jni.h>
#include <android/log.h>
#define TAG "qsbk.jni"
#define LOGD(...) ((void)__android_log_print(ANDROID_LOG_DEBUG, TAG, __VA_ARGS__))
#define LOGI(...) ((void)__android_log_print(ANDROID_LOG_INFO, TAG, __VA_ARGS__))
#define LOGE(...) ((void)__android_log_print(ANDROID_LOG_ERROR, TAG, __VA_ARGS__))
// 内置自己APP的签名信息
const char *app_sha1 = "6DAA48C22BEA90A5928CCA3096D0E21967E74CB6";
const char hexcode[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
// 反射获取应用的签名
char *getSha1FromSo(JNIEnv *env, jobject context_object) {
//上下文对象
jclass context_class = env->GetObjectClass(context_object);
//反射获取PackageManager
jmethodID methodId = env->GetMethodID(context_class, "getPackageManager", "()Landroid/content/pm/PackageManager;");
jobject package_manager = env->CallObjectMethod(context_object, methodId);
if (package_manager == NULL) {
LOGD("[getSha1FromSo] package_manager is NULL!!!");
return NULL;
}
//反射获取包名
methodId = env->GetMethodID(context_class, "getPackageName", "()Ljava/lang/String;");
jstring package_name = (jstring) env->CallObjectMethod(context_object, methodId);
if (package_name == NULL) {
LOGD("[getSha1FromSo] package_name is NULL!!!");
return NULL;
}
env->DeleteLocalRef(context_class);
//获取PackageInfo对象
jclass pack_manager_class = env->GetObjectClass(package_manager);
methodId = env->GetMethodID(pack_manager_class, "getPackageInfo",
"(Ljava/lang/String;I)Landroid/content/pm/PackageInfo;");
env->DeleteLocalRef(pack_manager_class);
jobject package_info = env->CallObjectMethod(package_manager, methodId, package_name, 0x40);
if (package_info == NULL) {
LOGD("[getSha1FromSo] getPackageInfo() is NULL!!!");
return NULL;
}
env->DeleteLocalRef(package_manager);
//获取签名信息
jclass package_info_class = env->GetObjectClass(package_info);
jfieldID fieldId = env->GetFieldID(package_info_class, "signatures", "[Landroid/content/pm/Signature;");
env->DeleteLocalRef(package_info_class);
jobjectArray signature_object_array = (jobjectArray) env->GetObjectField(package_info, fieldId);
if (signature_object_array == NULL) {
LOGD("[getSha1FromSo] signature is NULL!!!");
return NULL;
}
jobject signature_object = env->GetObjectArrayElement(signature_object_array, 0);
env->DeleteLocalRef(package_info);
//签名信息转换成sha1值
jclass signature_class = env->GetObjectClass(signature_object);
methodId = env->GetMethodID(signature_class, "toByteArray", "()[B");
env->DeleteLocalRef(signature_class);
jbyteArray signature_byte = (jbyteArray) env->CallObjectMethod(signature_object, methodId);
jclass byte_array_input_class = env->FindClass("java/io/ByteArrayInputStream");
methodId = env->GetMethodID(byte_array_input_class, "<init>", "([B)V");
jobject byte_array_input = env->NewObject(byte_array_input_class, methodId, signature_byte);
jclass certificate_factory_class = env->FindClass("java/security/cert/CertificateFactory");
methodId = env->GetStaticMethodID(certificate_factory_class, "getInstance",
"(Ljava/lang/String;)Ljava/security/cert/CertificateFactory;");
jstring x_509_jstring = env->NewStringUTF("X.509");
jobject cert_factory = env->CallStaticObjectMethod(certificate_factory_class, methodId, x_509_jstring);
methodId = env->GetMethodID(certificate_factory_class, "generateCertificate",
("(Ljava/io/InputStream;)Ljava/security/cert/Certificate;"));
jobject x509_cert = env->CallObjectMethod(cert_factory, methodId, byte_array_input);
env->DeleteLocalRef(certificate_factory_class);
jclass x509_cert_class = env->GetObjectClass(x509_cert);
methodId = env->GetMethodID(x509_cert_class, "getEncoded", "()[B");
jbyteArray cert_byte = (jbyteArray) env->CallObjectMethod(x509_cert, methodId);
env->DeleteLocalRef(x509_cert_class);
jclass message_digest_class = env->FindClass("java/security/MessageDigest");
methodId = env->GetStaticMethodID(message_digest_class, "getInstance",
"(Ljava/lang/String;)Ljava/security/MessageDigest;");
jstring sha1_jstring = env->NewStringUTF("SHA1");
jobject sha1_digest = env->CallStaticObjectMethod(message_digest_class, methodId, sha1_jstring);
methodId = env->GetMethodID(message_digest_class, "digest", "([B)[B");
jbyteArray sha1_byte = (jbyteArray) env->CallObjectMethod(sha1_digest, methodId, cert_byte);
env->DeleteLocalRef(message_digest_class);
//转换成char
jsize array_size = env->GetArrayLength(sha1_byte);
jbyte *sha1 = env->GetByteArrayElements(sha1_byte, NULL);
char *hex_sha = new char[array_size * 2 + 1];
for (int i = 0; i < array_size; ++i) {
hex_sha[2 * i] = hexcode[((unsigned char) sha1[i]) / 16];
hex_sha[2 * i + 1] = hexcode[((unsigned char) sha1[i]) % 16];
}
hex_sha[array_size * 2] = '\0';
LOGD("[getSha1FromSo]获取当前APP的签名SHA1:%s ", hex_sha);
return hex_sha;
}
// 比较签名是否一致
jint checkAppSignatureValidity(JNIEnv *env, char *sha1) {
//比较签名
if (strcmp(sha1, app_sha1) == JNI_OK) {
LOGD("[checkAppSignatureValidity]SO签名验证成功");
return JNI_OK;
}
LOGE("[checkAppSignatureValidity]SO签名验证失败");
return JNI_ERR;
}
// 反射获取Application
static jobject getApplication(JNIEnv *env) {
jobject application = NULL;
jclass activity_thread_clz = env->FindClass("android/app/ActivityThread");
if (activity_thread_clz != NULL) {
jmethodID currentApplication = env->GetStaticMethodID(
activity_thread_clz, "currentApplication", "()Landroid/app/Application;");
if (currentApplication != NULL) {
application = env->CallStaticObjectMethod(activity_thread_clz, currentApplication);
} else {
LOGE("Cannot find method: currentApplication() in ActivityThread.");
}
env->DeleteLocalRef(activity_thread_clz);
} else {
LOGE("Cannot find class: android.app.ActivityThread");
}
return application;
}
// 在so加载时,进行签名的校验
jint JNI_OnLoad(JavaVM *vm, void *reserved) {
JNIEnv *env = NULL;
if (vm->GetEnv((void **) &env, JNI_VERSION_1_4) != JNI_OK) {
LOGE("[JNI_OnLoad] JNI_VERSION_1_4 Not OK!!!");
return JNI_ERR;
}
if (checkAppSignatureValidity(env, getSha1FromSo(env, getApplication(env))) == JNI_OK) {
LOGI("[JNI_OnLoad] JNI SO签名校验成功");
return JNI_VERSION_1_4;
}
LOGE("[JNI_OnLoad] 签名不一致 JNI_ERR!");
return JNI_ERR;
}
- qbvoicechatsecurity.cpp
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
#include <jni.h>
#include <string>
#include "valid.cpp"
extern "C"
JNIEXPORT jstring JNICALL
Java_qsbk_app_voice_common_net_SecurityUtils_getSignaturesSha1(
JNIEnv *env,
jclass type/*,
jobject contextObject*/) {
char *signature = getSha1FromSo(env, getApplication(env));
return env->NewStringUTF(signature);
}
extern "C"
JNIEXPORT jboolean JNICALL
Java_qsbk_app_voice_common_net_SecurityUtils_checkSha1(
JNIEnv *env,
jclass type
/*,jobject contextObject*/) {
char *sha1 = getSha1FromSo(env, getApplication(env));
jboolean result = checkAppSignatureValidity(env, sha1);
return result;
}
- Java 代码
1
2
3
4
5
6
7
8
public final class SecurityUtils {
/**
* A native method that is implemented by the 'security' native library,
* which is packaged with this application.
*/
public static native String getSignaturesSha1();
public static native boolean checkSha1();
}
- CMakeLists.txt
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
# For more information about using CMake with Android Studio, read the
# documentation: https://d.android.com/studio/projects/add-native-code.html
# Sets the minimum version of CMake required to build the native library.
cmake_minimum_required(VERSION 3.4.1)
# Creates and names a library, sets it as either STATIC
# or SHARED, and provides the relative paths to its source code.
# You can define multiple libraries, and CMake builds them for you.
# Gradle automatically packages shared libraries with your APK.
add_library( # Sets the name of the library.
qbvoicechatsecurity-lib
# Sets the library as a shared library.
SHARED
# Provides a relative path to your source file(s).
src/main/cpp/qbvoicechatsecurity.cpp)
# Searches for a specified prebuilt library and stores the path as a
# variable. Because CMake includes system libraries in the search path by
# default, you only need to specify the name of the public NDK library
# you want to add. CMake verifies that the library exists before
# completing its build.
find_library( # Sets the name of the path variable.
log-lib
# Specifies the name of the NDK library that
# you want CMake to locate.
log)
# Specifies libraries CMake should link to your target library. You
# can link multiple libraries, such as libraries you define in this
# build script, prebuilt third-party libraries, or system libraries.
target_link_libraries( # Specifies the target library.
qbvoicechatsecurity-lib
# Links the target library to the log library
# included in the NDK.
${log-lib})
set_target_properties(qbvoicechatsecurity-lib PROPERTIES OUTPUT_NAME "qbsec")
- Android 使用 jni 校验应用签名 sha1 值,防止 so 文件逆向盗用
https://blog.csdn.net/liyi0930/article/details/77413525
https://github.com/aizuzi/SignatureVerificationDemo
密钥保存
CMake simple highlighter
插件
Gradle 配置 cmake
1
2
3
4
5
6
7
8
android {
// ...
externalNativeBuild {
cmake {
path "CMakeLists.txt"
}
}
}
Java 层定义 native 方法
1
2
// me.hacket.assistant.samples.apps.inews.utils.SecurityUtils
public static native String getSecretKey();
编写 cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// src/main/cpp/qbvoicechatsecurity.cpp
#include <string>
#include <jni.h>
using namespace std;
#define LOGINFO(...) ((void)__android_log_print(ANDROID_LOG_INFO, "security", __VA_ARGS__))
#define LOGERROR(...) ((void)__android_log_print(ANDROID_LOG_ERROR, "security", __VA_ARGS__))
#ifdef __cplusplus
extern "C" // C++中以C的方式编译
{
JNIEXPORT jstring
JNICALL Java_me_hacket_assistant_samples_apps_inews_utils_SecurityUtils_getSecretKey
(JNIEnv *env,
jclass type) {
std::string hello = "1234567890";
return env->NewStringUTF(hello.c_str());
}
}
#endif
编写 CMakeLists.txt
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
# For more information about using CMake with Android Studio, read the
# documentation: https://d.android.com/studio/projects/add-native-code.html
# Sets the minimum version of CMake required to build the native library.
cmake_minimum_required(VERSION 3.4.1)
# Creates and names a library, sets it as either STATIC
# or SHARED, and provides the relative paths to its source code.
# You can define multiple libraries, and CMake builds them for you.
# Gradle automatically packages shared libraries with your APK.
add_library( # Sets the name of the library.
qbvoicechatsecurity
# Sets the library as a shared library.
SHARED
# Provides a relative path to your source file(s).
src/main/cpp/qbvoicechatsecurity.cpp)
# Searches for a specified prebuilt library and stores the path as a
# variable. Because CMake includes system libraries in the search path by
# default, you only need to specify the name of the public NDK library
# you want to add. CMake verifies that the library exists before
# completing its build.
find_library( # Sets the name of the path variable.
log-lib
# Specifies the name of the NDK library that
# you want CMake to locate.
log)
# Specifies libraries CMake should link to your target library. You
# can link multiple libraries, such as libraries you define in this
# build script, prebuilt third-party libraries, or system libraries.
target_link_libraries( # Specifies the target library.
qbvoicechatsecurity
# Links the target library to the log library
# included in the NDK.
${log-lib})
Java 层加载
1
2
3
4
5
6
7
public final class SecurityUtils {
// ...
static {
System.loadLibrary("qbvoicechatsecurity");
}
// ...
}
增量更新
增量更新解决方案及其 Kotlin 实现
增量更新和普通更新
- 普通更新:有新版本发布,将完整的包放到服务器里面,供客户端下载完整下载
- 增量更新
服务器在新版本 apk,在服务器和老版本生成一个差分包(apk.patch),供客户端下载;客户端下载后,在客户端本地和旧版本合成新版本的 apk,然后安装
多了个服务器一个差分,客户端合并操作
增量更新 apk 分析
- 手机淘宝
libBSPatch.so 57K - QQ 空间
- 爱奇艺
libbspatch.so 157K
BsPatch/BsDiff
http://www.daemonology.net/bsdiff/
bsdiff:
bspatch:
bzip2:/usr/bin 一般有这个工具
客户端合并过程
- 写一个带有 native 的类,并生产 c 头文件
- 引入 bspatch 的 c 文件
- 配置 CmakeLists.txt
- 在 bspatch.c 文件中实现合成操作
bspatch.c 的 main() 函数参数,第一个固定为 4,第二个参数为长度为 4 的数组(第 1 个为 log 输出,第 2 个为 oldpath,第 3 个为 newPath,第 4 个为 patchPath) - 解决多个 main 函数问题
技术点
- bspatch、bsdiff
- apk 安装到手机的一个过程
- 差分包一般小于 10M?
- 哈夫曼算法
- CmakeLists.txt 文件
编译器用的 llvm,Android.mk 用的是 gcc
tencent 插件化:tws framework
- HFS 简易网络服务器
- 用了混淆加密有啥影响
没影响,文件级的文件;算法基于文件的,差分合并后和你要升级的新 apk 的 md5 值一样
windows 生成文件的 md5 值
certutil -hashfile file MD5
增量更新解决方案
https://github.com/cundong/SmartAppUpdates
bsdiff bspatch
https://github.com/ha-excited/BigNews
Android 增量更新框架差分包升级
差分
BSDiff
http://www.daemonology.net/bsdiff/
DexDiff
dexDiff 是微信结合 Dex 文件格式设计的一个专门针对 Dex 的差分算法。根据 Dex 的文件格式,对两个 Dex 中每一项
数据进行差分记录。
本文由作者按照 CC BY 4.0 进行授权