ApkTool原码修改记录

转载请注明:本文源自http://blog.csdn.net/ytmfdw


本人在反编译一个apk时,发现在反编译时总是报错:

Exception in thread "main" java.lang.ClassCastException: brut.androlib.res.data.value.ResStringValue cannot be cast to brut.androlib.res.dat
        at brut.androlib.res.decoder.ResAttrDecoder.decode(ResAttrDecoder.java:36)
        at brut.androlib.res.decoder.AXmlResourceParser.getAttributeValue(AXmlResourceParser.java:369)
        at org.xmlpull.v1.wrapper.classic.XmlPullParserDelegate.getAttributeValue(XmlPullParserDelegate.java:69)
        at org.xmlpull.v1.wrapper.classic.StaticXmlSerializerWrapper.writeStartTag(StaticXmlSerializerWrapper.java:267)
        at org.xmlpull.v1.wrapper.classic.StaticXmlSerializerWrapper.event(StaticXmlSerializerWrapper.java:211)
        at brut.androlib.res.decoder.XmlPullStreamDecoder$1.event(XmlPullStreamDecoder.java:83)
        at brut.androlib.res.decoder.XmlPullStreamDecoder.decode(XmlPullStreamDecoder.java:141)
        at brut.androlib.res.decoder.ResStreamDecoderContainer.decode(ResStreamDecoderContainer.java:33)
        at brut.androlib.res.decoder.ResFileDecoder.decode(ResFileDecoder.java:114)
        at brut.androlib.res.decoder.ResFileDecoder.decode(ResFileDecoder.java:99)
        at brut.androlib.res.AndrolibResources.decode(AndrolibResources.java:323)
        at brut.androlib.Androlib.decodeResourcesFull(Androlib.java:131)
        at brut.androlib.ApkDecoder.decode(ApkDecoder.java:101)
        at brut.apktool.Main.cmdDecode(Main.java:165)
        at brut.apktool.Main.main(Main.java:81)

网上搜时,要用最新的apktool.jar,于是,下载了apktool_2.0.0rc3.jar,apktool_2.0.0rc2.jar,apktool_2.0.0rc4.jar,估计4是网上大神自己发布的,但没有一个能正确反编译xml,都报错,有人给出答案:加-r参数:pktool d -r xx.apk xx,其实这样做,xml是解压出来的,根本没解析。折腾了好久,没搞定,一怒之下,找了apktool源码,但这个源码是在Google服务器上的一个开源项目,国内的环境,你懂的!没办法,只能下载一个,忘了这个下载地址在哪,真的对不起,但我把修改过的源码放到了开源中国git服务器上,git地址:https://git.oschina.net/ytmfdw/ApkTool.git

生成可用的jar文件下载地址:请点击这里,(不好意思,设置了5分,但评论完后,会返回的,你的评论,是对我最好的支持!)

有了源码,接下下就是调试了,找到源码at brut.androlib.res.decoder.ResAttrDecoder.decode(ResAttrDecoder.java:36)这个地方,发现原来是类型强转不对,接下问题就好解了

问题出在这:

ResAttr attr = (ResAttr) getCurrentPackage().getResTable()
                    .getResSpec(attrResId).getDefaultResource().getValue();
            decoded=attr.convertToResXmlFormat(resValue);

看了下,getCurrentPackage().getResTable().getResSpec(attrResId).getDefaultResource().getValue();报错时返回值是:ResStringValue类型的,ResStringValue与ResAttr其实不是父子关系,而是兄弟关系,所以强转肯定会报错的,解决方法:

ResValue attr = (ResValue) getCurrentPackage().getResTable()
                    .getResSpec(attrResId).getDefaultResource().getValue();
            if (attr instanceof ResStringValue) {
                decoded=((ResStringValue)attr).encodeAsResXmlAttr();
//                System.out.println("decoded="+decoded);
            } else if (attr instanceof ResAttr) {

                decoded = ((ResAttr) attr).convertToResXmlFormat(resValue);
            }

判断下返回值的类型,如果是ResStringValue,就按ResStringValue类型处理,如果是ResAttr,就按ResAttr类型处理 ,再反编译下,竟然正常了!有点小激动!

