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
[feature] Improve constant replacement by using android sdk libs #2119
Comments
@nitram84 thanks for the suggestion, but such change have a lot of things to consider, so I need some time for deeper investigation.
|
Hi @skylot Thank you for your answer! Before you start investigating this issue, I should provide more details about android sdk constants and the related "R.styleable"-issue. Let's take my first sample app: https://candy-bubble-shooter.apk.gold/ In
What is
And indeed (You might have an idea how I would restore Currently At the moment it's not easy to inject new constant definitions to The next questions is "About how many unique constants are we talking"? 8418 constants for android-34, counted quick and dirty with: // Use dependency org.ow2.asm:asm:9.6 and pass path to android.jar as single argument, dump all unique constants as csv to stdout
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashSet;
import java.util.HashMap;
import java.util.Set;
import java.util.Map;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.FieldVisitor;
import org.objectweb.asm.Opcodes;
public class AndroidConstDump {
protected static Set<String> constValues = new HashSet<>();
protected static Map<String, String> constants = new HashMap<>();
public static final void main(String[] args) {
if (args.length != 1) {
System.exit(1);
}
String pathToAndroidJar = args[0];
try (ZipInputStream zis = new ZipInputStream(new FileInputStream(pathToAndroidJar))) {
ZipEntry zipEntry = zis.getNextEntry();
while (zipEntry != null) {
if (!zipEntry.isDirectory()) {
String className = zipEntry.getName();
if (className.endsWith(".class")) {
processClass(zis, className);
}
}
zipEntry = zis.getNextEntry();
}
} catch (IOException e) {
e.printStackTrace();
}
for(String constRecord: constants.values()) {
System.out.println(constRecord);
}
}
private static void processClass(InputStream in, final String className) {
try {
ClassReader cr = new ClassReader(in);
cr.accept(new ClassVisitor(Opcodes.ASM9) {
@Override
public FieldVisitor visitField(int access, String name, String descriptor, String signature, Object value) {
if(value != null && access == 25) {
String constValue = value.toString();
String constRecord = className.replace('/', '.').replace('$', '.').replace(".class", ".") + name + ";" + descriptor + ";" + value.toString();
if (constants.get(constValue) != null) {
constants.remove(constValue);
}
if (!constValues.contains(constValue)) {
constants.put(constValue, constRecord);
}
constValues.add(constValue);
}
return super.visitField(access, name, descriptor, signature, value);
}
}, 0);
} catch (IOException e) {
e.printStackTrace();
}
}
} You added the resources flag, but in my opinion this issue is not about resources: Resource constants are included in the list of unique constants, but the missing constants are not restricted to resources. If needed I can provide a sample with a non-replaced constant of For the moment I would ignore constants of "org.apache.http.legacy.jar" or "android.car.jar". I have a last sample to show that this issue is not restricted to R files and to show a special case how (some) non-unique constants could be replaced without false-positives: https://mahjong-solitaire-free2.apk.gold/ Class Using the unique constants But what is the constant in Example: https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/view/View.java#1105 -> https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/view/View.java#13018 If those rules could be extracted, I could imagine an AndroidLinterPass to replace those constants. Linter rules would be related to "core.jcst" because rules are bound to method signatures loaded from there. In the past linter rules helped me a bit to understand obfuscated code. If I find a way to export the linter rules I would open a new issue. Edit: typo, more formatting |
@nitram84 thank you for details! It becomes much easier to understand the issue. Mostly because I am not very familiar with Android resources processing/usage (relevant jadx code was added by contributors). Also, I add
Actually I was more conserned about constant fields with duplicated values, such fields will "taking space" in "core.jcst" but can't be used for constant replacement. So I made a simple script to count such duplications: import jadx.core.dex.info.ConstStorage
val jadx = getJadxInstance()
jadx.args.isReplaceConsts = false
val constMap = mutableMapOf<Any, Int>()
jadx.stages.prepare { root ->
for (cls in root.classes) {
for (fld in cls.fields) {
ConstStorage.getFieldConstValue(fld)?.let { constVal ->
constMap.merge(constVal, 1, Int::plus)
}
}
}
}
jadx.afterLoad {
for (entry in constMap.entries.sortedBy { it.value }) {
log.debug { "value: ${entry.key} - found in ${entry.value} fields" }
}
val total = constMap.values.sum()
val unique = constMap.values.count { it == 1 }
val duplicate = constMap.values.filter { it > 1 }.sum()
log.info { "Constant fields: total=$total, unique=$unique, duplicate=$duplicate" }
} Running it with
As you can see, duplication rate is very high. This mostly caused by fields which used for enumeration or flags, like in mentioned public static final int VISIBLE = 0x00000000;
public static final int INVISIBLE = 0x00000004;
public static final int GONE = 0x00000008;
@IntDef({VISIBLE, INVISIBLE, GONE})
@Retention(RetentionPolicy.SOURCE)
public @interface Visibility {} So, current options to resolve this issue are:
About android flags like in I put Small Note: This issue can be slightly related to #2134 because it may need to change a way to store all possible additional information for other classes without involving these classes in decompilation. |
Thank you for your deeper analysis! With your information I would now argue for using fake field info to add new constants to
Perhaps it's better to apply sdk constants before constants defined in the app itself. This could reduce false positives. For a possible android linter pass:
It is also possible to add custom |
Ok. I commit changes to put resources fields (from res-map.txt) into const storage. Please check 🙂
Oh, I didn't know that, thanks! |
Thank you very much for your work. All constants in I'll try to write and provide a script that identifies more useful android constants (non resource constants, unique value) that could be added in the same way. For a android linter pass I first have to parse and analyze the annotation xml files. I don't think we all const field from android.jar, but be need a collection of all int/log/string-def rule. I have to check how many rules and constants we have and find a way to merge them to one rules file. In my opinion one file per library instead of one file per package would be easier to work with. But it may take some time until I can provide more details. |
Describe your idea
In some apps I recompiled recently, I had compilation errors because attributes of custom views in
values/attrs.xml
were not defined as "declare-styleable". So I looked into the<app package>.R.styleable.*
definitions. For each view the is an array of attributes inR.styleable
. I found that not all literals in R.styleable array were resolved as constants. All those literals could be replaced to constants by usingjadx-core/src/main/resources/android/res-map.txt
or android.R.* of android sdk.Examples for apps with unresolved
R.styleable
literals:https://candy-bubble-shooter.apk.gold/
https://apkpure.com/rest-api-client/in.pounkumar.restclient/download/1.1.2
Here is my feature request:
Would it be possible to add all public constant definitions of android sdk libs to
jadx-core/src/main/resources/clst/core.jcst
and use the constants in ConstStorage? Resolving android internal constants would make obfuscated code a bit more readable, lots of android linter warnings would be fixed (I'm thinking of android linter warnings due to unresolved constants ofandroid.content.Context
.) and it might be possible to recover declare-styleable attributes *).If constant information could be loaded from
jadx-core/src/main/resources/clst/core.jcst
the filejadx-core/src/main/resources/android/res-map.txt
wouldn't be needed anymore because this file is redundant to the constants ofandroid.R.*
.This issue is a duplicate to #244.
*) Recovering declare-styleable attributes is a known issue for jadx and apktool:
#247
#244
iBotPeaches/Apktool#775
iBotPeaches/Apktool#1217
iBotPeaches/Apktool#1910
iBotPeaches/Apktool#224
For Apktool this issue is considered as unsolvable. But I think, there is a way for jadx to recover "declare-styleable" attributes when all values in
R.styleable.*
arrays are resolved to constants.The text was updated successfully, but these errors were encountered: