-
Notifications
You must be signed in to change notification settings - Fork 5k
New issue
Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? # 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. |
Hi @skylot,
I did that and I was able to implement a linter pass as a jadx-plugin: https://github.com/nitram84/jadx/tree/android-linter-plugin. And even more (See https://github.com/nitram84/jadx/blob/android-linter-plugin/jadx-plugins/jadx-android-linter/README.md):
Before I open a pull request, I would like to ask if this is something potentially mergeable or should this plugin become standalone? |
@nitram84 great work 👍
Actually, this is up to you to decide 🙂 |
Thank you too. Your changes for this issue helped be to implement this. And perhaps using third party rules should be optional and configurable. |
Well, "part of jadx" is a vague term, it can mean several things:
Actually, I am still thinking about moving jadx repo into
These parts look tightly coupled, not sure if splitting is really necessary. But sure if you plan to update database very often it is better to make it downloadable and auto update on changes. And, please consider to support option for manual update to minimize network access because it can be long and very faulty (and better to avoid at all in jadx-cli).
Sure, this is possible. And, if you want, we can put your plugin repo also into this group. |
Option 4 sounds good. This way the plugin may help to test a modularized version of jadx. I'm not sure how frequent there will be rule updates. Rule updates may be caused by a new Android api level, api changes to third party libs (means breaking changes of libraries) and new libraries (either scraped or suggested by a user). Most updates may come from new rules of maven central. I will add some rules from maven central, but I don't think scraping is an option. So I don't expect very frequent updates to the database. Working with an older state of the database my produce incomplete results but should not produce incorrect results. With this assumptions I would always bundle a static database with the plugin to make the plugin usable in offline mode. This way there is also no need to separate database and plugin into different repos anymore. For client side updates incremental updates should be possible, if I use a different index layout (with timestamps and hashes for each file, a bit like npm). When the database is updated the first time, a copy of the internal rule db could be created. The copy will be updated and used. If a user deletes the updated database, the plugin would still be usable with the internal static database. A different index layout may solve some more problems (there are libs without having own constants -> no empty constant file needed and there may be libs with "legacy rules" that only exists in earlier library versions) and would allow recoverable updates with low bandwidth usage. With an updatable copy of the database I think it should also be possible for users to add their own custom rules in a special directory. I will work on better updatable database. |
Hi @skylot, here is finally a stand alone plugin: https://github.com/nitram84/jadx-android-linter-plugin. I updated the database (API level 35, new repositories with selected libraries: maven central, jitpack). There is now an option (jadx-gui only) to update the linter index. In cli mode there is no code path with network access. For the moment I do not plan automatic index updates - I tested an update interval of several months and there were only a few changes for some libs. I'm using a shadow copy in ~/.cache/jadx/linter/ for updates. At the moment the local linter database won't be updated automatically with the bundled database when a new plugin version is installed - when the database is compatible, I don't want to override a local database, especially when a user has custom changes. Index is versioned, so changes can be implemented in the future. (Off topic: I' don't support localization of menu items yet. Is i18n support for menus and dialogs in plugins a possible feature for the plugin api?) I would consider this plugin in incubator state - If this plugin passes a review I'm ready to transfer this plugin to https://github.com/jadx-decompiler. Database updates are planned anyway. This issue can be closed, I think. I'll open a follow-up issue at https://github.com/jadx-decompiler/jadx-plugins-list when there is a first release. |
@nitram84 looks great 👍
I can allow getting current language selected and localized strings from jadx, but not sure how to handle plugin specific strings for now.
Sure. I created https://github.com/jadx-decompiler/jadx-android-linter-plugin, and you should be able to push into it. |
Thank you, I added the missing tests and pushed my work to the new location. I think i18n in plugins is currently a minor issue. Of course getting the selecting language and accessing localized strings would help a lot. I can't propose API functions for now. Currently I'm working on a new plugin with dialogs, where i18n will be more important. Perhaps I can propose some ideas or give an example. |
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: