原文章美团Android资源混淆保护实践,但是该文章并没有给出具体的混淆方案,只是放了一个函数,函数的实现过程需要自己去实现,本篇文章也并没有实现该函数,只是对实现该函数有一个前期的准备。
在android 5.0的系统源码中,要修改的代码位于
/frameworks/base/tools/aapt/Resource.cpp
未修改前的代码
static status_t makeFileResources(Bundle* bundle, const sp<AaptAssets>& assets, ResourceTable* table, const sp<ResourceTypeSet>& set, const char* resType){ String8 type8(resType); String16 type16(resType); bool hasErrors = false; ResourceDirIterator it(set, String8(resType)); ssize_t res; while ((res=it.next()) == NO_ERROR) { if (bundle->getVerbose()) { printf(" (new resource id %s from %s)\n", it.getBaseName().string(), it.getFile()->getPrintableSource().string()); } String16 baseName(it.getBaseName()); const char16_t* str = baseName.string(); const char16_t* const end = str + baseName.size(); while (str < end) { if (!((*str >= 'a' && *str <= 'z') || (*str >= '0' && *str <= '9') || *str == '_' || *str == '.')) { fprintf(stderr, "%s: Invalid file name: must contain only [a-z0-9_.]\n", it.getPath().string()); hasErrors = true; } str++; } String8 resPath = it.getPath(); resPath.convertToResPath(); table->addEntry(SourcePos(it.getPath(), 0), String16(assets->getPackage()), type16, baseName, String16(resPath), NULL, &it.getParams()); assets->addResource(it.getLeafName(), resPath, it.getFile(), type8); } return hasErrors ? STATUST(UNKNOWN_ERROR) : NO_ERROR;}
美团给我们的差异代码如下
String8 obfuscationName;String8 obfuscationPath = getObfuscationName(resPath, obfuscationName);table->addEntry(SourcePos(it.getPath(), 0), String16(assets->getPackage()), type16, baseName, // String16(obfuscationName), String16(obfuscationPath), // resPath NULL, &it.getParams());assets->addResource(it.getLeafName(), obfuscationPath/*resPath*/, it.getFile(), type8);
只是调用了getObfuscationName函数进行混淆,返回值就是混淆后的路径,然后在addEntry和addResource中将resPath替换为了obfuscationPath。
如果要我们自己实现这个混淆过程,首先我们得熟悉里面的一些属性的意义,比如getBaseName,getLeafName,getParams, convertToResPath等方法的意义。
- getBaseName是获得资源的名字,不包含后缀,如icon
- getLeafName是获得资源的名字,包含后缀,如icon.png
- getParams是获得限定符,如xxhdpi-v4,获得的是一个对象,可以调用它的toString()方法将其转换为字符串形式,也就是前面的xxhdpi-v4,注意不包含前缀-
- convertToResPath是将路径中的\\转换为/,如res\\drawable会被转换为res/drawable,该方法位于String8这个类中
- resType,这个是参数传进来的,代表资源的类型,内部使用了 String8 type8(resType);转换为了String8 类型,我们直接使用就可以了,但是这个值不包含限定符,如drawable
默认的打包过程,资源文件都是在res目录下,现在我们通过一定的逻辑,将资源转移到r目录下。
首先获得限定符
String8 params=it.getParams().toString();
由于该限定符可能为空,我们需要用if进行处理,后面再说。
再定义一个用于保存我们转换后的目录
String8 obfuscationPath("");
如果资源限定符是空的,那么我们直接将目录进行拼接,别把限定符包含进去就可以了
if(params.isEmpty()){ obfuscationPath.append("r/");//我们放在r目录下,所以最开始的是r/ obfuscationPath.append(type8);//资源类型 obfuscationPath.append("/");//添加/,因为是目录 obfuscationPath.append(it.getLeafName());//之后跟上全面,包含后缀的 }
那么假设限定符不为空呢,当然我们需要将其拼接上去
if(params.isEmpty()){ //...... }else{ obfuscationPath.append("r/");//我们放在r目录下,所以最开始的是r/ obfuscationPath.append(type8);//资源类型 obfuscationPath.append("-");//增加资源限定符前缀- obfuscationPath.append(params);//将资源限定符添加上去 obfuscationPath.append("/");//添加/,因为是目录 obfuscationPath.append(it.getLeafName());//之后跟上全面,包含后缀的 }
然后后面打包资源的时候使用obfuscationPath代替resPath即可
table->addEntry(SourcePos(it.getPath(), 0), String16(assets->getPackage()), type16, baseName, String16(obfuscationPath), NULL, &it.getParams()); assets->addResource(it.getLeafName(), obfuscationPath, it.getFile(), type8);
完整的函数如下
static status_t makeFileResources(Bundle* bundle, const sp<AaptAssets>& assets, ResourceTable* table, const sp<ResourceTypeSet>& set, const char* resType){ String8 type8(resType); String16 type16(resType); bool hasErrors = false; ResourceDirIterator it(set, String8(resType)); ssize_t res; while ((res=it.next()) == NO_ERROR) { if (bundle->getVerbose()) { printf(" (new resource id %s from %s)\n", it.getBaseName().string(), it.getFile()->getPrintableSource().string()); } String16 baseName(it.getBaseName()); const char16_t* str = baseName.string(); const char16_t* const end = str + baseName.size(); while (str < end) { if (!((*str >= 'a' && *str <= 'z') || (*str >= '0' && *str <= '9') || *str == '_' || *str == '.')) { fprintf(stderr, "%s: Invalid file name: must contain only [a-z0-9_.]\n", it.getPath().string()); hasErrors = true; } str++; } String8 resPath = it.getPath(); resPath.convertToResPath(); //String8 obfuscationPath = getObfuscationName(resPath, obfuscationName); String8 params=it.getParams().toString(); String8 obfuscationPath(""); if(params.isEmpty()){ obfuscationPath.append("r/"); obfuscationPath.append(type8); obfuscationPath.append("/"); obfuscationPath.append(it.getLeafName()); }else{ obfuscationPath.append("r/"); obfuscationPath.append(type8); obfuscationPath.append("-"); obfuscationPath.append(params); obfuscationPath.append("/"); obfuscationPath.append(it.getLeafName()); } fprintf(stderr, "our Path:%s\n",obfuscationPath.string()); table->addEntry(SourcePos(it.getPath(), 0), String16(assets->getPackage()), type16, baseName, String16(obfuscationPath), NULL, &it.getParams()); assets->addResource(it.getLeafName(), obfuscationPath, it.getFile(), type8); } return hasErrors ? STATUST(UNKNOWN_ERROR) : NO_ERROR;}
这样进行编译,编译的步骤参考之前写的博客http://blog.csdn.net/sbsujjbcy/article/details/47778879
替换我们的aapt进行编译。
可以看到我们自己输出的目录,不在是res目录下,而是r目录下,这时候将打包好的apk文件进行反编译。你发现res目录下并没有我们自己写的资源文件,全是系统自身的,然后多了一个unknown目录,里面按着原目录排列着我们的资源文件
很显然,我们已经成功地将res中的文件转移到了r文件夹中。那么资源混淆也就变得简单了。我们参考我们java中的类的混淆,我们的类会被混淆成a,b,c之类的符号,于是我们只要定义一个转换函数,将原目录映射到a,b,c等目录,比如anim映射为a,color映射为b,….因此类推。然后对应目录中的文件也依次重命名。a-z不够用的可以使用aa-zz,甚至aaa-zzz。关键就是这么一个转换函数。
这个函数的实现其实应该不难,但是出于个人太懒,就没去写这个函数了,读者有兴趣可以在上面的代码基础上进行修改。
版权声明:本文为博主原创文章,未经博主允许不得转载。
- 3楼scgg48484昨天 16:07
- 学习了!
- 2楼dream_zze昨天 19:29
- 。。n。n。
- 1楼u012583459昨天 17:29
- good