Click here to Skip to main content
15,888,816 members
Articles / Programming Languages / C#

Steganography 17 - FTP Through a Proxy

Rate me:
Please Sign up or sign in to vote.
4.85/5 (29 votes)
16 Nov 2007CPOL10 min read 75.6K   934   65  
Transporting piggyback data in FTP transfers
using System;
using System.Collections.Generic;
using System.Text;
using System.Collections.ObjectModel;
using System.IO;

namespace SteganoFTP.Common
{
	public class Stego
	{
		public const int BUFFER_SIZE = 72; //2048;

		private Collection<byte> hide_waitingBuffer;
		private Collection<byte> extract_waitingBuffer;
		private int hide_blockSize;
		private int extract_blockSize;
		private int hide_CurrentMessageIndex;
		private byte[] hide_Message; // overall message
		private byte[] hide_MessagePart; // partial message fitting into current transmission
		private long hide_ExpectedStreamSize;
		private int extract_RemainingMessageSize;
		
		public long ExpectedStreamSize
		{
			get { return hide_ExpectedStreamSize; }
			set
			{
				this.hide_ExpectedStreamSize = value;

				if (hide_ExpectedStreamSize > -1)
				{
					// break the transmission into one block per message byte

					if (hide_ExpectedStreamSize < hide_Message.Length)
					{	// Message is longer than transmission.
						// send only [buffer.Length] message bytes
						// and wait for the next transmission
						hide_MessagePart = new byte[hide_ExpectedStreamSize];
					}
					else
					{	// Message is shorter than transmission.
						// send the whole text
						hide_MessagePart = new byte[hide_Message.Length];
					}

					this.hide_blockSize = (int)Math.Floor((float)hide_ExpectedStreamSize / hide_MessagePart.Length);
					Array.Copy(hide_Message, 0, hide_MessagePart, 0, hide_MessagePart.Length);
				}
			}
		}

		public bool Hide_HasWaitingBuffer
		{
			get
			{
				return this.hide_waitingBuffer.Count > 0;
			}
		}

		public bool Extract_HasWaitingBuffer
		{
			get
			{
				return this.extract_waitingBuffer.Count > 0;
			}
		}

		public byte[] Hide_WaitingBuffer
		{
			get
			{
				byte[] buffer = new byte[this.hide_waitingBuffer.Count];
				this.hide_waitingBuffer.CopyTo(buffer, 0);
				return buffer;
			}
		}

		public byte[] Extract_WaitingBuffer
		{
			get
			{
				byte[] buffer = new byte[this.extract_waitingBuffer.Count];
				this.extract_waitingBuffer.CopyTo(buffer, 0);
				return buffer;
			}
		}

		public Stego(byte[] messageToSend)
		{
			Initialize();			
			this.hide_Message = messageToSend;
		}

		public Stego()
		{
			Initialize();
			this.hide_Message = new byte[0];	
		}

		private void Initialize()
		{
			this.hide_waitingBuffer = new Collection<byte>();
			this.hide_blockSize = -1;
			this.hide_CurrentMessageIndex = 0;
			this.hide_ExpectedStreamSize = -1;
			this.extract_waitingBuffer = new Collection<byte>();
			this.extract_blockSize = -1;
			this.extract_RemainingMessageSize = -1;	
		}

		public long CalculateSizeAfterHiding(long sizeBeforeHiding)
		{
			long sizeAfterHiding;

			if (sizeBeforeHiding < hide_Message.Length)
			{	// Message is longer than transmission.
				// send only [buffer.Length] message bytes
				// and wait for the next transmission
				sizeAfterHiding = sizeBeforeHiding * 2;
			}
			else
			{	// Message is shorter than transmission.
				// send the whole file
				sizeAfterHiding = sizeBeforeHiding + hide_Message.Length;
			}

			// every streams contains 8 bytes for block/message size
			sizeAfterHiding += 8;
			
			return sizeAfterHiding;
		}

