更新代码

This commit is contained in:
mahongyin
2021-03-03 18:02:36 +08:00
parent a8c51116b8
commit d0c4367180
22 changed files with 1040 additions and 268 deletions

479
README.md
View File

@@ -63,3 +63,482 @@ Android API Security(.so)安卓APP/API安全加密so库防二次打包
1.验证签名是否符合自己预设
2.验证包名
3验证apk源文件签名信息
![jni属性签名](jni属性签名.jpg)
过以下命令就可以拿到指定类的所有属性、方法的签名了,很方便有木有?!
javap -s -p 完整类名
descriptor就是我们需要的签名了注意签名中末尾的分号不能省略
方法签名的规律就是,括号不可以省略:
(参数类型签名)返回值类型签名
C/C++访问Java的属性、方法
有以下几种情况:
访问Java类的非静态属性。
访问Java类的静态属性。
访问Java类的非静态方法。
访问Java类的静态方法。
间接访问Java类的父类的方法。
访问Java类的构造方法。
一、访问Java的非静态属性
Java方法中通过调用accessField利用C修改静态属性
public String str = "Li lu";
//访问非静态属性str修改它的值
public native void accessField();
C代码如下头文件可以不写直接写实现
JNIEXPORT void JNICALL Java_com_test_JniTest_accessField
(JNIEnv * env, jobject jobj){
//通过对象拿到Class
jclass clz = (*env)->GetObjectClass(env, jobj);
//拿到对应属性的ID
jfieldID fid = (*env)->GetFieldID(env, clz, "str", "Ljava/lang/String;");
//通过属性ID拿到属性的值
jstring jstr = (*env)->GetObjectField(env, jobj, fid);
//通过Java字符串拿到C字符串第三个参数是一个出参用来告诉我们GetStringUTFChars内部是否复制了一份字符串
//如果没有复制那么出参为isCopy这时候就不能修改字符串的值了因为Java中常量池中的字符串是不允许修改的但是jstr可以指向另外一个字符串
char* cstr = (*env)->GetStringUTFChars(env, jstr, NULL);
//在C层修改这个属性的值
char res[20] = "I love you : ";
strcat(res, cstr);
//重新生成Java的字符串并且设置给对应的属性
jstring jstr_new = (*env)->NewStringUTF(env, res);
(*env)->SetObjectField(env, jobj, fid, jstr_new);
//最后释放资源,通知垃圾回收器来回收
//良好的习惯就是每次GetStringUTFChars结束的时候都有一个ReleaseStringUTFChars与之呼应
(*env)->ReleaseStringUTFChars(env, jstr, cstr);
}
最后在Java中测试
public static void main(String[] args) {
JniTest test = new JniTest();
System.out.println(test.str);
//修改非静态属性str
test.accessField();
System.out.println(test.str);
}
二、访问Java的静态属性
Java代码如下
//访问静态属性NUM修改它的值
public static int NUM = 1;
public native void accessStaticField();
C代码如下
JNIEXPORT void JNICALL Java_com_test_JniTest_accessStaticField
(JNIEnv * env, jobject jobj){
//与上面类似只不过是某些方法需要加上Static
jclass clz = (*env)->GetObjectClass(env, jobj);
jfieldID fid = (*env)->GetStaticFieldID(env, clz, "NUM", "I");
jint jInt = (*env)->GetStaticIntField(env, clz, fid);
jInt++;
(*env)->SetStaticIntField(env, clz, fid, jInt);
}
最后在Java中测试
public static void main(String[] args) {
JniTest test = new JniTest();
System.out.println(NUM);
test.accessStaticField();
System.out.println(NUM);
}
三、访问Java的非静态方法
Java代码如下通过调用accessMethod在底层用C语言调用genRandomInt方法
//产生指定范围的随机数
public int genRandomInt(int max){
System.out.println("genRandomInt 执行了...max = "+ max);
return new Random().nextInt(max);
}
public native void accessMethod();
C代码如下
JNIEXPORT void JNICALL Java_com_test_JniTest_accessMethod
(JNIEnv * env, jobject jobj){
jclass clz = (*env)->GetObjectClass(env, jobj);
//拿到方法的ID最后一个参数是方法的签名
jmethodID mid = (*env)->GetMethodID(env, clz, "genRandomInt", "(I)I");
//调用该方法,最后一个是可变参数,就是调用该方法所传入的参数
//套路是如果返回是Call返回类型Method
jint jInt = (*env)->CallIntMethod(env, jobj, mid, 100);
printf("output from C %d", jInt);
}
最后在Java中测试
public static void main(String[] args) {
JniTest test = new JniTest();
test.accessMethod();
}
四、访问Java的静态方法
Java代码如下通过调用accessStaticMethod在底层用C语言调用getUUID方法
public native void accessStaticMethod();
//产生UUID字符串
public static String getUUID(){
System.out.println("getUUID 执行了...");
return UUID.randomUUID().toString();
}
C代码如下
JNIEXPORT void JNICALL Java_com_test_JniTest_accessStaticMethod
(JNIEnv * env, jobject jobj){
jclass clz = (*env)->GetObjectClass(env, jobj);
jmethodID mid = (*env)->GetStaticMethodID(env, clz, "getUUID", "()Ljava/lang/String;");
//调用java的静态方法拿到返回值
jstring jstr = (*env)->CallStaticObjectMethod(env, clz, mid);
//把拿到的Java字符串转换为C的字符串
char* cstr= (*env)->GetStringUTFChars(env, jstr, NULL);
//后续操作产生以UUID为文件名的文件
char fielName[100];
sprintf(fielName, "D:\\%s.txt", cstr);
FILE* f = fopen(fielName, "w");
fputs(cstr, f);
fclose(f);
printf("output from C : File had saved", jstr);
}
最后在Java中测试
public static void main(String[] args) {
JniTest test = new JniTest();
test.accessStaticMethod();
}
五、间接访问Java类的父类的方法
Java代码如下
父类:
package com.test;
public class Human {
protected void speek() {
System.out.println("Human Speek");
}
}
子类:
package com.test;
public class Man extends Human {
@Override
protected void speek() {
// 可以通过super关键字来访问父类的方法
// super.speek();
System.out.println("Man Speek");
}
}
在TestJni类中有Human属性
//父类的引用指向子类的对象
Human man= new Man();
public native void accessNonvirtualMethod();
如果是直接使用man.speek()的话访问的是子类Man的方法
但是通过底层C的方式可以间接访问到父类Human的方法跳过子类的实现甚至你可以直接哪个父类如果父类有多个的话这是Java做不到的。
下面是C代码实现无非就是属性和方法的访问
JNIEXPORT void JNICALL Java_com_test_JniTest_accessNonvirtualMethod
(JNIEnv * env, jobject jobj){
//先拿到属性man
jclass clz=(*env)->GetObjectClass(env, jobj);
jfieldID fid = (*env)->GetFieldID(env, clz, "man", "Lcom/test/Human;");
jobject man = (*env)->GetObjectField(env, jobj, fid);
//拿到父类的类以及speek的方法id
jclass clz_human = (*env)->FindClass(env, "com/test/Human");
jmethodID mid = (*env)->GetMethodID(env, clz_human, "speek", "()V");
//调用自己的speek实现
(*env)->CallVoidMethod(env, man, mid);
//调用父类的speek实现
(*env)->CallNonvirtualVoidMethod(env, man, clz_human, mid);
}
当有这个类的对象的时候,使用(*env)->GetObjectClass()相当于Java中的test.getClass()
当有没有这个类的对象的时候,(*env)->FindClass()相当于Java中的Class.forName("com.test.TestJni")
这里直接使用CallVoidMethod虽然传进去的是父类的Method ID但是访问的让然是子类的实现。
最后通过CallNonvirtualVoidMethod访问不覆盖的父类方法C++使用virtual关键字来覆盖父类的实现当然你也可以指定哪个父类如果有多个父类的话
最后在Java中测试
public static void main(String[] args) {
JniTest test = new JniTest();
//这时候是调用子类Man的方法
test.man.speek();
//但是通过JNI的方式可以访问到父类的speek方法
test.accessNonvirtualMethod();
}
六、访问Java类的构造方法
Java代码如下通过调用accessConstructor在底层用C语言调用java.util.Date产生一个当前的时间戳并且返回。
//调用Date的构造函数
public native long accessConstructor();
C代码如下
JNIEXPORT jlong JNICALL Java_com_test_JniTest_accessConstructor
(JNIEnv * env, jobject jobj){
jclass clz_date = (*env)->FindClass(env, "java/util/Date");
//构造方法的函数名的格式是:<init>
//不能写类名,因为构造方法函数名都一样区分不了,只能通过参数列表(签名)区分
jmethodID mid_Date = (*env)->GetMethodID(env, clz_date, "<init>", "()V");;
//调用构造函数
jobject date = (*env)->NewObject(env, clz_date, mid_Date);
//注意签名返回值long的属性签名是J
jmethodID mid_getTime= (*env)->GetMethodID(env, clz_date, "getTime", "()J");
//调用getTime方法
jlong jtime = (*env)->CallLongMethod(env, date, mid_getTime);
return jtime;
}
最后在Java中测试
public static void main(String[] args) {
JniTest test = new JniTest();
//直接在Java中构造Date然后调用getTime
Date date = new Date();
System.out.println(date.getTime());
//通过C语音构造Date然后调用getTime
long time = test.accessConstructor();
System.out.println(time);
}
总结
属性、方法的访问的使用是和Java的反射API类似的。
综合进阶案例——JNI返回中文乱码问题
测试乱码问题:
public native void testChineseIn(String chinese);//传进去
public native String testChineseOut();//取出来会乱码
public static void main(String[] args) {
//传中文进去然后转为C字符串直接在C层输出是没有问题的
JniTest test = new JniTest();
test.testChineseIn("我爱你");
//C层将C字符串转换为JavaString然后输出就会乱码
System.out.println(test.testChineseOut());
}
C代码如下
JNIEXPORT void JNICALL Java_com_test_JniTest_testChineseIn
(JNIEnv * env, jobject jobj, jstring chinese){
char* c_chinese = (*env)->GetStringUTFChars(env, chinese, NULL);
printf("%s", c_chinese);
}
JNIEXPORT jstring JNICALL Java_com_test_JniTest_testChineseOut
(JNIEnv * env, jobject jobj){
char* c_str = "我爱你";
jstring j_str = (*env)->NewStringUTF(env, c_str);
return j_str;
}
结果输出其中第一条是C返回的乱码第二条是传进去在C层打印的结果
ÎҰ®Ä
我爱你
可以看到C执行的速度要比Java快。
原因分析调用NewStringUTF的时候产生的是UTF-16的字符串但是我们需要的时候UTF-8字符串。
解决办法通过Java的String类的构造方法来进行字符集变换。
JNIEXPORT jstring JNICALL Java_com_test_JniTest_testChineseOut
(JNIEnv * env, jobject jobj){
//需要返回的字符串
char* c_str = "我爱你";
//jstring j_str = (*env)->NewStringUTF(env, c_str);
//通过调用构造方法String string = new String(byte[], charsetName);来解决乱码问题
//0.找到String类
jclass clz_String = (*env)->FindClass(env, "java/lang/String");
jmethodID mid = (*env)->GetMethodID(env, clz_String, "<init>", "([BLjava/lang/String;)V");
//准备new String的参数byte数组以及字符集
//1.创建字节数组并且将C的字符串拷贝进去
jbyteArray j_byteArray = (*env)->NewByteArray(env, strlen(c_str));
(*env)->SetByteArrayRegion(env, j_byteArray, 0, strlen(c_str), c_str);
//2.创建字符集的参数这里用Windows的more字符集GB2312
jstring charsetName = (*env)->NewStringUTF(env, "GB2312");
//调用
jstring j_new_str = (*env)->NewObject(env, clz_String, mid, j_byteArray, charsetName);
return j_new_str;
}
原文链接http://www.apkbus.com/blog-0-65619.html
SystemProperties.getInt("ro.build.version.sdk", 0);
Build.VERSION.SDK_INT > Build.VERSION_CODES.Q
###C++代码取Android系统版本号
```
#include <jni.h>
#include <string>
#include <sys/system_properties.h>
// 日志打印
#include <android/log.h>
#define LOG_TAG "TAG_LOG"
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
versionI() {
// 1. 获取 SDK 版本号 , 存储于 C 字符串 sdk_verison_str 中
char sdk[128] = "0";
// 获取版本号方法
__system_property_get("ro.build.version.sdk", sdk);
//将版本号转为 int 值
int sdk_verison = atoi(sdk);
}
# begin build properties (开始设置系统性能)
# autogenerated (通过设置形成系统信息)
ro.=GRI40 (版本ID)
ro.build.=GRJ22 (版本号)
ro.build.version.incremental=eng.buildbot.20110619.060228 (版本增量)
ro.build.version.sdk=10 sdk版本
ro.build.version.codename=REL (版本代号)
ro.build.version.release=2.3.4 Android 2.3.4系统)
ro.build.date=Sun Jun 19 06:02:58 UTC 2011 (制作者及制作时间)
ro.build.date.utc=0
ro.build.type=user (编译模式,如user,userdebug,eng,test模式)
ro.build.user=buildbot (编译账户)
ro.build.host=bb1 (编译主机系统)
ro.build.tags=test-keys (编译标签)
ro.product.model=HTC Wildfire HTC内部手机代号
ro.product.brand=htc_wwe (手机品牌)
ro.product.name=htc_buzz (手机正式名称)
ro.product.device=buzz (采用的设备)
ro.product.board=buzz (采用的处理器)
ro.product.cpu.abi=armeabi-v6j cpu的版本
ro.product.cpu.abi2=armeabi cpu的品牌
ro.product.manufacturer=HTC (手机制造商)
ro.product.locale.language=zh (手机默认语言)
ro.product.locale.region=CN (地区语言)
ro.wifi.channels= WIFI连接的渠道
ro.board.platform=msm7k (主板平台)
# ro.build.product is obsolete; use ro.product.device 旧代码ro.build.product使用代码ro.product.device
ro.build.product=buzz (建立产品)
# Do not try to parse ro.build.description or .fingerprint 不要试图修改description和fingerprint
ro.build.description=passion-user 2.3.3 GRI40 102588 release-keys 用户的KEY
ro.build.fingerprint=google/passion/passion:2.3.3/GRI40/102588:user/release-keys (系统指纹)
# end build properties (性能代码完毕)
#
# system.prop for buzz 系统技术支持由BUZZ提供
#
# Density in DPI of the LCD of this board. This is used to scale the UI 高密度的液晶的DPI板。这是用来大规模UI的
# appropriately. If this property is not defined, the default value is 160 dpi. appropriately.如果这个属性没有定义,缺省值是160 dpi的分辨率
ro.sf.lcd_density=240 显示屏分辨率数值越大分辨率越底240就是800*480的
# View configuration for QVGA. (屏幕的设置)
view.fading_edge_length=8
view.touch_slop=15 (触摸屏灵敏度,数值越大越灵敏)
view.minimum_fling_velocity=25 (滑动速度)
view.scroll_friction=0.008 (滑动误差)
# RIL specific configuration. (特定设置)
rild.libpath=/system/lib/libhtc_
ro.ril.ecc.HTC-WWE=999
ro.ril.ecc.HTC-ELL=92,93,94
ro.ril.enable.a52.HTC-ITA=1
ro.ril.enable.a53.HTC-ITA=1
ro.ril.enable.a52=0
ro.ril.enable.a53=1
ro.ril.vmail.23415=1571,BT
ro.ril.hsdpa.category=8 hsdpa全称High Speed Downlink Packet Access中文意思高速下行分组接入,设置的数越大传输越快)
ro.ril.htcmaskw1.bitmask=429496
ro.ril.htcmaskw1=14449
ro.ril.def.agps.mode=2 打开AGPS服务支持可改为ro.ril.def.agps.mode=0 改后能省电但GPS定位速度会变慢
ro.ril.gprsclass=12 GPRS设置
# For HSDPA low throughput HSDPA低输量
ro.ril.disable.power.collapse=1 (关闭电源)
# Modify MMS APN retry timer from 5s to 2s. 修改短信的APN设置5秒为2秒
ro.gsm.2nd_data_retry_config=max_retries=3, 2000, 2000, 2000
# Time between scans in seconds. Keep it high to minimize battery drain.(扫描在几秒之内,可降低用电量)
# This only affects the case in which there are remembered access points, (这个修改仅能影响此文件)
# but none are in range.(但是没有一项是在范围内的)
wifi.interface=eth0 WIFI界面
wifi.supplicant_scan_interval=45 WIFI扫描间隔时间这里设置是45秒。把这个时间设置长点能省电
# Mobile data interfaces (移动数据的接口)
mobiledata.interfaces=rmnet0,rmnet1,rmnet2
# Allow or deny tethering. (允许和拒绝绑定)
ro.tether.denied=false
# Default network type. (默认的网络类型)
# 0 => WCDMA Preferred. 0=WCDMA优先
ro.telephony.default_network=0
# Enable Google-specific location features, (谷歌特定地点的设置)
# like NetworkLocationProvider and LocationCollector.(如网络服务器提供商和服务器位置)
ro.c o m.google.locationfeatures=1
# The OpenGL ES API level that is natively supported by this device. (开放式绘图介面)
# This is a 16.16 fixed point number. 界面有16个点16个不动点数量
ro.opengles.version=65536 (开放式绘图介面参数)
# Disable fs check on boot by default. 开机时默认禁用FS检查
sys.checkfs.fat=false
# Performance settings. (性能设置)
dalvik.vm.execution-mode=int:jit
dalvik.vm.heapsize=24m 虚拟内存大小可设置为16m或24m或32m或48m
persist.sys.use_dithering=1
persist.sys.purgeable_assets=1
# Increase SKIA decode memory capability for progressive jpg file.
ro.media.dec.jpeg.memcap=20000000
#
# ADDITIONAL_BUILD_PROPERTIES (其他性能设置)
no_require_sim=true (手机卡保护设置)
ro.rommanager.developerid=cyanogenmodnightly 固件管理器开发者是CM大神
ro.url.legal=http://www./intl/%s/mobile/android/basic/phone-legal.html
ro.url.legal.android_privacy=http://www]/intl/%s/mobile/android/basic/privacy.html
ro. com.google.clientidbase=android-google (谷歌客户身份)
ro. com.android.wifi-watchlist=GoogleGuest WIFI用户名单
ro.setupwizard.enterprise_mode=1 (默认情景模式)
ro. com.android.dateformat=MM-dd-yyyy 默认时间格式改为yyyy-MM-dd显示效果就是XXXX年XX月XX日
ro. com.android.dataroaming=false (漫游设置)
ro.config.ringtone=Playa.ogg (默认铃声设置,文件在/system/media/audio/ringtones 把喜欢的铃声放这里比如123. MP3放入ringtones文件夹中这里代码改为ro.config.ringtone=123. mp3
ro.config.notification_sound=regulus.ogg (默认提示音,文件在/system/media/audio/notifications 修改方法同上)
ro.config.alarm_alert=Alarm_Beep_03.ogg (默认闹铃,文件在/system/media/audio/alarms 修改方法同上)
ro.modversion=CyanogenMod-7-06192011-NIGHTLY-buzz 版本信息改这个能让你大名出现系统关于中改为ro.modversion=xxxxx
ro.setupwizard.mode=OPTIONAL (安装向导模式)
net. bt. name=Android (系统名称)
dalvik.vm.stack-trace-file=/data/anr/traces.txt
```

