From 778485b4a988adbebed9e0fa7731e7b0bb537ae1 Mon Sep 17 00:00:00 2001 From: Christopher Schnick Date: Thu, 1 Dec 2022 11:27:07 +0100 Subject: [PATCH] Deobfuscate received server error messages --- .../java/io/xpipe/beacon/BeaconClient.java | 10 +- .../java/io/xpipe/core/util/Deobfuscator.java | 94 +++++++++++++++++++ .../java/io/xpipe/core/util/ModuleHelper.java | 13 +++ .../io/xpipe/core/util/XPipeInstallation.java | 20 +--- 4 files changed, 115 insertions(+), 22 deletions(-) create mode 100644 core/src/main/java/io/xpipe/core/util/Deobfuscator.java create mode 100644 core/src/main/java/io/xpipe/core/util/ModuleHelper.java diff --git a/beacon/src/main/java/io/xpipe/beacon/BeaconClient.java b/beacon/src/main/java/io/xpipe/beacon/BeaconClient.java index 7ab55224e..0c8a0467c 100644 --- a/beacon/src/main/java/io/xpipe/beacon/BeaconClient.java +++ b/beacon/src/main/java/io/xpipe/beacon/BeaconClient.java @@ -12,6 +12,7 @@ import io.xpipe.beacon.exchange.MessageExchanges; import io.xpipe.beacon.exchange.data.ClientErrorMessage; import io.xpipe.beacon.exchange.data.ServerErrorMessage; import io.xpipe.core.store.ShellStore; +import io.xpipe.core.util.Deobfuscator; import io.xpipe.core.util.JacksonMapper; import lombok.Builder; import lombok.EqualsAndHashCode; @@ -276,8 +277,8 @@ public class BeaconClient implements AutoCloseable { } try { - var reader = JacksonMapper.newMapper().readerFor(ClientErrorMessage.class); - return Optional.of(reader.readValue(content)); + var message = JacksonMapper.getDefault().treeToValue(content, ClientErrorMessage.class); + return Optional.of(message); } catch (IOException ex) { throw new ConnectorException("Couldn't parse client error message", ex); } @@ -290,8 +291,9 @@ public class BeaconClient implements AutoCloseable { } try { - var reader = JacksonMapper.newMapper().readerFor(ServerErrorMessage.class); - return Optional.of(reader.readValue(content)); + var message = JacksonMapper.getDefault().treeToValue(content, ServerErrorMessage.class); + Deobfuscator.deobfuscate(message.getError()); + return Optional.of(message); } catch (IOException ex) { throw new ConnectorException("Couldn't parse server error message", ex); } diff --git a/core/src/main/java/io/xpipe/core/util/Deobfuscator.java b/core/src/main/java/io/xpipe/core/util/Deobfuscator.java new file mode 100644 index 000000000..d7256dd1a --- /dev/null +++ b/core/src/main/java/io/xpipe/core/util/Deobfuscator.java @@ -0,0 +1,94 @@ +package io.xpipe.core.util; + +import io.xpipe.core.charsetter.NewLine; +import io.xpipe.core.process.OsType; + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.nio.file.Files; +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class Deobfuscator { + + public static void deobfuscate(Throwable throwable) { + if (!System.getenv().containsKey("XPIPE_MAPPING")) { + return; + } + + String deobf = deobfuscateToString(throwable); + if (deobf == null) { + return; + } + + try { + // "at package.class.method(source.java:123)" + Pattern tracePattern = Pattern.compile("\\s*at\\s+([\\w.$_]+)\\.([\\w$_]+)\\((.*):(\\d+)\\)(\\n|\\r\\n)"); + Matcher traceMatcher = tracePattern.matcher(deobf); + List stackTrace = new ArrayList<>(); + while (traceMatcher.find()) { + String className = traceMatcher.group(1); + String methodName = traceMatcher.group(2); + String sourceFile = traceMatcher.group(3); + int lineNum = Integer.parseInt(traceMatcher.group(4)); + stackTrace.add(new StackTraceElement(className, methodName, sourceFile, lineNum)); + } + + throwable.setStackTrace(stackTrace.toArray(StackTraceElement[]::new)); + + // Also deobfuscate any other exceptions + if (throwable.getCause() != null && throwable.getCause() != throwable) { + deobfuscate(throwable.getCause()); + } + for (Throwable suppressed : throwable.getSuppressed()) { + if (suppressed != throwable) deobfuscate(suppressed); + } + } catch (Throwable ex) { + System.err.println("Deobfuscation failed"); + ex.printStackTrace(); + } + } + + public static String deobfuscateToString(Throwable t) { + StringWriter sw = new StringWriter(); + PrintWriter pw = new PrintWriter(sw); + t.printStackTrace(pw); + String stackTrace = sw.toString(); + stackTrace = stackTrace.replaceAll("at .+/(.+)", "at $1"); + + if (!System.getenv().containsKey("XPIPE_MAPPING")) { + return stackTrace; + } + + try { + var file = Files.createTempFile("xpipe_stracktrace", null); + Files.writeString(file, stackTrace); + var proc = new ProcessBuilder( + "retrace." + (OsType.getLocal().equals(OsType.WINDOWS) ? "bat" : "sh"), + System.getenv("XPIPE_MAPPING"), + file.toString()) + .redirectErrorStream(true); + var active = proc.start(); + var out = new String(active.getInputStream().readAllBytes()).replaceAll("\\r\\n", NewLine.LF.getNewLineString()); + var code = active.waitFor(); + if (code == 0) { + return out; + } else { + System.err.println("Deobfuscation failed: " + out); + } + } catch (Exception ex) { + System.err.println("Deobfuscation failed"); + ex.printStackTrace(); + return null; + } + + return stackTrace; + } + + public static void printStackTrace(Throwable t) { + var s = deobfuscateToString(t); + System.err.println(s); + } +} diff --git a/core/src/main/java/io/xpipe/core/util/ModuleHelper.java b/core/src/main/java/io/xpipe/core/util/ModuleHelper.java new file mode 100644 index 000000000..59a831c64 --- /dev/null +++ b/core/src/main/java/io/xpipe/core/util/ModuleHelper.java @@ -0,0 +1,13 @@ +package io.xpipe.core.util; + +public class ModuleHelper { + + public static boolean isImage() { + return ModuleHelper.class + .getProtectionDomain() + .getCodeSource() + .getLocation() + .getProtocol() + .equals("jrt"); + } +} diff --git a/core/src/main/java/io/xpipe/core/util/XPipeInstallation.java b/core/src/main/java/io/xpipe/core/util/XPipeInstallation.java index a0843bf49..88e52ffe4 100644 --- a/core/src/main/java/io/xpipe/core/util/XPipeInstallation.java +++ b/core/src/main/java/io/xpipe/core/util/XPipeInstallation.java @@ -6,8 +6,6 @@ import io.xpipe.core.process.CommandProcessControl; import io.xpipe.core.process.OsType; import io.xpipe.core.process.ShellProcessControl; -import java.io.IOException; - public class XPipeInstallation { public static String getInstallationBasePathForCLI(ShellProcessControl p, String cliExecutable) throws Exception { @@ -32,7 +30,7 @@ public class XPipeInstallation { public static boolean containsCompatibleDefaultInstallation(ShellProcessControl p, String version) throws Exception { var defaultBase = getDefaultInstallationBasePath(p); var executable = getInstallationExecutable(p, defaultBase); - if (executable.isEmpty()) { + if (!p.executeBooleanSimpleCommand(p.getShellType().createFileExistsCommand(executable))) { return false; } @@ -44,14 +42,7 @@ public class XPipeInstallation { public static String getInstallationExecutable(ShellProcessControl p, String installation) throws Exception { var executable = getDaemonExecutablePath(p.getOsType()); var file = FileNames.join(installation, executable); - try (CommandProcessControl c = - p.command(p.getShellType().createFileExistsCommand(file)).start()) { - if (!c.startAndCheckExit()) { - throw new IOException("File not found: " + file); - } - - return file; - } + return file; } public static String getDataBasePath(ShellProcessControl p) throws Exception { @@ -83,13 +74,6 @@ public class XPipeInstallation { path = "/opt/xpipe"; } - try (CommandProcessControl c = - p.command(p.getShellType().createFileExistsCommand(path)).start()) { - if (!c.discardAndCheckExit()) { - throw new IOException("Installation not found in " + path); - } - } - return path; }