计算机黑客或者说是黑帽子和病毒及木马是软件业和互联网发展的三大癌细胞,靠不停的攫取互联网的养分茁壮成长,生生不息,无孔不入。近些年智能手机的飞速发展使其成为替代pc成为新的黑客乐园,因此研究APP安全问题,保卫个人企业的劳动成果,建立防御战线必不可少。
要解决app安全问题,APP的攻击手段不能不知道。无非解包,修改,编辑,打包,一招一式,一攻一守,都要有应对之道。
1.APP攻击首先是解包,APK本身是一个压缩包,对于未加密的app来说,使用apktool及其更高级的组合解包工具可以轻松获取APP的资源,smail代码,这是第一步攻击,这种攻击危害比较小,可能主要目的是获取我们的APP和资源和实现方法。这一步可以通过一些加固手段以防范,可以是通用的爱加密或者360之类或者自己研发的独有的,
2,解包后进一步对代码进行分析,如还原为JAVA源码,这就可以非常方便的修改原码,学习APP核心实现,把更多的资源用于其他方面。比如去掉原app广告,加入自己广告,加入支付宝红包代码,破解一些UI效果或者算法库用于自己APP,窃取用户信息,收集APP资源。这一步对于所有APP来说危害是最大的,尤其是以算法为核心的APP如美颜,日历类危险是致命的。这一步可以通过混淆,通过jni等方法把一些核心的算法加密,并通过md5检测安装文件完整性。
3重新打包当作新APP发布或者当作盗版app发布,这一步危害也不小,比如本身不带广告的产品被加入广告发布,本身不收集用户信息的变成了收集信息,本身有广告的,被去掉了广告,对原APP声誉和挣钱影响非常大,同时也会挤占官方APP的市场。通过校验签名可解决。
4.通过网络通信抓包解析网络协议,分析协议规律,服务器漏洞,既可以用于攻击服务器,也可以用于盗取服务器核心资源链接。影响服务器正常业务,同样增加了企业维护成本。还能开发盗版APP,自己不需要服务器,空手套白狼赚钱。还能研究出牛逼的爬虫对服务器攻击,很多互联网资源提供商都被抓虫攻击的休无完肤。这里可以使用https双向加密等。
下面通过签名和MD5校验APP,使用JNI实现,以防止逆向工程。签名检验可以防重新打包,MD5可以防止修改代码,提供两种方试的MD5校验,一种是APK,一种是DEX,通过网络校验可以使用APK,因为apk每次修改,都会导致生成的MD5码发生改变,本地校验需要把正确的MD5存在APP中,每次存md5都会导致生成新的MD5,故无法在本地使用,但只校验DEX则可以,可以把正确的MD5存在JNI中或者资源中,如果害怕破解者修改,可以使用AES、DES加密,也可以使用RSA加密。不过本地校验其实可以跳过,最好使用网络下发正确的MD5并加密传输,或者在请求协议中加入加密的MD5给服务器校验,一旦不正确,不下发数据返回错误,在客户端收到错误时提醒用户更新正版APP
char* getSha1(JNIEnv *env, jobject context_object){
//上下文对象
jclass context_class = env->GetObjectClass(context_object);
//反射获取PackageManager
jmethodID methodId = env->GetMethodID(context_class, "getPackageManager", "()Landroid/content/pm/PackageManager;");
jobject package_manager = env->CallObjectMethod(context_object, methodId);
if (package_manager == NULL) {
LOGD("package_manager is NULL!!!");
return NULL;
}
//反射获取包名
methodId = env->GetMethodID(context_class, "getPackageName", "()Ljava/lang/String;");
jstring package_name = (jstring)env->CallObjectMethod(context_object, methodId);
if (package_name == NULL) {
LOGD("package_name is NULL!!!");
return NULL;
}
env->DeleteLocalRef(context_class);
//获取PackageInfo对象
jclass pack_manager_class = env->GetObjectClass(package_manager);
methodId = env->GetMethodID(pack_manager_class, "getPackageInfo", "(Ljava/lang/String;I)Landroid/content/pm/PackageInfo;");
env->DeleteLocalRef(pack_manager_class);
jobject package_info = env->CallObjectMethod(package_manager, methodId, package_name, 0x40);
if (package_info == NULL) {
LOGD("getPackageInfo() is NULL!!!");
return NULL;
}
env->DeleteLocalRef(package_manager);
//获取签名信息
jclass package_info_class = env->GetObjectClass(package_info);
jfieldID fieldId = env->GetFieldID(package_info_class, "signatures", "[Landroid/content/pm/Signature;");
env->DeleteLocalRef(package_info_class);
jobjectArray signature_object_array = (jobjectArray)env->GetObjectField(package_info, fieldId);
if (signature_object_array == NULL) {
LOGD("signature is NULL!!!");
return NULL;
}
jobject signature_object = env->GetObjectArrayElement(signature_object_array, 0);
env->DeleteLocalRef(package_info);
//签名信息转换成sha1值
jclass signature_class = env->GetObjectClass(signature_object);
methodId = env->GetMethodID(signature_class, "toByteArray", "()[B");
env->DeleteLocalRef(signature_class);
jbyteArray signature_byte = (jbyteArray) env->CallObjectMethod(signature_object, methodId);
jclass byte_array_input_class=env->FindClass("java/io/ByteArrayInputStream");
methodId=env->GetMethodID(byte_array_input_class,"<init>","([B)V");
jobject byte_array_input=env->NewObject(byte_array_input_class,methodId,signature_byte);
jclass certificate_factory_class=env->FindClass("java/security/cert/CertificateFactory");
methodId=env->GetStaticMethodID(certificate_factory_class,"getInstance","(Ljava/lang/String;)Ljava/security/cert/CertificateFactory;");
jstring x_509_jstring=env->NewStringUTF("X.509");
jobject cert_factory=env->CallStaticObjectMethod(certificate_factory_class,methodId,x_509_jstring);
methodId=env->GetMethodID(certificate_factory_class,"generateCertificate",("(Ljava/io/InputStream;)Ljava/security/cert/Certificate;"));
jobject x509_cert=env->CallObjectMethod(cert_factory,methodId,byte_array_input);
env->DeleteLocalRef(certificate_factory_class);
jclass x509_cert_class=env->GetObjectClass(x509_cert);
methodId=env->GetMethodID(x509_cert_class,"getEncoded","()[B");
jbyteArray cert_byte=(jbyteArray)env->CallObjectMethod(x509_cert,methodId);
env->DeleteLocalRef(x509_cert_class);
jclass message_digest_class=env->FindClass("java/security/MessageDigest");
methodId=env->GetStaticMethodID(message_digest_class,"getInstance","(Ljava/lang/String;)Ljava/security/MessageDigest;");
jstring sha1_jstring=env->NewStringUTF("SHA1");
jobject sha1_digest=env->CallStaticObjectMethod(message_digest_class,methodId,sha1_jstring);
methodId=env->GetMethodID(message_digest_class,"digest","([B)[B");
jbyteArray sha1_byte=(jbyteArray)env->CallObjectMethod(sha1_digest,methodId,cert_byte);
env->DeleteLocalRef(message_digest_class);
//转换成char
jsize array_size=env->GetArrayLength(sha1_byte);
jbyte* sha1 =env->GetByteArrayElements(sha1_byte,NULL);
char *hex_sha=new char[array_size*2+1];
for (int 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';
LOGD("hex_sha %s ",hex_sha);
return hex_sha;
}
jboolean checkValidity(JNIEnv *env,char *sha1){
//比较签名
LOGI("jiaABC>>>>>>>>>>>>>>>>>>>--------------------sha1=%s", sha1);
LOGI("jiaABC>>>>>>>>>>>>>>>>>>>--------------------app_sha1=%s", app_sha1);
LOGI("jiaABC>>>>>>>>>>>>>>>>>>>--------------------strcmp(sha1, app_sha1)=%d", strcmp(sha1, app_sha1));
if (strcmp(sha1, app_sha1)==0)
{
LOGI("jiaABC>>>>>>>>>>>>>>>>>>>--------------------签名校验成功");
return JNI_TRUE;
}
LOGI("jiaABC>>>>>>>>>>>>>>>>>>>--------------------签名校验失败");
return JNI_FALSE;
}
extern "C"
JNIEXPORT jstring JNICALL
Java_com_signture_getSignaturesSha1(
JNIEnv *env,
jobject,
jobject contextObject) {
return env->NewStringUTF(app_sha1);
}
/**
* 签名校验
*/
extern "C"
JNIEXPORT jboolean JNICALL
Java_com_signture_checkSha1(
JNIEnv *env,
jobject,
jobject contextObject) {
char *sha1 = getSha1(env, contextObject);
jboolean result = checkValidity(env, sha1);
return result;
}
/**
* 签名校验
*/
extern "C"
JNIEXPORT jstring JNICALL
Java_com_signture_getToken(
JNIEnv *env,
jobject,
jobject contextObject,
jstring userId) {
char *sha1 = getSha1(env, contextObject);
jboolean result = checkValidity(env, sha1);
if (result) {
return env->NewStringUTF("获取Token成功");
} else {
return env->NewStringUTF("获取失败,请检查valid.cpp文件配置的sha1值");
}
}
/**
* 获取MD5校验码
*/
extern "C"
JNIEXPORT jstring JNICALL
Java_com_signture_getMd5(
JNIEnv *env,
jobject,
jstring strText
) {
char *szText = (char *) env->GetStringUTFChars(strText, 0);
MD5_CTX context = {0};
MD5Init(&context);
MD5Update(&context, (unsigned char *) szText, strlen(szText));
unsigned char dest[16] = {0};
MD5Final(&context, dest);
env->ReleaseStringUTFChars(strText, szText);
int i = 0;
char szMd5[32] = {0};
for (i = 0; i < 16; i++) {
sprintf(szMd5, "%s%02x", szMd5, dest[i]);
}
return env->NewStringUTF(szMd5);
}
/**
*读取assets文件内容
*/
extern "C"
JNIEXPORT void JNICALL
Java_com_signture_readFromAssets(JNIEnv *env, jclass type,
jobject assetManager, jstring filename_)
{
LOGW("ReadAssets");
AAssetManager* mgr = AAssetManager_fromJava(env, assetManager);
if(mgr==NULL)
{
LOGI(" %s","AAssetManager==NULL");
return ;
}
jboolean iscopy;
const char *mfile = env->GetStringUTFChars(filename_, &iscopy);
AAsset* asset = AAssetManager_open(mgr, mfile,AASSET_MODE_UNKNOWN);
env->ReleaseStringUTFChars(filename_, mfile);
if(asset==NULL)
{
LOGI(" %s","asset==NULL");
return ;
}
off_t bufferSize = AAsset_getLength(asset);
//LOGI("file size : %d\n",bufferSize);
char *buffer=(char *)malloc(bufferSize+1);
buffer[bufferSize]=0;
int numBytesRead = AAsset_read(asset, buffer, bufferSize);
LOGI(": %s",buffer);
LOGW(">>>>>>>>>>>>>>>>>>>assets=%s", buffer);
LOGW(">>>>>>>>>>>>>>>>>>>assets=%d", numBytesRead);
free(buffer);
AAsset_close(asset);
}
extern "C"
JNIEXPORT jbyte * JNICALL
Java_com_signture_Hex2Byte(JNIEnv *env, jclass type, jstring str, jint len){
const char *src = env->GetStringUTFChars(str, 0);
unsigned char *des = (unsigned char *) malloc((len/2)+1);
HexStrToByte(src, des, len);
des[(len/2)] = 0x00;
env->ReleaseStringUTFChars(str, src);
jbyteArray retArr = env->NewByteArray((len/2)+1);
env->SetByteArrayRegion(retArr, 0, (len/2)+1, (const jbyte *) des);
jbyte * ret = env->GetByteArrayElements(retArr, 0);
free(des);
return ret;
}
extern "C"
JNIEXPORT jstring JNICALL
Java_com_signture_Byte2Hex(JNIEnv *env, jclass type, jbyteArray bytes, jint len){
const char *src = (char *)env->GetByteArrayElements(bytes, 0);
char * des = ( char *) malloc((len * 2) + 1);
ByteToHexStr((unsigned char*)src, des, len);
env->ReleaseByteArrayElements(bytes, (jbyte*)src, 0);
jstring ret = env->NewStringUTF(des);
free(des);
return ret;
}
extern "C"
JNIEXPORT jstring JNICALL
Java_com_signture_des_1Decrypt(JNIEnv *env, jclass type, jbyteArray bytes_,
jint len) {
jbyte *bytes = env->GetByteArrayElements(bytes_, NULL);
int des_len=0;
char * des = DES_Decrypt((char *) bytes, len, "abcdefg", &des_len);
env->ReleaseByteArrayElements(bytes_, bytes, 0);
return env->NewStringUTF(des);
}
extern "C"
JNIEXPORT jbyte* JNICALL
Java_com_signture_des_1Encrypt(JNIEnv *env, jclass type, jstring str_,
jint len) {
const char *str = env->GetStringUTFChars(str_, 0);
char * des = ( char *) malloc((len * 2) + 1);
HexStrToByte(str, (unsigned char *) des, len);
env->ReleaseStringUTFChars(str_, str);
jbyteArray arr = env->NewByteArray((len * 2) + 1);
env->SetByteArrayRegion(arr, 0, (len * 2) + 1, ( jbyte *) des);
jbyte* ret = env->GetByteArrayElements((jbyteArray) des, 0);//env->NewStringUTF(des);
free(des);
return ret;
}
extern "C"
JNIEXPORT jboolean JNICALL
Java_com_signture_isMd5Check(JNIEnv *env, jclass type, jobject contextObject) {
char * cc = "com/signture";
jclass cls = env->FindClass(cc);
jmethodID mothod = env->GetStaticMethodID(cls, "isVerification", "(Landroid/content/Context;)Z");
jboolean result = (jboolean) env->CallStaticBooleanMethod(cls, mothod, contextObject);
LOGI("jiaABC>>>>>>>>>>>>>>>>>>>--------------------result=%d", result);
char *sha1 = getSha1(env, contextObject);
jboolean bSignature = checkValidity(env, sha1);
LOGI("jiaABC>>>>>>>>>>>>>>>>>>>--------------------bSignature=%d", bSignature);
jmethodID dlg1 = env->GetStaticMethodID(cls, "ErrorDialog", "(Landroid/content/Context;)V");
jmethodID dlg2 = env->GetStaticMethodID(cls, "RightDialog", "(Landroid/content/Context;)V");
if (result && bSignature) {
env->CallStaticVoidMethod(cls, dlg2, contextObject);
}else{
env->CallStaticVoidMethod(cls, dlg1, contextObject);
}
return result && bSignature;
}
extern "C"
JNIEXPORT void JNICALL
Java_com_signture_showDialog(JNIEnv *env, jclass type, jobject context,
jboolean isRigh) {
}
extern "C"
JNIEXPORT void JNICALL
Java_com_signture_show(JNIEnv *env, jobject thiz, jobject context,
jstring cstr) {
jclass jc_Toast = env->FindClass("android/widget/Toast");
jmethodID jm_makeText = env->GetStaticMethodID(jc_Toast, "makeText",
"(Landroid/content/Context;Ljava/lang/CharSequence;I)Landroid/widget/Toast;");
jobject jo_Toast = env->CallStaticObjectMethod(jc_Toast, jm_makeText, context, cstr, 0);
jmethodID jm_Show = env->GetMethodID(jc_Toast, "show", "()V");
env->CallVoidMethod(jo_Toast, jm_Show);
}