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,就返回该文件地址,具体实现请参照代码中的写法!

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

时间: 2024-10-28 10:59:27

ApkTool原码修改记录的相关文章

原码、反码、补码

原码.反码.补码,计算机中负数的表示 1.表示范围 拿单字节整数来说,无符号型,其表示范围是[0,255],总共表示了256个数据.有符号型,其表示范围是[-128,127]. 先看无符号,0表示为0000 0000,255表示为1111 1111,刚好满足了要求,可以表示256个数据. 再看有符号的,若是用原码表示,0表示为0000 000.因为咱们有符号,所以应该也有个负0(虽然它还是0):1000 0000. 那我们看看这样还能够满足我们的要求,表示256个数据么? 正数,没问题,127是

计算机基础知识_原码反码补码

一.原码,反码,补码 1.原码 比如一个二进制数字 最高位是0,(0代表正数) 0010 1000 那么原码就是0010 1000 反码: 0010 1000 补码: 0010 1000 都是一样的,这个二进制数字的10进制是40 所以是正数 正数的原反补都是一样的 2.反码 反码就是原码的取反,二进制的 0变为1 1变为0 ,看最高符号位是0 还是1,如果是1,则你要0变为1,1变为0, 3.补码: 负数的的是原码 取反 在加1 变成补码(二进制数) 正数的原码 加上负数的补码就等于是做减法运

原码 反码 补码

出处来自:http://blog.csdn.net/liushuijinger/article/details/7429197 原码: 如果机器字长为n,那么一个数的原码就是用一个n位的二进制数,其中最高位为符号位:正数为0,负数为1.剩下的n-1位表示概数的绝对值. 例如: X=+101011 , [X]原= 00101011    X=-101011 , [X]原= 10101011 位数不够的用0补全. PS:正数的原.反.补码都一样:0的原码跟反码都有两个,因为这里0被分为+0和-0.

java基础:原码反码补码

计算机在操作的时候,都是采用数据对应二进制的补码来计算的: 原码 反码 补码 原码:用原码,反码,补码来分别表示+7,和-7. 首先得到7的二进制:111

基础之——原码、反码、补码 详解

本篇文章讲解了计算机的原码, 反码和补码. 并且进行了深入探求了为何要使用反码和补码, 以及更进一步的论证了为何可以用反码, 补码的加法计算原码的减法. 论证部分如有不对的地方请各位牛人帮忙指正! 希望本文对大家学习计算机基础有所帮助! 一. 机器数和真值 在学习原码, 反码和补码之前, 需要先了解机器数和真值的概念. 1.机器数 一个数在计算机中的二进制表示形式,  叫做这个数的机器数.机器数是带符号的,在计算机用一个数的最高位存放符号, 正数为0, 负数为1. 比如,十进制中的数 +3 ,计

原码, 反码, 补码 详解

本篇文章讲解了计算机的原码, 反码和补码. 并且进行了深入探求了为何要使用反码和补码, 以及更进一步的论证了为何可以用反码, 补码的加法计算原码的减法. 论证部分如有不对的地方请各位牛人帮忙指正! 希望本文对大家学习计算机基础有所帮助! 一. 机器数和真值 在学习原码, 反码和补码之前, 需要先了解机器数和真值的概念. 1.机器数 一个数在计算机中的二进制表示形式,  叫做这个数的机器数.机器数是带符号的,在计算机用一个数的最高位存放符号, 正数为0, 负数为1. 比如,十进制中的数 +3 ,计

C语言之原码、反码和补码

原码.反码和补码 1).数据在内存中存储的时候都是以二进制的形式存储的. int num = 10; 原码.反码.补码都是二进制.只不过是二进制的不同的表现形式. 数据是以补码的二进制存储的. 2). 1个int类型的变量.在内存中占据4个字节, 32位. 00000000 00000000 00000000 00000000 在不考虑正负的情况下.1个int类型的变量可以表示接近43e种数据. 为了可以表示正负性.使用最高为来表示这个数的正负性. 如果最高为是0 那么表示这个数是1个正数 如果

进制转换与原码补码

进制也就是进位制,是人们规定的一种进位方法. 我们先来回想一下生活中的十进制: (1) 数码: 指集合论中刻画任意集合所含元素数量多少的一个概念 十进制的基本符号是:0.1.2.3.4.5.6.7.8.9:我们把这些称为十进制的数码:也就是基本符号,所有的十进制都是有这十个数码组成的.每位在加时都是"逢十进一". (2) 位权: 数制中每一固定位置对应的单位值称为位权 那么大家考虑一个问题,说一个十进制数,已知第四位是5,其它位都是0,那么这个数是几?答案:5000,怎么算的是5*10

Java之&0xff用法解析以及原码、反码、补码相关知识

char强转至int为什么使用0xff? 备注:在Java中采用补码形式表示二进制 如果不希望进行符号扩展,可以采用与操作.例如char c:int i = c & 0xffff:其中,char有8位,int类型有32位,采用32/8=4个f(即0xffff)做与操作,即可屏蔽符号扩展. //负整数时,前面输入了多余的 FF ,没有去掉前面多余的 FF,按并双字节形式输出System.out.println(Integer.toHexString(-2).toUpperCase());//FFF