Skip to content

Commit d3018d7

Browse files
committed
add indexed multi-line format for chained b64 comments
1 parent 2a18fa6 commit d3018d7

2 files changed

Lines changed: 29 additions & 10 deletions

File tree

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

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

3+
import java.util.ArrayList;
34
import java.util.Collections;
45
import java.util.HashSet;
56
import java.util.LinkedHashMap;
@@ -68,6 +69,8 @@ public void visit(MethodNode mth) {
6869
}
6970
// Lazily built on the first B64 hit — avoids field scan for methods with no B64 strings
7071
Set<String> fieldConstants = null;
72+
// Decoded strings grouped by their top-level statement instruction (insertion = source order)
73+
Map<InsnNode, List<B64Result>> stmtCandidates = null;
7174
// Arrays where ≥1 string passed normal detect() — required anchor for contextual decoding
7275
Set<InsnNode> arrayAnchors = null;
7376
// All valid-B64+UTF-8 strings in arrays (superset of anchors; used for the final comment)
@@ -136,13 +139,20 @@ public void visit(MethodNode mth) {
136139
if (fieldConstants.contains(str)) {
137140
continue;
138141
}
139-
// Attach to the top-level statement rather than the ConstStringNode so that
140-
// comments from multiple decoded strings in one expression all survive.
141-
// inheritMetadata uses the untyped addAttr (map.put = replace), so comments
142-
// riding up through inlining chains overwrite each other; attaching directly
143-
// to the statement uses the typed addAttr (list.add = merge) and bypasses that.
144-
InsnNode stmtInsn = findStatementInsn(csn);
145-
stmtInsn.addAttr(AType.CODE_COMMENTS, new CodeComment(decoded.commentText(), CommentStyle.LINE));
142+
if (stmtCandidates == null) {
143+
stmtCandidates = new LinkedHashMap<>();
144+
}
145+
stmtCandidates.computeIfAbsent(findStatementInsn(csn), k -> new ArrayList<>()).add(decoded);
146+
}
147+
}
148+
149+
if (stmtCandidates != null) {
150+
for (Map.Entry<InsnNode, List<B64Result>> e : stmtCandidates.entrySet()) {
151+
List<B64Result> results = e.getValue();
152+
String comment = results.size() == 1
153+
? results.get(0).commentText()
154+
: buildChainComment(results);
155+
e.getKey().addAttr(AType.CODE_COMMENTS, new CodeComment(comment, CommentStyle.LINE));
146156
}
147157
}
148158

@@ -266,6 +276,15 @@ private static int argIndexOf(InsnNode insn, SSAVar ssaVar) {
266276
return -1;
267277
}
268278

279+
private static String buildChainComment(List<B64Result> results) {
280+
StringBuilder sb = new StringBuilder();
281+
for (int i = 0; i < results.size(); i++) {
282+
sb.append('\n');
283+
sb.append(results.get(i).indexedCommentText(i));
284+
}
285+
return sb.toString();
286+
}
287+
269288
private static String buildArrayComment(TreeMap<Integer, B64Result> decodings) {
270289
StringBuilder sb = new StringBuilder();
271290
for (Map.Entry<Integer, B64Result> e : decodings.entrySet()) {

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -366,11 +366,11 @@ public void multiB64InvokeChainTest() throws Exception {
366366
// Two const-strings both used as direct Base64.decode args in the same method chain:
367367
// Class.forName(new String(Base64.decode("...", 0)))
368368
// .getMethod(new String(Base64.decode("...", 0)), ...)
369-
// Both should get b64: comments — bug: only the first was annotated
369+
// Both should get indexed b64 comments in left-to-right source order
370370
String code = decompileSmali("b64/multi_b64_invoke.smali");
371371
System.out.println(code);
372-
assertThat(code).contains("b64: android.app.ActivityThread");
373-
assertThat(code).contains("b64: getPackageManager");
372+
assertThat(code).contains("b64[0]: android.app.ActivityThread");
373+
assertThat(code).contains("b64[1]: getPackageManager");
374374
}
375375

376376
@Test

0 commit comments

Comments
 (0)