apk签名验证实现
程序员文章站
2022-04-11 15:47:38
...
前言
在学习的路途中,留下一点痕迹。签名保护的方法很多,我简单实现一下。
使用全局Context
private static Context context;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
context=getApplicationContext();
boolean isOwnApp = TestDemo.isOwnApp();
Log.d("fuck","isownapp:"+isOwnApp);
if(!isOwnApp){
Log.d("fuck","is not own app ... exit app");
android.os.Process.killProcess(android.os.Process.myPid());
}
}
public static Context getContext(){
return context;
}
新建签名验证类TestDemo
private static final String APP_SIGN="aaa"; //假定的正确的签名
public static String getSignature(){
Context context = main.getContext();
try{
//通过包管理器获得指定报名包含的包信息
PackageInfo packageInfo = context.getPackageManager().getPackageInfo(context.getPackageName(), PackageManager.GET_SIGNATURES);
//通过返回的包信息获得签名数组
Signature [] signatures = packageInfo.signatures;
StringBuilder builder = new StringBuilder();
//遍历签名数组,进行拼接
for(Signature signature : signatures){
builder.append(signature.toCharsString());
}
return builder.toString();
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
}
return "";
}
public static boolean isOwnApp(){
String signstr = getSignature();
Log.d("fuck","this is"+signstr);
return APP_SIGN.equals(signstr);
}
运行程序,通过日志来观察。
adb logcat -s fuck
02-27 15:57:32.237 8496 8496 D fuck : this is308201dd30820146020101300d06092a864886f70d010105050030373116301406035504030c0d416e64726f69642044656275673110300e060355040a0c07416e64726f6964310b3009060355040613025553301e170d3137313030353035343032315a170d3437303932383035343032315a30373116301406035504030c0d416e64726f69642044656275673110300e060355040a0c07416e64726f6964310b300906035504061302555330819f300d06092a864886f70d010101050003818d0030818902818100e57dfaf64295833c4de7be5f7d8b6439f20b16436feb3d3c5dcc4483b12d8c06651a2ce19190391b6f76634e64d0e363b036b41bf3408e598debd688a1cf98ea2024dda40e50a2ca11ac4c0c891e4c24df5754204c71b8c4af6fdd1493ee482e1d7561d2620d9226b6668000da72c3a9a7d1db8a9151b607477cd191a54f40090203010001300d06092a864886f70d010105050003818100823ef063627e06cef66669be8031f9bfaf86e05581a2d86fded7b20aa2a0fd443cec1b44ef3966ce5b2c24cabc3c2f7a32f69e534b32f050b5720e3556e182552a8b5767f6de1a87d4e3a13f364429d424877741d005120c0b07e216d9ae488be962e46e97a090dc20475eb162cf138c3f8a6b254814270f3398fdf2f9fdf886
02-27 15:57:32.237 8496 8496 D fuck : isownapp:false
02-27 15:57:32.237 8496 8496 D fuck : is not own app ... exit app
可以看到保护已经生效了。
native层进行签名验证
对于一些**者来说,仅仅从java层进行签名验证是远远不够的,因此我们需要更深一步,在native层进行签名验证。当然签名验证的方法毕竟是通过代码来实现的,那么就可以通过代码进行**。只不过是加大**的难度而已。
要想绕过签名验证,关键在于最后的判断,因此我们需要将判断的函数放在native层中。
为了方便我新建了一个Util
类,并实现了获取应用的签名。
public static String getSignature(){
Context context = MainActivity.getContext();
try {
PackageInfo packageInfo = context.getPackageManager().getPackageInfo(context.getPackageName(),
PackageManager.GET_SIGNATURES);
Signature [] signatures = packageInfo.signatures;
StringBuilder builder = new StringBuilder();
for (Signature signature : signatures){
builder.append(signature.toCharsString());
}
return builder.toString();
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
}
return "";
}
接着声明public native int isEqual();
编写c代码,有关NDK开发,可以参考我的这篇文章
JNIEXPORT int JNICALL Java_com_code_jsk_handlenative_MainActivity_isEqual
(JNIEnv * env,jobject obj){
const char *app_signature = "308201dd30820146020101300d06092a864886f70d010105050030373116301406035504030c0d416e64726f69642044656275673110300e060355040a0c07416e64726f6964310b3009060355040613025553301e170d3137313030353035343032315a170d3437303932383035343032315a30373116301406035504030c0d416e64726f69642044656275673110300e060355040a0c07416e64726f6964310b300906035504061302555330819f300d06092a864886f70d010101050003818d0030818902818100e57dfaf64295833c4de7be5f7d8b6439f20b16436feb3d3c5dcc4483b12d8c06651a2ce19190391b6f76634e64d0e363b036b41bf3408e598debd688a1cf98ea2024dda40e50a2ca11ac4c0c891e4c24df5754204c71b8c4af6fdd1493ee482e1d7561d2620d9226b6668000da72c3a9a7d1db8a9151b607477cd191a54f40090203010001300d06092a864886f70d010105050003818100823ef063627e06cef66669be8031f9bfaf86e05581a2d86fded7b20aa2a0fd443cec1b44ef3966ce5b2c24cabc3c2f7a32f69e534b32f050b5720e3556e182552a8b5767f6de1a87d4e3a13f364429d424877741d005120c0b07e216d9ae488be962e46e97a090dc20475eb162cf138c3f8a6b254814270f3398fdf2f9fdf886";
char* className = "com/code/jsk/handlenative/Util";
jclass clazz = env->FindClass(className); //找到相应的class
if(clazz==NULL){
LOGD("not find class '%s'",className);
return 1;
}
LOGD("class name:%p",clazz);
jmethodID jmethodID1 = env->GetStaticMethodID(clazz,"getSignature","()Ljava/lang/String;");//找到class中的方法
if(jmethodID1==NULL){
LOGD("not find method '%s'",jmethodID1);
return 1;
}
LOGD("method name:%p",jmethodID1);
jstring jstring1 = (jstring)env->CallStaticObjectMethod(clazz,jmethodID1);//执行方法
if(jstring1==NULL){
LOGD("method invoke error ...");
return 1;
}
const char* strAry=env->GetStringUTFChars(jstring1,0);
LOGD("strAry is : %s",strAry);
int cmpval = strcmp(strAry,app_signature);//在native层中做比较
LOGD("cmpval is : %d",cmpval);
if(cmpval!=0){ //如果签名不匹配则退出
return 1;
}
env->ReleaseStringUTFChars(jstring1,strAry);//清除相应的内存空间
return 0;//如果匹配正确则返回0
}
这里为了在native层中也能打印日志。可以参考这篇文章
#include <android/log.h>
#define TAG "fuck"
// 定义info信息
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO,TAG,__VA_ARGS__)
// 定义debug信息
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, TAG, __VA_ARGS__)
// 定义error信息
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,TAG,__VA_ARGS__)
之后最好在写一个签名处理函数,对于正确与否进行响应的处理。
public native void checkSign();
#include <stdlib.h>
JNIEXPORT void JNICALL Java_com_code_jsk_handlenative_MainActivity_checkSign
(JNIEnv *env,jobject obj){
int check_sign = Java_com_code_jsk_handlenative_MainActivity_isEqual(env,obj);
LOGD("check_sign is %d",check_sign);
if(check_sign!=0){
LOGD("exit...");
exit(0);
}
}
做完这几步,就可以编译运行了,查看一下日志
在这里,我提前获取了签名。因此正确通过了验证。
总结
其实真正的签名验证函数,往往会比较复杂,我这里做了简化,将签名明文传输。有的也可以通过访问指定服务器进行签名验证。