内容纲要
Apktool
待完成
目前进度:对decode部分进行源码解析
源码
目录简介
- apktool主目录
- main函数入口
- 反编译和回编业务代码
- 常量声明,现在主要是异常
- 压缩文件处理
- 工具类、cmd
- apktool在各个系统中的执行脚本
源码分析
反编译——cmdDecode
前面是一堆查看cmdline选项设置参数值
ApkDecoder decoder = new ApkDecoder();
decoder.decode();
ApkDecoder.decode()
if (hasResources()) {
switch (mDecodeResources) {
case DECODE_RESOURCES_FULL:
if (hasManifest()) {
mAndrolib.decodeManifestWithResources(mApkFile, outDir, getResTable());
}
mAndrolib.decodeResourcesFull(mApkFile, outDir, getResTable());
break;
}
}
ApkDecoder.hasResources()
//查看是否包含resources.arsc资源文件
public boolean hasResources() throws AndrolibException {
try {
return mApkFile.getDirectory().containsFile("resources.arsc");
} catch (DirectoryException ex) {
throw new AndrolibException(ex);
}
}
解析Manifest文件
ResFileDecoder.decodeManifest(Directory inDir, String inFileName,Directory outDir, String outFileName)
try (
InputStream in = inDir.getFileInput(inFileName);
OutputStream out = outDir.getFileOutput(outFileName)
) {
((XmlPullStreamDecoder) mDecoders.getDecoder("xml")).decodeManifest(in, out);
ResStreamDecoder有三个实现
这里为XmlPullStreamDecoder
XmlPullStreamDecoder
//XmlPullStreamDecoder.decodeManifest调用了XmlPullStreamDecoder.decode
public void decodeManifest(InputStream in, OutputStream out)
throws AndrolibException {
decode(in, out);
}
decode主要包括了两个操作:parseManifest和parseAttr
if ("manifest".equalsIgnoreCase(pp.getName())) {
try {
hidePackageInfo = parseManifest(pp);
} catch (AndrolibException ignored) {}
} else if ("uses-sdk".equalsIgnoreCase(pp.getName())) {
try {
hideSdkInfo = parseAttr(pp);
if (hideSdkInfo) {
return;
}
} catch (AndrolibException ignored) {}
}
总结
ASRCDecoder对resource.asrc进行解析,对各个区域的属性进行偏移量解析,一步步动调的,看的有点晕,不好说= =
然后利用asrc文件对manifest进行解析,将解析完的arsc赋给resTable后,进行parseManifest和parseAttr
//parseManifest
private boolean parseManifest(XmlPullParser pp)
throws AndrolibException {
String attr_name;
// read <manifest> for package:
for (int i = 0; i < pp.getAttributeCount(); i++) {
attr_name = pp.getAttributeName(i);
if (attr_name.equalsIgnoreCase(("package"))) {
resTable.setPackageRenamed(pp.getAttributeValue(i));
} else if (attr_name.equalsIgnoreCase("versionCode")) {
resTable.setVersionCode(pp.getAttributeValue(i));
} else if (attr_name.equalsIgnoreCase("versionName")) {
resTable.setVersionName(pp.getAttributeValue(i));
}
}
return true;
}
//获得versionCode、versionName、package
//parseAttr
private boolean parseAttr(XmlPullParser pp)
throws AndrolibException {
for (int i = 0; i < pp.getAttributeCount(); i++) {
final String a_ns = "http://schemas.android.com/apk/res/android";
String ns = pp.getAttributeNamespace(i);
if (a_ns.equalsIgnoreCase(ns)) {
String name = pp.getAttributeName(i);
String value = pp.getAttributeValue(i);
if (name != null && value != null) {
if (name.equalsIgnoreCase("minSdkVersion")
|| name.equalsIgnoreCase("targetSdkVersion")
|| name.equalsIgnoreCase("maxSdkVersion")
|| name.equalsIgnoreCase("compileSdkVersion")) {
resTable.addSdkInfo(name, value);
} else {
resTable.clearSdkInfo();
return false;
}
}
} else {
resTable.clearSdkInfo();
if (i >= pp.getAttributeCount()) {
return false;
}
}
}
return ! resTable.getAnalysisMode();
}
//获得了minSdkVersion、targetSdkVersion、maxSdkVersion……
对xml进行解析,在flush后manifest文件中有了内容,我没调出来,但感觉应该是这一段解析
switch (eventType) {
case XmlPullParser.START_DOCUMENT:
//use Boolean.TRUE to make it standalone
Boolean standalone = (Boolean) pp.getProperty(PROPERTY_XMLDECL_STANDALONE);
startDocument(pp.getInputEncoding(), standalone);
break;
case XmlPullParser.END_DOCUMENT:
endDocument();
break;
case XmlPullParser.START_TAG:
writeStartTag(pp);
break;
case XmlPullParser.END_TAG:
endTag(pp.getNamespace (), pp.getName ());
break;
case XmlPullParser.IGNORABLE_WHITESPACE:
//comment it to remove ignorable whtespaces from XML infoset
String s = pp.getText ();
ignorableWhitespace(s);
break;
case XmlPullParser.TEXT:
if(pp.getDepth() > 0) {
text(pp.getText ());
} else {
ignorableWhitespace(pp.getText ());
}
break;
case XmlPullParser.ENTITY_REF:
entityRef (pp.getName ());
break;
case XmlPullParser.CDSECT:
cdsect( pp.getText () );
break;
case XmlPullParser.PROCESSING_INSTRUCTION:
processingInstruction( pp.getText ());
break;
case XmlPullParser.COMMENT:
comment (pp.getText ());
break;
case XmlPullParser.DOCDECL:
docdecl (pp.getText ());
break;
}
解析res文件并在路径下生成
解析class.dex
if (hasSources()) {
switch (mDecodeSources) {
case DECODE_SOURCES_NONE:
mAndrolib.decodeSourcesRaw(mApkFile, outDir, "classes.dex");
break;
case DECODE_SOURCES_SMALI:
case DECODE_SOURCES_SMALI_ONLY_MAIN_CLASSES:
mAndrolib.decodeSourcesSmali(mApkFile, outDir, "classes.dex", mBakDeb, mApiLevel);
break;
}
}
public boolean hasSources() throws AndrolibException {
try {
return mApkFile.getDirectory().containsFile("classes.dex");
} catch (DirectoryException ex) {
throw new AndrolibException(ex);
}
}
decodeSourcesSmali
File smaliDir;
if (filename.equalsIgnoreCase("classes.dex")) {
smaliDir = new File(outDir, SMALI_DIRNAME);
} else {
smaliDir = new File(outDir, SMALI_DIRNAME + "_" + filename.substring(0, filename.indexOf(".")));
}
OS.rmdir(smaliDir);
smaliDir.mkdirs();
LOGGER.info("Baksmaling " + filename + "...");
DexFile dexFile = SmaliDecoder.decode(apkFile, smaliDir, filename, bakDeb, apiLevel);
int minSdkVersion = dexFile.getOpcodes().api;
if (mMinSdkVersion == 0 || mMinSdkVersion > minSdkVersion) {
mMinSdkVersion = minSdkVersion;
}
查看decode
一系列dex检查后,调用baksmali进行返汇编