From eabb7d819b9a13f256153a174b8dd05e21c619f1 Mon Sep 17 00:00:00 2001 From: Connor Tumbleson Date: Mon, 14 Dec 2015 07:03:09 -0600 Subject: [PATCH] Correctly read sparse ResourceTables. Prior to this change, APKs usually went Package -> TypeSpec -> Config (all) -> Entries. Reading all configs under that TypeSpec. Now we have packages that go Package -> TypeSpec -> Config (single) -> Entries. So we have to read this correctly to make sure we can correctly decode sparse and packed Resource tables. --- .../brut/androlib/res/data/ResPackage.java | 7 ++ .../brut/androlib/res/data/ResResSpec.java | 9 ++ .../java/brut/androlib/res/data/ResType.java | 5 + .../brut/androlib/res/data/ResTypeSpec.java | 19 +++- .../androlib/res/decoder/ARSCDecoder.java | 92 ++++++++++++++----- 5 files changed, 107 insertions(+), 25 deletions(-) diff --git a/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/data/ResPackage.java b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/data/ResPackage.java index 4ef9b94b..7bbbf3f4 100644 --- a/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/data/ResPackage.java +++ b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/data/ResPackage.java @@ -156,6 +156,10 @@ public class ResPackage { return mSynthesizedRes.contains(resId); } + public void removeResSpec(ResResSpec spec) throws AndrolibException { + mResSpecs.remove(spec.getId()); + } + public void addResSpec(ResResSpec spec) throws AndrolibException { if (mResSpecs.put(spec.getId(), spec) != null) { throw new AndrolibException("Multiple resource specs: " + spec); @@ -179,6 +183,9 @@ public class ResPackage { public void addResource(ResResource res) { } + public void removeResource(ResResource res) { + } + public void addSynthesizedRes(int resId) { mSynthesizedRes.add(new ResID(resId)); } diff --git a/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/data/ResResSpec.java b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/data/ResResSpec.java index be81fb38..78d459e6 100755 --- a/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/data/ResResSpec.java +++ b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/data/ResResSpec.java @@ -101,6 +101,10 @@ public class ResResSpec { return mType; } + public boolean isDummyResSpec() { + return getName().startsWith("APKTOOL_DUMMY_"); + } + public void addResource(ResResource res) throws AndrolibException { addResource(res, false); } @@ -114,6 +118,11 @@ public class ResResSpec { } } + public void removeResource(ResResource res) throws AndrolibException { + ResConfigFlags flags = res.getConfig().getFlags(); + mResources.remove(flags); + } + @Override public String toString() { return mId.toString() + " " + mType.toString() + "/" + mName; diff --git a/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/data/ResType.java b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/data/ResType.java index 627736b6..e0669d23 100644 --- a/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/data/ResType.java +++ b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/data/ResType.java @@ -56,6 +56,11 @@ public class ResType { addResource(res, false); } + public void removeResource(ResResource res) throws AndrolibException { + ResResSpec spec = res.getResSpec(); + mResources.remove(spec); + } + public void addResource(ResResource res, boolean overwrite) throws AndrolibException { ResResSpec spec = res.getResSpec(); diff --git a/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/data/ResTypeSpec.java b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/data/ResTypeSpec.java index 48fb8765..7772ebae 100644 --- a/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/data/ResTypeSpec.java +++ b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/data/ResTypeSpec.java @@ -30,16 +30,29 @@ public final class ResTypeSpec { private final ResTable mResTable; private final ResPackage mPackage; - public ResTypeSpec(String name, ResTable resTable, ResPackage package_) { + private final byte mId; + private final int mEntryCount; + + public ResTypeSpec(String name, ResTable resTable, ResPackage package_, byte id, int entryCount) { this.mName = name; this.mResTable = resTable; this.mPackage = package_; + this.mId = id; + this.mEntryCount = entryCount; } public String getName() { return mName; } + public byte getId() { + return mId; + } + + public int getEntryCount() { + return mEntryCount; + } + public boolean isString() { return mName.equalsIgnoreCase("string"); } @@ -57,6 +70,10 @@ public final class ResTypeSpec { return spec; } + public void removeResSpec(ResResSpec spec) throws AndrolibException { + mResSpecs.remove(spec.getName()); + } + public void addResSpec(ResResSpec spec) throws AndrolibException { if (mResSpecs.put(spec.getName(), spec) != null) { throw new AndrolibException(String.format( diff --git a/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/decoder/ARSCDecoder.java b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/decoder/ARSCDecoder.java index 9e498ba4..258c4d3a 100644 --- a/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/decoder/ARSCDecoder.java +++ b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/decoder/ARSCDecoder.java @@ -138,39 +138,59 @@ public class ARSCDecoder { } private ResTypeSpec readTableTypeSpec() throws AndrolibException, IOException { + mTypeSpec = readSingleTableTypeSpec(); + addTypeSpec(mTypeSpec); + + int type = nextChunk().type; + ResTypeSpec resTypeSpec; + + while (type == Header.TYPE_SPEC_TYPE) { + resTypeSpec = readSingleTableTypeSpec(); + addTypeSpec(resTypeSpec); + type = nextChunk().type; + } + + while (type == Header.TYPE_TYPE) { + readTableType(); + type = nextChunk().type; + + addMissingResSpecs(); + } + + return mTypeSpec; + } + + private ResTypeSpec readSingleTableTypeSpec() throws AndrolibException, IOException { checkChunkType(Header.TYPE_SPEC_TYPE); byte id = mIn.readByte(); mIn.skipBytes(3); int entryCount = mIn.readInt(); - mMissingResSpecs = new boolean[entryCount]; - Arrays.fill(mMissingResSpecs, true); - if (mFlagsOffsets != null) { mFlagsOffsets.add(new FlagsOffset(mCountIn.getCount(), entryCount)); } + /* flags */mIn.skipBytes(entryCount * 4); - - mResId = (0xff000000 & mResId) | id << 16; - mTypeSpec = new ResTypeSpec(mTypeNames.getString(id - 1), mResTable, mPkg); + mTypeSpec = new ResTypeSpec(mTypeNames.getString(id - 1), mResTable, mPkg, id, entryCount); mPkg.addType(mTypeSpec); - - while (nextChunk().type == Header.TYPE_TYPE) { - readTableType(); - } - - addMissingResSpecs(); - return mTypeSpec; } private ResType readTableType() throws IOException, AndrolibException { checkChunkType(Header.TYPE_TYPE); - /* typeId */mIn.skipBytes(1); + byte typeId = mIn.readByte(); + if (mResTypeSpecs.containsKey(typeId)) { + mResId = (0xff000000 & mResId) | mResTypeSpecs.get(typeId).getId() << 16; + mTypeSpec = mResTypeSpecs.get(typeId); + } + /* res0, res1 */mIn.skipBytes(3); int entryCount = mIn.readInt(); /* entriesStart */mIn.skipInt(); + mMissingResSpecs = new boolean[entryCount]; + Arrays.fill(mMissingResSpecs, true); + ResConfigFlags flags = readConfigFlags(); int[] entryOffsets = mIn.readIntArray(entryCount); @@ -214,6 +234,14 @@ public class ARSCDecoder { ResResSpec spec; if (mPkg.hasResSpec(resId)) { spec = mPkg.getResSpec(resId); + + if (spec.isDummyResSpec()) { + removeResSpec(spec); + + spec = new ResResSpec(resId, mSpecNames.getString(specNamesId), mPkg, mTypeSpec); + mPkg.addResSpec(spec); + mTypeSpec.addResSpec(spec); + } } else { spec = new ResResSpec(resId, mSpecNames.getString(specNamesId), mPkg, mTypeSpec); mPkg.addResSpec(spec); @@ -386,6 +414,10 @@ public class ARSCDecoder { return string.toString(); } + private void addTypeSpec(ResTypeSpec resTypeSpec) { + mResTypeSpecs.put(resTypeSpec.getId(), resTypeSpec); + } + private void addMissingResSpecs() throws AndrolibException { int resId = mResId & 0xffff0000; @@ -395,19 +427,30 @@ public class ARSCDecoder { } ResResSpec spec = new ResResSpec(new ResID(resId | i), String.format("APKTOOL_DUMMY_%04x", i), mPkg, mTypeSpec); - mPkg.addResSpec(spec); - mTypeSpec.addResSpec(spec); - if (mType == null) { - mType = mPkg.getOrCreateConfig(new ResConfigFlags()); + // If we already have this resID dont add it again. + if (! mPkg.hasResSpec(new ResID(resId | i))) { + mPkg.addResSpec(spec); + mTypeSpec.addResSpec(spec); + + if (mType == null) { + mType = mPkg.getOrCreateConfig(new ResConfigFlags()); + } + + ResValue value = new ResBoolValue(false, 0, null); + ResResource res = new ResResource(mType, spec, value); + + mPkg.addResource(res); + mType.addResource(res); + spec.addResource(res); } + } + } - ResValue value = new ResBoolValue(false, 0, null); - ResResource res = new ResResource(mType, spec, value); - - mPkg.addResource(res); - mType.addResource(res); - spec.addResource(res); + private void removeResSpec(ResResSpec spec) throws AndrolibException { + if (mPkg.hasResSpec(spec.getId())) { + mPkg.removeResSpec(spec); + mTypeSpec.removeResSpec(spec); } } @@ -443,6 +486,7 @@ public class ARSCDecoder { private ResType mType; private int mResId; private boolean[] mMissingResSpecs; + private HashMap mResTypeSpecs = new HashMap<>(); private final static short ENTRY_FLAG_COMPLEX = 0x0001;