VPS

Android逆向安全那些事

Posted by on October 9, 2018

由于最近需要加密一些硬编码的字段

apk反编译成功之后,app中的所有硬编码都能看到

常见的加密方式

  • 字符串加密(编译时间增加,AS升级插件兼容问题,而且这个反编译完毕之后也能看到加密方法,只能增加破解者根据字符找突破口难度)
  • 硬编码写到JNI中(目前采用办法)

硬编码写到JNI中

  • 使用NDK开发出来的原生C++代码编译后生成的so库是一个二进制文件,这无疑增加了破解的难度。利用这个特性,可以将客户端敏感信息写在C++代码中,增强应用的安全性。

  • 虽然native方法与java代码存在一个对应关系,如包名、路径、方法名称,但这也是可以轻松实现的。所以,将敏感信息存放在so库中有一个不得不考虑的问题就是,万一别人将你的so库直接copy出来拿去用了呢?因此,我们还需要在native层对应用的包名、签名进行鉴权校验,如果不是自己的应用,不返回相关信息,或者直接退出应用!(目前这种方案较多爱奇艺,优酷,等一些软件重新打包之后基本都活验证签名,不一样直接退出或者返回失败)

  • 由于不太懂C,所以也是找了份代码改改,我们使用AS来创建一个NDK工程 这里可以看到多了个CPP目录这里就是我们要写的C代码

#include <jni.h>
#include <string>
#include <string.h>
#include <malloc.h>
#include "native-lib.h"


extern "C"
const char *AUTH_KEY = "xxx";
const char *AUTH_KGC_KEY = "assac";
const char *PACKAGE_NAME = "com.xasdasds";
const char *RELEASE_SIGN_MD5 = "2634EFD64B5CDD13A9CCA6049949D26E";

/**
 * getApplication
 * @param env
 * @return j_object
 */
static jobject getApplication(JNIEnv *env) {
    jobject application = NULL;
    jclass activity_thread_clz = env->FindClass("android/app/ActivityThread");
    if (activity_thread_clz != NULL) {
        jmethodID currentApplication = env->GetStaticMethodID(
                activity_thread_clz, "currentApplication", "()Landroid/app/Application;");
        if (currentApplication != NULL) {
            application = env->CallStaticObjectMethod(activity_thread_clz, currentApplication);
        }
        env->DeleteLocalRef(activity_thread_clz);
    }
    return application;
}

/**
 * HexToString
 * @param source
 * @param dest
 * @param sourceLen
 */
static void ToHexStr(const char *source, char *dest, int sourceLen) {
    short i;
    char highByte, lowByte;

    for (i = 0; i < sourceLen; i++) {
        highByte = source[i] >> 4;
        lowByte = (char) (source[i] & 0x0f);
        highByte += 0x30;

        if (highByte > 0x39) {
            dest[i * 2] = (char) (highByte + 0x07);
        } else {
            dest[i * 2] = highByte;
        }

        lowByte += 0x30;
        if (lowByte > 0x39) {
            dest[i * 2 + 1] = (char) (lowByte + 0x07);
        } else {
            dest[i * 2 + 1] = lowByte;
        }
    }
}

/**
 *
 * byteArrayToMd5
 * @param env
 * @param source
 * @return j_string
 */
