/** * 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.util; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.util.LinkedList; import java.util.List; import java.util.Random; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import de.rub.nds.tlsattacker.modifiablevariable.HoldsModifiableVariable; import de.rub.nds.tlsattacker.modifiablevariable.ModifiableVariable; import de.rub.nds.tlsattacker.modifiablevariable.ModifiableVariableProperty; import de.rub.nds.tlsattacker.modifiablevariable.util.ModifiableVariableAnalyzer; import de.rub.nds.tlsattacker.modifiablevariable.util.ModifiableVariableField; import de.rub.nds.tlsattacker.modifiablevariable.util.ModifiableVariableListHolder; import de.rub.nds.tlsattacker.tls.constants.ConnectionEnd; import de.rub.nds.tlsattacker.tls.exceptions.ModificationException; import de.rub.nds.tlsattacker.tls.protocol.ModifiableVariableHolder; import de.rub.nds.tlsattacker.tls.protocol.ProtocolMessage; import de.rub.nds.tlsattacker.tls.protocol.application.ApplicationMessage; import de.rub.nds.tlsattacker.tls.protocol.ccs.ChangeCipherSpecMessage; import de.rub.nds.tlsattacker.tls.protocol.handshake.ClientHelloMessage; import de.rub.nds.tlsattacker.tls.protocol.handshake.FinishedMessage; import de.rub.nds.tlsattacker.tls.protocol.handshake.RSAClientKeyExchangeMessage; import de.rub.nds.tlsattacker.tls.protocol.handshake.ServerHelloDoneMessage; import de.rub.nds.tlsattacker.tls.protocol.handshake.ServerHelloMessage; import de.rub.nds.tlsattacker.tls.protocol.heartbeat.HeartbeatMessage; import de.rub.nds.tlsattacker.tls.record.Record; import de.rub.nds.tlsattacker.tls.workflow.WorkflowTrace; import de.rub.nds.tlsattacker.util.RandomHelper; import de.rub.nds.tlsattacker.util.ReflectionHelper; import de.rub.nds.tlsattacker.util.UnoptimizedDeepCopy; /** * @author Juraj Somorovsky <juraj.somorovsky@rub.de> */ public class FuzzingHelper { private static final Logger LOGGER = LogManager.getLogger(FuzzingHelper.class); public static final int MAX_MODIFICATION_COUNT = 5; private FuzzingHelper() { } public static boolean executeFuzzingUnit(int percentage) { int random = RandomHelper.getRandom().nextInt(100); return (percentage > random); } /** * Picks a random workflow message, picks a random variable and executes a * modification. In a case a pattern was used, it matches the picked * variable contains this pattern. * * @param workflow * @param connectionEnd * @param pattern */ public static void executeRandomModifiableVariableModification(WorkflowTrace workflow, ConnectionEnd connectionEnd, String pattern) { Field f = null; ModifiableVariableHolder holder = null; while (f == null) { holder = getRandomModifiableVariableHolder(workflow, connectionEnd); Field randomField = holder.getRandomModifiableVariableField(); if (pattern == null || randomField.getName().toLowerCase().contains(pattern)) { f = randomField; } } LOGGER.debug("Executing random variable modification on field {} in {}", f, holder); executeModifiableVariableModification(holder, f); } /** * Picks a random workflow message, picks a random variable and executes a * modification. * * @param workflow * @param connectionEnd * @param allowedTypes * @param allowedFormats * @param whitelistRegex * @param blacklistRegex */ public static void executeRandomModifiableVariableModification(WorkflowTrace workflow, ConnectionEnd connectionEnd, List<ModifiableVariableProperty.Type> allowedTypes, List<ModifiableVariableProperty.Format> allowedFormats, String whitelistRegex, String blacklistRegex) { Field f = null; ModifiableVariableHolder holder = null; if (workflow.getClientMessages().isEmpty()) { return; } while (f == null) { holder = getRandomModifiableVariableHolder(workflow, connectionEnd); Field randomField = holder.getRandomModifiableVariableField(); if (isModifiableVariableModificationAllowed(randomField, allowedTypes, allowedFormats, whitelistRegex, blacklistRegex)) { f = randomField; } } LOGGER.debug("Executing random variable modification on field {}", f); executeModifiableVariableModification(holder, f); } public static boolean isModifiableVariableModificationAllowed(Field randomField, List<ModifiableVariableProperty.Type> allowedTypes, List<ModifiableVariableProperty.Format> allowedFormats, String whitelistRegex, String blacklistRegex) { ModifiableVariableProperty property = randomField.getAnnotation(ModifiableVariableProperty.class); if (property != null) { if ((allowedTypes == null || allowedTypes.contains(property.type())) && (allowedFormats == null || allowedFormats.contains(property.format())) && (whitelistRegex == null || randomField.getName().matches(whitelistRegex)) && (blacklistRegex == null || !randomField.getName().matches(blacklistRegex))) { return true; } } return false; } public static boolean isModifiableVariableFromMyPeer(ModifiableVariableField field, ConnectionEnd peer) { if (field.getObject() instanceof ProtocolMessage) { System.out.print("test"); } return false; } /** * Picks a random modifiable variable and executes a random modification on * this variable. * * @param object */ public static void executeRandomModifiableVariableModification(ModifiableVariableHolder object) { Field field = object.getRandomModifiableVariableField(); executeModifiableVariableModification(object, field); } /** * Executes a random modification on a defined field. Source: * http://stackoverflow.com/questions/1868333/how-can-i-determine-the * -type-of-a-generic-field-in-java * * @param object * @param field */ public static void executeModifiableVariableModification(ModifiableVariableHolder object, Field field) { try { // Type type = field.getGenericType(); // ParameterizedType pType = (ParameterizedType) type; // String typeString = ((Class) // pType.getActualTypeArguments()[0]).getSimpleName(); // LOGGER.debug("Modifying field {} of type {} from the following class: {} ", // field.getName(), typeString, // object.getClass().getSimpleName()); field.setAccessible(true); ModifiableVariable mv = (ModifiableVariable) field.get(object); if (mv == null) { mv = (ModifiableVariable) field.getType().getDeclaredConstructors()[0].newInstance(); } mv.createRandomModificationAtRuntime(); LOGGER.debug("Modifying field {} of type {} from the following class: {} ", field.getName(), field.getType(), object.getClass().getSimpleName()); field.set(object, mv); } catch (IllegalAccessException | IllegalArgumentException | InstantiationException | InvocationTargetException ex) { throw new ModificationException(ex.getLocalizedMessage(), ex); } } /** * Returns a list of all Modifiable variable holders from the workflow * trace. Currently, it returns all protocol messages. * * @param trace * @return */ public static List<ModifiableVariableHolder> getModifiableVariableHolders(WorkflowTrace trace) { List<ProtocolMessage> protocolMessages = trace.getProtocolMessages(); List<ModifiableVariableHolder> result = new LinkedList<>(); for (ProtocolMessage pm : protocolMessages) { result.addAll(pm.getAllModifiableVariableHolders()); } return result; } /** * Returns a list of all Modifiable variable holders from the workflow * trace, for a specific message issuer. * * @param trace * @param messageIssuer * @return */ public static List<ModifiableVariableHolder> getModifiableVariableHolders(WorkflowTrace trace, ConnectionEnd messageIssuer) { List<ProtocolMessage> protocolMessages = trace.getProtocolMessages(); List<ModifiableVariableHolder> result = new LinkedList<>(); for (ProtocolMessage pm : protocolMessages) { if (pm.getMessageIssuer() == messageIssuer) { result.addAll(pm.getAllModifiableVariableHolders()); } } return result; } /** * Returns a random Modifiable variable holder from the workflow trace * * @param trace * @param messageIssuer * @return */ public static ModifiableVariableHolder getRandomModifiableVariableHolder(WorkflowTrace trace, ConnectionEnd messageIssuer) { List<ModifiableVariableHolder> holders = getModifiableVariableHolders(trace, messageIssuer); int randomHolder = RandomHelper.getRandom().nextInt(holders.size()); return holders.get(randomHolder); } /** * Adds random records to the workflow trace * * @param trace * @param messageIssuer */ public static void addRecordsAtRandom(WorkflowTrace trace, ConnectionEnd messageIssuer) { List<ProtocolMessage> protocolMessages = trace.getProtocolMessages(); Random random = RandomHelper.getRandom(); int recordsNumber = random.nextInt(4); int i = 0; while (recordsNumber > 0 && i < MAX_MODIFICATION_COUNT) { i++; int randomPM = random.nextInt(protocolMessages.size()); ProtocolMessage pm = protocolMessages.get(randomPM); if (pm.getMessageIssuer() == messageIssuer) { Record r = new Record(); r.setMaxRecordLengthConfig(random.nextInt(50)); pm.addRecord(r); recordsNumber--; LOGGER.debug("Adding a new record to {}", pm.getClass()); } } } public static void removeRandomProtocolMessage(WorkflowTrace trace, ConnectionEnd messageIssuer) { List<ProtocolMessage> protocolMessages = trace.getProtocolMessages(); Random random = RandomHelper.getRandom(); int i = 0; while (i < MAX_MODIFICATION_COUNT) { i++; int position = random.nextInt(protocolMessages.size()); if (trace.getProtocolMessages().get(position).getMessageIssuer() == messageIssuer) { trace.getProtocolMessages().remove(position); return; } } } public static void addRandomProtocolMessage(WorkflowTrace trace, ConnectionEnd messageIssuer) { List<ProtocolMessage> protocolMessages = trace.getProtocolMessages(); Random random = RandomHelper.getRandom(); int position = random.nextInt(protocolMessages.size()); int protocolMessageType = random.nextInt(8); ProtocolMessage pm = null; switch (protocolMessageType) { case 0: pm = new ClientHelloMessage(messageIssuer); break; case 1: pm = new RSAClientKeyExchangeMessage(messageIssuer); break; case 2: pm = new ChangeCipherSpecMessage(messageIssuer); break; case 3: pm = new FinishedMessage(messageIssuer); break; case 4: pm = new ApplicationMessage(messageIssuer); break; case 5: pm = new HeartbeatMessage(messageIssuer); break; case 6: pm = new ServerHelloMessage(messageIssuer); break; case 7: pm = new ServerHelloDoneMessage(messageIssuer); break; } if (pm != null) { protocolMessages.add(position, pm); } } public static void duplicateRandomProtocolMessage(WorkflowTrace trace, ConnectionEnd messageIssuer) { List<ProtocolMessage> protocolMessages = trace.getProtocolMessages(); Random random = RandomHelper.getRandom(); int insertPosition = random.nextInt(protocolMessages.size()); ProtocolMessage pm = null; while (pm == null) { int takePosition = random.nextInt(protocolMessages.size()); if (protocolMessages.get(takePosition).getMessageIssuer() == messageIssuer) { pm = (ProtocolMessage) UnoptimizedDeepCopy.copy(protocolMessages.get(takePosition)); } } protocolMessages.add(insertPosition, pm); LOGGER.debug("Duplicating {} \n and inserting it at position {}", pm.getClass(), insertPosition); } public static ProtocolMessage getRandomProtocolMessage(WorkflowTrace trace, ConnectionEnd messageIssuer) { List<ProtocolMessage> protocolMessages = trace.getProtocolMessages(); Random random = RandomHelper.getRandom(); ProtocolMessage pm = null; while (true) { int position = random.nextInt(protocolMessages.size()); if (protocolMessages.get(position).getMessageIssuer() == messageIssuer) { return protocolMessages.get(position); } } } /** * Returns a list of all ModifiableVariableFields (object-field * representations) for a given object. * * @param object * @param myPeer * @return */ public static List<ModifiableVariableField> getAllModifiableVariableFieldsRecursively(Object object, ConnectionEnd myPeer) { List<ModifiableVariableListHolder> holders = getAllModifiableVariableHoldersRecursively(object, myPeer); List<ModifiableVariableField> fields = new LinkedList<>(); for (ModifiableVariableListHolder holder : holders) { if (!(holder.getObject() instanceof ProtocolMessage) || ((ProtocolMessage) holder.getObject()).getMessageIssuer() == myPeer) { for (Field f : holder.getFields()) { fields.add(new ModifiableVariableField(holder.getObject(), f)); } } } return fields; } /** * Returns a list of all the modifiable variable holders in the object, * including this instance. * * @param object * @param myPeer * @return */ public static List<ModifiableVariableListHolder> getAllModifiableVariableHoldersRecursively(Object object, ConnectionEnd myPeer) { List<ModifiableVariableListHolder> holders = new LinkedList<>(); List<Field> modFields = ModifiableVariableAnalyzer.getAllModifiableVariableFields(object); if (!modFields.isEmpty()) { holders.add(new ModifiableVariableListHolder(object, modFields)); } List<Field> allFields = ReflectionHelper.getFieldsUpTo(object.getClass(), null, null); for (Field f : allFields) { try { HoldsModifiableVariable holdsVariable = f.getAnnotation(HoldsModifiableVariable.class); f.setAccessible(true); Object possibleHolder = f.get(object); if (possibleHolder != null && holdsVariable != null) { if (possibleHolder instanceof List) { holders.addAll(ModifiableVariableAnalyzer .getAllModifiableVariableHoldersFromList((List) possibleHolder)); } else if (possibleHolder.getClass().isArray()) { holders.addAll(ModifiableVariableAnalyzer .getAllModifiableVariableHoldersFromArray((Object[]) possibleHolder)); } else { if (ProtocolMessage.class.isInstance(object) && ((ProtocolMessage) possibleHolder).getMessageIssuer() != myPeer) { LOGGER.info("Skipping {}", possibleHolder.getClass()); } else { holders.addAll(ModifiableVariableAnalyzer .getAllModifiableVariableHoldersRecursively(possibleHolder)); } } } } catch (IllegalAccessException | IllegalArgumentException ex) { LOGGER.info("Accessing field {} of type {} not possible: {}", f.getName(), f.getType(), ex.toString()); } } return holders; } }