美团Android资源混淆保护的具体实践

原文章美团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。关键就是这么一个转换函数。

这个函数的实现其实应该不难,但是出于个人太懒,就没去写这个函数了,读者有兴趣可以在上面的代码基础上进行修改。

评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值