Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Commit 7c77ed0

Browse files
sigmundchcommit-bot@chromium.org
authored andcommitted
Move deobfuscation tools to the SDK repo
This is an initial implementation of the dart deobfuscator tool. Let me know your thoughts on the package name. I used to have this named as `package:deobfuscate`, but it feels like we will want to add more tools that are not about deobfuscation in the future, so I picked `package:dart2js_tools` instead. That also gives us the opportunity to move over the dart2js_info code here too. Change-Id: I2ff948982969c9c76bc84cdc78cbe237abc87378 Reviewed-on: https://dart-review.googlesource.com/69243 Reviewed-by: Stephen Adams <[email protected]> Commit-Queue: Sigmund Cherem <[email protected]>
1 parent 666c8c1 commit 7c77ed0

12 files changed

Lines changed: 748 additions & 0 deletions

.packages

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ convert:third_party/pkg/convert/lib
2929
crypto:third_party/pkg/crypto/lib
3030
csslib:third_party/pkg/csslib/lib
3131
dart2js_info:third_party/pkg/dart2js_info/lib
32+
dart2js_tools:pkg/dart2js_tools/lib
3233
dart_internal:pkg/dart_internal/lib
3334
dart_messages:pkg/dart_messages/lib
3435
dart_style:third_party/pkg_tested/dart_style/lib

