Skip to content

[AI] Fix #78: Add TCP segmentation support for TLS records #228

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
import de.rub.nds.tlsattacker.core.record.preparator.RecordPreparator;
import de.rub.nds.tlsattacker.core.record.serializer.RecordSerializer;
import de.rub.nds.tlsattacker.core.state.Context;
import de.rub.nds.tlsattacker.core.tcp.TcpSegmentConfiguration;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
Expand Down Expand Up @@ -110,7 +111,15 @@ public LayerProcessingResult<Record> sendConfiguration() throws IOException {
RecordSerializer serializer = record.getRecordSerializer();
byte[] serializedMessage = serializer.serialize();
record.setCompleteRecordBytes(serializedMessage);
getLowerLayer().sendData(null, record.getCompleteRecordBytes().getValue());

// Check if TCP segmentation is configured for this record
if (record.getTcpSegmentConfiguration() != null) {
sendWithTcpSegmentation(
record.getCompleteRecordBytes().getValue(),
record.getTcpSegmentConfiguration());
} else {
getLowerLayer().sendData(null, record.getCompleteRecordBytes().getValue());
}
addProducedContainer(record);
}
}
Expand Down Expand Up @@ -299,6 +308,50 @@ public RecordCipher getDecryptorCipher() {
return decryptor.getRecordMostRecentCipher();
}

/**
* Sends data with TCP segmentation according to the provided configuration.
*
* @param data The data to send
* @param segmentConfig The TCP segmentation configuration
* @throws IOException If the data cannot be sent
*/
private void sendWithTcpSegmentation(byte[] data, TcpSegmentConfiguration segmentConfig)
throws IOException {
if (segmentConfig.getSegments() == null || segmentConfig.getSegments().isEmpty()) {
// No segments defined, send as single packet
getLowerLayer().sendData(null, data);
return;
}

for (TcpSegmentConfiguration.TcpSegment segment : segmentConfig.getSegments()) {
int offset = segment.getOffset() != null ? segment.getOffset() : 0;
int length = segment.getLength() != null ? segment.getLength() : data.length - offset;

// Ensure we don't go out of bounds
if (offset >= data.length) {
LOGGER.warn("TCP segment offset {} exceeds data length {}", offset, data.length);
continue;
}

int actualLength = Math.min(length, data.length - offset);
byte[] segmentData = new byte[actualLength];
System.arraycopy(data, offset, segmentData, 0, actualLength);

// Send the segment
getLowerLayer().sendData(null, segmentData);

// Add delay between segments if configured
if (segmentConfig.getSegmentDelay() != null && segmentConfig.getSegmentDelay() > 0) {
try {
Thread.sleep(segmentConfig.getSegmentDelay());
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
LOGGER.warn("TCP segment delay interrupted", e);
}
}
}
}

