Skip to content

Commit ba23bc3

Browse files
committed
using indexed b64[X] comments or cases its ambigous what string element is being decoded
1 parent e3ec48b commit ba23bc3

5 files changed

Lines changed: 107 additions & 6 deletions

File tree

src/main/java/jadx/plugins/stringdecoder/B64DeobfuscatePass.java

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -286,12 +286,7 @@ private static String buildChainComment(List<B64Result> results) {
286286
}
287287

288288
private static String buildArrayComment(TreeMap<Integer, B64Result> decodings) {
289-
StringBuilder sb = new StringBuilder();
290-
for (Map.Entry<Integer, B64Result> e : decodings.entrySet()) {
291-
sb.append('\n');
292-
sb.append(e.getValue().indexedCommentText(e.getKey()));
293-
}
294-
return sb.toString();
289+
return B64Result.buildIndexedComment(decodings);
295290
}
296291

297292
/**

src/main/java/jadx/plugins/stringdecoder/B64FieldInitPass.java

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package jadx.plugins.stringdecoder;
22

33
import java.util.Locale;
4+
import java.util.TreeMap;
45

56
import jadx.api.plugins.input.data.annotations.EncodedType;
67
import jadx.api.plugins.input.data.annotations.EncodedValue;
@@ -13,6 +14,7 @@
1314
import jadx.core.dex.info.FieldInfo;
1415
import jadx.core.dex.info.MethodInfo;
1516
import jadx.core.dex.instructions.ConstStringNode;
17+
import jadx.core.dex.instructions.FilledNewArrayNode;
1618
import jadx.core.dex.instructions.IndexInsnNode;
1719
import jadx.core.dex.instructions.InsnType;
1820
import jadx.core.dex.instructions.InvokeNode;
@@ -60,6 +62,12 @@ private void processField(FieldNode field) {
6062
InsnNode initInsn = initAttr.getInsn();
6163
if (initInsn instanceof ConstStringNode) {
6264
annotateField(field, ((ConstStringNode) initInsn).getString(), false);
65+
} else if (initInsn instanceof FilledNewArrayNode) {
66+
// B64DeobfuscatePass already handled small filled-new-array instructions
67+
// (original Dalvik opcode); skip re-processing them.
68+
if (!initInsn.contains(AType.CODE_COMMENTS)) {
69+
findAndAnnotateFilledArray(field, (FilledNewArrayNode) initInsn);
70+
}
6371
} else {
6472
findAndAnnotateInArgTree(field, initInsn, 0);
6573
}
@@ -76,6 +84,45 @@ private void processField(FieldNode field) {
7684
}
7785
}
7886

87+
/**
88+
* Processes a FilledNewArrayNode field init with indexed, anchor-based detection.
89+
* Mirrors B64DeobfuscatePass array logic: only emits a comment when at least one element
90+
* passes full detection (the anchor), then includes all valid-Base64+UTF-8 elements
91+
* with their array indices so the caller can identify which element was decoded.
92+
*/
93+
private void findAndAnnotateFilledArray(FieldNode field, FilledNewArrayNode filledArray) {
94+
TreeMap<Integer, B64Result> candidates = null;
95+
boolean hasAnchor = false;
96+
for (int idx = 0; idx < filledArray.getArgsCount(); idx++) {
97+
InsnArg arg = filledArray.getArg(idx);
98+
InsnNode argInsn = resolveArgInsn(arg);
99+
String str = null;
100+
if (argInsn instanceof ConstStringNode) {
101+
str = ((ConstStringNode) argInsn).getString();
102+
} else if (argInsn != null && argInsn.getType() == InsnType.SGET) {
103+
str = resolveConstStringFromSget(field, (IndexInsnNode) argInsn);
104+
}
105+
if (str == null || B64FalsePositives.contains(str)) {
106+
continue;
107+
}
108+
B64Result full = B64Detector.detect(str, options);
109+
B64Result candidate = full != null ? full : B64Detector.decodeIfValid(str, options.getMaxCommentLength());
110+
if (candidate == null) {
111+
continue;
112+
}
113+
if (candidates == null) {
114+
candidates = new TreeMap<>();
115+
}
116+
candidates.put(idx, candidate);
117+
if (full != null) {
118+
hasAnchor = true;
119+
}
120+
}
121+
if (hasAnchor && candidates != null) {
122+
field.addCodeComment(B64Result.buildIndexedComment(candidates));
123+
}
124+
}
125+
79126
/**
80127
* Recursively walks the instruction arg tree looking for a ConstStringNode.
81128
* When the ConstStringNode is a direct arg of a Base64.decode-like call, decodes

src/main/java/jadx/plugins/stringdecoder/B64Result.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package jadx.plugins.stringdecoder;
22

3+
import java.util.TreeMap;
4+
35
final class B64Result {
46
private final String decoded;
57
private final String tag; // "" = standard, "url", "mime"
@@ -22,4 +24,14 @@ String commentText() {
2224
String indexedCommentText(int index) {
2325
return tag.isEmpty() ? "b64[" + index + "]: " + decoded : "b64(" + tag + ")[" + index + "]: " + decoded;
2426
}
27+
28+
/** Builds a multi-line indexed comment from a sorted map of array-index → B64Result. */
29+
static String buildIndexedComment(TreeMap<Integer, B64Result> entries) {
30+
StringBuilder sb = new StringBuilder();
31+
for (java.util.Map.Entry<Integer, B64Result> e : entries.entrySet()) {
32+
sb.append('\n');
33+
sb.append(e.getValue().indexedCommentText(e.getKey()));
34+
}
35+
return sb.toString();
36+
}
2537
}

