原文章美团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等方法的意义。
默认的打包过程,资源文件都是在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。关键就是这么一个转换函数。
这个函数的实现其实应该不难,但是出于个人太懒,就没去写这个函数了,读者有兴趣可以在上面的代码基础上进行修改。