From dbf692cb3d6d30c1c89c76a5b3f25eff7f0efdd1 Mon Sep 17 00:00:00 2001 From: UnfamiliarLegacy <74633542+UnfamiliarLegacy@users.noreply.github.com> Date: Fri, 26 Nov 2021 01:06:57 +0100 Subject: [PATCH 01/12] Added nitro button, reworked last selected client mode --- .../gearth/protocol/connection/HClient.java | 3 ++- .../ui/connection/ConnectionController.java | 27 +++++++++++++++---- .../gearth/ui/connection/Connection.fxml | 16 +++++++---- 3 files changed, 35 insertions(+), 11 deletions(-) diff --git a/G-Earth/src/main/java/gearth/protocol/connection/HClient.java b/G-Earth/src/main/java/gearth/protocol/connection/HClient.java index 54653c3..027fafd 100644 --- a/G-Earth/src/main/java/gearth/protocol/connection/HClient.java +++ b/G-Earth/src/main/java/gearth/protocol/connection/HClient.java @@ -2,5 +2,6 @@ package gearth.protocol.connection; public enum HClient { UNITY, - FLASH + FLASH, + NITRO } diff --git a/G-Earth/src/main/java/gearth/ui/connection/ConnectionController.java b/G-Earth/src/main/java/gearth/ui/connection/ConnectionController.java index 287d70a..3a559da 100644 --- a/G-Earth/src/main/java/gearth/ui/connection/ConnectionController.java +++ b/G-Earth/src/main/java/gearth/ui/connection/ConnectionController.java @@ -2,6 +2,7 @@ package gearth.ui.connection; import gearth.Main; import gearth.misc.Cacher; +import gearth.protocol.connection.HClient; import gearth.protocol.connection.HState; import gearth.protocol.connection.proxy.ProxyProviderFactory; import gearth.services.Constants; @@ -38,10 +39,11 @@ public class ConnectionController extends SubForm { private volatile int fullyInitialized = 0; - public static final String USE_UNITY_CLIENT_CACHE_KEY = "use_unity"; + public static final String CLIENT_CACHE_KEY = "last_client_mode"; public ToggleGroup tgl_clientMode; public RadioButton rd_unity; public RadioButton rd_flash; + public RadioButton rd_nitro; public GridPane grd_clientSelection; private volatile int initcount = 0; @@ -54,9 +56,18 @@ public class ConnectionController extends SubForm { Constants.UNITY_PACKETS = rd_unity.isSelected(); }); - if (Cacher.getCacheContents().has(USE_UNITY_CLIENT_CACHE_KEY)) { - rd_unity.setSelected(Cacher.getCacheContents().getBoolean(USE_UNITY_CLIENT_CACHE_KEY)); - rd_flash.setSelected(!Cacher.getCacheContents().getBoolean(USE_UNITY_CLIENT_CACHE_KEY)); + if (Cacher.getCacheContents().has(CLIENT_CACHE_KEY)) { + switch (Cacher.getCacheContents().getEnum(HClient.class, CLIENT_CACHE_KEY)) { + case FLASH: + rd_flash.setSelected(true); + break; + case UNITY: + rd_unity.setSelected(true); + break; + case NITRO: + rd_nitro.setSelected(true); + break; + } } @@ -269,7 +280,13 @@ public class ConnectionController extends SubForm { @Override protected void onExit() { - Cacher.put(USE_UNITY_CLIENT_CACHE_KEY, rd_unity.isSelected()); + if (rd_flash.isSelected()) { + Cacher.put(CLIENT_CACHE_KEY, HClient.FLASH); + } else if (rd_unity.isSelected()) { + Cacher.put(CLIENT_CACHE_KEY, HClient.UNITY); + } else if (rd_nitro.isSelected()) { + Cacher.put(CLIENT_CACHE_KEY, HClient.NITRO); + } getHConnection().abort(); } diff --git a/G-Earth/src/main/resources/gearth/ui/connection/Connection.fxml b/G-Earth/src/main/resources/gearth/ui/connection/Connection.fxml index 96b87e5..bf7cb35 100644 --- a/G-Earth/src/main/resources/gearth/ui/connection/Connection.fxml +++ b/G-Earth/src/main/resources/gearth/ui/connection/Connection.fxml @@ -5,7 +5,7 @@ - + @@ -132,9 +132,9 @@ - - - + + + @@ -156,7 +156,13 @@ - + + + + + + + From 1c6d086bffcef64755330d053a346e2d576ba414 Mon Sep 17 00:00:00 2001 From: UnfamiliarLegacy <74633542+UnfamiliarLegacy@users.noreply.github.com> Date: Fri, 26 Nov 2021 04:12:47 +0100 Subject: [PATCH 02/12] Add HTTP(S) MITM and replace websocket in config --- .gitignore | 4 + G-Earth/pom.xml | 6 +- .../java/gearth/protocol/HConnection.java | 7 + .../proxy/nitro/NitroProxyProvider.java | 53 ++++++++ .../proxy/nitro/http/NitroAuthority.java | 24 ++++ .../proxy/nitro/http/NitroHttpProxy.java | 89 +++++++++++++ .../nitro/http/NitroHttpProxyFilter.java | 124 ++++++++++++++++++ .../http/NitroHttpProxyFilterSource.java | 46 +++++++ .../http/NitroHttpProxyServerCallback.java | 14 ++ .../proxy/nitro/os/NitroOsFunctions.java | 13 ++ .../nitro/os/NitroOsFunctionsFactory.java | 21 +++ .../proxy/nitro/os/windows/NitroWindows.java | 42 ++++++ .../ui/connection/ConnectionController.java | 27 +++- 13 files changed, 464 insertions(+), 6 deletions(-) create mode 100644 G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/NitroProxyProvider.java create mode 100644 G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/http/NitroAuthority.java create mode 100644 G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/http/NitroHttpProxy.java create mode 100644 G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/http/NitroHttpProxyFilter.java create mode 100644 G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/http/NitroHttpProxyFilterSource.java create mode 100644 G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/http/NitroHttpProxyServerCallback.java create mode 100644 G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/os/NitroOsFunctions.java create mode 100644 G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/os/NitroOsFunctionsFactory.java create mode 100644 G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/os/windows/NitroWindows.java diff --git a/.gitignore b/.gitignore index 902afbc..beed7d5 100644 --- a/.gitignore +++ b/.gitignore @@ -21,3 +21,7 @@ Extensions/BlockReplacePackets/.classpath *.settings/org.eclipse.jdt.apt.core.prefs *.settings/org.eclipse.jdt.core.prefs *.settings/org.eclipse.m2e.core.prefs + +# Certificates +*.p12 +*.pem \ No newline at end of file diff --git a/G-Earth/pom.xml b/G-Earth/pom.xml index 09b1b80..9164616 100644 --- a/G-Earth/pom.xml +++ b/G-Earth/pom.xml @@ -224,7 +224,11 @@ bytes 1.5.0 - + + com.github.ganskef + littleproxy-mitm + 1.1.0 + diff --git a/G-Earth/src/main/java/gearth/protocol/HConnection.java b/G-Earth/src/main/java/gearth/protocol/HConnection.java index db1c083..70bd0c4 100644 --- a/G-Earth/src/main/java/gearth/protocol/HConnection.java +++ b/G-Earth/src/main/java/gearth/protocol/HConnection.java @@ -1,6 +1,7 @@ package gearth.protocol; import gearth.misc.listenerpattern.Observable; +import gearth.protocol.connection.proxy.nitro.NitroProxyProvider; import gearth.services.packet_info.PacketInfoManager; import gearth.protocol.connection.HClient; import gearth.protocol.connection.HProxy; @@ -68,6 +69,12 @@ public class HConnection { startMITM(); } + public void startNitro() { + HConnection selff = this; + proxyProvider = new NitroProxyProvider(proxy -> selff.proxy = proxy, selff::setState, this); + startMITM(); + } + private void startMITM() { try { if (proxyProvider != null) { diff --git a/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/NitroProxyProvider.java b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/NitroProxyProvider.java new file mode 100644 index 0000000..5ba1172 --- /dev/null +++ b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/NitroProxyProvider.java @@ -0,0 +1,53 @@ +package gearth.protocol.connection.proxy.nitro; + +import gearth.protocol.HConnection; +import gearth.protocol.connection.HProxySetter; +import gearth.protocol.connection.HState; +import gearth.protocol.connection.HStateSetter; +import gearth.protocol.connection.proxy.ProxyProvider; +import gearth.protocol.connection.proxy.nitro.http.NitroHttpProxy; + +import java.io.IOException; + +public class NitroProxyProvider implements ProxyProvider { + + private final HProxySetter proxySetter; + private final HStateSetter stateSetter; + private final HConnection hConnection; + private final NitroHttpProxy nitroProxy; + + public NitroProxyProvider(HProxySetter proxySetter, HStateSetter stateSetter, HConnection hConnection) { + this.proxySetter = proxySetter; + this.stateSetter = stateSetter; + this.hConnection = hConnection; + this.nitroProxy = new NitroHttpProxy(); + } + + @Override + public void start() throws IOException { + if (!nitroProxy.start()) { + System.out.println("Failed to start nitro proxy"); + + stateSetter.setState(HState.NOT_CONNECTED); + return; + } + + stateSetter.setState(HState.WAITING_FOR_CLIENT); + } + + @Override + public void abort() { + stateSetter.setState(HState.ABORTING); + + new Thread(() -> { + try { + nitroProxy.stop(); + } catch (Exception e) { + e.printStackTrace(); + } finally { + stateSetter.setState(HState.NOT_CONNECTED); + } + }).start(); + } + +} diff --git a/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/http/NitroAuthority.java b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/http/NitroAuthority.java new file mode 100644 index 0000000..fd5bd4e --- /dev/null +++ b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/http/NitroAuthority.java @@ -0,0 +1,24 @@ +package gearth.protocol.connection.proxy.nitro.http; + +import org.littleshoot.proxy.mitm.Authority; + +import java.io.File; + +public class NitroAuthority extends Authority { + + private static final String CERT_ALIAS = "gearth-nitro"; + private static final String CERT_ORGANIZATION = "G-Earth Nitro"; + private static final String CERT_DESCRIPTION = "G-Earth nitro support"; + + public NitroAuthority() { + super(new File("."), + CERT_ALIAS, + "verysecure".toCharArray(), + CERT_DESCRIPTION, + CERT_ORGANIZATION, + "Certificate Authority", + CERT_ORGANIZATION, + CERT_DESCRIPTION); + } + +} diff --git a/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/http/NitroHttpProxy.java b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/http/NitroHttpProxy.java new file mode 100644 index 0000000..13acc03 --- /dev/null +++ b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/http/NitroHttpProxy.java @@ -0,0 +1,89 @@ +package gearth.protocol.connection.proxy.nitro.http; + +import gearth.protocol.connection.proxy.nitro.os.NitroOsFunctions; +import gearth.protocol.connection.proxy.nitro.os.NitroOsFunctionsFactory; +import org.littleshoot.proxy.HttpProxyServer; +import org.littleshoot.proxy.impl.DefaultHttpProxyServer; +import org.littleshoot.proxy.mitm.Authority; +import org.littleshoot.proxy.mitm.CertificateSniffingMitmManager; +import org.littleshoot.proxy.mitm.RootCertificateException; + +public class NitroHttpProxy { + + private final Authority authority; + private final NitroOsFunctions osFunctions; + + private HttpProxyServer proxyServer = null; + + public NitroHttpProxy() { + this.authority = new NitroAuthority(); + this.osFunctions = NitroOsFunctionsFactory.create(); + } + + private boolean initializeCertificate() { + return this.osFunctions.installRootCertificate(this.authority.aliasFile(".pem")); + } + + /** + * Register HTTP(s) proxy on the system. + */ + private boolean registerProxy() { + return this.osFunctions.registerSystemProxy("127.0.0.1", 9090); + } + + /** + * Unregister HTTP(s) proxy from system. + */ + private boolean unregisterProxy() { + return this.osFunctions.unregisterSystemProxy(); + } + + public boolean start() { + + + try { + proxyServer = DefaultHttpProxyServer.bootstrap() + .withPort(9090) + .withManInTheMiddle(new CertificateSniffingMitmManager(authority)) + // TODO: Replace lambda with some class + .withFiltersSource(new NitroHttpProxyFilterSource((configUrl, websocketUrl) -> { + System.out.printf("Found %s at %s%n", websocketUrl, configUrl); + + return "wss://127.0.0.1:2096"; + })) + .start(); + + if (!initializeCertificate()) { + proxyServer.stop(); + + System.out.println("Failed to initialize certificate"); + return false; + } + + if (!registerProxy()) { + proxyServer.stop(); + + System.out.println("Failed to register certificate"); + return false; + } + + return true; + } catch (RootCertificateException e) { + e.printStackTrace(); + return false; + } + } + + public void stop() { + if (!unregisterProxy()) { + System.out.println("Failed to unregister system proxy, please check manually"); + } + + if (proxyServer == null) { + return; + } + + proxyServer.stop(); + proxyServer = null; + } +} diff --git a/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/http/NitroHttpProxyFilter.java b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/http/NitroHttpProxyFilter.java new file mode 100644 index 0000000..28a33ae --- /dev/null +++ b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/http/NitroHttpProxyFilter.java @@ -0,0 +1,124 @@ +package gearth.protocol.connection.proxy.nitro.http; + +import io.netty.buffer.ByteBuf; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.http.*; +import io.netty.util.CharsetUtil; +import org.littleshoot.proxy.HttpFiltersAdapter; + +import java.nio.charset.StandardCharsets; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class NitroHttpProxyFilter extends HttpFiltersAdapter { + + private static final String NitroConfigSearch = "\"socket.url\""; + private static final Pattern NitroConfigPattern = Pattern.compile("\"socket\\.url\":.?\"(wss?://.*?)\"", Pattern.MULTILINE); + + private static final String HeaderAcceptEncoding = "Accept-Encoding"; + private static final String HeaderAge = "Age"; + private static final String HeaderCacheControl = "Cache-Control"; + private static final String HeaderETag = "ETag"; + private static final String HeaderIfNoneMatch = "If-None-Match"; + private static final String HeaderIfModifiedSince = "If-Modified-Since"; + private static final String HeaderLastModified = "Last-Modified"; + + private final NitroHttpProxyServerCallback callback; + private final String url; + + public NitroHttpProxyFilter(HttpRequest originalRequest, ChannelHandlerContext ctx, NitroHttpProxyServerCallback callback, String url) { + super(originalRequest, ctx); + this.callback = callback; + this.url = url; + } + + @Override + public HttpResponse clientToProxyRequest(HttpObject httpObject) { + if (httpObject instanceof HttpRequest) { + HttpRequest request = (HttpRequest) httpObject; + HttpHeaders headers = request.headers(); + + // Only support gzip or deflate. + // The LittleProxy library does not support brotli. + if (headers.contains(HeaderAcceptEncoding)) { + String encoding = headers.get(HeaderAcceptEncoding); + + if (encoding.contains("br")) { + if (encoding.contains("gzip") && encoding.contains("deflate")) { + headers.set(HeaderAcceptEncoding, "gzip, deflate"); + } else if (encoding.contains("gzip")) { + headers.set(HeaderAcceptEncoding, "gzip, deflate"); + } else { + headers.remove(HeaderAcceptEncoding); + } + } + } + + // Disable caching. + stripCacheHeaders(headers); + } + + return super.clientToProxyRequest(httpObject); + } + + @Override + public HttpObject serverToProxyResponse(HttpObject httpObject) { + if (httpObject instanceof FullHttpResponse) { + final FullHttpResponse response = (FullHttpResponse) httpObject; + + // Find nitro configuration file. + boolean responseModified = false; + String responseBody = responseRead(response); + + if (responseBody.contains(NitroConfigSearch)) { + final Matcher matcher = NitroConfigPattern.matcher(responseBody); + + if (matcher.find()) { + final String originalWebsocket = matcher.group(1); + final String replacementWebsocket = callback.replaceWebsocketServer(url, originalWebsocket); + + if (replacementWebsocket != null) { + responseBody = responseBody.replace(originalWebsocket, replacementWebsocket); + responseModified = true; + } + } + } + + // Apply changes. + if (responseModified) { + responseWrite(response, responseBody); + } + } + + return httpObject; + } + + private static String responseRead(FullHttpResponse response) { + final ByteBuf contentBuf = response.content(); + return contentBuf.toString(CharsetUtil.UTF_8); + } + + private static void responseWrite(FullHttpResponse response, String content) { + final byte[] body = content.getBytes(StandardCharsets.UTF_8); + + // Update content. + response.content().clear().writeBytes(body); + + // Update content-length. + HttpHeaders.setContentLength(response, body.length); + + // Ensure modified response is not cached. + stripCacheHeaders(response.headers()); + } + + private static void stripCacheHeaders(HttpHeaders headers) { + headers.remove(HeaderAcceptEncoding); + headers.remove(HeaderAge); + headers.remove(HeaderCacheControl); + headers.remove(HeaderETag); + headers.remove(HeaderIfNoneMatch); + headers.remove(HeaderIfModifiedSince); + headers.remove(HeaderLastModified); + } + +} diff --git a/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/http/NitroHttpProxyFilterSource.java b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/http/NitroHttpProxyFilterSource.java new file mode 100644 index 0000000..fae9deb --- /dev/null +++ b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/http/NitroHttpProxyFilterSource.java @@ -0,0 +1,46 @@ +package gearth.protocol.connection.proxy.nitro.http; + +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.http.HttpMethod; +import io.netty.handler.codec.http.HttpRequest; +import io.netty.util.AttributeKey; +import org.littleshoot.proxy.HttpFilters; +import org.littleshoot.proxy.HttpFiltersAdapter; +import org.littleshoot.proxy.HttpFiltersSourceAdapter; + +public class NitroHttpProxyFilterSource extends HttpFiltersSourceAdapter { + + private static final AttributeKey CONNECTED_URL = AttributeKey.valueOf("connected_url"); + + private final NitroHttpProxyServerCallback callback; + + public NitroHttpProxyFilterSource(NitroHttpProxyServerCallback callback) { + this.callback = callback; + } + + @Override + public HttpFilters filterRequest(HttpRequest originalRequest, ChannelHandlerContext ctx) { + // https://github.com/ganskef/LittleProxy-mitm#resolving-uri-in-case-of-https + String uri = originalRequest.getUri(); + if (originalRequest.getMethod() == HttpMethod.CONNECT) { + if (ctx != null) { + String prefix = "https://" + uri.replaceFirst(":443$", ""); + ctx.channel().attr(CONNECTED_URL).set(prefix); + } + return new HttpFiltersAdapter(originalRequest, ctx); + } + + String connectedUrl = ctx.channel().attr(CONNECTED_URL).get(); + if (connectedUrl == null) { + return new NitroHttpProxyFilter(originalRequest, ctx, callback, uri); + } + + return new NitroHttpProxyFilter(originalRequest, ctx, callback, connectedUrl + uri); + } + + @Override + public int getMaximumResponseBufferSizeInBytes() { + // Increasing this causes LittleProxy to output "FullHttpResponse" objects. + return 1024 * 1024 * 1024; + } +} diff --git a/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/http/NitroHttpProxyServerCallback.java b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/http/NitroHttpProxyServerCallback.java new file mode 100644 index 0000000..3f04f11 --- /dev/null +++ b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/http/NitroHttpProxyServerCallback.java @@ -0,0 +1,14 @@ +package gearth.protocol.connection.proxy.nitro.http; + +public interface NitroHttpProxyServerCallback { + + /** + * Specify a replacement for the given websocket url. + * + * @param configUrl The url at which the websocket url was found. + * @param websocketUrl The hotel websocket url. + * @return Return null to not replace anything, otherwise specify an alternative websocket url. + */ + String replaceWebsocketServer(String configUrl, String websocketUrl); + +} diff --git a/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/os/NitroOsFunctions.java b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/os/NitroOsFunctions.java new file mode 100644 index 0000000..d77a4c6 --- /dev/null +++ b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/os/NitroOsFunctions.java @@ -0,0 +1,13 @@ +package gearth.protocol.connection.proxy.nitro.os; + +import java.io.File; + +public interface NitroOsFunctions { + + boolean installRootCertificate(File certificate); + + boolean registerSystemProxy(String host, int port); + + boolean unregisterSystemProxy(); + +} diff --git a/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/os/NitroOsFunctionsFactory.java b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/os/NitroOsFunctionsFactory.java new file mode 100644 index 0000000..18bbf7c --- /dev/null +++ b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/os/NitroOsFunctionsFactory.java @@ -0,0 +1,21 @@ +package gearth.protocol.connection.proxy.nitro.os; + +import gearth.misc.OSValidator; +import gearth.protocol.connection.proxy.nitro.os.windows.NitroWindows; +import org.apache.commons.lang3.NotImplementedException; + +public final class NitroOsFunctionsFactory { + + public static NitroOsFunctions create() { + if (OSValidator.isWindows()) { + return new NitroWindows(); + } + + if (OSValidator.isUnix()) { + throw new NotImplementedException("unix nitro is not implemented yet"); + } + + throw new NotImplementedException("macOS nitro is not implemented yet"); + } + +} diff --git a/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/os/windows/NitroWindows.java b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/os/windows/NitroWindows.java new file mode 100644 index 0000000..27c7b12 --- /dev/null +++ b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/os/windows/NitroWindows.java @@ -0,0 +1,42 @@ +package gearth.protocol.connection.proxy.nitro.os.windows; + +import gearth.protocol.connection.proxy.nitro.os.NitroOsFunctions; + +import java.io.File; + +public class NitroWindows implements NitroOsFunctions { + + @Override + public boolean installRootCertificate(File certificate) { + // TODO: Prompt registration + System.out.println(certificate.toString()); + return true; + } + + @Override + public boolean registerSystemProxy(String host, int port) { + try { + final String proxy = String.format("%s:%d", host, port); + Runtime.getRuntime().exec("reg add \"HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings\" /v ProxyServer /t REG_SZ /d \"" + proxy + "\" /f"); + Runtime.getRuntime().exec("reg add \"HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings\" /v ProxyEnable /t REG_DWORD /d 1 /f"); + return true; + } catch (Exception e) { + e.printStackTrace(); + } + + return false; + } + + @Override + public boolean unregisterSystemProxy() { + try { + Runtime.getRuntime().exec("reg add \"HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings\" /v ProxyEnable /t REG_DWORD /d 0 /f"); + return true; + } catch (Exception e) { + e.printStackTrace(); + } + + return false; + } + +} diff --git a/G-Earth/src/main/java/gearth/ui/connection/ConnectionController.java b/G-Earth/src/main/java/gearth/ui/connection/ConnectionController.java index 3a559da..b908666 100644 --- a/G-Earth/src/main/java/gearth/ui/connection/ConnectionController.java +++ b/G-Earth/src/main/java/gearth/ui/connection/ConnectionController.java @@ -246,6 +246,10 @@ public class ConnectionController extends SubForm { Platform.runLater(() -> rd_unity.setSelected(true)); getHConnection().startUnity(); } + else if (connectMode.equals("nitro")) { + Platform.runLater(() -> rd_nitro.setSelected(true)); + getHConnection().startNitro(); + } Platform.runLater(this::updateInputUI); } } @@ -255,16 +259,16 @@ public class ConnectionController extends SubForm { btnConnect.setDisable(true); new Thread(() -> { - if (useFlash()) { + if (isClientMode(HClient.FLASH)) { if (cbx_autodetect.isSelected()) { getHConnection().start(); - } - else { + } else { getHConnection().start(inpHost.getEditor().getText(), Integer.parseInt(inpPort.getEditor().getText())); } - } - else { + } else if (isClientMode(HClient.UNITY)) { getHConnection().startUnity(); + } else if (isClientMode(HClient.NITRO)) { + getHConnection().startNitro(); } @@ -297,4 +301,17 @@ public class ConnectionController extends SubForm { private boolean useFlash() { return rd_flash.isSelected(); } + + private boolean isClientMode(HClient client) { + switch (client) { + case FLASH: + return rd_flash.isSelected(); + case UNITY: + return rd_unity.isSelected(); + case NITRO: + return rd_nitro.isSelected(); + } + + return false; + } } From 368a21ffcf525487df7f372ff837521e88b9b3ba Mon Sep 17 00:00:00 2001 From: UnfamiliarLegacy <74633542+UnfamiliarLegacy@users.noreply.github.com> Date: Fri, 26 Nov 2021 06:19:07 +0100 Subject: [PATCH 03/12] Added websocket proxying for nitro with broken outgoing packets --- .../proxy/nitro/NitroConstants.java | 11 +++ .../proxy/nitro/NitroProxyProvider.java | 64 ++++++++++++--- .../proxy/nitro/http/NitroHttpProxy.java | 24 +++--- .../proxy/nitro/websocket/NitroForwarder.java | 17 ++++ .../nitro/websocket/NitroPacketHandler.java | 56 +++++++++++++ .../proxy/nitro/websocket/NitroSession.java | 9 +++ .../nitro/websocket/NitroWebsocketClient.java | 78 +++++++++++++++++++ .../nitro/websocket/NitroWebsocketProxy.java | 66 ++++++++++++++++ .../nitro/websocket/NitroWebsocketServer.java | 65 ++++++++++++++++ .../NitroWebsocketServerConfigurator.java | 28 +++++++ .../packet_info/PacketInfoManager.java | 3 +- 11 files changed, 395 insertions(+), 26 deletions(-) create mode 100644 G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/NitroConstants.java create mode 100644 G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/websocket/NitroForwarder.java create mode 100644 G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/websocket/NitroPacketHandler.java create mode 100644 G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/websocket/NitroSession.java create mode 100644 G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/websocket/NitroWebsocketClient.java create mode 100644 G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/websocket/NitroWebsocketProxy.java create mode 100644 G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/websocket/NitroWebsocketServer.java create mode 100644 G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/websocket/NitroWebsocketServerConfigurator.java diff --git a/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/NitroConstants.java b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/NitroConstants.java new file mode 100644 index 0000000..d64a88f --- /dev/null +++ b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/NitroConstants.java @@ -0,0 +1,11 @@ +package gearth.protocol.connection.proxy.nitro; + +public final class NitroConstants { + + public static final int PORT_WEBSOCKET = 2096; + public static final int PORT_HTTP = 9090; + + public static final int WEBSOCKET_BUFFER_SIZE = 1024 * 1024 * 10; + public static final String WEBSOCKET_REVISION = "PRODUCTION-201611291003-338511768"; + +} diff --git a/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/NitroProxyProvider.java b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/NitroProxyProvider.java index 5ba1172..a3ac2ec 100644 --- a/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/NitroProxyProvider.java +++ b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/NitroProxyProvider.java @@ -1,34 +1,52 @@ package gearth.protocol.connection.proxy.nitro; import gearth.protocol.HConnection; +import gearth.protocol.StateChangeListener; import gearth.protocol.connection.HProxySetter; import gearth.protocol.connection.HState; import gearth.protocol.connection.HStateSetter; import gearth.protocol.connection.proxy.ProxyProvider; import gearth.protocol.connection.proxy.nitro.http.NitroHttpProxy; +import gearth.protocol.connection.proxy.nitro.http.NitroHttpProxyServerCallback; +import gearth.protocol.connection.proxy.nitro.websocket.NitroWebsocketProxy; import java.io.IOException; -public class NitroProxyProvider implements ProxyProvider { +public class NitroProxyProvider implements ProxyProvider, NitroHttpProxyServerCallback, StateChangeListener { private final HProxySetter proxySetter; private final HStateSetter stateSetter; - private final HConnection hConnection; - private final NitroHttpProxy nitroProxy; + private final HConnection connection; + private final NitroHttpProxy nitroHttpProxy; + private final NitroWebsocketProxy nitroWebsocketProxy; - public NitroProxyProvider(HProxySetter proxySetter, HStateSetter stateSetter, HConnection hConnection) { + private String originalWebsocketUrl; + + public NitroProxyProvider(HProxySetter proxySetter, HStateSetter stateSetter, HConnection connection) { this.proxySetter = proxySetter; this.stateSetter = stateSetter; - this.hConnection = hConnection; - this.nitroProxy = new NitroHttpProxy(); + this.connection = connection; + this.nitroHttpProxy = new NitroHttpProxy(this); + this.nitroWebsocketProxy = new NitroWebsocketProxy(proxySetter, stateSetter, connection, this); + } + + public String getOriginalWebsocketUrl() { + return originalWebsocketUrl; } @Override public void start() throws IOException { - if (!nitroProxy.start()) { - System.out.println("Failed to start nitro proxy"); + connection.getStateObservable().addListener(this); - stateSetter.setState(HState.NOT_CONNECTED); + if (!nitroHttpProxy.start()) { + System.out.println("Failed to start nitro proxy"); + abort(); + return; + } + + if (!nitroWebsocketProxy.start()) { + System.out.println("Failed to start nitro websocket proxy"); + abort(); return; } @@ -41,13 +59,35 @@ public class NitroProxyProvider implements ProxyProvider { new Thread(() -> { try { - nitroProxy.stop(); + nitroHttpProxy.stop(); } catch (Exception e) { e.printStackTrace(); - } finally { - stateSetter.setState(HState.NOT_CONNECTED); } + + try { + nitroWebsocketProxy.stop(); + } catch (Exception e) { + e.printStackTrace(); + } + + stateSetter.setState(HState.NOT_CONNECTED); + + connection.getStateObservable().removeListener(this); }).start(); } + @Override + public String replaceWebsocketServer(String configUrl, String websocketUrl) { + originalWebsocketUrl = websocketUrl; + return String.format("ws://127.0.0.1:%d", NitroConstants.PORT_WEBSOCKET); + } + + @Override + public void stateChanged(HState oldState, HState newState) { + if (oldState == HState.WAITING_FOR_CLIENT && newState == HState.CONNECTED) { + // Unregister but do not stop http proxy. + // We are not stopping the http proxy because some requests might still require it to be running. + nitroHttpProxy.pause(); + } + } } diff --git a/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/http/NitroHttpProxy.java b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/http/NitroHttpProxy.java index 13acc03..7e23a75 100644 --- a/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/http/NitroHttpProxy.java +++ b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/http/NitroHttpProxy.java @@ -1,5 +1,6 @@ package gearth.protocol.connection.proxy.nitro.http; +import gearth.protocol.connection.proxy.nitro.NitroConstants; import gearth.protocol.connection.proxy.nitro.os.NitroOsFunctions; import gearth.protocol.connection.proxy.nitro.os.NitroOsFunctionsFactory; import org.littleshoot.proxy.HttpProxyServer; @@ -12,10 +13,12 @@ public class NitroHttpProxy { private final Authority authority; private final NitroOsFunctions osFunctions; + private final NitroHttpProxyServerCallback serverCallback; private HttpProxyServer proxyServer = null; - public NitroHttpProxy() { + public NitroHttpProxy(NitroHttpProxyServerCallback serverCallback) { + this.serverCallback = serverCallback; this.authority = new NitroAuthority(); this.osFunctions = NitroOsFunctionsFactory.create(); } @@ -28,7 +31,7 @@ public class NitroHttpProxy { * Register HTTP(s) proxy on the system. */ private boolean registerProxy() { - return this.osFunctions.registerSystemProxy("127.0.0.1", 9090); + return this.osFunctions.registerSystemProxy("127.0.0.1", NitroConstants.PORT_HTTP); } /** @@ -39,18 +42,11 @@ public class NitroHttpProxy { } public boolean start() { - - try { proxyServer = DefaultHttpProxyServer.bootstrap() - .withPort(9090) + .withPort(NitroConstants.PORT_HTTP) .withManInTheMiddle(new CertificateSniffingMitmManager(authority)) - // TODO: Replace lambda with some class - .withFiltersSource(new NitroHttpProxyFilterSource((configUrl, websocketUrl) -> { - System.out.printf("Found %s at %s%n", websocketUrl, configUrl); - - return "wss://127.0.0.1:2096"; - })) + .withFiltersSource(new NitroHttpProxyFilterSource(serverCallback)) .start(); if (!initializeCertificate()) { @@ -74,10 +70,14 @@ public class NitroHttpProxy { } } - public void stop() { + public void pause() { if (!unregisterProxy()) { System.out.println("Failed to unregister system proxy, please check manually"); } + } + + public void stop() { + pause(); if (proxyServer == null) { return; diff --git a/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/websocket/NitroForwarder.java b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/websocket/NitroForwarder.java new file mode 100644 index 0000000..42433da --- /dev/null +++ b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/websocket/NitroForwarder.java @@ -0,0 +1,17 @@ +package gearth.protocol.connection.proxy.nitro.websocket; + +import gearth.protocol.HConnection; + +public class NitroForwarder { + + private final HConnection connection; + private final NitroSession client; + private final NitroSession server; + + public NitroForwarder(HConnection connection, NitroSession client, NitroSession server) { + this.connection = connection; + this.client = client; + this.server = server; + } + +} diff --git a/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/websocket/NitroPacketHandler.java b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/websocket/NitroPacketHandler.java new file mode 100644 index 0000000..a6283e3 --- /dev/null +++ b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/websocket/NitroPacketHandler.java @@ -0,0 +1,56 @@ +package gearth.protocol.connection.proxy.nitro.websocket; + +import gearth.protocol.HMessage; +import gearth.protocol.HPacket; +import gearth.protocol.packethandler.PacketHandler; +import gearth.services.extension_handler.ExtensionHandler; +import gearth.services.extension_handler.OnHMessageHandled; + +import java.io.IOException; +import java.nio.ByteBuffer; + +public class NitroPacketHandler extends PacketHandler { + + private final HMessage.Direction direction; + private final NitroSession session; + + protected NitroPacketHandler(HMessage.Direction direction, NitroSession session, ExtensionHandler extensionHandler, Object[] trafficObservables) { + super(extensionHandler, trafficObservables); + this.direction = direction; + this.session = session; + } + + @Override + public boolean sendToStream(byte[] buffer) { + session.getSession().getAsyncRemote().sendBinary(ByteBuffer.wrap(buffer)); + return true; + } + + @Override + public void act(byte[] buffer) throws IOException { + HMessage hMessage = new HMessage(new HPacket(deepCopy(buffer)), direction, currentIndex); + + OnHMessageHandled afterExtensionIntercept = hMessage1 -> { + notifyListeners(2, hMessage1); + + if (!hMessage1.isBlocked()) { + sendToStream(hMessage1.getPacket().toBytes()); + } + }; + + notifyListeners(0, hMessage); + notifyListeners(1, hMessage); + extensionHandler.handle(hMessage, afterExtensionIntercept); + + currentIndex++; + } + + public static byte[] deepCopy(byte[] org) { + if (org == null) + return null; + byte[] result = new byte[org.length]; + System.arraycopy(org, 0, result, 0, org.length); + return result; + } + +} diff --git a/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/websocket/NitroSession.java b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/websocket/NitroSession.java new file mode 100644 index 0000000..f85854e --- /dev/null +++ b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/websocket/NitroSession.java @@ -0,0 +1,9 @@ +package gearth.protocol.connection.proxy.nitro.websocket; + +import javax.websocket.Session; + +public interface NitroSession { + + Session getSession(); + +} diff --git a/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/websocket/NitroWebsocketClient.java b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/websocket/NitroWebsocketClient.java new file mode 100644 index 0000000..d1c8fa5 --- /dev/null +++ b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/websocket/NitroWebsocketClient.java @@ -0,0 +1,78 @@ +package gearth.protocol.connection.proxy.nitro.websocket; + +import gearth.protocol.HConnection; +import gearth.protocol.HMessage; +import gearth.protocol.connection.*; +import gearth.protocol.connection.proxy.nitro.NitroConstants; +import gearth.protocol.connection.proxy.nitro.NitroProxyProvider; +import gearth.services.internal_extensions.uilogger.hexdumper.Hexdump; + +import javax.websocket.*; +import javax.websocket.server.ServerEndpoint; +import java.io.IOException; + +@ServerEndpoint(value = "/") +public class NitroWebsocketClient implements NitroSession { + + private final HProxySetter proxySetter; + private final HStateSetter stateSetter; + private final HConnection connection; + private final NitroProxyProvider proxyProvider; + private final NitroWebsocketServer server; + private final NitroPacketHandler packetHandler; + + private Session activeSession = null; + private HProxy proxy = null; + + public NitroWebsocketClient(HProxySetter proxySetter, HStateSetter stateSetter, HConnection connection, NitroProxyProvider proxyProvider) { + this.proxySetter = proxySetter; + this.stateSetter = stateSetter; + this.connection = connection; + this.proxyProvider = proxyProvider; + this.server = new NitroWebsocketServer(connection, this); + this.packetHandler = new NitroPacketHandler(HMessage.Direction.TOSERVER, server, connection.getExtensionHandler(), connection.getTrafficObservables()); + } + + @OnOpen + public void onOpen(Session session) throws IOException { + activeSession = session; + activeSession.setMaxBinaryMessageBufferSize(NitroConstants.WEBSOCKET_BUFFER_SIZE); + + server.connect(proxyProvider.getOriginalWebsocketUrl()); + + proxy = new HProxy(HClient.NITRO, "", "", -1, -1, ""); + proxy.verifyProxy( + this.server.getPacketHandler(), + this.packetHandler, + NitroConstants.WEBSOCKET_REVISION, + "HTML5" // TODO: What is its purpose? + ); + + proxySetter.setProxy(proxy); + stateSetter.setState(HState.CONNECTED); + } + + @OnMessage + public void onMessage(byte[] b, Session session) throws IOException { + System.out.printf("onMessage (%d)%n", b.length); + System.out.println(session); + System.out.println(Hexdump.hexdump(b)); + + packetHandler.act(b); + } + + @OnClose + public void onClose(Session session) throws IOException { + activeSession = null; + } + + @OnError + public void onError(Session session, Throwable throwable) { + + } + + @Override + public Session getSession() { + return activeSession; + } +} diff --git a/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/websocket/NitroWebsocketProxy.java b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/websocket/NitroWebsocketProxy.java new file mode 100644 index 0000000..d162347 --- /dev/null +++ b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/websocket/NitroWebsocketProxy.java @@ -0,0 +1,66 @@ +package gearth.protocol.connection.proxy.nitro.websocket; + +import gearth.protocol.HConnection; +import gearth.protocol.connection.HProxySetter; +import gearth.protocol.connection.HStateSetter; +import gearth.protocol.connection.proxy.nitro.NitroConstants; +import gearth.protocol.connection.proxy.nitro.NitroProxyProvider; +import org.eclipse.jetty.server.Handler; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.handler.HandlerList; +import org.eclipse.jetty.servlet.ServletContextHandler; +import org.eclipse.jetty.websocket.jsr356.server.deploy.WebSocketServerContainerInitializer; + +import javax.websocket.server.ServerContainer; +import javax.websocket.server.ServerEndpointConfig; + +public class NitroWebsocketProxy { + + private final HProxySetter proxySetter; + private final HStateSetter stateSetter; + private final HConnection connection; + private final NitroProxyProvider proxyProvider; + + private final Server server; + + public NitroWebsocketProxy(HProxySetter proxySetter, HStateSetter stateSetter, HConnection connection, NitroProxyProvider proxyProvider) { + this.proxySetter = proxySetter; + this.stateSetter = stateSetter; + this.connection = connection; + this.proxyProvider = proxyProvider; + this.server = new Server(NitroConstants.PORT_WEBSOCKET); + } + + public boolean start() { + try { + final ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS); + context.setContextPath("/"); + + final HandlerList handlers = new HandlerList(); + handlers.setHandlers(new Handler[] { context }); + + final ServerContainer wscontainer = WebSocketServerContainerInitializer.configureContext(context); + wscontainer.addEndpoint(ServerEndpointConfig.Builder + .create(NitroWebsocketClient.class, "/") + .configurator(new NitroWebsocketServerConfigurator(proxySetter, stateSetter, connection, proxyProvider)) + .build()); + + server.setHandler(handlers); + server.start(); + + return true; + } catch (Exception e) { + e.printStackTrace(); + } + + return false; + } + + public void stop() { + try { + server.stop(); + } catch (Exception e) { + e.printStackTrace(); + } + } +} diff --git a/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/websocket/NitroWebsocketServer.java b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/websocket/NitroWebsocketServer.java new file mode 100644 index 0000000..f7928df --- /dev/null +++ b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/websocket/NitroWebsocketServer.java @@ -0,0 +1,65 @@ +package gearth.protocol.connection.proxy.nitro.websocket; + +import gearth.protocol.HConnection; +import gearth.protocol.HMessage; +import gearth.protocol.connection.proxy.nitro.NitroConstants; +import gearth.protocol.packethandler.PacketHandler; + +import javax.websocket.*; +import java.io.IOException; +import java.net.URI; + +@ClientEndpoint +public class NitroWebsocketServer implements NitroSession { + + private final PacketHandler packetHandler; + private Session activeSession = null; + + public NitroWebsocketServer(HConnection connection, NitroWebsocketClient client) { + this.packetHandler = new NitroPacketHandler(HMessage.Direction.TOCLIENT, client, connection.getExtensionHandler(), connection.getTrafficObservables()); + } + + public void connect(String websocketUrl) throws IOException { + try { + ContainerProvider.getWebSocketContainer().connectToServer(this, URI.create(websocketUrl)); + } catch (DeploymentException e) { + throw new IOException("Failed to deploy websocket client", e); + } + } + + @OnOpen + public void onOpen(Session Session) { + this.activeSession = Session; + this.activeSession.setMaxBinaryMessageBufferSize(NitroConstants.WEBSOCKET_BUFFER_SIZE); + } + + @OnMessage + public void onMessage(byte[] b, Session session) throws IOException { + //System.out.printf("onMessage (%d)%n", b.length); + //System.out.println(session); + //System.out.println(Hexdump.hexdump(b)); + + packetHandler.act(b); + } + + @OnClose + public void onClose(Session userSession, CloseReason reason) { + //System.out.println("closing websocket"); + } + + @OnError + public void onError(Session session, Throwable throwable) { + //System.out.println("onError"); + //System.out.println(session); + //System.out.println(throwable); + } + + @Override + public Session getSession() { + return activeSession; + } + + public PacketHandler getPacketHandler() { + return packetHandler; + } +} diff --git a/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/websocket/NitroWebsocketServerConfigurator.java b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/websocket/NitroWebsocketServerConfigurator.java new file mode 100644 index 0000000..8155f27 --- /dev/null +++ b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/websocket/NitroWebsocketServerConfigurator.java @@ -0,0 +1,28 @@ +package gearth.protocol.connection.proxy.nitro.websocket; + +import gearth.protocol.HConnection; +import gearth.protocol.connection.HProxySetter; +import gearth.protocol.connection.HStateSetter; +import gearth.protocol.connection.proxy.nitro.NitroProxyProvider; + +import javax.websocket.server.ServerEndpointConfig; + +public class NitroWebsocketServerConfigurator extends ServerEndpointConfig.Configurator { + + private final HProxySetter proxySetter; + private final HStateSetter stateSetter; + private final HConnection connection; + private final NitroProxyProvider proxyProvider; + + public NitroWebsocketServerConfigurator(HProxySetter proxySetter, HStateSetter stateSetter, HConnection connection, NitroProxyProvider proxyProvider) { + this.proxySetter = proxySetter; + this.stateSetter = stateSetter; + this.connection = connection; + this.proxyProvider = proxyProvider; + } + + @Override + public T getEndpointInstance(Class endpointClass) { + return (T) new NitroWebsocketClient(proxySetter, stateSetter, connection, proxyProvider); + } +} diff --git a/G-Earth/src/main/java/gearth/services/packet_info/PacketInfoManager.java b/G-Earth/src/main/java/gearth/services/packet_info/PacketInfoManager.java index 27904b4..af1fda8 100644 --- a/G-Earth/src/main/java/gearth/services/packet_info/PacketInfoManager.java +++ b/G-Earth/src/main/java/gearth/services/packet_info/PacketInfoManager.java @@ -114,8 +114,7 @@ public class PacketInfoManager { if (clientType == HClient.UNITY) { result.addAll(new GEarthUnityPacketInfoProvider(hotelversion).provide()); - } - else if (clientType == HClient.FLASH) { + } else if (clientType == HClient.FLASH || clientType == HClient.NITRO) { try { List providers = new ArrayList<>(); providers.add(new HarblePacketInfoProvider(hotelversion)); From 7a635ffe6a3c6c597eac8e4786b649d0b86b8d1d Mon Sep 17 00:00:00 2001 From: UnfamiliarLegacy <74633542+UnfamiliarLegacy@users.noreply.github.com> Date: Fri, 26 Nov 2021 06:48:33 +0100 Subject: [PATCH 04/12] Fix outgoing packet --- .../nitro/websocket/NitroPacketHandler.java | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/websocket/NitroPacketHandler.java b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/websocket/NitroPacketHandler.java index a6283e3..8ea0ee8 100644 --- a/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/websocket/NitroPacketHandler.java +++ b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/websocket/NitroPacketHandler.java @@ -22,13 +22,18 @@ public class NitroPacketHandler extends PacketHandler { @Override public boolean sendToStream(byte[] buffer) { + // Required to prevent garbage buffer within the UI logger. + if (direction == HMessage.Direction.TOSERVER) { + buffer = buffer.clone(); + } + session.getSession().getAsyncRemote().sendBinary(ByteBuffer.wrap(buffer)); return true; } @Override public void act(byte[] buffer) throws IOException { - HMessage hMessage = new HMessage(new HPacket(deepCopy(buffer)), direction, currentIndex); + HMessage hMessage = new HMessage(new HPacket(buffer), direction, currentIndex); OnHMessageHandled afterExtensionIntercept = hMessage1 -> { notifyListeners(2, hMessage1); @@ -44,13 +49,4 @@ public class NitroPacketHandler extends PacketHandler { currentIndex++; } - - public static byte[] deepCopy(byte[] org) { - if (org == null) - return null; - byte[] result = new byte[org.length]; - System.arraycopy(org, 0, result, 0, org.length); - return result; - } - } From dd0312c020d299bb56740a475526f8801299cf3f Mon Sep 17 00:00:00 2001 From: UnfamiliarLegacy <74633542+UnfamiliarLegacy@users.noreply.github.com> Date: Fri, 26 Nov 2021 18:33:38 +0100 Subject: [PATCH 05/12] Improve nitro constants --- .../protocol/connection/proxy/nitro/NitroConstants.java | 6 ++++-- .../protocol/connection/proxy/nitro/NitroProxyProvider.java | 2 +- .../connection/proxy/nitro/http/NitroHttpProxy.java | 4 ++-- .../proxy/nitro/http/NitroHttpProxyFilterSource.java | 3 ++- .../proxy/nitro/websocket/NitroWebsocketClient.java | 3 +-- .../proxy/nitro/websocket/NitroWebsocketProxy.java | 2 +- 6 files changed, 11 insertions(+), 9 deletions(-) diff --git a/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/NitroConstants.java b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/NitroConstants.java index d64a88f..2d111f3 100644 --- a/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/NitroConstants.java +++ b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/NitroConstants.java @@ -2,10 +2,12 @@ package gearth.protocol.connection.proxy.nitro; public final class NitroConstants { - public static final int PORT_WEBSOCKET = 2096; - public static final int PORT_HTTP = 9090; + public static final int HTTP_PORT = 9090; + public static final int HTTP_BUFFER_SIZE = 1024 * 1024 * 10; + public static final int WEBSOCKET_PORT = 2096; public static final int WEBSOCKET_BUFFER_SIZE = 1024 * 1024 * 10; public static final String WEBSOCKET_REVISION = "PRODUCTION-201611291003-338511768"; + public static final String WEBSOCKET_CLIENT_IDENTIFIER = "HTML5"; } diff --git a/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/NitroProxyProvider.java b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/NitroProxyProvider.java index a3ac2ec..3cb47a1 100644 --- a/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/NitroProxyProvider.java +++ b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/NitroProxyProvider.java @@ -79,7 +79,7 @@ public class NitroProxyProvider implements ProxyProvider, NitroHttpProxyServerCa @Override public String replaceWebsocketServer(String configUrl, String websocketUrl) { originalWebsocketUrl = websocketUrl; - return String.format("ws://127.0.0.1:%d", NitroConstants.PORT_WEBSOCKET); + return String.format("ws://127.0.0.1:%d", NitroConstants.WEBSOCKET_PORT); } @Override diff --git a/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/http/NitroHttpProxy.java b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/http/NitroHttpProxy.java index 7e23a75..84885ee 100644 --- a/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/http/NitroHttpProxy.java +++ b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/http/NitroHttpProxy.java @@ -31,7 +31,7 @@ public class NitroHttpProxy { * Register HTTP(s) proxy on the system. */ private boolean registerProxy() { - return this.osFunctions.registerSystemProxy("127.0.0.1", NitroConstants.PORT_HTTP); + return this.osFunctions.registerSystemProxy("127.0.0.1", NitroConstants.HTTP_PORT); } /** @@ -44,7 +44,7 @@ public class NitroHttpProxy { public boolean start() { try { proxyServer = DefaultHttpProxyServer.bootstrap() - .withPort(NitroConstants.PORT_HTTP) + .withPort(NitroConstants.HTTP_PORT) .withManInTheMiddle(new CertificateSniffingMitmManager(authority)) .withFiltersSource(new NitroHttpProxyFilterSource(serverCallback)) .start(); diff --git a/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/http/NitroHttpProxyFilterSource.java b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/http/NitroHttpProxyFilterSource.java index fae9deb..c736458 100644 --- a/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/http/NitroHttpProxyFilterSource.java +++ b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/http/NitroHttpProxyFilterSource.java @@ -1,5 +1,6 @@ package gearth.protocol.connection.proxy.nitro.http; +import gearth.protocol.connection.proxy.nitro.NitroConstants; import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.http.HttpMethod; import io.netty.handler.codec.http.HttpRequest; @@ -41,6 +42,6 @@ public class NitroHttpProxyFilterSource extends HttpFiltersSourceAdapter { @Override public int getMaximumResponseBufferSizeInBytes() { // Increasing this causes LittleProxy to output "FullHttpResponse" objects. - return 1024 * 1024 * 1024; + return NitroConstants.HTTP_BUFFER_SIZE; } } diff --git a/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/websocket/NitroWebsocketClient.java b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/websocket/NitroWebsocketClient.java index d1c8fa5..ab890da 100644 --- a/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/websocket/NitroWebsocketClient.java +++ b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/websocket/NitroWebsocketClient.java @@ -5,7 +5,6 @@ import gearth.protocol.HMessage; import gearth.protocol.connection.*; import gearth.protocol.connection.proxy.nitro.NitroConstants; import gearth.protocol.connection.proxy.nitro.NitroProxyProvider; -import gearth.services.internal_extensions.uilogger.hexdumper.Hexdump; import javax.websocket.*; import javax.websocket.server.ServerEndpoint; @@ -45,7 +44,7 @@ public class NitroWebsocketClient implements NitroSession { this.server.getPacketHandler(), this.packetHandler, NitroConstants.WEBSOCKET_REVISION, - "HTML5" // TODO: What is its purpose? + NitroConstants.WEBSOCKET_CLIENT_IDENTIFIER ); proxySetter.setProxy(proxy); diff --git a/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/websocket/NitroWebsocketProxy.java b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/websocket/NitroWebsocketProxy.java index d162347..11122d1 100644 --- a/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/websocket/NitroWebsocketProxy.java +++ b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/websocket/NitroWebsocketProxy.java @@ -28,7 +28,7 @@ public class NitroWebsocketProxy { this.stateSetter = stateSetter; this.connection = connection; this.proxyProvider = proxyProvider; - this.server = new Server(NitroConstants.PORT_WEBSOCKET); + this.server = new Server(NitroConstants.WEBSOCKET_PORT); } public boolean start() { From ae4ccff20ec9cc12a0618177924b4d5186fac55e Mon Sep 17 00:00:00 2001 From: UnfamiliarLegacy <74633542+UnfamiliarLegacy@users.noreply.github.com> Date: Fri, 26 Nov 2021 18:51:31 +0100 Subject: [PATCH 06/12] Add proper shutdown mechanism for Nitro --- .../nitro/websocket/NitroPacketHandler.java | 9 +++- .../nitro/websocket/NitroWebsocketClient.java | 53 ++++++++++++++++--- .../nitro/websocket/NitroWebsocketServer.java | 33 +++++++++--- 3 files changed, 80 insertions(+), 15 deletions(-) diff --git a/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/websocket/NitroPacketHandler.java b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/websocket/NitroPacketHandler.java index 8ea0ee8..d6ed58a 100644 --- a/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/websocket/NitroPacketHandler.java +++ b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/websocket/NitroPacketHandler.java @@ -6,6 +6,7 @@ import gearth.protocol.packethandler.PacketHandler; import gearth.services.extension_handler.ExtensionHandler; import gearth.services.extension_handler.OnHMessageHandled; +import javax.websocket.Session; import java.io.IOException; import java.nio.ByteBuffer; @@ -22,12 +23,18 @@ public class NitroPacketHandler extends PacketHandler { @Override public boolean sendToStream(byte[] buffer) { + final Session localSession = session.getSession(); + + if (localSession == null) { + return false; + } + // Required to prevent garbage buffer within the UI logger. if (direction == HMessage.Direction.TOSERVER) { buffer = buffer.clone(); } - session.getSession().getAsyncRemote().sendBinary(ByteBuffer.wrap(buffer)); + localSession.getAsyncRemote().sendBinary(ByteBuffer.wrap(buffer)); return true; } diff --git a/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/websocket/NitroWebsocketClient.java b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/websocket/NitroWebsocketClient.java index ab890da..aabb946 100644 --- a/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/websocket/NitroWebsocketClient.java +++ b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/websocket/NitroWebsocketClient.java @@ -9,6 +9,7 @@ import gearth.protocol.connection.proxy.nitro.NitroProxyProvider; import javax.websocket.*; import javax.websocket.server.ServerEndpoint; import java.io.IOException; +import java.util.concurrent.atomic.AtomicBoolean; @ServerEndpoint(value = "/") public class NitroWebsocketClient implements NitroSession { @@ -19,9 +20,9 @@ public class NitroWebsocketClient implements NitroSession { private final NitroProxyProvider proxyProvider; private final NitroWebsocketServer server; private final NitroPacketHandler packetHandler; + private final AtomicBoolean shutdownLock; private Session activeSession = null; - private HProxy proxy = null; public NitroWebsocketClient(HProxySetter proxySetter, HStateSetter stateSetter, HConnection connection, NitroProxyProvider proxyProvider) { this.proxySetter = proxySetter; @@ -30,6 +31,7 @@ public class NitroWebsocketClient implements NitroSession { this.proxyProvider = proxyProvider; this.server = new NitroWebsocketServer(connection, this); this.packetHandler = new NitroPacketHandler(HMessage.Direction.TOSERVER, server, connection.getExtensionHandler(), connection.getTrafficObservables()); + this.shutdownLock = new AtomicBoolean(); } @OnOpen @@ -39,7 +41,8 @@ public class NitroWebsocketClient implements NitroSession { server.connect(proxyProvider.getOriginalWebsocketUrl()); - proxy = new HProxy(HClient.NITRO, "", "", -1, -1, ""); + final HProxy proxy = new HProxy(HClient.NITRO, "", "", -1, -1, ""); + proxy.verifyProxy( this.server.getPacketHandler(), this.packetHandler, @@ -53,25 +56,63 @@ public class NitroWebsocketClient implements NitroSession { @OnMessage public void onMessage(byte[] b, Session session) throws IOException { - System.out.printf("onMessage (%d)%n", b.length); - System.out.println(session); - System.out.println(Hexdump.hexdump(b)); - packetHandler.act(b); } @OnClose public void onClose(Session session) throws IOException { activeSession = null; + shutdownProxy(); } @OnError public void onError(Session session, Throwable throwable) { + throwable.printStackTrace(); + // Shutdown. + shutdownProxy(); } @Override public Session getSession() { return activeSession; } + + /** + * Shutdown and clean up the client connection. + */ + private void shutdown() { + if (activeSession == null) { + return; + } + + try { + activeSession.close(); + } catch (IOException e) { + e.printStackTrace(); + } finally { + activeSession = null; + } + } + + /** + * Shutdown all connections and reset program state. + */ + public void shutdownProxy() { + if (shutdownLock.get()) { + return; + } + + if (shutdownLock.compareAndSet(false, true)) { + // Close client connection. + shutdown(); + + // Close server connection. + server.shutdown(); + + // Reset program state. + proxySetter.setProxy(null); + stateSetter.setState(HState.NOT_CONNECTED); + } + } } diff --git a/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/websocket/NitroWebsocketServer.java b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/websocket/NitroWebsocketServer.java index f7928df..04b238c 100644 --- a/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/websocket/NitroWebsocketServer.java +++ b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/websocket/NitroWebsocketServer.java @@ -13,9 +13,11 @@ import java.net.URI; public class NitroWebsocketServer implements NitroSession { private final PacketHandler packetHandler; + private final NitroWebsocketClient client; private Session activeSession = null; public NitroWebsocketServer(HConnection connection, NitroWebsocketClient client) { + this.client = client; this.packetHandler = new NitroPacketHandler(HMessage.Direction.TOCLIENT, client, connection.getExtensionHandler(), connection.getTrafficObservables()); } @@ -35,23 +37,21 @@ public class NitroWebsocketServer implements NitroSession { @OnMessage public void onMessage(byte[] b, Session session) throws IOException { - //System.out.printf("onMessage (%d)%n", b.length); - //System.out.println(session); - //System.out.println(Hexdump.hexdump(b)); - packetHandler.act(b); } @OnClose public void onClose(Session userSession, CloseReason reason) { - //System.out.println("closing websocket"); + // Hotel closed connection. + client.shutdownProxy(); } @OnError public void onError(Session session, Throwable throwable) { - //System.out.println("onError"); - //System.out.println(session); - //System.out.println(throwable); + throwable.printStackTrace(); + + // Shutdown. + client.shutdownProxy(); } @Override @@ -62,4 +62,21 @@ public class NitroWebsocketServer implements NitroSession { public PacketHandler getPacketHandler() { return packetHandler; } + + /** + * Shutdown and clean up the server connection. + */ + public void shutdown() { + if (activeSession == null) { + return; + } + + try { + activeSession.close(); + } catch (IOException e) { + e.printStackTrace(); + } finally { + activeSession = null; + } + } } From 322cb05ceb31cfa5ab8ab713336f15d8d2f66adf Mon Sep 17 00:00:00 2001 From: UnfamiliarLegacy <74633542+UnfamiliarLegacy@users.noreply.github.com> Date: Fri, 26 Nov 2021 18:52:29 +0100 Subject: [PATCH 07/12] Remove unused class --- .../proxy/nitro/websocket/NitroForwarder.java | 17 ----------------- 1 file changed, 17 deletions(-) delete mode 100644 G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/websocket/NitroForwarder.java diff --git a/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/websocket/NitroForwarder.java b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/websocket/NitroForwarder.java deleted file mode 100644 index 42433da..0000000 --- a/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/websocket/NitroForwarder.java +++ /dev/null @@ -1,17 +0,0 @@ -package gearth.protocol.connection.proxy.nitro.websocket; - -import gearth.protocol.HConnection; - -public class NitroForwarder { - - private final HConnection connection; - private final NitroSession client; - private final NitroSession server; - - public NitroForwarder(HConnection connection, NitroSession client, NitroSession server) { - this.connection = connection; - this.client = client; - this.server = server; - } - -} From 09105e956b1ee7503c9be1141f6935f2036631b2 Mon Sep 17 00:00:00 2001 From: UnfamiliarLegacy <74633542+UnfamiliarLegacy@users.noreply.github.com> Date: Fri, 26 Nov 2021 19:58:34 +0100 Subject: [PATCH 08/12] Prompt root certificate installation --- .../main/java/gearth/misc/RuntimeUtil.java | 31 ++++++++++++ .../proxy/nitro/http/NitroHttpProxy.java | 47 +++++++++++++++++++ .../proxy/nitro/os/NitroOsFunctions.java | 2 + .../proxy/nitro/os/windows/NitroWindows.java | 45 +++++++++++++++++- .../nitro/os/windows/NitroWindowsShell32.java | 17 +++++++ 5 files changed, 140 insertions(+), 2 deletions(-) create mode 100644 G-Earth/src/main/java/gearth/misc/RuntimeUtil.java create mode 100644 G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/os/windows/NitroWindowsShell32.java diff --git a/G-Earth/src/main/java/gearth/misc/RuntimeUtil.java b/G-Earth/src/main/java/gearth/misc/RuntimeUtil.java new file mode 100644 index 0000000..0e17736 --- /dev/null +++ b/G-Earth/src/main/java/gearth/misc/RuntimeUtil.java @@ -0,0 +1,31 @@ +package gearth.misc; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; + +public final class RuntimeUtil { + + public static String getCommandOutput(String[] command) throws IOException { + try { + final Runtime rt = Runtime.getRuntime(); + final Process proc = rt.exec(command); + + final BufferedReader stdInput = new BufferedReader(new InputStreamReader(proc.getInputStream())); + final StringBuilder result = new StringBuilder(); + + String line; + + while ((line = stdInput.readLine()) != null) { + result.append(line); + result.append("\n"); + } + + return result.toString(); + } catch (IOException e) { + e.printStackTrace(); + throw e; + } + } + +} diff --git a/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/http/NitroHttpProxy.java b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/http/NitroHttpProxy.java index 84885ee..475d912 100644 --- a/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/http/NitroHttpProxy.java +++ b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/http/NitroHttpProxy.java @@ -1,16 +1,26 @@ package gearth.protocol.connection.proxy.nitro.http; +import gearth.misc.ConfirmationDialog; import gearth.protocol.connection.proxy.nitro.NitroConstants; import gearth.protocol.connection.proxy.nitro.os.NitroOsFunctions; import gearth.protocol.connection.proxy.nitro.os.NitroOsFunctionsFactory; +import javafx.application.Platform; +import javafx.scene.control.Alert; +import javafx.scene.control.ButtonType; import org.littleshoot.proxy.HttpProxyServer; import org.littleshoot.proxy.impl.DefaultHttpProxyServer; import org.littleshoot.proxy.mitm.Authority; import org.littleshoot.proxy.mitm.CertificateSniffingMitmManager; import org.littleshoot.proxy.mitm.RootCertificateException; +import java.io.File; +import java.util.concurrent.Semaphore; +import java.util.concurrent.atomic.AtomicBoolean; + public class NitroHttpProxy { + private static final String ADMIN_WARNING_KEY = "admin_warning_dialog"; + private final Authority authority; private final NitroOsFunctions osFunctions; private final NitroHttpProxyServerCallback serverCallback; @@ -24,6 +34,43 @@ public class NitroHttpProxy { } private boolean initializeCertificate() { + final File certificate = this.authority.aliasFile(".pem"); + + // All good if certificate is already trusted. + if (this.osFunctions.isRootCertificateTrusted(certificate)) { + return true; + } + + // Let the user know about admin permissions. + final Semaphore waitForDialog = new Semaphore(0); + final AtomicBoolean shouldInstall = new AtomicBoolean(); + + Platform.runLater(() -> { + Alert alert = ConfirmationDialog.createAlertWithOptOut(Alert.AlertType.WARNING, ADMIN_WARNING_KEY, + "Root certificate installation", null, + "G-Earth detected that you do not have the root certificate authority installed. " + + "This is required for Nitro to work, do you want to continue? " + + "G-Earth will ask you for Administrator permission if you do so.", "Remember my choice", + ButtonType.YES, ButtonType.NO + ); + + shouldInstall.set(!(alert.showAndWait().filter(t -> t == ButtonType.YES).isPresent())); + waitForDialog.release(); + }); + + // Wait for dialog choice. + try { + waitForDialog.acquire(); + } catch (InterruptedException e) { + e.printStackTrace(); + return false; + } + + // User opted out. + if (!shouldInstall.get()) { + return false; + } + return this.osFunctions.installRootCertificate(this.authority.aliasFile(".pem")); } diff --git a/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/os/NitroOsFunctions.java b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/os/NitroOsFunctions.java index d77a4c6..0bfaf29 100644 --- a/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/os/NitroOsFunctions.java +++ b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/os/NitroOsFunctions.java @@ -4,6 +4,8 @@ import java.io.File; public interface NitroOsFunctions { + boolean isRootCertificateTrusted(File certificate); + boolean installRootCertificate(File certificate); boolean registerSystemProxy(String host, int port); diff --git a/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/os/windows/NitroWindows.java b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/os/windows/NitroWindows.java index 27c7b12..fc5abf5 100644 --- a/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/os/windows/NitroWindows.java +++ b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/os/windows/NitroWindows.java @@ -1,15 +1,56 @@ package gearth.protocol.connection.proxy.nitro.os.windows; +import com.sun.jna.platform.win32.Kernel32; +import com.sun.jna.platform.win32.WinBase; +import com.sun.jna.platform.win32.WinDef; +import com.sun.jna.ptr.IntByReference; +import gearth.misc.RuntimeUtil; import gearth.protocol.connection.proxy.nitro.os.NitroOsFunctions; import java.io.File; +import java.io.IOException; public class NitroWindows implements NitroOsFunctions { + /** + * Checks if the certificate is trusted by the local machine. + * @param certificate Absolute path to the certificate. + * @return true if trusted + */ + @Override + public boolean isRootCertificateTrusted(File certificate) { + try { + final String output = RuntimeUtil.getCommandOutput(new String[] {"cmd", "/c", " certutil.exe -f -verify " + certificate.getAbsolutePath()}); + + return !output.contains("CERT_TRUST_IS_UNTRUSTED_ROOT") && + output.contains("dwInfoStatus=10c dwErrorStatus=0"); + } catch (IOException e) { + e.printStackTrace(); + } + + return false; + } + @Override public boolean installRootCertificate(File certificate) { - // TODO: Prompt registration - System.out.println(certificate.toString()); + final String certificatePath = certificate.getAbsolutePath(); + + // Prompt UAC elevation. + WinDef.HINSTANCE result = NitroWindowsShell32.INSTANCE.ShellExecuteA(null, "runas", "cmd.exe", "/S /C \"certutil -addstore root " + certificatePath + "\"", null, 1); + + // Wait for exit. + Kernel32.INSTANCE.WaitForSingleObject(result, WinBase.INFINITE); + + // Exit code for certutil. + final IntByReference statusRef = new IntByReference(-1); + Kernel32.INSTANCE.GetExitCodeProcess(result, statusRef); + + // Check if process exited without errors + if (statusRef.getValue() != -1) { + System.out.printf("Certutil command exited with exit code %s%n", statusRef.getValue()); + return false; + } + return true; } diff --git a/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/os/windows/NitroWindowsShell32.java b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/os/windows/NitroWindowsShell32.java new file mode 100644 index 0000000..69087aa --- /dev/null +++ b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/os/windows/NitroWindowsShell32.java @@ -0,0 +1,17 @@ +package gearth.protocol.connection.proxy.nitro.os.windows; + +import com.sun.jna.Native; +import com.sun.jna.platform.win32.ShellAPI; +import com.sun.jna.platform.win32.WinDef; +import com.sun.jna.win32.StdCallLibrary; + +public interface NitroWindowsShell32 extends ShellAPI, StdCallLibrary { + NitroWindowsShell32 INSTANCE = Native.loadLibrary("shell32", NitroWindowsShell32.class); + + WinDef.HINSTANCE ShellExecuteA(WinDef.HWND hwnd, + String lpOperation, + String lpFile, + String lpParameters, + String lpDirectory, + int nShowCmd); +} From cc7dece9a48c78556847c3350653354d60618f43 Mon Sep 17 00:00:00 2001 From: UnfamiliarLegacy <74633542+UnfamiliarLegacy@users.noreply.github.com> Date: Fri, 26 Nov 2021 20:07:42 +0100 Subject: [PATCH 09/12] Add shutdown hook to ensure system proxy is unregistered --- .../proxy/nitro/http/NitroHttpProxy.java | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/http/NitroHttpProxy.java b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/http/NitroHttpProxy.java index 475d912..231abb7 100644 --- a/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/http/NitroHttpProxy.java +++ b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/http/NitroHttpProxy.java @@ -20,6 +20,7 @@ import java.util.concurrent.atomic.AtomicBoolean; public class NitroHttpProxy { private static final String ADMIN_WARNING_KEY = "admin_warning_dialog"; + private static final AtomicBoolean SHUTDOWN_HOOK = new AtomicBoolean(); private final Authority authority; private final NitroOsFunctions osFunctions; @@ -89,6 +90,8 @@ public class NitroHttpProxy { } public boolean start() { + setupShutdownHook(); + try { proxyServer = DefaultHttpProxyServer.bootstrap() .withPort(NitroConstants.HTTP_PORT) @@ -133,4 +136,18 @@ public class NitroHttpProxy { proxyServer.stop(); proxyServer = null; } + + /** + * Ensure the system proxy is removed when G-Earth exits. + * Otherwise, users might complain that their browsers / discord stop working when closing G-Earth incorrectly. + */ + private static void setupShutdownHook() { + if (SHUTDOWN_HOOK.get()) { + return; + } + + if (SHUTDOWN_HOOK.compareAndSet(false, true)) { + Runtime.getRuntime().addShutdownHook(new Thread(() -> NitroOsFunctionsFactory.create().unregisterSystemProxy())); + } + } } From dd0aeeb1db46aed4d389a0f71a15431dc3f71134 Mon Sep 17 00:00:00 2001 From: UnfamiliarLegacy <74633542+UnfamiliarLegacy@users.noreply.github.com> Date: Fri, 26 Nov 2021 20:20:51 +0100 Subject: [PATCH 10/12] Modify content-security-policy header --- .../nitro/http/NitroHttpProxyFilter.java | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/http/NitroHttpProxyFilter.java b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/http/NitroHttpProxyFilter.java index 28a33ae..ea0cddc 100644 --- a/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/http/NitroHttpProxyFilter.java +++ b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/http/NitroHttpProxyFilter.java @@ -13,11 +13,13 @@ import java.util.regex.Pattern; public class NitroHttpProxyFilter extends HttpFiltersAdapter { private static final String NitroConfigSearch = "\"socket.url\""; + private static final String NitroClientSearch = "configurationUrls:"; private static final Pattern NitroConfigPattern = Pattern.compile("\"socket\\.url\":.?\"(wss?://.*?)\"", Pattern.MULTILINE); private static final String HeaderAcceptEncoding = "Accept-Encoding"; private static final String HeaderAge = "Age"; private static final String HeaderCacheControl = "Cache-Control"; + private static final String HeaderContentSecurityPolicy = "Content-Security-Policy"; private static final String HeaderETag = "ETag"; private static final String HeaderIfNoneMatch = "If-None-Match"; private static final String HeaderIfModifiedSince = "If-Modified-Since"; @@ -88,11 +90,37 @@ public class NitroHttpProxyFilter extends HttpFiltersAdapter { if (responseModified) { responseWrite(response, responseBody); } + + // CSP. + if (responseBody.contains(NitroClientSearch)) { + stripContentSecurityPolicy(response); + } } return httpObject; } + /** + * Modify Content-Security-Policy header, which could prevent Nitro from connecting with G-Earth. + */ + private void stripContentSecurityPolicy(FullHttpResponse response) { + final HttpHeaders headers = response.headers(); + + if (!headers.contains(HeaderContentSecurityPolicy)){ + return; + } + + String csp = headers.get(HeaderContentSecurityPolicy); + + if (csp.contains("connect-src")) { + csp = csp.replace("connect-src", "connect-src *"); + } else if (csp.contains("default-src")) { + csp = csp.replace("default-src", "default-src *"); + } + + headers.set(HeaderContentSecurityPolicy, csp); + } + private static String responseRead(FullHttpResponse response) { final ByteBuf contentBuf = response.content(); return contentBuf.toString(CharsetUtil.UTF_8); From 4681b916eb56feeeb5696bebb359850beec99006 Mon Sep 17 00:00:00 2001 From: UnfamiliarLegacy <74633542+UnfamiliarLegacy@users.noreply.github.com> Date: Fri, 26 Nov 2021 20:25:12 +0100 Subject: [PATCH 11/12] Add ignored hosts for proxy --- .../connection/proxy/nitro/os/windows/NitroWindows.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/os/windows/NitroWindows.java b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/os/windows/NitroWindows.java index fc5abf5..049a6be 100644 --- a/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/os/windows/NitroWindows.java +++ b/G-Earth/src/main/java/gearth/protocol/connection/proxy/nitro/os/windows/NitroWindows.java @@ -12,6 +12,11 @@ import java.io.IOException; public class NitroWindows implements NitroOsFunctions { + /** + * Semicolon separated hosts to ignore for proxying. + */ + private static final String PROXY_IGNORE = "discord.com;github.com;"; + /** * Checks if the certificate is trusted by the local machine. * @param certificate Absolute path to the certificate. @@ -59,6 +64,7 @@ public class NitroWindows implements NitroOsFunctions { try { final String proxy = String.format("%s:%d", host, port); Runtime.getRuntime().exec("reg add \"HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings\" /v ProxyServer /t REG_SZ /d \"" + proxy + "\" /f"); + Runtime.getRuntime().exec("reg add \"HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings\" /v ProxyOverride /t REG_SZ /d \"" + PROXY_IGNORE + "\" /f"); Runtime.getRuntime().exec("reg add \"HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings\" /v ProxyEnable /t REG_DWORD /d 1 /f"); return true; } catch (Exception e) { From bfe13966452e84406589098259c54843dd872363 Mon Sep 17 00:00:00 2001 From: UnfamiliarLegacy <74633542+UnfamiliarLegacy@users.noreply.github.com> Date: Fri, 26 Nov 2021 20:27:04 +0100 Subject: [PATCH 12/12] Do not select nitro by default --- G-Earth/src/main/resources/gearth/ui/connection/Connection.fxml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/G-Earth/src/main/resources/gearth/ui/connection/Connection.fxml b/G-Earth/src/main/resources/gearth/ui/connection/Connection.fxml index bf7cb35..16982b0 100644 --- a/G-Earth/src/main/resources/gearth/ui/connection/Connection.fxml +++ b/G-Earth/src/main/resources/gearth/ui/connection/Connection.fxml @@ -158,7 +158,7 @@ - +