View File

@@ -1,4 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="cn.android.security"/>
package="cn.android.security">
<application>
<!--android:authorities="${applicationId}.library-installer"
android:authorities="cn.android.security.InitProvider"-->
<provider
android:name=".InitProvider"
android:authorities="${applicationId}.InitProvider"
android:exported="false"
android:multiprocess="true" />
</application>
</manifest>

View File

@@ -1,5 +1,6 @@
package cn.android.security;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
@@ -21,6 +22,7 @@ import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
@@ -49,15 +51,15 @@ public class APISecurity {
* 被Native调用的Java方法
*/
public void javaMethod(String msg) {
Log.e("错误代码", msg);
Log.e("mhyLog错误代码", msg);
// System.exit(1);
}
public static void verify(Context context) {
Log.e("mhyLog", "hash"+AppSigning.getSignatureHash(context));
runCommand();
Log.e("路径文件签名", getApkSignatures(context, context.getPackageName()));
Log.e("已安装APP签名", getInstalledAPKSignature(context, context.getPackageName()));
Log.e("mhyLog", "hash:"+AppSigning.getSignatureHash(context));
//runCommand();
Log.e("mhyLog包文件签名", getApkSignatures(context,context.getPackageName()));
Log.e("mhyLog已安装签名", getInstalledAPKSignature(context, context.getPackageName()));
//通过获取其他应用的签名 如果一样那么被hook了
}
@@ -67,7 +69,7 @@ public class APISecurity {
public static String getInstalledAPKSignature(Context context, String packageName) {
PackageManager pm = context.getPackageManager();
try {
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.P) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
PackageInfo appInfo = pm.getPackageInfo(packageName.trim(), PackageManager.GET_SIGNING_CERTIFICATES);
if (appInfo == null || appInfo.signingInfo == null)
return "";
@@ -86,47 +88,91 @@ public class APISecurity {
/** 防破签名 2
* C调用Java 从源安装文件获取签名信息
* 有bug
* */
public static String getApkSignatures(Context context, String packname) {
String sign = "";
String path = null;
// try {//获取此包安装路径
// //第一种方法
// ApplicationInfo applicationInfo = context.getPackageManager().getApplicationInfo(packname, 0);
// path = applicationInfo.sourceDir;
// //第二种方法
//// ApplicationInfo applicationInfo = context.getPackageManager().getPackageInfo(packname, PackageManager.GET_META_DATA).applicationInfo;
//// path = applicationInfo.publicSourceDir;//sourceDir; // 获取当前apk包的绝对路径
// Log.e("其他已知包名apk的安装路径", applicationInfo.sourceDir+ "&---&" + applicationInfo.publicSourceDir);
// } catch (PackageManager.NameNotFoundException e) {
// e.printStackTrace();
// //第三中方法
// path=context.getPackageResourcePath();
// Log.e("在apk中获取自身安装路径", path);
// }
String path="";
if (!TextUtils.isEmpty(packname)) {
try {//获取此包安装路径
//第一种方法
ApplicationInfo applicationInfo = context.getPackageManager().getApplicationInfo(packname, 0);
path = applicationInfo.sourceDir;
//第二种方法
// ApplicationInfo applicationInfo = context.getPackageManager().getPackageInfo(packname, PackageManager.GET_META_DATA).applicationInfo;
// path = applicationInfo.publicSourceDir;//sourceDir; // 获取当前apk包的绝对路径
// Log.e("mhyLog其他已知包名apk的安装路径", applicationInfo.sourceDir + "&---&" + applicationInfo.publicSourceDir);
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
//第三中方法
// path = context.getPackageResourcePath();
path= context.getApplicationInfo().sourceDir;
// Log.e("mhyLOg在apk中获取自身安装路径", path);
}
}else {
//第三中方法
// path= context.getApplicationInfo().sourceDir;
path=context.getPackageResourcePath();
// Log.e("在apk中获取自身安装路径", path);
}
File apkFile = new File(path);
if (apkFile != null && apkFile.exists()) {
Log.e("包安装路径", apkFile.getAbsolutePath());
if (apkFile.exists()) {
Log.e("mhyLog包安装路径", apkFile.getAbsolutePath());
PackageManager pm = context.getPackageManager();
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.P) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
PackageInfo pkgInfo = pm.getPackageArchiveInfo(apkFile.getAbsolutePath(), PackageManager.GET_SIGNING_CERTIFICATES);
if (pkgInfo != null && pkgInfo.signingInfo != null && pkgInfo.signingInfo.getApkContentsSigners().length > 0) {
sign = AppSigning.getSignatureString(pkgInfo.signingInfo.getApkContentsSigners(), AppSigning.SHA1);
if (pkgInfo != null && pkgInfo.signingInfo != null) {
return AppSigning.getSignatureString(pkgInfo.signingInfo.getApkContentsSigners(), AppSigning.SHA1);
}
} else {
PackageInfo pkgInfo = pm.getPackageArchiveInfo(apkFile.getAbsolutePath(), PackageManager.GET_SIGNATURES);
if (pkgInfo != null && pkgInfo.signatures != null && pkgInfo.signatures.length > 0) {
sign = AppSigning.getSignatureString(pkgInfo.signatures, AppSigning.SHA1);
if (pkgInfo != null && pkgInfo.signatures != null) {
return AppSigning.getSignatureString(pkgInfo.signatures, AppSigning.SHA1);
}
}
}
return sign;
return "";
}
/**
* 手动构建 Context
*/
@SuppressLint({"DiscouragedPrivateApi","PrivateApi"})
public static Context createContext() throws ClassNotFoundException,
NoSuchMethodException,
InvocationTargetException,
IllegalAccessException,
NoSuchFieldException,
NullPointerException{
// 反射获取 ActivityThread 的 currentActivityThread 获取 mainThread
Class activityThreadClass = Class.forName("android.app.ActivityThread");
Method currentActivityThreadMethod =
activityThreadClass.getDeclaredMethod("currentActivityThread");
currentActivityThreadMethod.setAccessible(true);
Object mainThreadObj = currentActivityThreadMethod.invoke(null);
// 反射获取 mainThread 实例中的 mBoundApplication 字段
Field mBoundApplicationField = activityThreadClass.getDeclaredField("mBoundApplication");
mBoundApplicationField.setAccessible(true);
Object mBoundApplicationObj = mBoundApplicationField.get(mainThreadObj);
// 获取 mBoundApplication 的 packageInfo 变量
if (mBoundApplicationObj == null) throw new NullPointerException("mBoundApplicationObj 反射值空");
Class mBoundApplicationClass = mBoundApplicationObj.getClass();
Field infoField = mBoundApplicationClass.getDeclaredField("info");
infoField.setAccessible(true);
Object packageInfoObj = infoField.get(mBoundApplicationObj);
// 反射调用 ContextImpl.createAppContext(ActivityThread mainThread, LoadedApk packageInfo)
if (mainThreadObj == null) throw new NullPointerException("mainThreadObj 反射值空");
if (packageInfoObj == null) throw new NullPointerException("packageInfoObj 反射值空");
Method createAppContextMethod = Class.forName("android.app.ContextImpl").getDeclaredMethod(
"createAppContext",
mainThreadObj.getClass(),
packageInfoObj.getClass());
createAppContextMethod.setAccessible(true);
return (Context) createAppContextMethod.invoke(null, mainThreadObj, packageInfoObj);
}
//需要读取应用列表权限
public void getAppList(Context context) {
@@ -146,8 +192,6 @@ public class APISecurity {
/**
* 通过指令获取已安装的包
*
* @return
*/
private static ArrayList<String> runCommand() {
list.clear();

View File

@@ -6,6 +6,7 @@ import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.Signature;
import android.os.Build;
import android.util.Base64;
import android.util.DisplayMetrics;
import android.util.Log;
@@ -54,11 +55,12 @@ public class AppSigning {
* 获取相应的类型的签名信息把签名的byte[]信息转换成16进制
*/
public static String getSignatureString(Signature[] sigs, @SigniType String type) {
String fingerprint = "error!";
if (sigs.length>0) {
for (Signature sig : sigs) {
Log.e("mhyLog","getSignatureString:"+sig.toCharsString());
Log.e("mhyLog", "Signature64:" + Base64.encodeToString(sig.toCharsString().getBytes(),Base64.DEFAULT));
}
byte[] hexBytes = sigs[0].toByteArray();
String fingerprint = "error!";
try {
StringBuffer buffer = new StringBuffer();
MessageDigest digest = MessageDigest.getInstance(type);
@@ -78,6 +80,7 @@ public class AppSigning {
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
}
return fingerprint;
}
@@ -168,10 +171,11 @@ public class AppSigning {
return str.toString();
}
/** 防破签名 3 使原生检测签名不被hook 助力防破签名1而存在
/** 防破签名 3 使原生检测签名能获取真实值 助力防破签名1而存在
* 通过重置PackageManager防止getPackageInfo方法被代理设置
* 亲测MT管理器当前2.9.1)的一键去签名校验(包括加强版)无效!
* 当然如果别人反编译把代码删除的话那就没办法了
* getBaseContext()
*/
public static void resetPackageManager(Context baseContext) {
try {
@@ -207,7 +211,7 @@ public class AppSigning {
* 检测 PackageManager 代理
*/
@SuppressLint("PrivateApi")
private boolean checkPMProxy(Context context){
public static boolean checkPMProxy(Context context){
String truePMName = "android.content.pm.IPackageManager$Stub$Proxy";
String nowPMName = "";
try {
@@ -227,7 +231,9 @@ public class AppSigning {
/** 防破签名 2
* 安装路径获取签名
* PackageParser 28新api
*/
@SuppressLint("PrivateApi")
public static String getAPKSignatures(String apkPath) {
String PATH_PackageParser = "android.content.pm.PackageParser";
try {
@@ -252,7 +258,7 @@ public class AppSigning {
Object pkgParserPkg = pkgParser_parsePackageMtd.invoke(pkgParser, new File(apkPath), PackageManager.GET_SIGNATURES);
if (Build.VERSION.SDK_INT >= 28) {
Method pkgParser_collectCertificatesMtd = pkgParserCls.getDeclaredMethod("collectCertificates", pkgParserPkg.getClass(), Boolean.TYPE);
Method pkgParser_collectCertificatesMtd = pkgParserCls.getDeclaredMethod("collectCertificates", pkgParserPkg.getClass(), Boolean.TYPE);//true skipVerify
pkgParser_collectCertificatesMtd.invoke(pkgParser, pkgParserPkg, Build.VERSION.SDK_INT > 28);
// Method pkgParser_collectCertificatesMtd = pkgParserCls.getDeclaredMethod("collectCertificates", pkgParserPkg.getClass(), Boolean.TYPE);

View File

@@ -0,0 +1,115 @@
/*
* Copyright (c) 2020-2021. 安卓
* FileName: ${NAME}
* Author: ${USER}
* Date: ${DATE} ${TIME}
* Description: ${DESCRIPTION}
* History:
* <author> <time> <version> <desc>
* 作者姓名 修改时间 版本号 描述
* 本代码未经许可,不得私自修改何使用
*/
package cn.android.security;
import android.annotation.SuppressLint;
import android.app.Application;
import android.content.ContentProvider;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
/**
* Created By Mahongyin
* Date 2021/3/2 17:24
* ContentProvider的onCreate会先于Appliction的onCreate调用 防止application被掉包后 仍有这里可以重置pm代理+检测
*/
public class InitProvider extends ContentProvider {
@Override
public boolean onCreate() {
Context application = getContext().getApplicationContext();
if (application == null) {
application = getApplicationByReflect();
}
Log.e("mhyLog", "initContentProvider:");
// chekSignature(application);
checkApplication();
return true;
}
void chekSignature(Context application){
//防hook 并检测
if (AppSigning.checkPMProxy(application)) {
AppSigning.resetPackageManager(application);
String sing = APISecurity.getInstalledAPKSignature(application, application.getPackageName());
Log.e("mhyLog手动InitProvider", sing);
}
}
/**
* 校验 application
*/
private boolean checkApplication(){
//在这里使用反射 获取比较靠谱 如果 被替换换 就查出来了
Application nowApplication = getApplicationByReflect();
String trueApplicationName = "MyApplication";//自己的Application类名 防止替换
String nowApplicationName = nowApplication.getClass().getSimpleName();
Log.e("mhyLogAppName", "反射获取:"+nowApplicationName);
return trueApplicationName.equals(nowApplicationName);
}
/**
* 通过反射获取 当前application
*/
@SuppressLint("PrivateApi")
public Application getApplicationByReflect() {
try {
Class<?> activityThread = Class.forName("android.app.ActivityThread");
Object thread = activityThread.getMethod("currentActivityThread").invoke(null);
Object app = activityThread.getMethod("getApplication").invoke(thread);
if (app == null) {
throw new NullPointerException("you should init first");
}
return (Application) app;
} catch (Exception e) {
e.printStackTrace();
}
throw new NullPointerException("you should init first");
}
@Nullable
@Override
public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder) {
return query(uri, projection, selection, selectionArgs, sortOrder);
}
@Nullable
@Override
public String getType(@NonNull Uri uri) {
return getType(uri);
}
@Nullable
@Override
public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) {
return insert(uri, values);
}
@Override
public int delete(@NonNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs) {
return delete(uri, selection, selectionArgs);
}
@Override
public int update(@NonNull Uri uri, @Nullable ContentValues values, @Nullable String selection, @Nullable String[] selectionArgs) {
return update(uri, values, selection, selectionArgs);
}
}

View File

@@ -5,7 +5,7 @@
<application
android:allowBackup="true"
android:name=".HookApplication"
android:name=".MyApplication"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"

View File

@@ -1,12 +1,15 @@
#include <jni.h>
#include <string>
#include <android/log.h>
#include <cstring>
#include <string.h>
#include <dirent.h>
#include <unistd.h>
//系统信息
#include <sys/system_properties.h>
//log定义
#define LOG "APISECURITY" // 这个是自定义的LOG的TAG
#define LOG "mhyLog_APISECURITY" // 这个是自定义的LOG的TAG
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG,LOG,__VA_ARGS__)
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO,LOG,__VA_ARGS__)
#define LOGW(...) __android_log_print(ANDROID_LOG_WARN,LOG,__VA_ARGS__)
@@ -14,8 +17,8 @@
#define LOGF(...) __android_log_print(ANDROID_LOG_FATAL,LOG,__VA_ARGS__)
//此处改为你的APP签名
//#define SHA1 "a8e3d91a4f77dd7ccb8d43ee5046a4b6833f4785"//真实test.keystore
#define SHA1 "04c1411b0662acd9e4aa300559677e5f106a5255"//区分da小写
#define SHA1 "a8e3d91a4f77dd7ccb8d43ee5046a4b6833f4785"//真实test.keystore
//#define SHA1 "04c1411b0662acd9e4aa300559677e5f106a5255"//区分da小写
#define ALGORITHM_SHA1 "SHA1"
#define ALGORITHM_MD5 "MD5"
@@ -28,19 +31,34 @@
static bool isInit = false;
static char *secret;
void printByte(JNIEnv *env, jbyteArray jbytes) {
//转换成char
jsize array_size = env->GetArrayLength(jbytes);
jbyte *sha1 = env->GetByteArrayElements(jbytes, nullptr);
char *hexA = new char[array_size * 2 + 1]();
for (int i = 0; i < array_size; ++i) {
sprintf(hexA + 2 * i, "%02x", (u_char) sha1[i]);
jint version() {
// 1. 获取 SDK 版本号 , 存储于 C 字符串 sdk_verison_str 中
char sdk[128] = "0";
// 获取版本号方法
__system_property_get("ro.build.version.sdk", sdk);
//将版本号转为 int 值
int sdk_verison = atoi(sdk);
return sdk_verison;
}
LOGD("printByte:%s", hexA);
jint getDeviceVersion(JNIEnv *env) {
jclass build_claz = env->FindClass("android/os/Build$VERSION");
jfieldID sdk_int = env->GetStaticFieldID(build_claz, "SDK_INT", "I");
return env->GetStaticIntField(build_claz, sdk_int);
}
//void printByte(JNIEnv *env, jbyteArray jbytes) {
// //转换成char
// jsize array_size = env->GetArrayLength(jbytes);
// jbyte *sha1 = env->GetByteArrayElements(jbytes, nullptr);
//
// char *hexA = new char[array_size * 2 + 1]();
// for (int i = 0; i < array_size; ++i) {
// sprintf(hexA + 2 * i, "%02x", (u_char) sha1[i]);
// }
// LOGD("printByte:%s", hexA);
//}
char *digest(JNIEnv *env, const char *algorithm, jbyteArray cert_byte) {
jclass message_digest_class = env->FindClass("java/security/MessageDigest");
jmethodID methodId = env->GetStaticMethodID(message_digest_class, "getInstance",
@@ -130,19 +148,23 @@ jstring getApkPath(JNIEnv *env, jobject applicationInfo_object) {
jstring apkPath = (jstring) env->GetObjectField(applicationInfo_object, sourceDir);
return apkPath;
}
//context.getPackageResourcePath()
jstring getApkResPath(JNIEnv *env, jclass context_class, jobject context_object) {
jmethodID methodId = env->GetMethodID(context_class, "getPackageResourcePath", "()Ljava/lang/String;");
jmethodID methodId = env->GetMethodID(context_class, "getPackageResourcePath",
"()Ljava/lang/String;");
jstring apkPath = (jstring) env->CallObjectMethod(context_object, methodId);
return apkPath;
}
//弃用 安装路径 package_info= pm.getPackageInfo(packname, 0x80).applicationInfo
jstring getAbsolutePath(JNIEnv *env, jobject package_info) {
jclass package_info_class = env->GetObjectClass(package_info);//packageInfo
jfieldID field = env->GetFieldID(package_info_class, "applicationInfo",
"Landroid/content/pm/ApplicationInfo;");//applicationInfo获取类对象 以获取方法
env->DeleteLocalRef(package_info_class);
jobject apppack_info_object = (jstring) env->GetObjectField(package_info,field);//applicationInfo
jobject apppack_info_object = (jstring) env->GetObjectField(package_info,
field);//applicationInfo
jclass apppack_info_class = env->GetObjectClass(apppack_info_object);//ApplicationInfo
jfieldID appfield = env->GetFieldID(apppack_info_class, "sourceDir",
"Ljava/lang/String;");//获取此类中的sourceDir成员id P->publicSourceDir
@@ -156,7 +178,36 @@ jstring getAbsolutePath(JNIEnv *env, jobject package_info) {
/**
* 获取签名信息
*/
//需要sdk28 新API
jobject getSignature28(JNIEnv *env, jobject package_info) {
jclass package_info_class = env->GetObjectClass(package_info);
jfieldID signingInfo = env->GetFieldID(package_info_class, "signingInfo",
"Landroid/content/pm/SigningInfo;");
env->DeleteLocalRef(package_info_class);
jobject signingInfo_object = env->GetObjectField(package_info, signingInfo);
LOGE("%s", "fieldId:signingInfo");
if (signingInfo_object == nullptr) {
return nullptr;//为空咋回事?
}
// jclass signingInfo_class = env->GetObjectClass(signingInfo_object);
jclass signingInfo_class = env->FindClass("android/content/pm/SigningInfo");
jmethodID methodId = env->GetMethodID(signingInfo_class, "getApkContentsSigners",
"()[Landroid/content/pm/Signature;");
env->DeleteLocalRef(signingInfo_class);
jobjectArray signature_object_array = (jobjectArray) env->CallObjectMethod(signingInfo_object,
methodId);
LOGE("%s", "methodId:getApkContentsSigners");
if (signature_object_array == nullptr)
return nullptr;
return env->GetObjectArrayElement(signature_object_array, 0);
}
jobject getSignature(JNIEnv *env, jobject package_info) {
// jint sdk = getDeviceVersion(env);
// if (sdk >= 28) {
// LOGE("sdk版本%d", sdk);
// return getSignature28(env, package_info);
// }
jclass package_info_class = env->GetObjectClass(package_info);
jfieldID fieldId = env->GetFieldID(package_info_class, "signatures",
"[Landroid/content/pm/Signature;");
@@ -240,7 +291,7 @@ jboolean getApkPathSignatures(JNIEnv *env, jobject context_object) {
}
/**
* 调用
* 调用 init
*/
extern "C" JNIEXPORT jboolean JNICALL
Java_cn_android_security_APISecurity_init(
@@ -290,32 +341,32 @@ Java_cn_android_security_APISecurity_init(
return JNI_FALSE;
}
/*********接着调用Java方法验证 安装目录apk文件de签名**/
// jclass cls_util = env->FindClass(
// "cn/android/security/APISecurity");
// //注意,这里的使用的斜杠而不是点
// if (cls_util == nullptr) {
// return JNI_FALSE;
// }
// jobject j_obj = env->AllocObject(cls_util);
// //**这里是关键**类,方法,(参数类型)返回类型
// jmethodID mtd_static_method = env->GetStaticMethodID(cls_util,
// "getApkSignatures",
// "(Landroid/content/Context;Ljava/lang/String;)Ljava/lang/String;");
// if (mtd_static_method == nullptr) {
// return JNI_FALSE;
// }
// //调用Java方法
// jstring sigin = static_cast<jstring>(env->CallStaticObjectMethod(cls_util, mtd_static_method,
// context_object, package_name));
// const char *ss = env->GetStringUTFChars(sigin, nullptr);
// //删除引用
// env->DeleteLocalRef(cls_util);
// env->DeleteLocalRef(j_obj);
////调用Java方法结束
// if (strcmp(ss, SHA1) != 0) {
// LOGE("非法调用3SHA1: %s", ss);
// return JNI_FALSE;
// }
jclass cls_util = env->FindClass(
"cn/android/security/APISecurity");
//注意,这里的使用的斜杠而不是点
if (cls_util == nullptr) {
return JNI_FALSE;
}
jobject j_obj = env->AllocObject(cls_util);
//**这里是关键**类,方法,(参数类型)返回类型
jmethodID mtd_static_method = env->GetStaticMethodID(cls_util,
"getApkSignatures",
"(Landroid/content/Context;Ljava/lang/String;)Ljava/lang/String;");
if (mtd_static_method == nullptr) {
return JNI_FALSE;
}
//调用Java方法
jstring sigin = static_cast<jstring>(env->CallStaticObjectMethod(cls_util, mtd_static_method,
context_object, package_name));
const char *ss = env->GetStringUTFChars(sigin, nullptr);
//删除引用
env->DeleteLocalRef(cls_util);
env->DeleteLocalRef(j_obj);
//调用Java方法结束
if (strcmp(ss, SHA1) != 0) {
LOGE("非法调用3SHA1: %s", ss);
return JNI_FALSE;
}
/*******************调用Java方法结束***/
//加强验证
if (!getApkPathSignatures(env, context_object)) {

View File

@@ -1,118 +0,0 @@
package cn.android.sample;
/**
* 项目名 FileTransfer
* 所在包 bin.mt.apksignaturekillerplus
* 作者 mahongyin
* 时间 2020-03-16 10:55
* 邮箱 mhy.work@qq.com
* 描述 说明: 将application替换到AndroidMainfest.xml 代码里改放APPlication
* 代码里替换要hook的签名值 始终返回原始签名,达到欺骗
*/
import android.app.Application;
import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.Signature;
import android.util.Base64;
import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import cn.android.security.AppSigning;
//继承原application
public class HookApplication extends Application implements InvocationHandler {
private static final int GET_SIGNATURES = 64;
private String appPkgName = "";
private Object base;
private byte[][] sign;
static HookApplication app;
@Override
public void onCreate() {
super.onCreate();
app = this;
//在签名校验被hook 之后重置PackageManager
/*在这里 重置PackageManager 只要在验证前重置即可*/
AppSigning.resetPackageManager(getBaseContext());
}
public static Context getContext() {
if (app == null) {
app = new HookApplication();
app.onCreate();
}
return app;
}
@Override
protected void attachBaseContext(Context context) {
//在这里hook 签名校验被
hook(context);
super.attachBaseContext(context);
}
/**
* 全局hook 签名校验
*
* @param context
*/
private void hook(Context context) {
try {
String relay="308203273082020fa003020102020477d6d1f6300d06092a864886f70d01010b05003044310c300a06035504061303303231310e300c060355040813056368696e613111300f060355040713087368616e676861693111300f0603550403130877757a6f6e67626f301e170d3139303330353032343132345a170d3434303232373032343132345a3044310c300a06035504061303303231310e300c060355040813056368696e613111300f060355040713087368616e676861693111300f0603550403130877757a6f6e67626f30820122300d06092a864886f70d01010105000382010f003082010a0282010100a3ac52268a32e8420a20a727c184c133d513998a207e198f5a535d628a436ba5e095e7ba3f92535234a83fb6272e70ed6113d8f6facc3dee2cfc076a3bd93dad3520fd5d9d9ae4c48afe56e7b421f5de2adfbc23e450f7a5f71e0afdec047b1ce8d7be62ef754a9d43bf36d9b9e0728fc268cb845b464cce1370573dfafd6c40b2efb98ba1f20c5a63c417264b69d86adb839241dc37d1a7113295a9c51623e51e9408f9623ed49a63a3ba6269172872088213332f38370af530d5be56e54115b0884ace6813911bfc6873bea28207741f4b2471b797bab156e4c6ead91659076553cee1db82c0cebdd17b64802a20c7ee6a3414f959133e6c435efe9241ab7d0203010001a321301f301d0603551d0e041604143806aea351c74f2a8b83fa26c0a9e3d3820b6699300d06092a864886f70d01010b050003820101006ebcc664b996f15c1e03d041eebbdf74a0976d117d68f34d21ef67855b614f5a2bfede66c9d4ea78fe3b50e3673890dfa2eb9eaf4321b30eb76be6f5944004b6501b2629ae4f2c6750f784ea2f9be6c26318258f98772fd3ff0c6ea817fb76d9ae02daa1fa1b91653d531db345f52aa4e7b21e8f92387a2d15d1afd5556213b0c32aadd529bac330516536948bcf85398fb86a65dbae95ef0e5582a87e26b1dbcceeaf77e6e93c63042acdf49c74927561df508020547426ad37776e360feb219523ef4e2a6f5f41a43cd0c0514c53f8644c71014080cfbe036f120a6daad6e12d6b1a07939ca840af2b3373388c0ed6b18594dd838122174304d5eb720f1cef";
String singnStr = "AQAAAjAwggIsMIIBlaADAgECAgMY2gowDQYJKoZIhvcNAQEFBQAwWzELMAkGA1UEBhMCQ04xCzAJ\nBgNVBAgTAmhlMQwwCgYDVQQHEwNzanoxDDAKBgNVBAoTA2VkdTEPMA0GA1UECxMGc2Nob29sMRIw\nEAYDVQQDEwltYWhvbmd5aW4wHhcNMTgxMDExMDcwMjE1WhcNNDMxMDA1MDcwMjE1WjBbMQswCQYD\nVQQGEwJDTjELMAkGA1UECBMCaGUxDDAKBgNVBAcTA3NqejEMMAoGA1UEChMDZWR1MQ8wDQYDVQQL\nEwZzY2hvb2wxEjAQBgNVBAMTCW1haG9uZ3lpbjCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA\nr0aFNvrxBnBEEbAANDcsrmBlcQBGJKsvT5onXngek2ZbkWZx8/1o8nbgCBSjAZvnXEYYjjkC5k+A\nIne1PJUF5bPKTjIQepNmtK+KVHsAJLjn6rG4fQ3oaeu0vvNBehuzt54bACbzkXZj9nV5rs8OllD9\nRronLsOb3DVJ95DyLIMCAwEAATANBgkqhkiG9w0BAQUFAAOBgQCpF6kB++zR0FW4eZaJCEAnQNP0\nGtwAnrXEpvP7ePcakk/JT/e56uTS/OAbpmM/tWETvPtx9hOB4RoPwRl3Q0G1ieCMeVyIABmGAeOk\ntARqtiExfHvorrmk4mxVIiPTwUJSWzAKuhLV93pMxTFZSZK0iTJFVVM/l8Wh3CTdFtpW+w==\n";
DataInputStream dataInputStream = new DataInputStream(
new ByteArrayInputStream(Base64.decode(singnStr, 0)));
byte[][] bArr = new byte[(dataInputStream.read() & 255)][];
for (int i = 0; i < bArr.length; i++) {
bArr[i] = new byte[dataInputStream.readInt()];
dataInputStream.readFully(bArr[i]);
}
Class cls = Class.forName("android.app.ActivityThread");
Object invoke = cls.getDeclaredMethod("currentActivityThread", new Class[0]).invoke(null, new Object[0]);
Field declaredField = cls.getDeclaredField("sPackageManager");
declaredField.setAccessible(true);
Object obj = declaredField.get(invoke);
Class cls2 = Class.forName("android.content.pm.IPackageManager");
this.base = obj;
this.sign = bArr;
this.appPkgName = context.getPackageName();
Object newProxyInstance = Proxy.newProxyInstance(cls2.getClassLoader(), new Class[]{cls2}, this);
declaredField.set(invoke, newProxyInstance);
PackageManager packageManager = context.getPackageManager();
Field declaredField2 = packageManager.getClass().getDeclaredField("mPM");
declaredField2.setAccessible(true);
declaredField2.set(packageManager, newProxyInstance);
System.out.println("PmsHook success.");
} catch (Exception e) {
System.err.println("PmsHook failed.");
e.printStackTrace();
}
}
@Override
public Object invoke(Object obj, Method method, Object[] objArr) throws Throwable {
if ("getPackageInfo".equals(method.getName())) {//方法名对上
String str = (String) objArr[0];
if ((((Integer) objArr[1]).intValue() & 64) != 0 && this.appPkgName.equals(str)) {
PackageInfo packageInfo = (PackageInfo) method.invoke(this.base, objArr);
packageInfo.signatures = new Signature[this.sign.length];
for (int i = 0; i < packageInfo.signatures.length; i++) {
packageInfo.signatures[i] = new Signature(this.sign[i]);
}
return packageInfo;
}
}
return method.invoke(this.base, objArr);
}
}

View File

@@ -21,12 +21,12 @@ public class MainActivity extends AppCompatActivity {
tv = findViewById(R.id.sample_text);
if(APISecurity.init(this)){
if(APISecurity.init(getApplicationContext())){
tv.setText("初始化ok");
}else {
tv.setText("初始化fail");
}
APISecurity.verify(this);
APISecurity.verify(getApplicationContext());
findViewById(R.id.btnTest).setOnClickListener(new View.OnClickListener() {
@SuppressLint("SetTextI18n")
@Override

View File

@@ -0,0 +1,229 @@
package cn.android.sample;
/**
* 项目名 FileTransfer
* 所在包 bin.mt.apksignaturekillerplus
* 作者 mahongyin
* 时间 2020-03-16 10:55
* 邮箱 mhy.work@qq.com
* 描述 说明: 将application替换到AndroidMainfest.xml 代码里改放APPlication
* 代码里替换要hook的签名值 始终返回原始签名,达到欺骗
*/
import android.annotation.SuppressLint;
import android.app.Application;
import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.Signature;
import android.content.pm.SigningInfo;
import android.os.Build;
import android.util.Base64;
import android.util.Log;
import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.io.File;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import cn.android.security.APISecurity;
//继承原application
public class MyApplication extends Application implements InvocationHandler {
private static final int GET_SIGNATURES = 64;
private String appPkgName = "";
private String appPath = "";
private Object base;
private byte[][] sign;
static MyApplication app;
public MyApplication() {
// 在构造函数里提早检测
// earlyCheckSign();
}
void earlyCheckSign() {
// 手动构造 context
try {
Context context = APISecurity.createContext();
//用新 context 校验签名的过程(正常的检测一样)
String sing = APISecurity.getInstalledAPKSignature(context, context.getPackageName());
Log.e("mhyLog手动构造", sing);
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
protected void attachBaseContext(Context context) {
//在这里hook 签名校验被
// hook(context);
super.attachBaseContext(context);
}
@Override
public void onCreate() {
super.onCreate();
app = this;
Application nowApplication = app;
// SystemProperties.getInt("ro.build.version.sdk", 0);
//再这看 不一定靠谱
Log.e("mhyLog","检查App名:"+nowApplication.getClass().getSimpleName());
//在签名校验被hook 之后重置PackageManager
/*在这里 重置PackageManager 只要在验证前重置即可*/
// AppSigning.resetPackageManager(getBaseContext());
}
/**
* 全局hook 签名校验
*
* @param context
*/
private void hook(Context context) {
try {
// String realy = "308203273082020fa003020102020477d6d1f6300d06092a864886f70d01010b05003044310c300a06035504061303303231310e300c060355040813056368696e613111300f060355040713087368616e676861693111300f0603550403130877757a6f6e67626f301e170d3139303330353032343132345a170d3434303232373032343132345a3044310c300a06035504061303303231310e300c060355040813056368696e613111300f060355040713087368616e676861693111300f0603550403130877757a6f6e67626f30820122300d06092a864886f70d01010105000382010f003082010a0282010100a3ac52268a32e8420a20a727c184c133d513998a207e198f5a535d628a436ba5e095e7ba3f92535234a83fb6272e70ed6113d8f6facc3dee2cfc076a3bd93dad3520fd5d9d9ae4c48afe56e7b421f5de2adfbc23e450f7a5f71e0afdec047b1ce8d7be62ef754a9d43bf36d9b9e0728fc268cb845b464cce1370573dfafd6c40b2efb98ba1f20c5a63c417264b69d86adb839241dc37d1a7113295a9c51623e51e9408f9623ed49a63a3ba6269172872088213332f38370af530d5be56e54115b0884ace6813911bfc6873bea28207741f4b2471b797bab156e4c6ead91659076553cee1db82c0cebdd17b64802a20c7ee6a3414f959133e6c435efe9241ab7d0203010001a321301f301d0603551d0e041604143806aea351c74f2a8b83fa26c0a9e3d3820b6699300d06092a864886f70d01010b050003820101006ebcc664b996f15c1e03d041eebbdf74a0976d117d68f34d21ef67855b614f5a2bfede66c9d4ea78fe3b50e3673890dfa2eb9eaf4321b30eb76be6f5944004b6501b2629ae4f2c6750f784ea2f9be6c26318258f98772fd3ff0c6ea817fb76d9ae02daa1fa1b91653d531db345f52aa4e7b21e8f92387a2d15d1afd5556213b0c32aadd529bac330516536948bcf85398fb86a65dbae95ef0e5582a87e26b1dbcceeaf77e6e93c63042acdf49c74927561df508020547426ad37776e360feb219523ef4e2a6f5f41a43cd0c0514c53f8644c71014080cfbe036f120a6daad6e12d6b1a07939ca840af2b3373388c0ed6b18594dd838122174304d5eb720f1cef";
// realy= Base64.encodeToString(realy.getBytes(),Base64.DEFAULT);
String singnStr = "AQAAAjAwggIsMIIBlaADAgECAgMY2gowDQYJKoZIhvcNAQEFBQAwWzELMAkGA1UEBhMCQ04xCzAJBgNVBAgTAmhlMQwwCgYDVQQHEwNzanoxDDAKBgNVBAoTA2VkdTEPMA0GA1UECxMGc2Nob29sMRIwEAYDVQQDEwltYWhvbmd5aW4wHhcNMTgxMDExMDcwMjE1WhcNNDMxMDA1MDcwMjE1WjBbMQswCQYDVQQGEwJDTjELMAkGA1UECBMCaGUxDDAKBgNVBAcTA3NqejEMMAoGA1UEChMDZWR1MQ8wDQYDVQQLEwZzY2hvb2wxEjAQBgNVBAMTCW1haG9uZ3lpbjCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAr0aFNvrxBnBEEbAANDcsrmBlcQBGJKsvT5onXngek2ZbkWZx8/1o8nbgCBSjAZvnXEYYjjkC5k+AIne1PJUF5bPKTjIQepNmtK+KVHsAJLjn6rG4fQ3oaeu0vvNBehuzt54bACbzkXZj9nV5rs8OllD9RronLsOb3DVJ95DyLIMCAwEAATANBgkqhkiG9w0BAQUFAAOBgQCpF6kB++zR0FW4eZaJCEAnQNP0GtwAnrXEpvP7ePcakk/JT/e56uTS/OAbpmM/tWETvPtx9hOB4RoPwRl3Q0G1ieCMeVyIABmGAeOktARqtiExfHvorrmk4mxVIiPTwUJSWzAKuhLV93pMxTFZSZK0iTJFVVM/l8Wh3CTdFtpW+w==";
DataInputStream dataInputStream = new DataInputStream(
new ByteArrayInputStream(Base64.decode(singnStr, Base64.DEFAULT)));
byte[][] bArr = new byte[(dataInputStream.read() & 255)][];
for (int i = 0; i < bArr.length; i++) {
bArr[i] = new byte[dataInputStream.readInt()];
dataInputStream.readFully(bArr[i]);
}
//hook全局sPackageManager对象
Class cls = Class.forName("android.app.ActivityThread");
Object invoke = cls.getDeclaredMethod("currentActivityThread", new Class[0]).invoke(null, new Object[0]);
Field declaredField = cls.getDeclaredField("sPackageManager");
declaredField.setAccessible(true);
Object obj = declaredField.get(invoke);
Class cls2 = Class.forName("android.content.pm.IPackageManager");
this.base = obj;
this.sign = bArr;
this.appPkgName = context.getPackageName();
this.appPath = context.getPackageResourcePath();
Object newProxyInstance = Proxy.newProxyInstance(cls2.getClassLoader(), new Class[]{cls2}, this);
declaredField.set(invoke, newProxyInstance);
PackageManager packageManager = context.getPackageManager();
Field declaredField2 = packageManager.getClass().getDeclaredField("mPM");
declaredField2.setAccessible(true);
declaredField2.set(packageManager, newProxyInstance);
System.out.println("PmsHook success.");
} catch (Exception e) {
System.err.println("PmsHook failed.");
e.printStackTrace();
}
}
@SuppressLint({"PrivateApi","DiscouragedPrivateApi"})
@Override
public Object invoke(Object obj, Method method, Object[] objArr) throws Throwable {
if ("getPackageInfo".equals(method.getName())) {//方法名对上
Log.e("mhyLogHook","getPackageInfo");
String str = (String) objArr[0];
if ((((Integer) objArr[1]).intValue() & 64) != 0 && this.appPkgName.equals(str)) {
PackageInfo packageInfo = (PackageInfo) method.invoke(this.base, objArr);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
//packageInfo.signingInfo.getApkContentsSigners();//mSigningDetails.signatures
//mSigningDetails.signatures;//PackageParser.SigningDetails
SigningInfo signingInfo= packageInfo.signingInfo;
// Class<?> sgInfo_clazz=SigningInfo.class;//getClass()&Class.forName(“类完整的路径”)仅限于引用类型的对象
// Field mSigningDetails_f=sgInfo_clazz.getDeclaredField("mSigningDetails");
// mSigningDetails_f.setAccessible(true);
// Object mSigningDetails = mSigningDetails_f.get(signingInfo);//PackageParser.SigningDetails
Class<?> pkgParserCls = Class.forName("android.content.pm.PackageParser");
Constructor<?> pkgParserCt = pkgParserCls.getConstructor();//null;
Object pkgParser = pkgParserCt.newInstance();//null;
// pkgParserCt =
// pkgParser =
Method pkgParser_parsePackageMtd = pkgParserCls.getDeclaredMethod("parsePackage", File.class, int.class);
Object pkgParserPkg = pkgParser_parsePackageMtd.invoke(pkgParser, new File(getPackageResourcePath()), PackageManager.GET_SIGNING_CERTIFICATES);
Method pkgParser_collectCertificatesMtd = pkgParserCls.getDeclaredMethod("collectCertificates", pkgParserPkg.getClass(), Boolean.TYPE);
pkgParser_collectCertificatesMtd.invoke(pkgParser, pkgParserPkg, true);
// Method pkgParser_collectCertificatesMtd = pkgParserCls.getDeclaredMethod("collectCertificates", pkgParserPkg.getClass(), Boolean.TYPE);
// pkgParser_collectCertificatesMtd.invoke(pkgParser, pkgParserPkg, false);
Field mSigningDetailsField = pkgParserPkg.getClass().getDeclaredField("mSigningDetails"); // SigningDetails
mSigningDetailsField.setAccessible(true);
Object mSigningDetails = mSigningDetailsField.get(pkgParserPkg);
Field infoField = mSigningDetails.getClass().getDeclaredField("signatures");
infoField.setAccessible(true);
Signature[] info = new Signature[this.sign.length];
for (int i = 0; i < this.sign.length; i++) {
info[i] = new Signature(this.sign[i]);
}
// Signature[] info = (Signature[]) infoField.get(mSigningDetails);//获取属性值
infoField.set(mSigningDetails, info);
}// else { //双hook
packageInfo.signatures = new Signature[this.sign.length];
for (int i = 0; i < packageInfo.signatures.length; i++) {
packageInfo.signatures[i] = new Signature(this.sign[i]);
// }
}
return packageInfo;
}
}
if ("getPackageArchiveInfo".equals(method.getName())) {//从安装路径获取apk签名方法
Log.e("mhyLogHook","getPackageArchiveInfo");
// getPackageArchiveInfo(String archiveFilePath, @PackageInfoFlags int flags)
String str = (String) objArr[0];//第一个参数
if ((((Integer) objArr[1]).intValue() & 64) != 0 && this.appPath.equals(str)) {
PackageInfo packageInfo = (PackageInfo) method.invoke(this.base, objArr);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
SigningInfo signingInfo= packageInfo.signingInfo;
// Class<?> sgInfo_clazz=SigningInfo.class;
// Field mSigningDetails_f=sgInfo_clazz.getDeclaredField("mSigningDetails");
// mSigningDetails_f.setAccessible(true);
// Object mSigningDetails = mSigningDetails_f.get(signingInfo);
Class<?> pkgParserCls = Class.forName("android.content.pm.PackageParser");
Constructor<?> pkgParserCt = pkgParserCls.getConstructor();//null;
Object pkgParser = pkgParserCt.newInstance();//null;
// pkgParserCt =
// pkgParser =
Method pkgParser_parsePackageMtd = pkgParserCls.getDeclaredMethod("parsePackage", File.class, int.class);
Object pkgParserPkg = pkgParser_parsePackageMtd.invoke(pkgParser, new File(getPackageResourcePath()), PackageManager.GET_SIGNING_CERTIFICATES);
Method pkgParser_collectCertificatesMtd = pkgParserCls.getDeclaredMethod("collectCertificates", pkgParserPkg.getClass(), Boolean.TYPE);
pkgParser_collectCertificatesMtd.invoke(pkgParser, pkgParserPkg, true);
// Method pkgParser_collectCertificatesMtd = pkgParserCls.getDeclaredMethod("collectCertificates", pkgParserPkg.getClass(), Boolean.TYPE);
// pkgParser_collectCertificatesMtd.invoke(pkgParser, pkgParserPkg, false);
Field mSigningDetailsField = pkgParserPkg.getClass().getDeclaredField("mSigningDetails"); // SigningDetails
mSigningDetailsField.setAccessible(true);
Object mSigningDetails = mSigningDetailsField.get(pkgParserPkg);
Field infoField = mSigningDetails.getClass().getDeclaredField("signatures");
infoField.setAccessible(true);
Signature[] info = new Signature[this.sign.length];
for (int i = 0; i < this.sign.length; i++) {
info[i] = new Signature(this.sign[i]);
}
// Signature[] info = (Signature[]) infoField.get(mSigningDetails);//获取属性值
infoField.set(mSigningDetails, info);
} //else { //双hook
packageInfo.signatures = new Signature[this.sign.length];
for (int i = 0; i < packageInfo.signatures.length; i++) {
packageInfo.signatures[i] = new Signature(this.sign[i]);
// }
}
return packageInfo;
}
}
return method.invoke(this.base, objArr);
}
}

View File

@@ -1,34 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path
android:fillType="evenOdd"
android:pathData="M32,64C32,64 38.39,52.99 44.13,50.95C51.37,48.37 70.14,49.57 70.14,49.57L108.26,87.69L108,109.01L75.97,107.97L32,64Z"
android:strokeWidth="1"
android:strokeColor="#00000000">
<aapt:attr name="android:fillColor">
<gradient
android:endX="78.5885"
android:endY="90.9159"
android:startX="48.7653"
android:startY="61.0927"
android:type="linear">
<item
android:color="#44000000"
android:offset="0.0" />
<item
android:color="#00000000"
android:offset="1.0" />
</gradient>
</aapt:attr>
</path>
<path
android:fillColor="#FFFFFF"
android:fillType="nonZero"
android:pathData="M66.94,46.02L66.94,46.02C72.44,50.07 76,56.61 76,64L32,64C32,56.61 35.56,50.11 40.98,46.06L36.18,41.19C35.45,40.45 35.45,39.3 36.18,38.56C36.91,37.81 38.05,37.81 38.78,38.56L44.25,44.05C47.18,42.57 50.48,41.71 54,41.71C57.48,41.71 60.78,42.57 63.68,44.05L69.11,38.56C69.84,37.81 70.98,37.81 71.71,38.56C72.44,39.3 72.44,40.45 71.71,41.19L66.94,46.02ZM62.94,56.92C64.08,56.92 65,56.01 65,54.88C65,53.76 64.08,52.85 62.94,52.85C61.8,52.85 60.88,53.76 60.88,54.88C60.88,56.01 61.8,56.92 62.94,56.92ZM45.06,56.92C46.2,56.92 47.13,56.01 47.13,54.88C47.13,53.76 46.2,52.85 45.06,52.85C43.92,52.85 43,53.76 43,54.88C43,56.01 43.92,56.92 45.06,56.92Z"
android:strokeWidth="1"
android:strokeColor="#00000000" />
</vector>

View File

@@ -1,5 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>

View File

@@ -1,5 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

BIN
jni属性签名.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.4 KiB