public void updateCompressor() {
compressor.setMethod(context.getChooser().getSelectedCompressionMethod());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,10 @@
import de.rub.nds.tlsattacker.core.record.preparator.RecordPreparator;
import de.rub.nds.tlsattacker.core.record.serializer.RecordSerializer;
import de.rub.nds.tlsattacker.core.state.Context;
import de.rub.nds.tlsattacker.core.tcp.TcpSegmentConfiguration;
import jakarta.xml.bind.annotation.XmlAccessType;
import jakarta.xml.bind.annotation.XmlAccessorType;
import jakarta.xml.bind.annotation.XmlElement;
import jakarta.xml.bind.annotation.XmlRootElement;
import jakarta.xml.bind.annotation.XmlTransient;
import java.io.InputStream;
Expand Down Expand Up @@ -92,6 +94,10 @@ public class Record extends ModifiableVariableHolder implements DataContainer {

private RecordCryptoComputations computations;

/** TCP segmentation configuration for this record */
@XmlElement(name = "tcpSegmentation")
private TcpSegmentConfiguration tcpSegmentConfiguration;

public Record(Config config) {
this.maxRecordLengthConfig = config.getDefaultMaxRecordData();
}
Expand Down Expand Up @@ -313,6 +319,14 @@ public void prepareComputations() {
}
}

public TcpSegmentConfiguration getTcpSegmentConfiguration() {
return tcpSegmentConfiguration;
}

public void setTcpSegmentConfiguration(TcpSegmentConfiguration tcpSegmentConfiguration) {
this.tcpSegmentConfiguration = tcpSegmentConfiguration;
}

@Override
public String toString() {

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
/*
* TLS-Attacker - A Modular Penetration Testing Framework for TLS
*
* Copyright 2014-2023 Ruhr University Bochum, Paderborn University, Technology Innovation Institute, and Hackmanit GmbH
*
* Licensed under Apache License, Version 2.0
* http://www.apache.org/licenses/LICENSE-2.0.txt
*/
package de.rub.nds.tlsattacker.core.tcp;

import jakarta.xml.bind.annotation.XmlAccessType;
import jakarta.xml.bind.annotation.XmlAccessorType;
import jakarta.xml.bind.annotation.XmlElement;
import jakarta.xml.bind.annotation.XmlRootElement;
import java.io.Serializable;
import java.util.LinkedList;
import java.util.List;

/**
* Configuration for TCP segmentation. Allows specifying how data should be split across TCP
* segments.
*/
@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
public class TcpSegmentConfiguration implements Serializable {

@XmlElement(name = "segment")
private List<TcpSegment> segments = new LinkedList<>();

/** Default TCP segment delay in milliseconds between segments */
@XmlElement(name = "segmentDelay")
private Integer segmentDelay = 10;

public TcpSegmentConfiguration() {}

public TcpSegmentConfiguration(List<TcpSegment> segments) {
this.segments = segments;
}

public List<TcpSegment> getSegments() {
return segments;
}

public void setSegments(List<TcpSegment> segments) {
this.segments = segments;
}

public void addSegment(TcpSegment segment) {
if (segments == null) {
segments = new LinkedList<>();
}
segments.add(segment);
}

public Integer getSegmentDelay() {
return segmentDelay;
}

public void setSegmentDelay(Integer segmentDelay) {
this.segmentDelay = segmentDelay;
}

/** Represents a single TCP segment with offset and length */
@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
public static class TcpSegment implements Serializable {

@XmlElement(name = "offset")
private Integer offset;

@XmlElement(name = "length")
private Integer length;

public TcpSegment() {}

public TcpSegment(Integer offset, Integer length) {
this.offset = offset;
this.length = length;
}

public Integer getOffset() {
return offset;
}

public void setOffset(Integer offset) {
this.offset = offset;
}

public Integer getLength() {
return length;
}

public void setLength(Integer length) {
this.length = length;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
/*
* TLS-Attacker - A Modular Penetration Testing Framework for TLS
*
* Copyright 2014-2023 Ruhr University Bochum, Paderborn University, Technology Innovation Institute, and Hackmanit GmbH
*
* Licensed under Apache License, Version 2.0
* http://www.apache.org/licenses/LICENSE-2.0.txt
*/
package de.rub.nds.tlsattacker.core.tcp;

import static org.junit.jupiter.api.Assertions.*;

import de.rub.nds.tlsattacker.core.tcp.TcpSegmentConfiguration.TcpSegment;
import jakarta.xml.bind.JAXBContext;
import jakarta.xml.bind.JAXBException;
import jakarta.xml.bind.Marshaller;
import jakarta.xml.bind.Unmarshaller;
import java.io.StringReader;
import java.io.StringWriter;
import org.junit.jupiter.api.Test;

public class TcpSegmentConfigurationTest {

@Test
public void testTcpSegmentConfiguration() {
TcpSegmentConfiguration config = new TcpSegmentConfiguration();

// Test adding segments
config.addSegment(new TcpSegment(0, 3));
config.addSegment(new TcpSegment(3, 10));
config.setSegmentDelay(15);

assertEquals(2, config.getSegments().size());
assertEquals(0, config.getSegments().get(0).getOffset().intValue());
assertEquals(3, config.getSegments().get(0).getLength().intValue());
assertEquals(3, config.getSegments().get(1).getOffset().intValue());
assertEquals(10, config.getSegments().get(1).getLength().intValue());
assertEquals(15, config.getSegmentDelay().intValue());
}

@Test
public void testTcpSegmentConfigurationSerialization() throws JAXBException {
// Create configuration
TcpSegmentConfiguration config = new TcpSegmentConfiguration();
config.addSegment(new TcpSegment(0, 5));
config.addSegment(new TcpSegment(5, null));
config.setSegmentDelay(20);

// Serialize to XML
JAXBContext context = JAXBContext.newInstance(TcpSegmentConfiguration.class);
Marshaller marshaller = context.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);

StringWriter writer = new StringWriter();
marshaller.marshal(config, writer);
String xml = writer.toString();

// Verify XML contains expected elements
assertTrue(xml.contains("<tcpSegmentConfiguration>"));
assertTrue(xml.contains("<segment>"));
assertTrue(xml.contains("<offset>0</offset>"));
assertTrue(xml.contains("<length>5</length>"));
assertTrue(xml.contains("<offset>5</offset>"));
assertTrue(xml.contains("<segmentDelay>20</segmentDelay>"));

// Deserialize from XML
Unmarshaller unmarshaller = context.createUnmarshaller();
TcpSegmentConfiguration deserialized =
(TcpSegmentConfiguration) unmarshaller.unmarshal(new StringReader(xml));

// Verify deserialized object
assertNotNull(deserialized);
assertEquals(2, deserialized.getSegments().size());
assertEquals(0, deserialized.getSegments().get(0).getOffset().intValue());
assertEquals(5, deserialized.getSegments().get(0).getLength().intValue());
assertEquals(5, deserialized.getSegments().get(1).getOffset().intValue());
assertNull(deserialized.getSegments().get(1).getLength());
assertEquals(20, deserialized.getSegmentDelay().intValue());
}

@Test
public void testEmptyConfiguration() {
TcpSegmentConfiguration config = new TcpSegmentConfiguration();
assertNotNull(config.getSegments());
assertTrue(config.getSegments().isEmpty());
assertEquals(10, config.getSegmentDelay().intValue()); // Default value
}

@Test
public void testTcpSegment() {
TcpSegment segment = new TcpSegment(10, 20);
assertEquals(10, segment.getOffset().intValue());
assertEquals(20, segment.getLength().intValue());

segment.setOffset(15);
segment.setLength(25);
assertEquals(15, segment.getOffset().intValue());
assertEquals(25, segment.getLength().intValue());
}
}
Loading