Skip to content

Commit 2763368

Browse files
committed
新增 Windows AMSI 支持
1 parent d63ddcd commit 2763368

File tree

17 files changed

+279
-38
lines changed

17 files changed

+279
-38
lines changed

src/main/java/com/ghostchu/peerbanhelper/Main.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
import lombok.extern.slf4j.Slf4j;
3535
import org.bspfsystems.yamlconfiguration.configuration.InvalidConfigurationException;
3636
import org.bspfsystems.yamlconfiguration.file.YamlConfiguration;
37+
import org.jetbrains.annotations.NotNull;
3738
import org.jetbrains.annotations.Nullable;
3839
import org.pf4j.PluginManager;
3940
import org.slf4j.Logger;
@@ -51,6 +52,7 @@
5152
import java.nio.file.Files;
5253
import java.util.Arrays;
5354
import java.util.Locale;
55+
import java.util.Optional;
5456
import java.util.Properties;
5557

5658
import static com.ghostchu.peerbanhelper.text.TextManager.tlUI;
@@ -98,8 +100,8 @@ public class Main {
98100
private static String[] startupArgs;
99101
@Getter
100102
private static final long startupAt = System.currentTimeMillis();
101-
@Getter
102103
@Nullable
104+
@Getter
103105
private static Platform platform;
104106
private static String userAgent;
105107
public static final int PBH_BTN_PROTOCOL_IMPL_VERSION = 20;

src/main/java/com/ghostchu/peerbanhelper/PeerBanHelper.java

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515
import com.ghostchu.peerbanhelper.module.impl.monitor.SwarmTrackingModule;
1616
import com.ghostchu.peerbanhelper.module.impl.rule.*;
1717
import com.ghostchu.peerbanhelper.module.impl.webapi.*;
18-
import com.ghostchu.peerbanhelper.platform.Platform;
1918
import com.ghostchu.peerbanhelper.platform.impl.win32.workingset.jna.WorkingSetManagerFactory;
2019
import com.ghostchu.peerbanhelper.text.Lang;
2120
import com.ghostchu.peerbanhelper.text.TranslationComponent;
@@ -112,8 +111,8 @@ public void start() {
112111
private void loadPlatformFeatures() {
113112
var platform = Main.getPlatform();
114113
if (platform == null) return;
115-
116-
if (platform.getEcoQosAPI().supported() && Main.getMainConfig().getBoolean("performance.windows-ecoqos-api")) {
114+
var ecoQosAPI = platform.getEcoQosAPI();
115+
if (ecoQosAPI != null && Main.getMainConfig().getBoolean("performance.windows-ecoqos-api")) {
117116
platform.getEcoQosAPI().apply();
118117
}
119118
}

src/main/java/com/ghostchu/peerbanhelper/configuration/pf4j/PBHSpringPluginManager.java

Lines changed: 74 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
11
package com.ghostchu.peerbanhelper.configuration.pf4j;
22

3+
import com.ghostchu.peerbanhelper.Main;
4+
import com.ghostchu.peerbanhelper.text.Lang;
35
import jakarta.annotation.PostConstruct;
46
import lombok.extern.slf4j.Slf4j;
5-
import org.pf4j.ExtensionFactory;
6-
import org.pf4j.PluginState;
7-
import org.pf4j.PluginStateEvent;
8-
import org.pf4j.PluginWrapper;
7+
import org.pf4j.*;
98
import org.pf4j.spring.ExtensionsInjector;
109
import org.pf4j.spring.SpringExtensionFactory;
1110
import org.pf4j.spring.SpringPluginManager;
@@ -14,11 +13,15 @@
1413
import org.springframework.context.ApplicationContext;
1514
import org.springframework.context.ApplicationContextAware;
1615

16+
import java.io.File;
17+
import java.nio.file.Files;
1718
import java.nio.file.Path;
1819
import java.util.Collections;
1920
import java.util.Iterator;
2021
import java.util.List;
2122

23+
import static com.ghostchu.peerbanhelper.text.TextManager.tlUI;
24+
2225
@Slf4j
2326
public class PBHSpringPluginManager extends SpringPluginManager implements ApplicationContextAware {
2427
private ApplicationContext applicationContext;
@@ -62,6 +65,44 @@ public void init() {
6265
extensionsInjector.injectExtensions();
6366
}
6467

68+
@Override
69+
public void loadPlugins() {
70+
log.debug("Lookup plugins in '{}'", pluginsRoots);
71+
72+
// check for plugins roots
73+
if (pluginsRoots.isEmpty()) {
74+
log.warn("No plugins roots configured");
75+
return;
76+
}
77+
pluginsRoots.forEach(path -> {
78+
if (Files.notExists(path) || !Files.isDirectory(path)) {
79+
log.warn("No '{}' root", path);
80+
}
81+
});
82+
83+
// get all plugin paths from repository
84+
List<Path> pluginPaths = pluginRepository.getPluginPaths();
85+
86+
// check for no plugins
87+
if (pluginPaths.isEmpty()) {
88+
log.info("No plugins");
89+
return;
90+
}
91+
92+
log.debug("Found {} possible plugins: {}", pluginPaths.size(), pluginPaths);
93+
94+
// load plugins from plugin paths
95+
for (Path pluginPath : pluginPaths) {
96+
try {
97+
loadPluginFromPath(pluginPath);
98+
} catch (PluginRuntimeException e) {
99+
log.error("Cannot load plugin '{}'", pluginPath, e);
100+
}
101+
}
102+
103+
resolvePlugins();
104+
}
105+
65106
@Override
66107
public void startPlugins() {
67108
for (PluginWrapper pluginWrapper : resolvedPlugins) {
@@ -83,6 +124,7 @@ public void startPlugins() {
83124
}
84125
}
85126
}
127+
86128
/**
87129
* Stop all active plugins.
88130
*/
@@ -111,4 +153,32 @@ public void stopPlugins() {
111153
}
112154
}
113155
}
156+
157+
/**
158+
* Load a plugin from disk. If the path is a zip file, first unpack.
159+
*
160+
* @param pluginPath plugin location on disk
161+
* @return PluginWrapper for the loaded plugin or null if not loaded
162+
* @throws PluginRuntimeException if problems during load
163+
*/
164+
@Override
165+
protected PluginWrapper loadPluginFromPath(Path pluginPath) {
166+
var platform = Main.getPlatform();
167+
if (platform != null) {
168+
var scanner = platform.getMalwareScanner();
169+
if (scanner != null) {
170+
try (scanner) {
171+
File file = pluginPath.toFile();
172+
if (scanner.isMalicious(file)) {
173+
throw new PluginRuntimeException(tlUI(Lang.MALWARE_SCANNER_DETECTED, "[JavaPlugin", pluginPath.toAbsolutePath()));
174+
}
175+
} catch (PluginRuntimeException e) {
176+
throw e;
177+
} catch (Exception e) {
178+
log.debug("Malware scan failed for plugin '{}': Unable to close scanner", pluginPath, e);
179+
}
180+
}
181+
}
182+
return super.loadPluginFromPath(pluginPath);
183+
}
114184
}

src/main/java/com/ghostchu/peerbanhelper/module/impl/rule/ExpressionRule.java

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ public void onEnable() {
8080
}
8181
javalinWebContainer.javalin()
8282
.get("/api/" + getConfigName() + "/scripts", this::listScripts, Role.USER_READ)
83-
.get("/api/"+getConfigName()+"/editable", this::editable, Role.USER_READ)
83+
.get("/api/" + getConfigName() + "/editable", this::editable, Role.USER_READ)
8484
.get("/api/" + getConfigName() + "/{scriptId}", this::readScript, Role.USER_READ)
8585
.put("/api/" + getConfigName() + "/{scriptId}", this::writeScript, Role.USER_WRITE)
8686
.delete("/api/" + getConfigName() + "/{scriptId}", this::deleteScript, Role.USER_WRITE);
@@ -130,7 +130,20 @@ private void writeScript(Context context) throws IOException {
130130
context.json(new StdResp(false, "Access to this resource is disallowed", null));
131131
return;
132132
}
133-
Files.write(readFile.toPath(), context.bodyAsBytes(), StandardOpenOption.CREATE);
133+
var content = context.bodyAsBytes();
134+
var platform = Main.getPlatform();
135+
if (platform != null) {
136+
var scanner = platform.getMalwareScanner();
137+
if (scanner != null) {
138+
if (scanner.isMalicious(context.body())) {
139+
context.status(HttpStatus.BAD_REQUEST);
140+
context.json(new StdResp(false, tl(locale(context), Lang.MALWARE_SCANNER_DETECTED, "WebAPI-ScriptCreate", scriptId), null));
141+
log.error(tlUI(Lang.MALWARE_SCANNER_DETECTED, "WebAPI-ScriptCreate", scriptId));
142+
return;
143+
}
144+
}
145+
}
146+
Files.write(readFile.toPath(), content, StandardOpenOption.CREATE);
134147
context.json(new StdResp(true, tl(locale(context), Lang.EXPRESS_RULE_ENGINE_SAVED), null));
135148
reloadConfig();
136149
}
@@ -166,13 +179,13 @@ private File getIfAllowedScriptId(String scriptId) {
166179
return null;
167180
}
168181

