Implemented deobfuscation
This commit is contained in:
parent
cf791aef06
commit
2d43278851
13 changed files with 289 additions and 52 deletions
|
@ -30,6 +30,7 @@ dependencies {
|
|||
compile 'org.apache.commons:commons-collections4:4.1'
|
||||
compile 'org.ocpsoft.prettytime:prettytime:3.2.7.Final'
|
||||
compile 'commons-fileupload:commons-fileupload:1.3.2'
|
||||
compile 'net.sf.proguard:proguard-retrace:5.3.3'
|
||||
|
||||
compileOnly project(':annotation')
|
||||
apt project(':annotationprocessor')
|
||||
|
|
|
@ -15,12 +15,12 @@ public class Bug {
|
|||
|
||||
public Bug(Report report){
|
||||
reports = new ArrayList<>();
|
||||
this.trace = report.getContent().getString("STACK_TRACE");
|
||||
this.versionCode = report.getContent().getInt("APP_VERSION_CODE");
|
||||
this.trace = report.getStacktrace();
|
||||
this.versionCode = report.getVersionCode();
|
||||
}
|
||||
|
||||
public boolean is(Report report){
|
||||
return report.getContent().getString("STACK_TRACE").equals(trace) && report.getContent().getInt("APP_VERSION_CODE") == versionCode;
|
||||
return report.getStacktrace().equals(trace) && report.getVersionCode() == versionCode;
|
||||
}
|
||||
|
||||
public List<Report> getReports() {
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
package com.faendir.acra.data;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.domain.Example;
|
||||
import org.springframework.data.domain.ExampleMatcher;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author Lukas
|
||||
* @since 19.05.2017
|
||||
*/
|
||||
@Component
|
||||
public class MappingManager {
|
||||
private final MappingRepository mappingRepository;
|
||||
|
||||
@Autowired
|
||||
public MappingManager(MappingRepository mappingRepository) {
|
||||
this.mappingRepository = mappingRepository;
|
||||
}
|
||||
|
||||
public void addMapping(String app, int version, String mappings) {
|
||||
mappingRepository.save(new ProguardMapping(app, version, mappings));
|
||||
}
|
||||
|
||||
public ProguardMapping getMapping(String app, int version) {
|
||||
return mappingRepository.findOne(new ProguardMapping.MetaData(app, version));
|
||||
}
|
||||
|
||||
public List<ProguardMapping> getMappings(String app) {
|
||||
return mappingRepository.findAll(Example.of(new ProguardMapping(app, -1, null), ExampleMatcher.matchingAny()));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
package com.faendir.acra.data;
|
||||
|
||||
import org.springframework.data.mongodb.repository.MongoRepository;
|
||||
|
||||
/**
|
||||
* @author Lukas
|
||||
* @since 19.05.2017
|
||||
*/
|
||||
public interface MappingRepository extends MongoRepository<ProguardMapping, ProguardMapping.MetaData> {
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
package com.faendir.acra.data;
|
||||
|
||||
import org.springframework.data.mongodb.core.mapping.Document;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* @author Lukas
|
||||
* @since 19.05.2017
|
||||
*/
|
||||
@Document
|
||||
public class ProguardMapping {
|
||||
private String mappings;
|
||||
private MetaData id;
|
||||
|
||||
public ProguardMapping(){
|
||||
}
|
||||
|
||||
public ProguardMapping(String app, int version, String mappings) {
|
||||
this.id = new MetaData(app, version);
|
||||
this.mappings = mappings;
|
||||
}
|
||||
|
||||
public String getApp() {
|
||||
return id.app;
|
||||
}
|
||||
|
||||
public int getVersion() {
|
||||
return id.version;
|
||||
}
|
||||
|
||||
public String getMappings() {
|
||||
return mappings;
|
||||
}
|
||||
|
||||
static class MetaData implements Serializable {
|
||||
private String app;
|
||||
private int version;
|
||||
|
||||
MetaData(String app, int version) {
|
||||
this.app = app;
|
||||
this.version = version;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,10 +1,13 @@
|
|||
package com.faendir.acra.data;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
import org.springframework.data.mongodb.core.index.Indexed;
|
||||
import org.springframework.data.mongodb.core.mapping.Document;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Date;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* @author Lukas
|
||||
|
@ -16,14 +19,16 @@ public class Report {
|
|||
@Indexed
|
||||
private String app;
|
||||
private JSONObject content;
|
||||
private String deObfuscatedTrace;
|
||||
|
||||
public Report() {
|
||||
}
|
||||
|
||||
public Report(JSONObject content, String app) {
|
||||
id = content == null ? null : content.getString("REPORT_ID");
|
||||
this.content = content;
|
||||
this.app = app;
|
||||
id = content == null ? null : getValueSafe("REPORT_ID", content::getString, "");
|
||||
deObfuscatedTrace = null;
|
||||
}
|
||||
|
||||
public JSONObject getContent() {
|
||||
|
@ -31,10 +36,61 @@ public class Report {
|
|||
}
|
||||
|
||||
public Date getDate() {
|
||||
return ReportUtils.getDateFromString(content.getString("USER_CRASH_DATE"));
|
||||
return ReportUtils.getDateFromString(getValueSafe("USER_CRASH_DATE", content::getString, ""));
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public String getStacktrace() {
|
||||
return getValueSafe("STACK_TRACE", content::getString, "");
|
||||
}
|
||||
|
||||
public String getDeObfuscatedStacktrace(MappingManager mappingManager){
|
||||
if (deObfuscatedTrace != null) {
|
||||
return deObfuscatedTrace;
|
||||
}
|
||||
ProguardMapping mapping = mappingManager.getMapping(app, getVersionCode());
|
||||
if (mapping != null) {
|
||||
try {
|
||||
deObfuscatedTrace = ReportUtils.retrace(getStacktrace(), mapping);
|
||||
return deObfuscatedTrace;
|
||||
} catch (IOException ignored) {
|
||||
}
|
||||
}
|
||||
return getStacktrace();
|
||||
}
|
||||
|
||||
public int getVersionCode() {
|
||||
return getValueSafe("APP_VERSION_CODE", content::getInt, -1);
|
||||
}
|
||||
|
||||
public String getVersionName(){
|
||||
return getValueSafe("APP_VERSION_NAME", content::getString, "");
|
||||
}
|
||||
|
||||
public String getUserEmail(){
|
||||
return getValueSafe("USER_EMAIL", content::getString, "");
|
||||
}
|
||||
|
||||
public String getUserComment(){
|
||||
return getValueSafe("USER_COMMENT", content::getString, "");
|
||||
}
|
||||
|
||||
public String getAndroidVersion(){
|
||||
return getValueSafe("ANDROID_VERSION", content::getString, "");
|
||||
}
|
||||
|
||||
public String getPhoneModel(){
|
||||
return getValueSafe("PHONE_MODEL", content::getString, "");
|
||||
}
|
||||
|
||||
private <T> T getValueSafe(String key, Function<String, T> function, T defaultValue) {
|
||||
try {
|
||||
return function.apply(key);
|
||||
} catch (JSONException e) {
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,14 @@
|
|||
package com.faendir.acra.data;
|
||||
|
||||
import org.apache.commons.io.FileUtils;
|
||||
import proguard.retrace.ReTrace;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.LineNumberReader;
|
||||
import java.io.PrintWriter;
|
||||
import java.io.StringReader;
|
||||
import java.io.StringWriter;
|
||||
import java.text.ParseException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
|
@ -34,4 +43,12 @@ public final class ReportUtils {
|
|||
}
|
||||
}
|
||||
|
||||
static String retrace(String stacktrace, ProguardMapping mapping) throws IOException {
|
||||
File file = File.createTempFile("mapping", ".txt");
|
||||
FileUtils.writeStringToFile(file, mapping.getMappings());
|
||||
StringWriter writer = new StringWriter();
|
||||
new ReTrace(ReTrace.STACK_TRACE_EXPRESSION, false, file).retrace(new LineNumberReader(new StringReader(stacktrace)), new PrintWriter(writer));
|
||||
return writer.toString();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -2,16 +2,13 @@ package com.faendir.acra.ui.view;
|
|||
|
||||
import com.faendir.acra.data.App;
|
||||
import com.faendir.acra.data.AppManager;
|
||||
import com.faendir.acra.data.MappingManager;
|
||||
import com.faendir.acra.data.ReportManager;
|
||||
import com.faendir.acra.util.Style;
|
||||
import com.vaadin.data.ValueProvider;
|
||||
import com.vaadin.navigator.ViewChangeListener;
|
||||
import com.vaadin.shared.ui.ContentMode;
|
||||
import com.vaadin.spring.annotation.UIScope;
|
||||
import com.vaadin.ui.Grid;
|
||||
import com.vaadin.ui.Label;
|
||||
import com.vaadin.ui.TabSheet;
|
||||
import com.vaadin.ui.UI;
|
||||
import com.vaadin.ui.VerticalLayout;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
@ -26,18 +23,13 @@ public class AppView extends NamedView {
|
|||
|
||||
private final AppManager appManager;
|
||||
private final ReportManager reportManager;
|
||||
private final MappingManager mappingManager;
|
||||
|
||||
@Autowired
|
||||
public AppView(AppManager appManager, ReportManager reportManager) {
|
||||
public AppView(AppManager appManager, ReportManager reportManager, MappingManager mappingManager) {
|
||||
this.appManager = appManager;
|
||||
this.reportManager = reportManager;
|
||||
}
|
||||
|
||||
private <T> Grid.Column addColumn(Grid<T> grid, ValueProvider<T, String> valueProvider, String caption) {
|
||||
Grid.Column column = grid.addColumn(valueProvider);
|
||||
column.setId(caption);
|
||||
column.setCaption(caption);
|
||||
return column;
|
||||
this.mappingManager = mappingManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -51,14 +43,9 @@ public class AppView extends NamedView {
|
|||
VerticalLayout statistics = new VerticalLayout(new Label("Coming soon"));
|
||||
statistics.setCaption("Statistics");
|
||||
statistics.setSizeFull();
|
||||
String location = UI.getCurrent().getPage().getLocation().toASCIIString();
|
||||
location = location.substring(0, location.indexOf('#'));
|
||||
VerticalLayout properties = new VerticalLayout(new Label(String.format("Required ACRA configuration:<br><code>formUri = \"%sreport\",<br>" +
|
||||
"formUriBasicAuthLogin = \"%s\",<br>formUriBasicAuthPassword = \"%s\",<br>httpMethod = HttpSender.Method.POST,<br>reportType = HttpSender.Type.JSON</code>",
|
||||
location, app.getId(), app.getPassword()), ContentMode.HTML));
|
||||
properties.setCaption("Properties");
|
||||
properties.setSizeFull();
|
||||
TabSheet tabSheet = new TabSheet(new BugTab(app.getId(), getNavigationManager(), reportManager), new ReportList(app.getId(), getNavigationManager(), reportManager), statistics, properties);
|
||||
TabSheet tabSheet = new TabSheet(new BugTab(app.getId(), getNavigationManager(), reportManager),
|
||||
new ReportList(app.getId(), getNavigationManager(), reportManager),
|
||||
statistics, new DeObfuscationTab(app.getId(), mappingManager), new PropertiesTab(app));
|
||||
tabSheet.setSizeFull();
|
||||
VerticalLayout content = new VerticalLayout(tabSheet);
|
||||
content.setSizeFull();
|
||||
|
|
|
@ -0,0 +1,77 @@
|
|||
package com.faendir.acra.ui.view;
|
||||
|
||||
import com.faendir.acra.data.MappingManager;
|
||||
import com.faendir.acra.data.ProguardMapping;
|
||||
import com.vaadin.ui.Button;
|
||||
import com.vaadin.ui.ProgressBar;
|
||||
import com.vaadin.ui.TextField;
|
||||
import com.vaadin.ui.UI;
|
||||
import com.vaadin.ui.Upload;
|
||||
import com.vaadin.ui.VerticalLayout;
|
||||
import com.vaadin.ui.Window;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
|
||||
|
||||
/**
|
||||
* @author Lukas
|
||||
* @since 19.05.2017
|
||||
*/
|
||||
public class DeObfuscationTab extends MyGrid<ProguardMapping> {
|
||||
private final String app;
|
||||
private final MappingManager mappingManager;
|
||||
private boolean validNumber;
|
||||
private boolean validFile;
|
||||
|
||||
public DeObfuscationTab(String app, MappingManager mappingManager) {
|
||||
super("De-Obfuscation", mappingManager.getMappings(app));
|
||||
this.app = app;
|
||||
this.mappingManager = mappingManager;
|
||||
Column column = addColumn(mapping -> String.valueOf(mapping.getVersion()), "Version");
|
||||
setSizeFull();
|
||||
Button add = new Button("Add File", e -> addFile());
|
||||
add.setSizeFull();
|
||||
appendFooterRow().getCell(column).setComponent(add);
|
||||
}
|
||||
|
||||
private void addFile() {
|
||||
Window window = new Window("New Mapping Configuration");
|
||||
Button confirm = new Button("Confirm");
|
||||
confirm.setEnabled(false);
|
||||
TextField version = new TextField("Version code", e -> {
|
||||
try {
|
||||
//noinspection ResultOfMethodCallIgnored
|
||||
Integer.parseInt(e.getValue());
|
||||
validNumber = true;
|
||||
confirm.setEnabled(validFile);
|
||||
} catch (NumberFormatException ex) {
|
||||
validNumber = false;
|
||||
confirm.setEnabled(false);
|
||||
}
|
||||
});
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
Upload upload = new Upload("Mapping file:", (filename, mimeType) -> out);
|
||||
ProgressBar progressBar = new ProgressBar();
|
||||
progressBar.setSizeFull();
|
||||
upload.addProgressListener((readBytes, contentLength) -> progressBar.setValue(readBytes / contentLength));
|
||||
upload.addSucceededListener(e -> {
|
||||
validFile = true;
|
||||
confirm.setEnabled(validNumber);
|
||||
});
|
||||
upload.addFailedListener(e -> {
|
||||
validFile = false;
|
||||
confirm.setEnabled(false);
|
||||
});
|
||||
upload.setSizeFull();
|
||||
confirm.addClickListener(e -> {
|
||||
mappingManager.addMapping(app, Integer.parseInt(version.getValue()), out.toString());
|
||||
setItems(mappingManager.getMappings(app));
|
||||
window.close();
|
||||
});
|
||||
confirm.setSizeFull();
|
||||
VerticalLayout layout = new VerticalLayout(version, upload, progressBar, confirm);
|
||||
window.setContent(layout);
|
||||
window.center();
|
||||
UI.getCurrent().addWindow(window);
|
||||
}
|
||||
}
|
|
@ -57,9 +57,8 @@ public class Overview extends NamedView {
|
|||
Grid.Column column = grid.addColumn(App::getName, "Name");
|
||||
grid.addColumn(app -> String.valueOf(reportManager.getReports(app.getId()).size()), "Reports");
|
||||
FooterCell footerCell = grid.appendFooterRow().getCell(column.getId());
|
||||
Button add = new Button("New App");
|
||||
Button add = new Button("New App", e -> addApp());
|
||||
add.setSizeFull();
|
||||
add.addClickListener(e -> addApp());
|
||||
footerCell.setComponent(add);
|
||||
VerticalLayout layout = new VerticalLayout(grid);
|
||||
Style.apply(layout, Style.NO_PADDING, Style.PADDING_LEFT, Style.PADDING_RIGHT, Style.PADDING_BOTTOM);
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
package com.faendir.acra.ui.view;
|
||||
|
||||
import com.faendir.acra.data.App;
|
||||
import com.vaadin.shared.ui.ContentMode;
|
||||
import com.vaadin.ui.Label;
|
||||
import com.vaadin.ui.UI;
|
||||
import com.vaadin.ui.VerticalLayout;
|
||||
|
||||
/**
|
||||
* @author Lukas
|
||||
* @since 19.05.2017
|
||||
*/
|
||||
public class PropertiesTab extends VerticalLayout {
|
||||
public PropertiesTab(App app) {
|
||||
String location = UI.getCurrent().getPage().getLocation().toASCIIString();
|
||||
location = location.substring(0, location.indexOf('#'));
|
||||
addComponent(new Label(String.format("Required ACRA configuration:<br><code>formUri = \"%sreport\",<br>" +
|
||||
"formUriBasicAuthLogin = \"%s\",<br>formUriBasicAuthPassword = \"%s\",<br>" +
|
||||
"httpMethod = HttpSender.Method.POST,<br>reportType = HttpSender.Type.JSON</code>",
|
||||
location, app.getId(), app.getPassword()), ContentMode.HTML));
|
||||
setCaption("Properties");
|
||||
setSizeFull();
|
||||
}
|
||||
}
|
|
@ -20,21 +20,16 @@ public class ReportList extends MyGrid<Report> implements ReportManager.ChangeLi
|
|||
this.reportManager = reportManager;
|
||||
setSizeFull();
|
||||
addColumn(report -> StringUtils.distanceFromNowAsString(report.getDate()), "Date");
|
||||
addReportColumn("APP_VERSION_NAME", "App Version");
|
||||
addReportColumn("ANDROID_VERSION", "Android Version");
|
||||
addReportColumn("PHONE_MODEL", "Device");
|
||||
addColumn(report -> report.getContent().getString("STACK_TRACE").split("\n", 2)[0], "Stacktrace").setExpandRatio(1);
|
||||
addColumn(report -> String.valueOf(report.getVersionCode()), "App Version");
|
||||
addColumn(Report::getAndroidVersion, "Android Version");
|
||||
addColumn(Report::getPhoneModel, "Device");
|
||||
addColumn(report -> report.getStacktrace().split("\n", 2)[0], "Stacktrace").setExpandRatio(1);
|
||||
addColumn(report -> "Delete", new ButtonRenderer<>(e -> reportManager.remove(e.getItem())));
|
||||
addItemClickListener(e -> navigationManager.navigateTo(ReportView.class, e.getItem().getId()));
|
||||
addAttachListener(e -> reportManager.addListener(this));
|
||||
addDetachListener(e -> reportManager.removeListener(this));
|
||||
}
|
||||
|
||||
|
||||
private void addReportColumn(String key, String caption) {
|
||||
addColumn(report -> report.getContent().getString(key), caption);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChange() {
|
||||
setItems(reportManager.getReports(app));
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package com.faendir.acra.ui.view;
|
||||
|
||||
import com.faendir.acra.data.AttachmentManager;
|
||||
import com.faendir.acra.data.MappingManager;
|
||||
import com.faendir.acra.data.Report;
|
||||
import com.faendir.acra.data.ReportManager;
|
||||
import com.faendir.acra.util.Style;
|
||||
|
@ -36,11 +37,13 @@ public class ReportView extends NamedView {
|
|||
|
||||
private final ReportManager reportManager;
|
||||
private final AttachmentManager attachmentManager;
|
||||
private final MappingManager mappingManager;
|
||||
|
||||
@Autowired
|
||||
public ReportView(ReportManager reportManager, AttachmentManager attachmentManager) {
|
||||
public ReportView(ReportManager reportManager, AttachmentManager attachmentManager, MappingManager mappingManager) {
|
||||
this.reportManager = reportManager;
|
||||
this.attachmentManager = attachmentManager;
|
||||
this.mappingManager = mappingManager;
|
||||
}
|
||||
|
||||
private Stream<Component> getLayoutForEntry(String key, Object value) {
|
||||
|
@ -86,21 +89,10 @@ public class ReportView extends NamedView {
|
|||
return button;
|
||||
}).toArray(Component[]::new));
|
||||
GridLayout summaryGrid = new GridLayout(2, 1);
|
||||
summaryGrid.addComponents(new Label("Version", ContentMode.PREFORMATTED), new Label(report.getContent().getString("APP_VERSION_NAME"), ContentMode.PREFORMATTED));
|
||||
summaryGrid.addComponents(new Label("Email", ContentMode.PREFORMATTED), new Label(report.getContent().getString("USER_EMAIL"), ContentMode.PREFORMATTED));
|
||||
summaryGrid.addComponents(new Label("Comment", ContentMode.PREFORMATTED), new Label(report.getContent().getString("USER_COMMENT"), ContentMode.PREFORMATTED));
|
||||
String trace = report.getContent().getString("STACK_TRACE");
|
||||
String[] lines = trace.split("\n");
|
||||
String exception = lines[0];
|
||||
String cause = "";
|
||||
for(int i = lines.length -1; i >= 1; i--){
|
||||
if(lines[i].charAt(0) != '\t'){
|
||||
cause = lines[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
summaryGrid.addComponents(new Label("Exception", ContentMode.PREFORMATTED), new Label(exception, ContentMode.PREFORMATTED));
|
||||
summaryGrid.addComponents(new Label("Root cause", ContentMode.PREFORMATTED), new Label(cause, ContentMode.PREFORMATTED));
|
||||
summaryGrid.addComponents(new Label("Version", ContentMode.PREFORMATTED), new Label(report.getVersionName(), ContentMode.PREFORMATTED));
|
||||
summaryGrid.addComponents(new Label("Email", ContentMode.PREFORMATTED), new Label(report.getUserEmail(), ContentMode.PREFORMATTED));
|
||||
summaryGrid.addComponents(new Label("Comment", ContentMode.PREFORMATTED), new Label(report.getUserComment(), ContentMode.PREFORMATTED));
|
||||
summaryGrid.addComponents(new Label("De-obfuscated Stacktrace", ContentMode.PREFORMATTED), new Label(report.getDeObfuscatedStacktrace(mappingManager), ContentMode.PREFORMATTED));
|
||||
summaryGrid.addComponents(new Label("Attachments", ContentMode.PREFORMATTED), attachments);
|
||||
summaryGrid.setDefaultComponentAlignment(Alignment.MIDDLE_LEFT);
|
||||
summaryGrid.setSizeFull();
|
||||
|
|
Loading…
Reference in a new issue