apk反调试-模拟器检查-签名检测

一、模拟器检测

  1. 基于模拟器的IMSI、IDS、默认文件等几个方面特征进行检测
  2. 基于CPU的检测(如是否包含inter、amd等字段)
  3. 电池信息、温度、电量变化等

代码示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//需要添加android.permission.READ_PHONE_STATE权限
public boolean isEmulatorByImei(Context context){
TelephonyManager tm = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
String imei = tm.getDeviceId();
if (imei == null || imei.equals("000000000000000")){
return true;
}
return false;
}

public boolean isEmulatorByBuildModel() {
Log.e("MODEL=", Build.MODEL);
Log.e("MANUFACTURER=",Build.MANUFACTURER);
return ( Build.MODEL.equals("sdk")) || (Build.MODEL.equals("google_sdk") );
}

二、签名监测(native)

签名检测在一般在java层实现,通过与远程服务器交互完成校验。如果没有本地校验,则尽可能在native层实现,这里利用反射的方式。

代码示例:

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
void SignatureCheck(JNIEnv *env, jobject context)
{
jclass context_clazz = (*env)->GetObjectClass(env, context);//Context的类

jmethodID methodID_getPackageManager = (*env)->GetMethodID(env, context_clazz,"getPackageManager", "()Landroid/content/pm/PackageManager;");// 得到 getPackageManager 方法的 ID

jobject packageManager = (*env)->CallObjectMethod(env, context,
methodID_getPackageManager);// 获得PackageManager对象

jclass pm_clazz = (*env)->GetObjectClass(env, packageManager);// 获得 PackageManager 类

jmethodID methodID_pm = (*env)->GetMethodID(env, pm_clazz, "getPackageInfo",
"(Ljava/lang/String;I)Landroid/content/pm/PackageInfo;");// 得到 getPackageInfo 方法的 ID

jmethodID methodID_pack = (*env)->GetMethodID(env, context_clazz,
"getPackageName", "()Ljava/lang/String;");// 得到 getPackageName 方法的 ID


jstring application_package = (*env)->CallObjectMethod(env, context,
methodID_pack);// 获得当前应用的包名

const char *str = (*env)->GetStringUTFChars(env, application_package, 0);
//__android_log_print(ANDROID_LOG_DEBUG, "JNI", "packageName: %s\n", str);

jobject packageInfo = (*env)->CallObjectMethod(env, packageManager,
methodID_pm, application_package, 64);// 获得PackageInfo

jclass packageinfo_clazz = (*env)->GetObjectClass(env, packageInfo);
jfieldID fieldID_signatures = (*env)->GetFieldID(env, packageinfo_clazz,
"signatures", "[Landroid/content/pm/Signature;");
jobjectArray signature_arr = (jobjectArray)(*env)->GetObjectField(env,
packageInfo, fieldID_signatures);

jobject signature = (*env)->GetObjectArrayElement(env, signature_arr, 0);//Signature数组中取出第一个元素

jclass signature_clazz = (*env)->GetObjectClass(env, signature);//读signature的hashcode
jmethodID methodID_hashcode = (*env)->GetMethodID(env, signature_clazz,
"hashCode", "()I");
jint hashCode = (*env)->CallIntMethod(env, signature, methodID_hashcode);

//__android_log_print(ANDROID_LOG_DEBUG, "JNI", "hashcode: %d\n", hashCode);

if (hashCode != 1133691199)//判断与自己签名的hashcode是否相同
{
exit(0);
//__android_log_print(ANDROID_LOG_DEBUG, TAG, "Repackaged App, will exit :(",hashCode);
}


}

三、反调试

1. ptrace

ida、gdb等调试工具其实都是使用ptrace进行的, ptrace有一个很重要的特定,一个进程只能被其它一个进程调试。当没有被调试时,/proc/(pid)/status文件中TracePid为0,即

(1) 如果TracePid不为0时,说明进程正在被调试。

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
#include <stdio.h>
#include <string.h>
#include <jni.h>
#include <android/log.h>
#include <stdlib.h>
#include <sys/ptrace.h>
#include <unistd.h>
#include <pthread.h>
void be_attached_check()
{

const int bufsize = 1024;
char filename[bufsize];
char line[bufsize];
int pid = getpid();
sprintf(filename, "/proc/%d/status", pid);
FILE* fd = fopen(filename, "r");
if (fd != NULL)
{
while (fgets(line, bufsize, fd))
{
if (strncmp(line, "TracerPid", 9) == 0)
{
int statue = atoi(&line[10]);
//LOGD("%s", line);
if (statue != 0)
{
//LOGD("be attached !! kill %d", pid);
fclose(fd);
int ret = kill(pid, SIGKILL);
}
break;
}
}
fclose(fd);
} else
{
LOGD("open %s fail...", filename);
}


}

(2) 进程自己prace自己,就不会被第三方调试了。在jni_OnLoad函数中ptrace自身,代码如下:

1
2
3
4
5
6
7
8
9
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* pVm, void* reserved)
{
ptrace(PTRACE_TRACEME, 0, 0, 0);
JNIEnv* env;
if ((*pVm)->GetEnv(pVm, (void **)&env, JNI_VERSION_1_6)) {
return -1;
}
return JNI_VERSION_1_6;
}

2. 暗桩

ptrace容易被逆向搞掉,暗桩是在多个地方插入检测代码,增加逆向难度,例如,新建一个进程每隔几秒检测一次:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
void thread_task(int n)
{
while (1)//进程每隔3秒检测一次
{
//LOGD("start be_attached_check...");
be_attached_check();
sleep(3);
}
}

void anti_debug()
{
pthread_t pthread_id;
int i,ret;
int m=2;
ret=pthread_create(&pthread_id,NULL,(void *)thread_task,(void *)1);
if (ret!=0) {
printf("create pthread error!\n");
exit(0);
}
}

3. 多个进程互相ptrace