169-
private boolean isSafeNetworkEnvironment(Context context){
182+
private boolean isSafeNetworkEnvironment(Context context) {
170183
var value = ExternalSwitch.parse("pbh.please-disable-safe-network-environment-check-i-know-this-is-very-dangerous-and-i-may-lose-my-data-and-hacker-may-attack-me-via-this-endpoint-and-steal-my-data-or-destroy-my-computer-i-am-fully-responsible-for-this-action-and-i-will-not-blame-the-developer-for-any-loss");
171-
if(value != null && value.equals("true")){
184+
if (value != null && value.equals("true")) {
172185
return true;
173186
}
174187
var ip = IPAddressUtil.getIPAddress(context.ip());
175-
if(ip == null){
188+
if (ip == null) {
176189
throw new IllegalArgumentException("Safe check for IPAddress failed, the IP cannot be null");
177190
}
178191
return (ip.isLocal() || ip.isLoopback()) && !WebUtil.isUsingReserveProxy(context);
@@ -266,7 +279,7 @@ public CheckResult runExpression(CompiledScript script, @NotNull Torrent torrent
266279
returns = script.expression().execute(env);
267280
}
268281
}
269-
result = scriptEngine.handleResult(script,banDuration, returns);
282+
result = scriptEngine.handleResult(script, banDuration, returns);
270283
} catch (TimeoutException timeoutException) {
271284
return pass();
272285
} catch (Exception ex) {
@@ -310,8 +323,8 @@ private void reloadConfig() throws IOException {
310323
}
311324
try {
312325
String scriptContent = java.nio.file.Files.readString(script.toPath(), StandardCharsets.UTF_8);
313-
var compiledScript = scriptEngine.compileScript(script,script.getName(),scriptContent);
314-
if(compiledScript == null) return;
326+
var compiledScript = scriptEngine.compileScript(script, script.getName(), scriptContent);
327+
if (compiledScript == null) return;
315328
this.scripts.add(compiledScript);
316329
} catch (IOException e) {
317330
log.error("Unable to load script file", e);
Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
package com.ghostchu.peerbanhelper.platform;
22

33
import com.ghostchu.peerbanhelper.platform.types.EcoQosAPI;
4+
import com.ghostchu.peerbanhelper.platform.types.MalwareScanner;
45
import org.jetbrains.annotations.NotNull;
6+
import org.jetbrains.annotations.Nullable;
57

68
public interface Platform {
7-
@NotNull EcoQosAPI getEcoQosAPI();
9+
@Nullable EcoQosAPI getEcoQosAPI();
10+
@Nullable MalwareScanner getMalwareScanner();
811
}

src/main/java/com/ghostchu/peerbanhelper/platform/WindowsEcoQosAPI.java

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,6 @@
66
import com.ghostchu.peerbanhelper.text.Lang;
77
import lombok.extern.slf4j.Slf4j;
88

9-
import java.util.Locale;
10-
119
import static com.ghostchu.peerbanhelper.text.TextManager.tlUI;
1210

1311
@Slf4j
@@ -23,10 +21,4 @@ public void apply() throws UnsupportedOperationException {
2321
log.info(tlUI(Lang.IN_ECOMODE_DESCRIPTION));
2422
ExchangeMap.GUI_DISPLAY_FLAGS.add(new ExchangeMap.DisplayFlag("eco-mode", 10, tlUI(Lang.IN_ECOMODE_SHORT)));
2523
}
26-
27-
@Override
28-
public boolean supported() {
29-
String os = System.getProperty("os.name").toLowerCase(Locale.ROOT);
30-
return os.startsWith("win");
31-
}
3224
}
Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,36 @@
11
package com.ghostchu.peerbanhelper.platform.impl.win32;
22

3+
import com.ghostchu.peerbanhelper.ExternalSwitch;
34
import com.ghostchu.peerbanhelper.platform.Platform;
45
import com.ghostchu.peerbanhelper.platform.WindowsEcoQosAPI;
6+
import com.ghostchu.peerbanhelper.platform.impl.win32.amsi.AmsiScanner;
57
import com.ghostchu.peerbanhelper.platform.types.EcoQosAPI;
6-
import org.jetbrains.annotations.NotNull;
8+
import com.ghostchu.peerbanhelper.platform.types.MalwareScanner;
9+
import lombok.extern.slf4j.Slf4j;
10+
import org.jetbrains.annotations.Nullable;
711

12+
@Slf4j
813
public class WindowsPlatform implements Platform {
914
private final EcoQosAPI ecoQosAPI = new WindowsEcoQosAPI();
15+
1016
@Override
11-
public @NotNull EcoQosAPI getEcoQosAPI() {
17+
public @Nullable EcoQosAPI getEcoQosAPI() {
18+
if (!ExternalSwitch.parseBoolean("pbh.platform.ecoqos-api", true)) {
19+
return null;
20+
}
1221
return ecoQosAPI;
1322
}
23+
24+
@Override
25+
public @Nullable MalwareScanner getMalwareScanner() {
26+
if (!ExternalSwitch.parseBoolean("pbh.platform.malware-scanner", true)) {
27+
return null;
28+
}
29+
try {
30+
return new AmsiScanner("PeerBanHelper");
31+
} catch (Exception e) {
32+
log.debug("AMSI Scanner is not available: {}", e.getMessage());
33+
return null;
34+
}
35+
}
1436
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package com.ghostchu.peerbanhelper.platform.impl.win32.amsi;
2+
3+
import com.sun.jna.Library;
4+
import com.sun.jna.Native;
5+
import com.sun.jna.Pointer;
6+
import com.sun.jna.WString;
7+
import com.sun.jna.ptr.PointerByReference;
8+
9+
public interface AmsiLib extends Library {
10+
AmsiLib INSTANCE = Native.load("amsi", AmsiLib.class);
11+
12+
// HRESULT AmsiInitialize(LPCWSTR appName, HAMSICONTEXT* amsiContext)
13+
int AmsiInitialize(String appName, PointerByReference amsiContext);
14+
15+
// void AmsiUninitialize(HAMSICONTEXT amsiContext)
16+
void AmsiUninitialize(Pointer amsiContext);
17+
18+
// HRESULT AmsiOpenSession(HAMSICONTEXT amsiContext, HAMSISESSION* amsiSession)
19+
int AmsiOpenSession(Pointer amsiContext, PointerByReference amsiSession);
20+
21+
// void AmsiCloseSession(HAMSICONTEXT amsiContext, HAMSISESSION amsiSession)
22+
void AmsiCloseSession(Pointer amsiContext, Pointer amsiSession);
23+
24+
// HRESULT AmsiScanString(HAMSICONTEXT amsiContext, LPCWSTR string, LPCWSTR contentName, HAMSISESSION amsiSession, AMSI_RESULT* result)
25+
int AmsiScanString(Pointer amsiContext, WString string, WString contentName, Pointer amsiSession, int[] result);
26+
27+
// HRESULT AmsiScanBuffer(HAMSICONTEXT amsiContext, Pointer buffer, ULONG length, LPCWSTR contentName, HAMSISESSION amsiSession, AMSI_RESULT* result)
28+
int AmsiScanBuffer(Pointer amsiContext, Pointer buffer, int length, WString contentName, Pointer amsiSession, int[] result);
29+
}

0 commit comments

Comments
 (0)