最近在研究Android应用中的安全问题,貌似只有将核心代码写到JNI底层才是最安全的。通过底层来判断签名是否正确,如果正确则继续执行核心代码,否则退出程序,这样就可以防止别人恶意反编译,并进行二次打包。所以这里的关键就是如何在JNI中获得签名。
我上网查了好多资料,都没有现成的答案,但是我慢慢的找到了一些思路,于是潜心研究,终于有了结果。不敢独享,所以过来分享给大家。
大家都知道,在android中的Java代码里获得签名的哈希值,很简单,过程如下(此代码与下面下载地址的代码有所不同,已经修改了,多谢五楼 shysnower 提出的修改意见):
[java]
view plain
copy
try { PackageInfo info = getPackageManager().getPackageInfo(getPackageName(), 64); Signature sign = info.signatures[0]; Log.i("test", "hashCode : " + sign.hashCode()); } catch (NameNotFoundException e) { e.printStackTrace(); }
在JNI中提供了许多方法,可以反向调用java中的方法,比如下面一句代码:
PackageInfo packageInfo = getPackageManager().getPackageInfo("com.klxx.as", PackageManager.GET_SIGNATURES);
我们可以用JNI写成这样(此代码与下面下载地址的代码有所不同,已经修改了,多谢五楼 shysnower 提出的修改意见):
[cpp]
view plain
copy
jclass native_clazz = (*env)->GetObjectClass(env, context); jmethodID methodID_func = (*env)->GetMethodID(env, native_clazz, "getPackageManager", "()Landroid/content/pm/PackageManager;"); jobject package_manager = (*env)->CallObjectMethod(env, thiz, methodID_func); jclass pm_clazz = (*env)->GetObjectClass(env, package_manager); jmethodID methodID_pm = (*env)->GetMethodID(env, pm_clazz, "getPackageInfo", "(Ljava/lang/String;I)Landroid/content/pm/PackageInfo;"); jmethodID methodID_pack = (*env)->GetMethodID(env, native_clazz, "getPackageName", "()Ljava/lang/String;"); jstring application_package = (*env)->CallObjectMethod(env, context, methodID_pack); jobject package_info = (*env)->CallObjectMethod(env, package_manager, methodID_pm, application_package, 64);
这种方法在java中叫做反射,更多的JNI反射方法可以参考博客《android开发之绝对安全(三) JNI方法集合》。
通过类似于这种反射机制,我进行一步一步调试和解析,终于获得了应用的签名信息,并从签名信息中获得了签名的哈希值。
我将这段代码传到了CSDN上,欢迎大家下载,如果有什么漏洞,也欢迎大家指点一下。
下载地址:http://download.csdn.net/detail/iloveyoueveryday/6909583 。
源码使用注意事项:一定要传过来正确的context参数;项目中的包名("com.example.hellojni")别忘了修改;JNI的代码使用不是很容易,请耐心修改调试,我也是调了好久才测试通过的。
转载:http://blog.csdn.net/i5suoi/article/details/19036975
1. [代码][Java]代码
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
int
checkAPP(Context context) {
try
{
PackageInfo packageInfo = context.getPackageManager()
.getPackageInfo(context.getPackageName(),
PackageManager.GET_SIGNATURES);
Signature[] signs = packageInfo.signatures;
Signature sign = signs[
0
];
int
hashcode = sign.hashCode();
Log.i(
"test"
,
"hashCode : "
+ hashcode);
return
hashcode == -
82892576
?
1
:
0
;
}
catch
(Exception e) {
e.printStackTrace();
}
return
-
1
;
}
2. [代码][C/C++]代码
?
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
206
207
208
209
#include <string.h>
#include <stdio.h>
#include <jni.h>
#include<ALog.h>
jvalue JNU_CallMethodByName(JNIEnv *env, jboolean *hasException, jobject obj,
const
char
*name,
const
char
*descriptor, ...) {
va_list
args;
jclass clazz;
jmethodID mid;
jvalue result;
if
((*env)->EnsureLocalCapacity(env, 2) == JNI_OK) {
clazz = (*env)->GetObjectClass(env, obj);
mid = (*env)->GetMethodID(env, clazz, name, descriptor);
if
(mid) {
const
char
*p = descriptor;
/* skip over argument types to find out the
return type */
while
(*p !=
')'
)
p++;
/* skip ')' */
p++;
va_start
(args, descriptor);
switch
(*p) {
case
'V'
:
(*env)->CallVoidMethodV(env, obj, mid, args);
break
;
case
'['
:
case
'L'
:
result.l = (*env)->CallObjectMethodV(env, obj, mid, args);
break
;
case
'Z'
:
result.z = (*env)->CallBooleanMethodV(env, obj, mid, args);
break
;
case
'B'
:
result.b = (*env)->CallByteMethodV(env, obj, mid, args);
break
;
case
'C'
:
result.c = (*env)->CallCharMethodV(env, obj, mid, args);
break
;
case
'S'
:
result.s = (*env)->CallShortMethodV(env, obj, mid, args);
break
;
case
'I'
:
result.i = (*env)->CallIntMethodV(env, obj, mid, args);
break
;
case
'J'
:
result.j = (*env)->CallLongMethodV(env, obj, mid, args);
break
;
case
'F'
:
result.f = (*env)->CallFloatMethodV(env, obj, mid, args);
break
;
case
'D'
:
result.d = (*env)->CallDoubleMethodV(env, obj, mid, args);
break
;
default
:
(*env)->FatalError(env,
"illegaldescriptor"
);
}
va_end
(args);
}
(*env)->DeleteLocalRef(env, clazz);
}
if
(hasException) {
*hasException = (*env)->ExceptionCheck(env);
}
return
result;
}
//合法的APP包名
const
char
*global_app_packageName =
"com.example.abc"
;
//合法的hashcode
const
int
global_app_signature_hash_code = -82892576;
//合法标记
int
legitimate = 0;
jint Java_com_example_abc_MainActivity_jniCheckAPP(JNIEnv* env, jobject context,
jobject thiz) {
LOGI(
"start jniCheckAPP"
);
// 获得 Context 类
jboolean hasException;
//获取包名
jstring jstr_packageName = (jstring) JNU_CallMethodByName(env,
&hasException, thiz,
"getPackageName"
,
"()Ljava/lang/String;"
).l;
if
((*env)->ExceptionCheck(env) || jstr_packageName == NULL) {
LOGI(
"can't get jstr of getPackageName"
);
return
-1;
}
//获取包名的字符串
const
char
* loc_str_app_packageName = (*env)->GetStringUTFChars(env,
jstr_packageName, NULL);
if
(loc_str_app_packageName == NULL) {
LOGI(
"can't get packagename from jstring"
);
return
-2;
}
//当前应用包名与合法包名对比
if
(
strcmp
(loc_str_app_packageName, global_app_packageName) != 0) {
LOGI(
"this app is illegal"
);
return
-3;
}
//释放loc_str_app_packageName
(*env)->ReleaseStringUTFChars(env, jstr_packageName,
loc_str_app_packageName);
// 获得应用包的管理器
jobject package_manager = JNU_CallMethodByName(env, &hasException, thiz,
"getPackageManager"
,
"()Landroid/content/pm/PackageManager;"
).l;
if
((*env)->ExceptionCheck(env) || package_manager == NULL) {
LOGI(
"can't get obj of getPackageManager"
);
return
-4;
}
// 获得应用包的信息
jobject package_info = JNU_CallMethodByName(env, &hasException,
package_manager,
"getPackageInfo"
,
"(Ljava/lang/String;I)Landroid/content/pm/PackageInfo;"
,
(*env)->NewStringUTF(env, global_app_packageName), 64).l;
if
((*env)->ExceptionCheck(env) || package_info == NULL) {
(*env)->ExceptionClear(env);
LOGI(
"can't get obj of package_info"
);
return
-5;
}
// 获得 PackageInfo 类
jclass pi_clazz = (*env)->GetObjectClass(env, package_info);
// 获得签名数组属性的 ID
jfieldID fieldID_signatures = (*env)->GetFieldID(env, pi_clazz,
"signatures"
,
"[Landroid/content/pm/Signature;"
);
(*env)->DeleteLocalRef(env, pi_clazz);
// 得到签名数组,待修改
jobjectArray signatures = (*env)->GetObjectField(env, package_info,
fieldID_signatures);
if
((*env)->ExceptionCheck(env) || signatures == NULL) {
LOGI(
"can't get jobjectArray of signatures"
);
return
-6;
}
// 得到签名
jobject signature = (*env)->GetObjectArrayElement(env, signatures, 0);
if
((*env)->ExceptionCheck(env) || signature == NULL) {
LOGI(
"can't get obj of signature"
);
return
-7;
}
//获取当前应用hashcode
int
hash_code = JNU_CallMethodByName(env, &hasException, signature,
"hashCode"
,
"()I"
).i;
if
((*env)->ExceptionCheck(env) || package_manager == NULL) {
LOGI(
"can't get hash_code of signature"
);
return
-8;
}
LOGI(
"this app hash_code of signature is %d"
, hash_code);
//合法返回1,否则返回0,并改变legitimate的值
return
legitimate = (hash_code == global_app_signature_hash_code);
}
//=====================================================================
typedef
union
{
JNIEnv* env;
void
* venv;
} UnionJNIEnvToVoid;
static
JNINativeMethod methods[] = { {
"jniCheckAPP"
,
"(Landroid/content/Context;)I"
,
(
void
*) Java_com_example_abc_MainActivity_jniCheckAPP } };
static
const
char
*classPathName =
"com/example/abc/MainActivity"
;
static
int
registerNativeMethods(JNIEnv* env,
const
char
* className,
JNINativeMethod* gMethods,
int
numMethods) {
jclass clazz;
clazz = (*env)->FindClass(env, className);
if
(clazz == NULL) {
return
JNI_FALSE;
}
if
((*env)->RegisterNatives(env, clazz, gMethods, numMethods) < 0) {
LOGE(
"RegisterNatives failed"
);
return
JNI_FALSE;
}
return
JNI_TRUE;
}
static
int
registerNatives(JNIEnv* env) {
if
(!registerNativeMethods(env, classPathName, methods,
sizeof
(methods) /
sizeof
(methods[0]))) {
return
JNI_FALSE;
}
return
JNI_TRUE;
}
jint JNI_OnLoad(JavaVM* vm,
void
* reserved) {
UnionJNIEnvToVoid uenv;
uenv.venv = NULL;
jint result = -1;
JNIEnv* env = NULL;
if
((*vm)->GetEnv(vm, &uenv.venv, JNI_VERSION_1_4) != JNI_OK) {
goto
bail;
}
env = uenv.env;
if
(registerNatives(env) != JNI_TRUE) {
goto
bail;
}
result = JNI_VERSION_1_4;
bail: LOGI(
"JNI_ONload result '%d' "
, result);
return
result;
}
3. [代码][C/C++]代码
?
1
2
3
4
5
6
7
8
#pragma once
#include<android/log.h>
#define LOG_TAG "debug log"
#define LOGI(fmt, args...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, fmt, ##args)
#define LOGD(fmt, args...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, fmt, ##args)
#define LOGE(fmt, args...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, fmt, ##args)
4. [代码]Android.mk
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# Copyright (C) 2009 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_LDLIBS += -L$(SYSROOT)/usr/lib -llog
LOCAL_MODULE := checkapp-jni
LOCAL_SRC_FILES := jni.c
include $(BUILD_SHARED_LIBRARY)
5. [代码]Application.mk
?
1
APP_ABI := armeabi armeabi-v7a x86
转载:https://www.oschina.net/code/snippet_196085_37562