Skip to content

Commit

Permalink
Correct Attribute Resolution (#3123)
Browse files Browse the repository at this point in the history
* refactor: rename package_ to pkgId

* refactor: ResAttrDecoder takes ResTable

* fix: fallback on attr decoding to proper prefix

* fix: reverse order of processing to trust string pool 2nd

Android prefers the resource map value over what the String block has.
This can be seen quite often in obfuscated apps where values such as:
 <item android:state_enabled="true" app:state_collapsed="false" app:state_collapsible="true">
Are improperly decoded when trusting the String block.
Leveraging the resource map allows us to get the proper value.
 <item android:state_enabled="true" app:d2="false" app:d3="true">

* refactor: set default value if no string/res value
  • Loading branch information
iBotPeaches authored Jul 4, 2023
1 parent f42ce82 commit 79cfdd1
Show file tree
Hide file tree
Showing 6 changed files with 67 additions and 80 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -136,9 +136,7 @@ private void decodeManifest(ResTable resTable, ExtFile apkFile, File outDir)
// Set ResAttrDecoder
duo.m2.setAttrDecoder(new ResAttrDecoder());
ResAttrDecoder attrDecoder = duo.m2.getAttrDecoder();

// Fake ResPackage
attrDecoder.setCurrentPackage(new ResPackage(resTable, 0, null));
attrDecoder.setResTable(resTable);

Directory inApk, out;
try {
Expand All @@ -159,8 +157,7 @@ private void decodeManifestWithResources(ResTable resTable, ExtFile apkFile, Fil
Duo<ResFileDecoder, AXmlResourceParser> duo = getManifestFileDecoder(true);
ResFileDecoder fileDecoder = duo.m1;
ResAttrDecoder attrDecoder = duo.m2.getAttrDecoder();

attrDecoder.setCurrentPackage(resTable.listMainPackages().iterator().next());
attrDecoder.setResTable(resTable);

Directory inApk, out;
try {
Expand Down Expand Up @@ -259,8 +256,7 @@ private void decodeResources(ResTable resTable, ExtFile apkFile, File outDir)
Duo<ResFileDecoder, AXmlResourceParser> duo = getResFileDecoder();
ResFileDecoder fileDecoder = duo.m1;
ResAttrDecoder attrDecoder = duo.m2.getAttrDecoder();

attrDecoder.setCurrentPackage(resTable.listMainPackages().iterator().next());
attrDecoder.setResTable(resTable);
Directory in, out;

try {
Expand All @@ -273,7 +269,6 @@ private void decodeResources(ResTable resTable, ExtFile apkFile, File outDir)

ExtMXSerializer xmlSerializer = getResXmlSerializer();
for (ResPackage pkg : resTable.listMainPackages()) {
attrDecoder.setCurrentPackage(pkg);

LOGGER.info("Decoding file-resources...");
for (ResResource res : pkg.listFiles()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,22 +17,21 @@
package brut.androlib.res.data;

public class ResID {
public final int package_;
public final int pkgId;
public final int type;
public final int entry;

public final int id;

public ResID(int package_, int type, int entry) {
this(package_, type, entry, (package_ << 24) + (type << 16) + entry);
public ResID(int pkgId, int type, int entry) {
this(pkgId, type, entry, (pkgId << 24) + (type << 16) + entry);
}

public ResID(int id) {
this((id >> 24) & 0xff, (id >> 16) & 0x000000ff, id & 0x0000ffff, id);
}

public ResID(int package_, int type, int entry, int id) {
this.package_ = (package_ == 0) ? 2 : package_;
public ResID(int pkgId, int type, int entry, int id) {
this.pkgId = (pkgId == 0) ? 2 : pkgId;
this.type = type;
this.entry = entry;
this.id = id;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ public ResResSpec getResSpec(int resID) throws AndrolibException {
}

public ResResSpec getResSpec(ResID resID) throws AndrolibException {
return getPackage(resID.package_).getResSpec(resID);
return getPackage(resID.pkgId).getResSpec(resID);
}

public Set<ResPackage> listMainPackages() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -282,8 +282,8 @@ public String getAttributeNamespace(int index) {
String value = m_strings.getString(namespace);

if (value == null || value.length() == 0) {
ResID resourceId = new ResID(getAttributeNameResource(index));
if (resourceId.package_ == PRIVATE_PKG_ID) {
ResID resId = new ResID(getAttributeNameResource(index));
if (resId.pkgId == PRIVATE_PKG_ID) {
value = getNonDefaultNamespaceUri(offset);
} else {
value = android_ns;
Expand Down Expand Up @@ -327,29 +327,32 @@ public String getAttributeName(int index) {
return "";
}

String value = m_strings.getString(name);
String namespace = getAttributeNamespace(index);
String resourceMapValue;
String stringBlockValue = m_strings.getString(name);
int resourceId = getAttributeNameResource(index);

// If attribute name is lacking or a private namespace emerges,
// retrieve the exact attribute name by its id.
if (value == null || value.length() == 0) {
try {
value = mAttrDecoder.decodeManifestAttr(getAttributeNameResource(index));
if (value == null) {
value = "";
}
} catch (AndrolibException e) {
value = "";
}
} else if (! namespace.equals(android_ns)) {
try {
String obfuscatedName = mAttrDecoder.decodeManifestAttr(getAttributeNameResource(index));
if (! (obfuscatedName == null || obfuscatedName.equals(value))) {
value = obfuscatedName;
}
} catch (AndrolibException ignored) {}
try {
resourceMapValue = mAttrDecoder.decodeFromResourceId(resourceId);
} catch (AndrolibException ignored) {
resourceMapValue = null;
}
return value;

// Android prefers the resource map value over what the String block has.
// This can be seen quite often in obfuscated apps where values such as:
// <item android:state_enabled="true" app:state_collapsed="false" app:state_collapsible="true">
// Are improperly decoded when trusting the String block.
// Leveraging the resource map allows us to get the proper value.
// <item android:state_enabled="true" app:d2="false" app:d3="true">
if (resourceMapValue != null) {
return resourceMapValue;
}

if (stringBlockValue != null) {
return stringBlockValue;
}

// In this case we have a bogus resource. If it was not found in either.
return "APKTOOL_MISSING_" + Integer.toHexString(resourceId);
}

@Override
Expand Down Expand Up @@ -383,18 +386,22 @@ public String getAttributeValue(int index) {

if (mAttrDecoder != null) {
try {
String value = valueRaw == -1 ? null : ResXmlEncoders.escapeXmlChars(m_strings.getString(valueRaw));
String obfuscatedValue = mAttrDecoder.decodeManifestAttr(valueData);
String stringBlockValue = valueRaw == -1 ? null : ResXmlEncoders.escapeXmlChars(m_strings.getString(valueRaw));
String resourceMapValue = mAttrDecoder.decodeFromResourceId(valueData);
String value = stringBlockValue;

if (! (value == null || obfuscatedValue == null)) {
int slashPos = value.lastIndexOf("/");
if (stringBlockValue != null && resourceMapValue != null) {
int slashPos = stringBlockValue.lastIndexOf("/");
int colonPos = stringBlockValue.lastIndexOf(":");

// Handle a value with a format of "@yyy/xxx", but avoid "@yyy/zzz:xxx"
if (slashPos != -1) {
// Handle a value with a format of "@yyy/xxx"
String dir = value.substring(0, slashPos);
value = dir + "/"+ obfuscatedValue;
} else if (! value.equals(obfuscatedValue)) {
value = obfuscatedValue;
if (colonPos == -1) {
String type = stringBlockValue.substring(0, slashPos);
value = type + "/" + resourceMapValue;
}
} else if (! stringBlockValue.equals(resourceMapValue)) {
value = resourceMapValue;
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,72 +17,58 @@
package brut.androlib.res.decoder;

import brut.androlib.exceptions.AndrolibException;
import brut.androlib.exceptions.CantFindFrameworkResException;
import brut.androlib.exceptions.UndefinedResObjectException;
import brut.androlib.res.data.ResID;
import brut.androlib.res.data.ResPackage;
import brut.androlib.res.data.ResResSpec;
import brut.androlib.res.data.ResTable;
import brut.androlib.res.data.value.ResAttr;
import brut.androlib.res.data.value.ResScalarValue;

public class ResAttrDecoder {
public String decode(int type, int value, String rawValue, int attrResId)
throws AndrolibException {
ResScalarValue resValue = mCurrentPackage.getValueFactory().factory(
type, value, rawValue);
throws AndrolibException {
ResScalarValue resValue = mResTable.getCurrentResPackage().getValueFactory().factory(type, value, rawValue);

String decoded = null;
if (attrResId > 0) {
try {
ResAttr attr = (ResAttr) getCurrentPackage().getResTable()
.getResSpec(attrResId).getDefaultResource().getValue();
ResAttr attr = (ResAttr) mResTable.getResSpec(attrResId).getDefaultResource().getValue();

decoded = attr.convertToResXmlFormat(resValue);
} catch (UndefinedResObjectException | ClassCastException ex) {
// ignored
}
} catch (UndefinedResObjectException | ClassCastException ignored) {}
}

return decoded != null ? decoded : resValue.encodeAsResXmlAttr();
}

public String decodeManifestAttr(int attrResId)
public String decodeFromResourceId(int attrResId)
throws AndrolibException {

if (attrResId != 0) {
int attrId = attrResId;

// See also: brut.androlib.res.data.ResTable.getResSpec
if (attrId >> 24 == 0) {
ResPackage pkg = getCurrentPackage();
int packageId = pkg.getId();
int pkgId = (packageId == 0 ? 2 : packageId);
attrId = (0xFF000000 & (pkgId << 24)) | attrId;
}
ResID resId = new ResID(attrResId);

// Retrieve the ResSpec in a package by id
ResID resId = new ResID(attrId);
ResPackage pkg = getCurrentPackage();
if (pkg.hasResSpec(resId)) {
ResResSpec resResSpec = pkg.getResSpec(resId);
try {
ResResSpec resResSpec = mResTable.getResSpec(resId);
if (resResSpec != null) {
return resResSpec.getName();
}
}
} catch (UndefinedResObjectException | CantFindFrameworkResException ignored) {}
}

return null;
}

public ResPackage getCurrentPackage() throws AndrolibException {
if (mCurrentPackage == null) {
throw new AndrolibException("Current package not set");
public ResTable getResTable() throws AndrolibException {
if (mResTable == null) {
throw new AndrolibException("Res Table not set");
}
return mCurrentPackage;
return mResTable;
}

public void setCurrentPackage(ResPackage currentPackage) {
mCurrentPackage = currentPackage;
public void setResTable(ResTable resTable) {
mResTable = resTable;
}

private ResPackage mCurrentPackage;
private ResTable mResTable;
}
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ public void decode(InputStream in, OutputStream out)
try {
XmlPullWrapperFactory factory = XmlPullWrapperFactory.newInstance();
XmlPullParserWrapper par = factory.newPullParserWrapper(mParser);
final ResTable resTable = ((AXmlResourceParser) mParser).getAttrDecoder().getCurrentPackage().getResTable();
final ResTable resTable = ((AXmlResourceParser) mParser).getAttrDecoder().getResTable();

XmlSerializerWrapper ser = new StaticXmlSerializerWrapper(mSerial, factory) {
boolean hideSdkInfo = false;
Expand Down

0 comments on commit 79cfdd1

Please sign in to comment.