		/// <summary>
		/// Inserts data from a file into a binary buffer.
		/// </summary>
		/// <param name="buffer">Buffer.</param>
		/// <param name="size">Size of the buffer.</param>
		/// <returns>Buffer with hidden data.</returns>
		public byte[] Hide(byte[] buffer, int size, long remainingStreamSize)
		{
			if (hide_ExpectedStreamSize < 0)
			{	// Up-/Download size is not known yet.
				// queue the buffer and wait
				foreach (byte b in buffer)
				{
					hide_waitingBuffer.Add(b);
				}
				return null;
			}

			byte[] returnArray = null;

			// copy buffers to collection
			Collection<byte> transmissionBuffer = new Collection<byte>();
			foreach (byte b in hide_waitingBuffer)
			{
				transmissionBuffer.Add(b);
			}
			for (int n = 0; n < size; n++)
			{
				transmissionBuffer.Add(buffer[n]);
			}
			
			if (transmissionBuffer.Count < hide_blockSize)
			{	// The buffer is too small for a block.
				// wait until the socket received more data
				hide_waitingBuffer = transmissionBuffer;
			}
			else
			{
				hide_waitingBuffer.Clear();

				// transmission must be an integer number of blocks
				int countBlocksInCurrentBuffer = (int)Math.Floor((float)transmissionBuffer.Count / hide_blockSize);
				int maxTransmissionSize = countBlocksInCurrentBuffer * hide_blockSize;
				// move the unused rest of the current buffer to the waiting collection
				MoveEndToCollection(transmissionBuffer, hide_waitingBuffer, maxTransmissionSize);

				int insertAtIndex = 0;

				if (hide_CurrentMessageIndex == 0)
				{	// sending first buffer
					// append [blockSize] and [messageLength] to buffer
					PrependInt32(hide_blockSize, transmissionBuffer);
					PrependInt32(hide_MessagePart.Length, transmissionBuffer);
					insertAtIndex = 8;
				}

				// insert message into carrier
				for(int blockIndex=0; blockIndex<countBlocksInCurrentBuffer; blockIndex++)
				{
					if (hide_CurrentMessageIndex == hide_MessagePart.Length)
					{	// end of message
						break;
					}

					transmissionBuffer.Insert(
						insertAtIndex,
						hide_Message[hide_CurrentMessageIndex]);
					// proceed with next part
					hide_CurrentMessageIndex++;
					// each insertion adds 1 byte, so the next insert position is '+n'
					insertAtIndex += hide_blockSize + 1;
				}

				// return new transmission buffer
				byte[] newBuffer = new byte[transmissionBuffer.Count];
				transmissionBuffer.CopyTo(newBuffer, 0);
				returnArray = newBuffer;
			}

			return returnArray;
		}

		/// <summary>
		/// Removes hidden data from a binary buffer and stores it in a file.
		/// </summary>
		/// <param name="buffer">Buffer.</param>
		/// <param name="size">Size of the buffer.</param>
		/// <returns>Buffer without the extracted bytes.</returns>
		public byte[] Extract(byte[] buffer, int size, Stream outMessage)
		{
			byte[] returnArray = null;

			// copy buffers to collection
			Collection<byte> transmissionBuffer = new Collection<byte>();
			foreach (byte b in extract_waitingBuffer)
			{
				transmissionBuffer.Add(b);
			}
			for (int n = 0; n < size; n++)
			{
				transmissionBuffer.Add(buffer[n]);
			}

			if (extract_blockSize < 0 && transmissionBuffer.Count >= 8)
			{	// The first 8 octets arrived.
				// read/remove block and message size
				this.extract_blockSize = RemoveInt32(transmissionBuffer, 7);
				this.extract_RemainingMessageSize = RemoveInt32(transmissionBuffer, 3);
			}

			if (transmissionBuffer.Count < extract_blockSize || extract_blockSize < 0)
			{	// The buffer is too small for a block.
				// wait until the socket received more data
				extract_waitingBuffer = transmissionBuffer;
			}
			else
			{
				extract_waitingBuffer.Clear();

				// transmission must be an integer number of blocks
				int countBlocksInCurrentBuffer = (int)Math.Floor((float)(transmissionBuffer.Count / (extract_blockSize+1)));
				int maxTransmissionSize = countBlocksInCurrentBuffer * (extract_blockSize+1);
				// move the unused rest of the current buffer to the waiting collection
				MoveEndToCollection(transmissionBuffer, extract_waitingBuffer, maxTransmissionSize);
				
				// read and remove message from carrier
				int transmissionBufferIndex = 0;
				while (extract_RemainingMessageSize > 0 && transmissionBufferIndex < transmissionBuffer.Count)
				{
					outMessage.WriteByte(transmissionBuffer[transmissionBufferIndex]);
					transmissionBuffer.RemoveAt(transmissionBufferIndex);
					transmissionBufferIndex += extract_blockSize;
					extract_RemainingMessageSize--;
				}

				// return new transmission buffer
				byte[] newBuffer = new byte[transmissionBuffer.Count];
				transmissionBuffer.CopyTo(newBuffer, 0);
				returnArray = newBuffer;
			}

			return returnArray;
		}

		private void MoveEndToCollection(Collection<byte> source, Collection<byte> destination, int sourceMaxSize)
		{
			while (source.Count > sourceMaxSize)
			{
				destination.Add(source[sourceMaxSize]);
				source.RemoveAt(sourceMaxSize);
			}
		}

		private void PrependInt32(int value, Collection<byte> buffer)
		{
			int restValue = value;

			// split value into octets
			byte value1 = (byte)(restValue >> 24);
			restValue -= value1 << 24;
			byte value2 = (byte)(restValue >> 16);
			restValue -= value2 << 16;
			byte value3 = (byte)(restValue >> 8);
			byte value4 = (byte)(restValue - (value3 << 8));

			// append value to buffer
			buffer.Insert(0, value1);
			buffer.Insert(0, value2);
			buffer.Insert(0, value3);
			buffer.Insert(0, value4);
		}

		private int RemoveInt32(Collection<byte> buffer, int offset)
		{	// get value from 4 octets
			int value = 0;
			for (int n = 3; n > -1; n--)
			{
				value += buffer[offset] << (n*8);
				buffer.RemoveAt(offset);
				offset--;
			}
			return value;
		}
	}
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer
Germany Germany
Corinna lives in Hanover/Germany and works as a C# developer.

Comments and Discussions