Android反调试浅析
2017-08-19 本文已影响281人
大批
侵删:)
本文的主要内容
- 验证apk是否被重新打包
- 验证apk是否开启了调试,以及是否有debugger已经连接
- 检测进程的TracerPid
验证签名
破解apk的时候一般都会将apk反编译,然后修改一些东西,再编译成apk(这个时候的签名和发布版本的签名是不一样的)
- 验证签名是否和发布版本一致最好是放到服务器验证(本地传一个签名信息到服务器,服务器只需返回是否正确就行)
- java层验证 (这里仅仅是获取了签名信息的 hascode)
public static int getSignature(Context context){
PackageManager pm = context.getPackageManager();
PackageInfo pi;
StringBuilder sb = new StringBuilder();
try {
pi = pm.getPackageInfo(context.getPackageName(),PackageManager.GET_SIGNATURES);
Signature[] signatures = pi.signatures;
for(Signature signature : signatures){
sb.append(signature.toCharsString());
}
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
}
Log.i(LOG_TAG,"获取到的签名信息 : "+sb.toString());
return sb.toString().hashCode();
}
//这个是获取SHA1的方法 上面那个方法是获取签名的hash值 这个和cmd里面获取的是一样的
public static String getCertificateSHA1Fingerprint(Context context) {
//获取包管理器
PackageManager pm = context.getPackageManager();
//获取当前要获取SHA1值的包名,也可以用其他的包名,但需要注意,
//在用其他包名的前提是,此方法传递的参数Context应该是对应包的上下文。
String packageName = context.getPackageName();
//返回包括在包中的签名信息
int flags = PackageManager.GET_SIGNATURES;
PackageInfo packageInfo = null;
try {
//获得包的所有内容信息类
packageInfo = pm.getPackageInfo(packageName, flags);
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
}
//签名信息
Signature[] signatures = packageInfo.signatures;
byte[] cert = signatures[0].toByteArray();
//将签名转换为字节数组流
InputStream input = new ByteArrayInputStream(cert);
//证书工厂类,这个类实现了出厂合格证算法的功能
CertificateFactory cf = null;
try {
cf = CertificateFactory.getInstance("X509");
} catch (CertificateException e) {
e.printStackTrace();
}
//X509证书,X.509是一种非常通用的证书格式
X509Certificate c = null;
try {
c = (X509Certificate) cf.generateCertificate(input);
} catch (CertificateException e) {
e.printStackTrace();
}
String hexString = null;
try {
//加密算法的类,这里的参数可以使MD4,MD5等加密算法
MessageDigest md = MessageDigest.getInstance("SHA1");
//获得公钥
byte[] publicKey = md.digest(c.getEncoded());
//字节到十六进制的格式转换
hexString = byte2HexFormatted(publicKey);
} catch (NoSuchAlgorithmException e1) {
e1.printStackTrace();
} catch (CertificateEncodingException e) {
e.printStackTrace();
}
return hexString;
}
//这里是将获取到得编码进行16进制转换
private static String byte2HexFormatted(byte[] arr) {
StringBuilder str = new StringBuilder(arr.length * 2);
for (int i = 0; i < arr.length; i++) {
String h = Integer.toHexString(arr[i]);
int l = h.length();
if (l == 1)
h = "0" + h;
if (l > 2)
h = h.substring(l - 2, l);
str.append(h.toUpperCase());
if (i < (arr.length - 1))
str.append(':');
}
return str.toString();
}
- native层验证(将获取签名信息方法放到native层,这里顺带讲讲jni开发)
-
现在的Android studio 2.2 之后就已经可以使用cmake进行ndk开发了(cmake相比以前的传统方法来说更加方便)
-
首先是配置app的build.gradle
- 主要是配置需要生成的平台和CMakeLists.txt的路径
android { ... defaultConfig { ... externalNativeBuild { cmake { abiFilters 'x86', 'x86_64', 'armeabi', 'armeabi-v7a', 'arm64-v8a' } } } externalNativeBuild { cmake { path "src/main/cpp/CMakeLists.txt" } } }
-
创建保存源文件的目录(传统的目录名字是jni,cmake的方式是cpp)
-
创建一个c源文件,这里已经不需要先生成头文件了(源文件里面直接按照jni的写法写函数就行了)
-
#include <string.h>
#include <jni.h>
jstring
Java_com_suse_yuxin_opengldemo_OpenGL20DemoActivity_helloJni( JNIEnv* env,
jobject thiz ){
return (*env)->NewStringUTF(env,"hello Jni.");
}
public native String helloJni();
static {
System.loadLibrary("native-lib");
}
- 配置cmake文件(CMakeLists.txt里面的配置也比较简单 百度百度就知道了)
#指定 cmake的版本
cmake_minimum_required(VERSION 3.4.1)
add_library( native-lib
SHARED
native-lib.c )
target_link_libraries(native-lib log android)
- 然后就可以运行了,回到正题就是jni获取签名信息
#include <string.h>
#include <jni.h>
const char HexCode[]={'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'};
jobject getApplication(JNIEnv *env) {
jclass localClass = (*env)->FindClass(env,"android/app/ActivityThread");
if (localClass!=NULL)
{
// LOGI("class have find");
jmethodID getapplication = (*env)->GetStaticMethodID(env,localClass, "currentApplication", "()Landroid/app/Application;");
if (getapplication!=NULL)
{
jobject application = (*env)->CallStaticObjectMethod(env,localClass, getapplication);
return application;
}
return NULL;
}
return NULL;
}
jstring
Java_com_suse_yuxin_opengldemo_OpenGL20DemoActivity_stringFromJNI( JNIEnv* env,
jobject thiz )
{
//获取到Context
jobject context= getApplication(env);
if(context == NULL){
return NULL;
}
jclass activity = (*env)->GetObjectClass(env,context);
// 得到 getPackageManager 方法的 ID
jmethodID methodID_func = (*env)->GetMethodID(env,activity, "getPackageManager", "()Landroid/content/pm/PackageManager;");
// 获得PackageManager对象
jobject packageManager = (*env)->CallObjectMethod(env,context,methodID_func);
jclass packageManagerclass = (*env)->GetObjectClass(env,packageManager);
//得到 getPackageName 方法的 ID
jmethodID methodID_pack = (*env)->GetMethodID(env,activity,"getPackageName", "()Ljava/lang/String;");
//获取包名
jstring name_str = (jstring)((*env)->CallObjectMethod(env,context, methodID_pack));
// 得到 getPackageInfo 方法的 ID
jmethodID methodID_pm = (*env)->GetMethodID(env,packageManagerclass,"getPackageInfo", "(Ljava/lang/String;I)Landroid/content/pm/PackageInfo;");
// 获得应用包的信息
jobject package_info = (*env)->CallObjectMethod(env,packageManager, methodID_pm, name_str, 64);
// 获得 PackageInfo 类
jclass package_infoclass = (*env)->GetObjectClass(env,package_info);
// 获得签名数组属性的 ID
jfieldID fieldID_signatures = (*env)->GetFieldID(env,package_infoclass,"signatures", "[Landroid/content/pm/Signature;");
// 得到签名数组,待修改
jobject signatur = (*env)->GetObjectField(env,package_info, fieldID_signatures);
jobjectArray signatures = (jobjectArray)(signatur);
// 得到签名
jobject signature = (*env)->GetObjectArrayElement(env,signatures, 0);
// 获得 Signature 类,待修改
jclass signature_clazz = (*env)->GetObjectClass(env,signature);
//---获得签名byte数组
jmethodID tobyte_methodId = (*env)->GetMethodID(env,signature_clazz, "toByteArray", "()[B");
jbyteArray signature_byte = (jbyteArray) (*env)->CallObjectMethod(env,signature, tobyte_methodId);
//把byte数组转成流
jclass byte_array_input_class=(*env)->FindClass(env,"java/io/ByteArrayInputStream");
jmethodID init_methodId=(*env)->GetMethodID(env,byte_array_input_class,"<init>","([B)V");
jobject byte_array_input=(*env)->NewObject(env,byte_array_input_class,init_methodId,signature_byte);
//实例化X.509
jclass certificate_factory_class=(*env)->FindClass(env,"java/security/cert/CertificateFactory");
jmethodID certificate_methodId=(*env)->GetStaticMethodID(env,certificate_factory_class,"getInstance","(Ljava/lang/String;)Ljava/security/cert/CertificateFactory;");
jstring x_509_jstring=(*env)->NewStringUTF(env,"X.509");
jobject cert_factory=(*env)->CallStaticObjectMethod(env,certificate_factory_class,certificate_methodId,x_509_jstring);
//certFactory.generateCertificate(byteIn);
jmethodID certificate_factory_methodId=(*env)->GetMethodID(env,certificate_factory_class,"generateCertificate",("(Ljava/io/InputStream;)Ljava/security/cert/Certificate;"));
jobject x509_cert=(*env)->CallObjectMethod(env,cert_factory,certificate_factory_methodId,byte_array_input);
jclass x509_cert_class=(*env)->GetObjectClass(env,x509_cert);
jmethodID x509_cert_methodId=(*env)->GetMethodID(env,x509_cert_class,"getEncoded","()[B");
jbyteArray cert_byte=(jbyteArray)(*env)->CallObjectMethod(env,x509_cert,x509_cert_methodId);
//MessageDigest.getInstance("SHA1")
jclass message_digest_class=(*env)->FindClass(env,"java/security/MessageDigest");
jmethodID methodId=(*env)->GetStaticMethodID(env,message_digest_class,"getInstance","(Ljava/lang/String;)Ljava/security/MessageDigest;");
//如果取SHA1则输入SHA1
//jstring sha1_jstring=(*env)->NewStringUTF(env,"SHA1");
jstring sha1_jstring=(*env)->NewStringUTF(env,"MD5");
jobject sha1_digest=(*env)->CallStaticObjectMethod(env,message_digest_class,methodId,sha1_jstring);
//sha1.digest (certByte)
methodId=(*env)->GetMethodID(env,message_digest_class,"digest","([B)[B");
jbyteArray sha1_byte=(jbyteArray)(*env)->CallObjectMethod(env,sha1_digest,methodId,cert_byte);
//toHexString
jsize array_size=(*env)->GetArrayLength(env,sha1_byte);
jbyte* sha1 =(*env)->GetByteArrayElements(env,sha1_byte,NULL);
char hex_sha[array_size*2+1];
int i;
for (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';
return (*env)->NewStringUTF(env, hex_sha);
}
可调式状态校验
-
一个是AndroidManifest的调试flag是否打开
/**
*
* 验证是否可以调试
* i != 0 已经打开可调式
*/
int i = getApplicationInfo().flags & ApplicationInfo.FLAG_DEBUGGABLE;
- debugger是否已经连接
boolean debuggerConnected = Debug.isDebuggerConnected();
Log.i(LOG_TAG,"是否连接调试 : "+debuggerConnected);
获取获取TracerPid来判断(TracerPid正常情况是0,如果被调试这个是不为0的)
/**
*
* 获取TracerPid来判断
*
*/
int pid = android.os.Process.myPid();
String info = null;
File file = new File("/proc/" + pid + "/status");
try {
FileInputStream fileInputStream = new FileInputStream(file);
InputStreamReader reader = new InputStreamReader(fileInputStream);
BufferedReader bufferedReader = new BufferedReader(reader);
while((info = bufferedReader.readLine()) != null){
Log.i(LOG_TAG,"proecc info : "+info);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
-
上面这个是获取到了进程的各种状态下面给一个log日志信息
Nothing is certain in this life. The only thing i know for sure is that. I love you and my life. That is the only thing i know. have a good day