Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Correct Attribute Resolution #3123

Merged
merged 5 commits into from
Jul 4, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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