/** * TLS-Attacker - A Modular Penetration Testing Framework for TLS * * Copyright 2014-2016 Ruhr University Bochum / Hackmanit GmbH * * Licensed under Apache License 2.0 * http://www.apache.org/licenses/LICENSE-2.0 */ package de.rub.nds.tlsattacker.fuzzer.impl; import static de.rub.nds.tlsattacker.fuzzer.util.FuzzingHelper.MAX_MODIFICATION_COUNT; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.Calendar; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Random; import java.util.Set; import javax.xml.bind.JAXBException; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.bouncycastle.asn1.x509.Certificate; import de.rub.nds.tlsattacker.attacks.config.BleichenbacherCommandConfig; import de.rub.nds.tlsattacker.attacks.config.InvalidCurveAttackCommandConfig; import de.rub.nds.tlsattacker.attacks.config.PaddingOracleCommandConfig; import de.rub.nds.tlsattacker.attacks.config.PoodleCommandConfig; import de.rub.nds.tlsattacker.attacks.impl.BleichenbacherAttack; import de.rub.nds.tlsattacker.attacks.impl.InvalidCurveAttack; import de.rub.nds.tlsattacker.attacks.impl.PaddingOracleAttack; import de.rub.nds.tlsattacker.attacks.impl.PoodleAttack; import de.rub.nds.tlsattacker.fuzzer.config.SimpleFuzzerConfig; import de.rub.nds.tlsattacker.fuzzer.util.FuzzingHelper; import de.rub.nds.tlsattacker.modifiablevariable.util.ModifiableVariableAnalyzer; import de.rub.nds.tlsattacker.modifiablevariable.util.ModifiableVariableField; import de.rub.nds.tlsattacker.tls.Attacker; import de.rub.nds.tlsattacker.tls.config.CommandConfig; import de.rub.nds.tlsattacker.tls.config.ConfigHandler; import de.rub.nds.tlsattacker.tls.config.ConfigHandlerFactory; import de.rub.nds.tlsattacker.tls.config.GeneralConfig; import de.rub.nds.tlsattacker.tls.config.WorkflowTraceSerializer; import de.rub.nds.tlsattacker.tls.constants.ConnectionEnd; import de.rub.nds.tlsattacker.tls.exceptions.ConfigurationException; import de.rub.nds.tlsattacker.tls.exceptions.WorkflowExecutionException; import de.rub.nds.tlsattacker.tls.protocol.ModifiableVariableHolder; import de.rub.nds.tlsattacker.tls.util.LogLevel; import de.rub.nds.tlsattacker.tls.workflow.TlsContext; import de.rub.nds.tlsattacker.tls.workflow.TlsContextAnalyzer; import de.rub.nds.tlsattacker.tls.workflow.WorkflowConfigurationFactory; import de.rub.nds.tlsattacker.tls.workflow.WorkflowExecutor; import de.rub.nds.tlsattacker.tls.workflow.WorkflowTrace; import de.rub.nds.tlsattacker.transport.TransportHandler; import de.rub.nds.tlsattacker.util.ServerStartCommandExecutor; import de.rub.nds.tlsattacker.util.UnoptimizedDeepCopy; /** * * @author Juraj Somorovsky <juraj.somorovsky@rub.de> */ public class SimpleFuzzer extends Fuzzer { public static Logger LOGGER = LogManager.getLogger(SimpleFuzzer.class); private final SimpleFuzzerConfig fuzzerConfig; private String fuzzingName = ""; private boolean interruptFuzzing; private Certificate certificate; private final List<WorkflowTrace> validWorkflowTraces; private ConfigHandler configHandler; private ServerStartCommandExecutor sce; private final Set<String> variablesWithoutHandshakeInfluence; private long totalProtocolFlows = 0; public SimpleFuzzer(SimpleFuzzerConfig fuzzerConfig, GeneralConfig generalConfig) { super(generalConfig); this.fuzzerConfig = fuzzerConfig; validWorkflowTraces = new LinkedList<>(); variablesWithoutHandshakeInfluence = new HashSet<>(); } @Override public void startFuzzer() { configHandler = ConfigHandlerFactory.createConfigHandler("client"); configHandler.initialize(generalConfig); String logFolder = initializeLogFolder(); try { if (fuzzerConfig.containsServerCommand()) { sce = startTestServer(fuzzerConfig.getResultingServerCommand()); } try { gatherWorkflowsAndCertificate(); } catch (ConfigurationException ex) { LOGGER.error(ex.getLocalizedMessage()); } startFuzzing(logFolder); } catch (ConfigurationException | JAXBException | IOException | IllegalAccessException | IllegalArgumentException ex) { // throw new ConfigurationException(ex.getLocalizedMessage(), ex); LOGGER.error(ex.getLocalizedMessage(), ex); } finally { if (fuzzerConfig.containsServerCommand() && !sce.isServerTerminated()) { sce.terminateServer(); // LOGGER.info(sce.getServerOutputString()); // LOGGER.info(sce.getServerErrorOutputString()); } } } private void gatherWorkflowsAndCertificate() { LOGGER.info("Gathering workflows from {}", fuzzerConfig.getWorkflowFolder()); File folder = new File(fuzzerConfig.getWorkflowFolder()); File[] listOfFiles = folder.listFiles(); if(null == listOfFiles) { listOfFiles = new File[0]; } List<File> xmlFiles = new LinkedList<>(); for (File file : listOfFiles) { if (file.isFile() && file.getName().endsWith(".xml")) { xmlFiles.add(file); } } for (File file : xmlFiles) { try { LOGGER.log(LogLevel.CONSOLE_OUTPUT, "Executing the TLS workflow according to {}", file.getPath()); fuzzerConfig.setWorkflowInput(file.getAbsolutePath()); TransportHandler transportHandler = configHandler.initializeTransportHandler(fuzzerConfig); TlsContext tlsContext = configHandler.initializeTlsContext(fuzzerConfig); WorkflowTrace tmpTrace = (WorkflowTrace) UnoptimizedDeepCopy.copy(tlsContext.getWorkflowTrace()); tmpTrace.setName(file.getName()); WorkflowExecutor workflowExecutor = configHandler.initializeWorkflowExecutor(transportHandler, tlsContext); workflowExecutor.executeWorkflow(); transportHandler.closeConnection(); if (TlsContextAnalyzer.containsFullWorkflow(tlsContext)) { LOGGER.log(LogLevel.CONSOLE_OUTPUT, "Successfully executed {}", file.getPath()); if (certificate == null) { certificate = tlsContext.getServerCertificate(); } validWorkflowTraces.add(tmpTrace); } } catch (WorkflowExecutionException | ConfigurationException ex) { LOGGER.log(LogLevel.CONSOLE_OUTPUT, "Not possible to execute a correct workflow", ex.getLocalizedMessage()); LOGGER.debug(ex); } } } private void startFuzzing(String logFolder) throws IOException, ConfigurationException, JAXBException, IllegalAccessException, IllegalArgumentException { LOGGER.log(LogLevel.CONSOLE_OUTPUT, "Starting fuzzing {}", fuzzingName); if (fuzzerConfig.isStage1()) { LOGGER.log(LogLevel.CONSOLE_OUTPUT, "Starting stage 1: crypto fuzzing "); startCryptoFuzzing(); } if (fuzzerConfig.isStage2()) { LOGGER.log(LogLevel.CONSOLE_OUTPUT, "Starting stage 2: tls fuzzing for boundary violations"); phase1(logFolder); LOGGER.log(LogLevel.CONSOLE_OUTPUT, "The following variables do not influence handshake (check manually for false-positives): {} ", variablesWithoutHandshakeInfluence); LOGGER.log(LogLevel.CONSOLE_OUTPUT, "Phase 1 is over, starting phase 2"); phase23(2, logFolder); LOGGER.log(LogLevel.CONSOLE_OUTPUT, "Phase 2 is over, starting phase 3"); phase23(3, logFolder); LOGGER.log(LogLevel.CONSOLE_OUTPUT, "Phase 3 finished"); LOGGER.log(LogLevel.CONSOLE_OUTPUT, "Total protocol flows: {}", totalProtocolFlows); } } private void startCryptoFuzzing() { Attacker<? extends CommandConfig> attacker; try { BleichenbacherCommandConfig bb = new BleichenbacherCommandConfig(); bb.setConnect(fuzzerConfig.getConnect()); attacker = new BleichenbacherAttack(bb); attacker.executeAttack(configHandler); InvalidCurveAttackCommandConfig icea = new InvalidCurveAttackCommandConfig(); icea.setConnect(fuzzerConfig.getConnect()); attacker = new InvalidCurveAttack(icea); attacker.executeAttack(configHandler); PoodleCommandConfig poodle = new PoodleCommandConfig(); poodle.setConnect(fuzzerConfig.getConnect()); attacker = new PoodleAttack(poodle); attacker.executeAttack(configHandler); PaddingOracleCommandConfig po = new PaddingOracleCommandConfig(); po.setConnect(fuzzerConfig.getConnect()); attacker = new PaddingOracleAttack(po); attacker.executeAttack(configHandler); } catch (Exception ex) { LOGGER.error(ex.getLocalizedMessage(), ex); } } /** * Use the TLS protocol flows and modify systematically specific variables. * The output of this phase is a list of variables that are most probably * not correctly checked by the server. * * @param logFolder * @throws IOException * @throws JAXBException */ private void phase1(String logFolder) throws IOException, JAXBException { for (WorkflowTrace trace : validWorkflowTraces) { List<ModifiableVariableField> fields = FuzzingHelper.getAllModifiableVariableFieldsRecursively(trace, ConnectionEnd.CLIENT); for (int fieldNumber = 0; fieldNumber < fields.size(); fieldNumber++) { if (!FuzzingHelper.isModifiableVariableModificationAllowed(fields.get(fieldNumber).getField(), fuzzerConfig.getModifiableVariableTypes(), fuzzerConfig.getModifiableVariableFormats(), fuzzerConfig.getModifiedVariableWhitelist(), fuzzerConfig.getModifiedVariableBlacklist())) { LOGGER.debug("skipping {}", fields.get(fieldNumber).getField().getName()); continue; } boolean influencesHandshake = false; String currentFieldName = ""; String currentMessageName = ""; for (int iter = 1; iter < fuzzerConfig.getVariableModificationIter() + 1; iter++) { TlsContext tlsContext = createTlsContext(trace); WorkflowTrace workflow = tlsContext.getWorkflowTrace(); List<ModifiableVariableField> currentFields = FuzzingHelper .getAllModifiableVariableFieldsRecursively(workflow, ConnectionEnd.CLIENT); ModifiableVariableField mvField = currentFields.get(fieldNumber); currentFieldName = mvField.getField().getName(); currentMessageName = mvField.getObject().getClass().getSimpleName(); FuzzingHelper.executeModifiableVariableModification((ModifiableVariableHolder) mvField.getObject(), mvField.getField()); TransportHandler transportHandler = configHandler.initializeTransportHandler(fuzzerConfig); WorkflowExecutor workflowExecutor = configHandler.initializeWorkflowExecutor(transportHandler, tlsContext); tlsContext.setServerCertificate(certificate); try { workflowExecutor.executeWorkflow(); } catch (WorkflowExecutionException ex) { LOGGER.debug(ex.getLocalizedMessage(), ex); } catch (Exception ex) { LOGGER.error(ex.getLocalizedMessage(), ex); } transportHandler.closeConnection(); if (!TlsContextAnalyzer.containsFullWorkflow(tlsContext)) { if (workflow.containsServerFinished() || workflow.getProtocolMessages().size() == 2) { influencesHandshake = true; } } // if the server was terminated, write file analyzeServerTerminationAndWriteFile(sce, logFolder, currentFieldName, trace.getName(), iter, workflow); // if the workflow contains an unexpected fields / // messages, // write them to a file analyzeResultingTlsContextAndWriteFile(tlsContext, logFolder, currentFieldName, trace.getName(), iter); totalProtocolFlows++; if (interruptFuzzing) { return; } } if (influencesHandshake) { variablesWithoutHandshakeInfluence.add(currentMessageName + "." + currentFieldName); } } } } private void phase23(int phase, String logFolder) throws IOException, JAXBException { long iter = 0; while (!interruptFuzzing && iter < fuzzerConfig.getRandomModificationIter()) { try { iter++; if (iter % 1000 == 0) { LOGGER.log(LogLevel.CONSOLE_OUTPUT, "Iteration {} in phase {}.", iter, phase); } WorkflowTrace validWorkflow = pickRandomTrace(); TlsContext tlsContext = createTlsContext(validWorkflow); WorkflowTrace workflow = tlsContext.getWorkflowTrace(); if (phase == 3) { executeProtocolModificationPhase(workflow, tlsContext.getMyConnectionEnd()); } addRandomRecords(workflow, ConnectionEnd.CLIENT); executeRandomFieldModifications(workflow, ConnectionEnd.CLIENT); TransportHandler transportHandler = configHandler.initializeTransportHandler(fuzzerConfig); WorkflowExecutor workflowExecutor = configHandler.initializeWorkflowExecutor(transportHandler, tlsContext); tlsContext.setServerCertificate(certificate); try { workflowExecutor.executeWorkflow(); } catch (WorkflowExecutionException ex) { LOGGER.debug(ex.getLocalizedMessage(), ex); } catch (Exception ex) { LOGGER.error(ex.getLocalizedMessage(), ex); } transportHandler.closeConnection(); // if the server was terminated, write file analyzeServerTerminationAndWriteFile(sce, logFolder, "", validWorkflow.getName(), iter, workflow); if (interruptFuzzing) { return; } // if the workflow contains an unexpected fields / // messages, // write them to a file analyzeResultingTlsContextAndWriteFile(tlsContext, logFolder, "", validWorkflow.getName(), iter); totalProtocolFlows++; } catch (ConfigurationException | IOException | JAXBException ex) { LOGGER.debug(ex.getLocalizedMessage(), ex); } } } private TlsContext createTlsContext(WorkflowTrace workflowTrace) { TlsContext tlsContext = new TlsContext(); WorkflowTrace tmpTrace = (WorkflowTrace) UnoptimizedDeepCopy.copy(workflowTrace); tlsContext.setWorkflowTrace(tmpTrace); WorkflowConfigurationFactory.initializeProtocolMessageOrder(tlsContext); return tlsContext; } private WorkflowTrace createClientTrace(WorkflowTrace workflowTrace) { WorkflowTrace tmpTrace = (WorkflowTrace) UnoptimizedDeepCopy.copy(workflowTrace); for (int i = tmpTrace.getProtocolMessages().size() - 1; i >= 0; i--) { if (tmpTrace.getProtocolMessages().get(i).getMessageIssuer() == ConnectionEnd.SERVER) { tmpTrace.getProtocolMessages().remove(i); } } return tmpTrace; } private WorkflowTrace pickRandomTrace() { Random r = new Random(); int pos = r.nextInt(validWorkflowTraces.size()); return validWorkflowTraces.get(pos); } private ModifiableVariableField pickRandomField(List<ModifiableVariableField> fields) { Random r = new Random(); while (true) { int fieldNumber = r.nextInt(fields.size()); if (FuzzingHelper.isModifiableVariableModificationAllowed(fields.get(fieldNumber).getField(), fuzzerConfig.getModifiableVariableTypes(), fuzzerConfig.getModifiableVariableFormats(), fuzzerConfig.getModifiedVariableWhitelist(), fuzzerConfig.getModifiedVariableBlacklist())) { return fields.get(fieldNumber); } } } private void startSystematicFuzzing(ConfigHandler configHandler, Certificate certificate, ServerStartCommandExecutor sce, String folder) throws JAXBException, IOException, IllegalAccessException, IllegalArgumentException { long phase = 0; interruptFuzzing = false; while (!interruptFuzzing) { try { TlsContext tmpTlsContext = configHandler.initializeTlsContext(fuzzerConfig); WorkflowTrace tmpWorkflow = tmpTlsContext.getWorkflowTrace(); // executeProtocolModification(tmpWorkflow, // tmpTlsContext.getMyConnectionEnd()); addRandomRecords(tmpWorkflow, ConnectionEnd.CLIENT); List<ModifiableVariableField> fields = ModifiableVariableAnalyzer .getAllModifiableVariableFieldsRecursively(tmpWorkflow); for (int fieldNumber = 0; fieldNumber < fields.size(); fieldNumber++) { if (!FuzzingHelper.isModifiableVariableModificationAllowed(fields.get(fieldNumber).getField(), fuzzerConfig.getModifiableVariableTypes(), fuzzerConfig.getModifiableVariableFormats(), fuzzerConfig.getModifiedVariableWhitelist(), fuzzerConfig.getModifiedVariableBlacklist())) { System.out.println("skipping " + fields.get(fieldNumber).getField().getName()); continue; } for (int i = 0; i < fuzzerConfig.getGenerateMessagePercentage(); i++) { if (fuzzerConfig.containsServerCommand() && fuzzerConfig.isRestartServerInEachInteration()) { sce = startTestServer(fuzzerConfig.getResultingServerCommand()); } TlsContext tlsContext = configHandler.initializeTlsContext(fuzzerConfig); WorkflowTrace workflow = (WorkflowTrace) UnoptimizedDeepCopy.copy(tmpWorkflow); tlsContext.setWorkflowTrace(workflow); List<ModifiableVariableField> currentFields = ModifiableVariableAnalyzer .getAllModifiableVariableFieldsRecursively(workflow); ModifiableVariableField mvField = currentFields.get(fieldNumber); FuzzingHelper.executeModifiableVariableModification( (ModifiableVariableHolder) mvField.getObject(), mvField.getField()); TransportHandler transportHandler = configHandler.initializeTransportHandler(fuzzerConfig); WorkflowExecutor workflowExecutor = configHandler.initializeWorkflowExecutor(transportHandler, tlsContext); tlsContext.setServerCertificate(certificate); try { workflowExecutor.executeWorkflow(); } catch (WorkflowExecutionException ex) { LOGGER.debug(ex.getLocalizedMessage(), ex); } catch (Exception ex) { LOGGER.error(ex.getLocalizedMessage(), ex); } transportHandler.closeConnection(); phase++; // if the server was terminated, terminate fuzzing analyzeServerTerminationAndWriteFile(sce, folder, "", "", phase, workflow); // if the workflow contains an unexpected fields / // messages, // write them to a file String fieldName = fields.get(fieldNumber).getField().getName(); analyzeResultingTlsContextAndWriteFile(tlsContext, folder, fieldName, "", phase); if (fuzzerConfig.isRestartServerInEachInteration()) { sce.terminateServer(); } } } } catch (ConfigurationException ex) { LOGGER.info(ex.getLocalizedMessage(), ex); } } } private void executeRandomFieldModifications(WorkflowTrace workflow, ConnectionEnd peer) { while (FuzzingHelper.executeFuzzingUnit(fuzzerConfig.getModifyVariablePercentage())) { FuzzingHelper.executeRandomModifiableVariableModification(workflow, peer, fuzzerConfig.getModifiableVariableTypes(), fuzzerConfig.getModifiableVariableFormats(), fuzzerConfig.getModifiedVariableWhitelist(), fuzzerConfig.getModifiedVariableBlacklist()); } } private void executeProtocolModificationPhase(WorkflowTrace workflow, ConnectionEnd myConnectionEnd) { int i = 0; while (i < MAX_MODIFICATION_COUNT && FuzzingHelper.executeFuzzingUnit(fuzzerConfig.getGenerateMessagePercentage())) { i++; FuzzingHelper.addRandomProtocolMessage(workflow, myConnectionEnd); } i = 0; while (i < MAX_MODIFICATION_COUNT && FuzzingHelper.executeFuzzingUnit(fuzzerConfig.getNotSendingMessagePercantage())) { i++; FuzzingHelper.removeRandomProtocolMessage(workflow, myConnectionEnd); } } private void addRandomRecords(WorkflowTrace workflow, ConnectionEnd myConnectionEnd) { int i = 0; while (i < MAX_MODIFICATION_COUNT && FuzzingHelper.executeFuzzingUnit(fuzzerConfig.getAddRecordPercentage())) { i++; FuzzingHelper.addRecordsAtRandom(workflow, myConnectionEnd); } } /** * Analyzes whether the server was terminated. If yes, the fuzzing is * stopped * * @param sce * @param folder * @param phase * @param workflow * @throws IOException * @throws JAXBException */ private void analyzeServerTerminationAndWriteFile(ServerStartCommandExecutor sce, String folder, String variableName, String workflowName, long phase, WorkflowTrace workflow) throws IOException, JAXBException { if (fuzzerConfig.containsServerCommand() && sce.isServerTerminated()) { interruptFuzzing = true; FileOutputStream fos = new FileOutputStream(folder + "/terminated" + variableName + workflowName + Long.toString(phase) + ".xml"); WorkflowTraceSerializer.write(fos, workflow); LOGGER.error(sce.getServerErrorOutputString()); LOGGER.error(sce.getServerOutputString()); } } /** * Analyzes the resulting workflow. It stores the workflow if the workflow * contains a missing message, or if it contains an unexpected new message, * or if it contains a modified message. * * @param tlsContext * @param folder * @param phase * @param fieldName * @throws JAXBException * @throws IOException */ private void analyzeResultingTlsContextAndWriteFile(TlsContext tlsContext, String folder, String fieldName, String workflowName, long phase) throws JAXBException, IOException { if (TlsContextAnalyzer.containsFullWorkflowWithMissingMessage(tlsContext) || TlsContextAnalyzer.containsServerFinishedWithModifiedHandshake(tlsContext) // || // TlsContextAnalyzer.containsAlertAfterMissingMessage(tlsContext) // == TlsContextAnalyzer.AnalyzerResponse.NO_ALERT || TlsContextAnalyzer.containsFullWorkflowWithModifiedMessage(tlsContext)) { String fileNameBasic = createFileName(folder, phase, tlsContext, fieldName); FileOutputStream fos = new FileOutputStream(fileNameBasic + workflowName + ".xml"); WorkflowTraceSerializer.write(fos, tlsContext.getWorkflowTrace()); } } private ServerStartCommandExecutor startTestServer(String serverCommand) throws IOException { sce = new ServerStartCommandExecutor(serverCommand); sce.startServer(); try { Thread.sleep(2500); } catch (InterruptedException ex) { } return sce; } private String initializeLogFolder() throws ConfigurationException { DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd-HH:mm:ss"); Calendar cal = Calendar.getInstance(); String folder = fuzzerConfig.getOutputFolder() + fuzzingName + dateFormat.format(cal.getTime()); File f = new File(folder); boolean created = f.mkdir(); if (!created) { throw new ConfigurationException("Unable to create a log folder " + folder); } return folder; } private String createFileName(String folder, long phase, TlsContext tlsContext, String fieldName) { String fileNameBasic = folder + "/" + Long.toString(phase); if (TlsContextAnalyzer.containsFullWorkflowWithMissingMessage(tlsContext)) { fileNameBasic += "-missing-"; } if (TlsContextAnalyzer.containsServerFinishedWithModifiedHandshake(tlsContext)) { fileNameBasic += "-modifiedhandshake-"; } if (TlsContextAnalyzer.containsFullWorkflowWithModifiedMessage(tlsContext)) { fileNameBasic += "-fullmod-"; } fileNameBasic += fieldName; return fileNameBasic; } public void setFuzzingName(String fuzzingName) { this.fuzzingName = fuzzingName; } }