反编译完后,再回编译,又陷入了坑中……但这反编译是没问题的,用其他版本apktool.jar可正常回编译,但这个源码处,怎么也不能正常回编!

苦思中……

很久……

没办法,再来,把回编译流程跟踪了一遍,再看下其他版本的apktool.jar代码,(通过gd-gui.exe)再把相关的函数对比了下,竟然没发现不同!!!

绝望了……

只好从报错处开始研究,发现是这个变量引起的:mAaptPath

mAaptPath是aapt.exe文件路径,调试时,发现如果加入-a ./aapt.exe,就可以正常回编,知道问题所在了,如果没指定-a参数,程序就会自动去找aapt.exe,如果没找到就会报错,然后跟踪了找aapt.exe文件的函数:


在Androlib.java中

mAndRes.aaptPackage(apkFile, new File(appDir,
                        "AndroidManifest.xml"), new File(appDir, "res"),
                        ninePatch, null, parseUsesFramework(usesFramework),
                        flags, mAaptPath);

往下走:AndrolibResources.java中的aaptPackage中:

这是我修改过的的代码,加入了打印

if (!aaptPath.isEmpty()) {
            File aaptFile = new File(aaptPath);
            if (aaptFile.canRead() && aaptFile.exists()) {
                aaptFile.setExecutable(true);
                cmd.add(aaptFile.getPath());
                LogDebug.i("aaptPackage 1");
                customAapt = true;

                if (flags.get("verbose")) {
                    LOGGER.info(aaptFile.getPath()
                            + " being used as aapt location.");
                }
            } else {
                LOGGER.warning("aapt location could not be found. Defaulting back to default");

                try {
                    LogDebug.i("aaptPackage 2");
                    cmd.add(getAaptBinaryFile().getAbsolutePath());
                } catch (BrutException ignored) {
                    LogDebug.i("aaptPackage 3");
                    cmd.add("aapt");
                }
            }
        } else {
            try {
                File aaptFile = new File(getAaptBinaryFile().getAbsolutePath());
                if (aaptFile.canRead() && aaptFile.exists()) {
                    aaptFile.setExecutable(true);
                    cmd.add(aaptFile.getPath());
                    LogDebug.i("aaptPackage 1111");
                    customAapt = true;

                    if (flags.get("verbose")) {
                        LOGGER.info(aaptFile.getPath()
                                + " being used as aapt location.");
                    }
                } else {

                    cmd.add(getAaptBinaryFile().getAbsolutePath());
                    LogDebug.i("aaptPackage 4"
                            + getAaptBinaryFile().getAbsolutePath());
                }

            } catch (BrutException ignored) {
                LogDebug.i("aaptPackage 5");
                cmd.add("aapt");
            }
        }

关键是:getAaptBinaryFile().getAbsolutePath()这个方法,出问题了,有问题就得改:

在getAaptBinaryFile函数中,有三个分支:

try {
            if (OSDetection.isMacOSX()) {
                mAaptBinary = Jar
                        .getResourceAsFile("/prebuilt/aapt/macosx/aapt");
            } else if (OSDetection.isUnix()) {
                mAaptBinary = Jar
                        .getResourceAsFile("/prebuilt/aapt/linux/aapt");
            } else if (OSDetection.isWindows()) {
                // mAaptBinary =
                // Jar.getResourceAsFile("/prebuilt/aapt/windows/aapt.exe");
                mAaptBinary = new File(getAaptPath());
            } else {
                LOGGER.warning("Unknown Operating System: "
                        + OSDetection.returnOS());
                return null;
            }
        } catch (BrutException ex) {
            throw new AndrolibException(ex);
        }

(以上代码注释部分为源码)

看方法名也知道,其他两个系统:Mac和Unix我就没去研究了,只是在Windows系统下调试,修改:mAaptBinary = new File(getAaptPath());

其中:getAaptPath()方法就是获取aapt.exe文件路径,实现原理:用java执行cmd命令:echo %PATH%

把得到的字符串(系统变量中的path变量,用“;”分隔)分割成字符串数组,对数组中的每个元素进行扫描(每个元素其实是目录地址),直到找到aapt.exe,就返回该文件地址,具体实现请参照代码中的写法!

纯手打,望尊重程序员的劳动成果!


评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ytmfdw

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值