static jstring ToMd5(JNIEnv *env, jbyteArray source) {
    // MessageDigest
    jclass classMessageDigest = env->FindClass("java/security/MessageDigest");
    // MessageDigest.getInstance()
    jmethodID midGetInstance = env->GetStaticMethodID(classMessageDigest, "getInstance",
                                                      "(Ljava/lang/String;)Ljava/security/MessageDigest;");
    // MessageDigest object
    jobject objMessageDigest = env->CallStaticObjectMethod(classMessageDigest, midGetInstance,
                                                           env->NewStringUTF("md5"));

    jmethodID midUpdate = env->GetMethodID(classMessageDigest, "update", "([B)V");
    env->CallVoidMethod(objMessageDigest, midUpdate, source);

    // Digest
    jmethodID midDigest = env->GetMethodID(classMessageDigest, "digest", "()[B");
    jbyteArray objArraySign = (jbyteArray) env->CallObjectMethod(objMessageDigest, midDigest);

    jsize intArrayLength = env->GetArrayLength(objArraySign);
    jbyte *byte_array_elements = env->GetByteArrayElements(objArraySign, NULL);
    size_t length = (size_t) intArrayLength * 2 + 1;
    char *char_result = (char *) malloc(length);
    memset(char_result, 0, length);

    ToHexStr((const char *) byte_array_elements, char_result, intArrayLength);
    // 在末尾补\0
    *(char_result + intArrayLength * 2) = '\0';

    jstring stringResult = env->NewStringUTF(char_result);
    // release
    env->ReleaseByteArrayElements(objArraySign, byte_array_elements, JNI_ABORT);
    // 指针
    free(char_result);

    return stringResult;
}

extern "C"
JNIEXPORT jstring JNICALL
Java_com_bdqn_kegongchang_utils_JniUtils_getSignature(JNIEnv *env, jclass type) {

    jobject context = getApplication(env);
    // 获得Context类
    jclass cls = env->GetObjectClass(context);
    // 得到getPackageManager方法的ID
    jmethodID mid = env->GetMethodID(cls, "getPackageManager",
                                     "()Landroid/content/pm/PackageManager;");

    // 获得应用包的管理器
    jobject pm = env->CallObjectMethod(context, mid);

    // 得到getPackageName方法的ID
    mid = env->GetMethodID(cls, "getPackageName", "()Ljava/lang/String;");
    // 获得当前应用包名
    jstring packageName = (jstring) env->CallObjectMethod(context, mid);
    const char *c_pack_name = env->GetStringUTFChars(packageName, NULL);

    // 比较包名,若不一致,直接return包名
    if (strcmp(c_pack_name, PACKAGE_NAME) != 0) {
        return (env)->NewStringUTF(c_pack_name);
    }
    //如果包名正确返回当前的信息
    return (env)->NewStringUTF(AUTH_KEY);
}

extern "C"
JNIEXPORT jstring JNICALL
Java_com_bdqn_kegongchang_utils_JniUtils_getCkSignature(JNIEnv *env, jclass type) {
  jobject context = getApplication(env);
  // 获得Context类
  jclass cls = env->GetObjectClass(context);
  // 得到getPackageManager方法的ID
  jmethodID mid = env->GetMethodID(cls, "getPackageManager",
                                   "()Landroid/content/pm/PackageManager;");

  // 获得应用包的管理器
  jobject pm = env->CallObjectMethod(context, mid);

  // 得到getPackageName方法的ID
  mid = env->GetMethodID(cls, "getPackageName", "()Ljava/lang/String;");
  // 获得当前应用包名
  jstring packageName = (jstring) env->CallObjectMethod(context, mid);
  const char *c_pack_name = env->GetStringUTFChars(packageName, NULL);

  // 比较包名,若不一致,直接return包名
  if (strcmp(c_pack_name, PACKAGE_NAME) != 0) {
    return (env)->NewStringUTF(c_pack_name);
  }
  //如果包名正确返回当前的信息
  return (env)->NewStringUTF(AUTH_KGC_KEY);
}

这里是我拷贝网上一段代码,注释很清楚,就是判断当前应用的包名,然后对比,如果一样就返回数据。

public class JniUtils {
    //加载so
    static {
        System.loadLibrary("native-lib");
    }


    /**
     *获取jni层的签名key
     * @return
     */
    public static native String getSignature();

    /**
     *获取jni层的签名key
     * @return
     */
    public static native String getCkSignature();
}

提供了两个方法供调用

  • 打包成so 当我们编译的时候已经自动生成了so库直接拷贝就可以使用了,这样做只能增加破解门槛,无法防止破解。