pkg/dart2js_tools/README.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
2+
### dart2js\_tools
3+
4+
This package collects tools used with dart2js.
5+
6+
For now, this contains scripts useful to work with obfuscated stack traces in
7+
production and reading data from the extensions added to source-maps by dart2js
8+
(like minified names and inlined stack frames).
9+
10+
In the future we plan to merge here tools in the dart2js\_info package as well.
Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
// Copyright (c) 2018, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
import 'dart:io';
6+
import 'package:source_maps/source_maps.dart';
7+
import 'package:source_maps/src/utils.dart';
8+
import 'package:dart2js_tools/src/trace.dart';
9+
import 'package:dart2js_tools/src/sourcemap_helper.dart';
10+
import 'package:dart2js_tools/src/name_decoder.dart';
11+
import 'package:dart2js_tools/src/dart2js_mapping.dart';
12+
import 'package:dart2js_tools/src/util.dart';
13+
14+
/// Script that deobuscates a stack-trace given in a text file.
15+
///
16+
/// To run this script you need 3 or more files:
17+
///
18+
/// * A stacktrace file
19+
/// * The deployed .js file
20+
/// * The corresponding .map file
21+
///
22+
/// There might be more than one .js/.map file if your app is divided in
23+
/// deferred chunks.
24+
///
25+
/// The stack trace file contains a copy/paste of a JavaScript stack trace, of
26+
/// this form:
27+
///
28+
/// at aB.a20 (main.dart.js:71969:32)
29+
/// at aNk.goV (main.dart.js:72040:52)
30+
/// at aNk.gfK (main.dart.js:72038:27)
31+
/// at FE.gtn (main.dart.js:72640:24)
32+
/// at aBZ.ghN (main.dart.js:72642:24)
33+
/// at inheritance (main.dart.js:105334:0)
34+
/// at FE (main.dart.js:5037:18)
35+
///
36+
/// If you download the stacktrace from a production service, you can keep the
37+
/// full URL (including http://....) and this script will simply try to match
38+
/// the name of the file at the end with a file in the current working
39+
/// directory.
40+
///
41+
/// The .js file must contain a `//# sourceMappingURL=` line at the end, which
42+
/// tells this script how to determine the name of the source-map file.
43+
main(List<String> args) {
44+
if (args.length != 1) {
45+
print('usage: deobfuscate.dart <stack-trace-file>');
46+
exit(1);
47+
}
48+
var sb = new StringBuffer();
49+
try {
50+
deobfuscate(new File(args[0]).readAsStringSync(), sb);
51+
} finally {
52+
print('$sb');
53+
}
54+
}
55+
56+
void deobfuscate(trace, StringBuffer sb) {
57+
String error = extractErrorMessage(trace);
58+
String translatedError;
59+
var provider = new CachingFileProvider();
60+
61+
List<StackTraceLine> jsStackTrace = parseStackTrace(trace);
62+
63+
for (StackTraceLine line in jsStackTrace) {
64+
var uri = resolveUri(line.fileName);
65+
var mapping = provider.mappingFor(uri);
66+
if (mapping == null) {
67+
printPadded('no mapping', line.inlineString, sb);
68+
continue;
69+
}
70+
71+
TargetEntry targetEntry = findColumn(line.lineNo - 1, line.columnNo - 1,
72+
findLine(mapping.sourceMap, line.lineNo - 1));
73+
if (targetEntry == null) {
74+
printPadded('no entry', line.inlineString, sb);
75+
continue;
76+
}
77+
78+
if (translatedError == null) {
79+
translatedError = translate(error, mapping, line, targetEntry);
80+
if (translatedError == null) translatedError = '<no error message found>';
81+
printPadded(translatedError, error, sb);
82+
}
83+
84+
int offset =
85+
provider.fileFor(uri).getOffset(line.lineNo - 1, line.columnNo - 1);
86+
87+
String nameOf(id) => id != 0 ? mapping.sourceMap.names[id] : null;
88+
String urlOf(id) => id != 0 ? mapping.sourceMap.urls[id] : null;
89+
90+
String fileName = urlOf(targetEntry.sourceUrlId ?? 0);
91+
int targetLine = (targetEntry.sourceLine ?? 0) + 1;
92+
int targetColumn = (targetEntry.sourceColumn ?? 0) + 1;
93+
94+
// Expand inlined frames.
95+
Map<int, List<FrameEntry>> frames = mapping.frames;
96+
List<int> index = mapping.frameIndex;
97+
int key = binarySearch(index, (i) => i > offset) - 1;
98+
int depth = 0;
99+
outer:
100+
while (key >= 0) {
101+
for (var frame in frames[index[key]].reversed) {
102+
if (frame.isEmpty) break outer;
103+
if (frame.isPush) {
104+
if (depth <= 0) {
105+
var mappedLine = new StackTraceLine(
106+
frame.inlinedMethodName + "(inlined)",
107+
fileName,
108+
targetLine,
109+
targetColumn);
110+
printPadded(mappedLine.inlineString, "", sb);
111+
fileName = frame.callUri;
112+
targetLine = (frame.callLine ?? 0) + 1;
113+
targetColumn = (frame.callColumn ?? 0) + 1;
114+
} else {
115+
depth--;
116+
}
117+
}
118+
if (frame.isPop) {
119+
depth++;
120+
}
121+
}
122+
key--;
123+
}
124+
125+
var functionEntry = findEnclosingFunction(provider, uri, offset);
126+
String methodName = nameOf(functionEntry.sourceNameId ?? 0);
127+
var mappedLine =
128+
new StackTraceLine(methodName, fileName, targetLine, targetColumn);
129+
printPadded(mappedLine.inlineString, line.inlineString, sb);
130+
}
131+
}
132+
133+
final green = stdout.hasTerminal ? '' : '';
134+
final none = stdout.hasTerminal ? '' : '';
135+
136+
printPadded(String mapping, String original, sb) {
137+
var len = mapping.length;
138+
var s = mapping.indexOf('\n');
139+
if (s >= 0) len -= s + 1;
140+
var pad = ' ' * (50 - len);
141+
sb.writeln('$green$mapping$none$pad ... $original');
142+
}
143+
144+
Uri resolveUri(String filename) {
145+
var uri = Uri.base.resolve(filename);
146+
if (uri.scheme == 'http' || uri.scheme == 'https') {
147+
filename = uri.path.substring(uri.path.lastIndexOf('/') + 1);
148+
uri = Uri.base.resolve(filename);
149+
}
150+
return uri;
151+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import 'dart:io';
2+
import 'dart:convert';
3+
import 'package:source_maps/source_maps.dart';
4+
5+
main(List<String> args) {
6+
if (args.length < 2) {
7+
print('usage: read.dart <source-map-file> <name>');
8+
exit(1);
9+
}
10+
var sourcemapFile = new File.fromUri(Uri.base.resolve(args[0]));
11+
if (!sourcemapFile.existsSync()) {
12+
print('no source-map-file in ${args[0]}');
13+
exit(1);
14+
}
15+
var name = args[1];
16+
var json = jsonDecode(sourcemapFile.readAsStringSync());
17+
SingleMapping mapping = parseJson(json);
18+
var extensions = json['x_org_dartlang_dart2js'];
19+
if (extensions == null) {
20+
print('source-map file has no dart2js extensions');
21+
exit(1);
22+
}
23+
var minifiedNames = extensions['minified_names'];
24+
if (minifiedNames == null) {
25+
print('source-map file has no minified names in the dart2js extensions');
26+
exit(1);
27+
}
28+
var gid = minifiedNames['global'][name];
29+
if (gid != null) print('$name => ${mapping.names[gid]} (a global name)');
30+
var iid = minifiedNames['instance'][name];
31+
if (iid != null) print('$name => ${mapping.names[iid]} (an instance name)');
32+
if (gid == null && iid == null) print('Name \'$name\' not found.');
33+
}

pkg/dart2js_tools/bin/read.dart

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import 'dart:io';
2+
import 'dart:convert';
3+
import 'package:source_maps/source_maps.dart';
4+
5+
main(List<String> args) {
6+
if (args.length != 1) {
7+
print('usage: read.dart <source-map-file>');
8+
exit(1);
9+
}
10+
11+
var sourcemapFile = new File.fromUri(Uri.base.resolve(args[0]));
12+
if (!sourcemapFile.existsSync()) {
13+
print('no source-map-file in ${args[0]}');
14+
}
15+
var bytes = sourcemapFile.readAsBytesSync();
16+
parse(utf8.decode(bytes));
17+
}
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
// Copyright (c) 2018, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
import 'dart:io';
6+
7+
import 'package:dart2js_tools/src/util.dart';
8+
9+
/// Script to show a text representation of the inlining data attached to
10+
/// source-map files.
11+
///
12+
/// This expands the push/pop operations and checks simple invariants (e.g. that
13+
/// the stack is always empty at the beginning of a function).
14+
main(List<String> args) {
15+
if (args.length != 1) {
16+
print('usage: show_inline_data.dart <js-file>');
17+
exit(1);
18+
}
19+
var uri = Uri.base.resolve(args[0]);
20+
var provider = new CachingFileProvider();
21+
22+
var mapping = provider.mappingFor(uri);
23+
var starts = functionStarts(provider.sourcesFor(uri));
24+
var file = provider.fileFor(uri);
25+
var frames = mapping.frames;
26+
var offsets = frames.keys.toList()..sort();
27+
var sb = new StringBuffer();
28+
int depth = 0;
29+
int lastFunctionStart = null;
30+
for (var offset in offsets) {
31+
int functionStart = nextFunctionStart(starts, offset, lastFunctionStart);
32+
if (lastFunctionStart == null || functionStart > lastFunctionStart) {
33+
sb.write('\n${location(starts[functionStart], file)}: function start\n');
34+
35+
if (depth != 0) {
36+
sb.write(
37+
"[invalid] function start with non-zero depth: $depth\n");
38+
}
39+
lastFunctionStart = functionStart;
40+
}
41+
42+
var offsetPrefix = '${location(offset, file)}:';
43+
var pad = ' ' * offsetPrefix.length;
44+
sb.write(offsetPrefix);
45+
bool first = true;
46+
for (var frame in frames[offset]) {
47+
if (!first) sb.write('$pad');
48+
sb.write(' $frame\n');
49+
first = false;
50+
if (frame.isPush) depth++;
51+
if (frame.isPop) depth--;
52+
if (frame.isEmpty && depth != 0) {
53+
sb.write("[invalid] pop-empty with non-zero depth: $depth\n");
54+
}
55+
if (!frame.isEmpty && depth == 0) {
56+
sb.write("[invalid] non-empty pop with zero depth: $depth\n");
57+
}
58+
if (depth < 0) {
59+
sb.write("[invalid] negative depth: $depth\n");
60+
}
61+
}
62+
}
63+
print('$sb');
64+
}
65+
66+
var _functionDeclarationRegExp = new RegExp(r':( )?function\(');
67+
68+
List<int> functionStarts(String sources) {
69+
List<int> result = [];
70+
int index = sources.indexOf(_functionDeclarationRegExp);
71+
while (index != -1) {
72+
result.add(index + 2);
73+
index = sources.indexOf(_functionDeclarationRegExp, index + 1);
74+
}
75+
return result;
76+
}
77+
78+
int nextFunctionStart(List<int> starts, int offset, int last) {
79+
int j = last ?? 0;
80+
for (; j < starts.length && starts[j] <= offset; j++);
81+
return j - 1;
82+
}
83+
84+
String location(int offset, file) {
85+
var line = file.getLine(offset) + 1;
86+
var column = file.getColumn(offset) + 1;
87+
var location = '$offset ($line:$column)';
88+
return location + (' ' * (16 - location.length));
89+
}

0 commit comments

Comments
 (0)