huaweiP9 会给主动给app加一个trace进程
通过学习四哥的
Android安全防护之旅—Android应用”反调试”操作的几种方案解析
我们可以知道通过
第一、自己附加进程,先占坑,ptrace(PTRACE_TRACEME, 0, 0, 0)!
第二、签名校验不可或缺的一个选择,本地校验和服务端校验双管齐下!
第三、借助系统api判断应用调试状态和调试属性,最基础的防护!
第四、轮训检查android_server调试端口信息和进程信息,防护IDA的一种有效方式!
第五、轮训检查自身status中的TracerPid字段值,防止被其他进程附加调试的一种有效方式!
的方式来进行反调试
四哥也放出了源码地址
https://github.com/fourbrother/android_anti_debug
但是该源码在编译时报过时异常,其实就是AndroidMake方式过时了,要求用Cmake。
该篇笔记用Cmake方式进行操作学习(源码地址在文末)
本质就是把c代码实现改为c++实现。
准备工作
1.基本的jni开发知识
可以参考
android ndk开发索引
对ndk开发有一个基本了解,比如返回值类型,jni如何调用java方法等
2.so库加载的基本概念
可以参考
Android逆向新手答疑解惑篇——JNI与动态注册
JNI解析以及在Android中的实际应用
了解加载流程。
正式动工
首先当做你已经明白了so加载为什么会走jni_onLoad函数,
实在不懂就当作 重写了onCreate方法
1.自己提前附加一个进程
很简单,一行代码
ptrace(PTRACE_TRACEME, 0, 0, 0);
2.校验签名
纯java方法的
public static String getSignature() {
Context ctx = MyApplication.getContext();
try {
//通过包管理器获得指定包名包含签名的包信息
PackageInfo packageInfo = ctx.
getPackageManager()
.getPackageInfo(ctx.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 "";
}
jni的(其实就是jni调java方法)
int check_signature(JNIEnv *env) {
//调用Java层的Utils中的获取签名的方法
jclass javaUtilClass = env->FindClass("com/example/lahm/ctest/Utils");
if (javaUtilClass == NULL) {
LOGD("not find class");
return JNI_FALSE;
}
LOGD("class name:%p", javaUtilClass);
jmethodID method = (env)->GetStaticMethodID(javaUtilClass, "getSignature",
"()Ljava/lang/String;");
if (method == NULL) {
LOGD("not find method '%s'", method);
return JNI_FALSE;
}
jstring obj = (jstring) (env)->CallStaticObjectMethod(javaUtilClass, method);
if (obj == NULL) {
LOGD("method invoke error:%p", obj);
return JNI_FALSE;
}
const char *strAry = (env)->GetStringUTFChars(obj, 0);
int cmpVal = strcmp(strAry, app_signature);
(env)->ReleaseStringUTFChars(obj, strAry);
if (cmpVal == 0)
return JNI_TRUE;
else
return JNI_FALSE;
}
3.检查debug状态
类似与第2点,更加简单,java方法就一行。
public static boolean checkIsDebugB() {
return android.os.Debug.isDebuggerConnected();
}
还可以去拿ApplicationInfo里的字段,这里不展开了。
4和5.查看调试端口&查看traceid
现在很多的加固方法,门槛就是这个,通过开启一个子线程,读取
/proc/[pid]/status文件,拿到traceid,如果不为0,则认为该app已经被调试了,此时杀死进程。
那么要点在
开启子线程,轮询文件
void create_thread_check_traceid() {
pthread_t t_id;
int err = pthread_create(&t_id, NULL, thread_function, NULL);
if (err != 0) {
LOGD("create thread fail: %s\n", strerror(err));
}
}
pthread_create的用法我是参考的
http://blog.csdn.net/liangxanhai/article/details/7767430
轮询文件
void *thread_function(void *argv) {
int pid = getpid();
char file_name[20] = {'\0'};
sprintf(file_name, "/proc/%d/status", pid);
char linestr[256];
int i = 0, traceid;
FILE *fp;
while (1) {
i = 0;
fp = fopen(file_name, "r");
if (fp == NULL) {
break;
}
while (!feof(fp)) {
fgets(linestr, 256, fp);
if (i == 5) {
traceid = get_number_for_str(linestr);
LOGD("traceId:%d", traceid);
if (traceid > 0) {
LOGD("I was be traced...trace pid:%d", traceid);
exit(0);
}
break;
}
i++;
}
fclose(fp);
sleep(5);
}
return ((void *) 0);
}
如果你已经使用了1方法,自己附加了一个进程,那就不要用轮询了traceid了,改用端口监听吧。
动态注册
JNIEXPORT jint JNICALL
JNI_OnLoad(JavaVM *vm, void *reserved) {
...
}
jni_onLoad的方法只给了JavaVM环境
需要先获得jni环境
if (vm->GetEnv((void **) &env, JNI_VERSION_1_6) != JNI_OK) {
LOGD("ERROR: GetEnv failed\n");
return JNI_ERR;
}
然后再调用RegisterNatives注册,传的参数
clazz 就是native函数所在的类,可提前调用env->FindClass获取,methods是一个数组,其中包含注册信息,nMethods是数量。
JNINativeMethod methods[] = {
{"isEquals", "(Ljava/lang/String;)Z", (void *) isEquals},
};
jclass clazz = env->FindClass("com/example/lahm/ctest/MyApplication");
if (clazz == NULL) {
LOGD("Native registration unable to find class '%s'", className);
return JNI_ERR;
}
int methodsLength;
//建立方法隐射关系
//取得方法长度
methodsLength = sizeof(methods) / sizeof(methods[0]);
if (env->RegisterNatives(clazz, methods, methodsLength) != 0) {
LOGD("RegisterNatives failed for '%s'", className);
return JNI_ERR;
}
完成动态注册,记得要在申明该native方法的类里先写上方法名,
cpp文件里写上同名方法(就不是自动生成的jni方法名),不然找不到。
liblog的调用
因为四哥的代码写了很多的log,所以他写了个宏
#include
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, "ceshi", __VA_ARGS__)
但是这样写,无法通过编译
需要在CmakeLists文件里链接上去(这个是官方指导)
find_library( # 设置外部引用库.这个库支持你在c/c++中打印log,具体请见 android/log.h
log-lib
# 外部引用库的名称
log )
target_link_libraries( # 指定被链接的库.
ctest
# 链接log-lib到ctest
${log-lib} )
#将不同的源文件编译成多份so包
add_library( ccheck SHARED src/main/cpp/ccheck.cpp )
target_link_libraries(ccheck ${log-lib})
至此,完成学习。
完整代码地址
https://github.com/lamster2018/learnNDK
至于以上方法的反制方式
四哥也写了
Android逆向之旅—应用的”反调试”方案解析(附加修改IDA调试端口和修改内核信息
另附上
看雪出品-企业壳反调试及hook检测分析
Android so加固
https://mp.weixin.qq.com/s/BhGxnJrRnrYAEWJ7zDZKmQ
----后续更--
还有一种类似检查签名的方法--检查dex的完整性防二次打包
Android代码保护(签名校验、classes.dex文件完整性校验)
private void checkCRC() {
String orginalCrc = getString(R.string.str_code);
ZipFile zf;
try {
zf = new ZipFile(getApplicationContext().getPackageCodePath());
ZipEntry ze = zf.getEntry("classes.dex");
String strCrc = String.valueOf(ze.getCrc());
String MD5Crc = MD5Util.GetMD5Code(strCrc);
Log.e("checkcrc", MD5Crc);
if (!orginalCrc.equals(MD5Crc)) {
//ActivityManagerUtil.getScreenManager().removeAllActivity();
Process.killProcess(Process.myPid());
}
} catch (IOException e) {
e.printStackTrace();
}
}
文中也提到
"DEX文件打包在APK文件中,针对APK代码的篡改攻击就是针对该文件,比如使用apktool工具反编译文件,修改smali代码。以下就是通过检查classes.dex文件的CRC32值来判断文件是否被篡改。
其中R.string.str_code是存放在本地的crc加密后的值,string.xml文件是不在dex文件中的,所以修改string.xml文件是不会造成crc值的变化(亲测),其实也可以把这个值存放在后台,通过请求网络来获取。"
本质和检查签名是一样的,不过是提供另一种思路。