src/test/java/jadx/plugins/stringdecoder/JadxStringDecoderPluginTest.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -295,6 +295,19 @@ public void filledArrayB64Test() throws Exception {
295295
assertThat(code).contains("b64[4]: testing");
296296
}
297297

298+
@Test
299+
public void stringArrayFieldIndexedB64Test() throws Exception {
300+
// String[] field initialised via new-array + aput-object (large-array path).
301+
// B64FieldInitPass.findAndAnnotateFilledArray should produce indexed comments
302+
// for the two B64 elements; plain strings at indices 0 and 2 must not appear.
303+
String code = decompileSmali("b64/string_array_field_b64.smali");
304+
System.out.println(code);
305+
assertThat(code).contains("b64[1]: Hello, World!");
306+
assertThat(code).contains("b64[3]: hello");
307+
assertThat(code).doesNotContain("b64[0]:");
308+
assertThat(code).doesNotContain("b64[2]:");
309+
}
310+
298311
@Test
299312
public void falsePositiveSystemJobSchedulerTest() throws Exception {
300313
// "SystemJobScheduler" is a known Android class name that slips past ratio filters
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
.class public Lb64/StringArrayFieldB64;
2+
.super Ljava/lang/Object;
3+
4+
# String[] field initialised with new-array + aput-object (large-array pattern).
5+
# After ReplaceNewArray converts the APUT sequence to FilledNewArrayNode,
6+
# B64FieldInitPass.findAndAnnotateFilledArray should produce indexed comments
7+
# for the two Base64 elements at indices 1 and 3.
8+
.field public static strings:[Ljava/lang/String;
9+
10+
.method static constructor <clinit>()V
11+
.registers 4
12+
13+
const/4 v3, 0x4
14+
new-array v0, v3, [Ljava/lang/String;
15+
16+
const-string v1, "just a plain string"
17+
const/4 v2, 0x0
18+
aput-object v1, v0, v2
19+
20+
const-string v1, "SGVsbG8sIFdvcmxkIQ=="
21+
const/4 v2, 0x1
22+
aput-object v1, v0, v2
23+
24+
const-string v1, "not base64 at all!!!"
25+
const/4 v2, 0x2
26+
aput-object v1, v0, v2
27+
28+
const-string v1, "aGVsbG8="
29+
const/4 v2, 0x3
30+
aput-object v1, v0, v2
31+
32+
sput-object v0, Lb64/StringArrayFieldB64;->strings:[Ljava/lang/String;
33+
return-void
34+
.end method

0 commit comments

Comments
 (0)