「ランサムウェア対策ソフト ビット・センサー開発 (2024/07/02)」
何かと騒がしい最近のネット界隈。
とりあえず、
ランサムウェア・流出対策の
PCアプリ 「ビット・センサー」を作りました。
原理はシンプル。
・自分のPCのあちこちに「!bs.txt」と言う名前のファイルを作成しておきます。
・ ビット・センサーはPCファイルアクセスを見張り、
誰かが「!bs.txt」をFileReadしたり、FileCopyしたらアラーム発動。
・警告音を鳴らす
・メールを管理者に送る
・PCをシャットダウン
の3点を実行します。
・ サーバー管理者は絶対に「!bs.txt」にアクセスしてはなりません。
これは侵入者を捉えるためのダミーファイル。『トラバサミ』です。
迅速な対応が必要なので
コードも公開しまーす。
アプリケーション形式:
C# WPF アプリケーション
ターゲットフレームワーク
.NET 7.0
必要のNuGetパッケージ:
・Microsoft.Diagnostics.Tracing.TraceEvent
・System.Speech
主要部分のソースコード:
using Microsoft.Diagnostics.Tracing.Parsers;
using Microsoft.Diagnostics.Tracing.Parsers.Kernel;
using Microsoft.Diagnostics.Tracing.Session;
using System.Diagnostics;
using System.Net;
using System.Net.Mail;
using System.Security.Cryptography;
using System.Speech.Synthesis;
using System.Text;
using System.Windows;
namespace BitSensor
{
public partial class MainWindow : Window
{
#region Setting
//-----------------最低限必要な設定-------------------
//反応させるファイル名。必ず変える事。
string REACT_FILENAME = "!bs.txt";
//メール送信用の「ドメイン」「ユーザー名」「パスワード」「To」の設定ファイル。AESで暗号化しておく
const string MAIL_TXT = "mail.aes";
//↑の解除キー
const string AES_KEY = "xxxxxxxxxx";
//メール送信のポート
const int MAIL_PORT = 587;
#endregion
//タイマーの間隔。この間隔ごとにアラートを鳴らす
const int ALERT_INTERVAL = 10 * 1000;
//ウィンドウ位置
const int WINDOW_LEFT = 1700;
const int WINDOW_TOP = 900;
///-----------------------------------
private TraceEventSession? session = null;
private KernelTraceEventParser? parser = null;
private Thread? thread = null;
//「侵入された」フラグ&ログ
const string intruder_dat = "_intruder.dat";
//侵入を検知したらtrueになる
bool intruder = false;
//メールの多重送信を防止
bool mail_sent = false;
//きちんとフィルターが動いてるかの確認用。
int test_count = 0;
int ok_count = 0;
//センサーが正しく動いてるか確かめるためのダミー侵入者
GhostIntruder ghost_intruder;
public MainWindow()
{
InitializeComponent();
ghost_intruder = new GhostIntruder();
if (forbidDoubleInstance()) return;
//大/小文字による不一致を避けるため、小文字に統一。
REACT_FILENAME = REACT_FILENAME.ToLower();
message = "初期化中";
this.Left = WINDOW_LEFT;
this.Top = WINDOW_TOP;
var timer = new System.Timers.Timer();
timer.Interval += ALERT_INTERVAL;
timer.Elapsed += Timer_Elapsed;
timer.Start();
}
//二重起動の禁止
private bool forbidDoubleInstance()
{
var processCurrent = System.Diagnostics.Process.GetCurrentProcess();
var list = System.Diagnostics.Process.GetProcessesByName(processCurrent.ProcessName);
if (list.Length >= 2)
{
System.Windows.Application.Current.Shutdown();
return true;
}
return false;
}
//一定時間ごとのルーチン
private void Timer_Elapsed(object? sender, System.Timers.ElapsedEventArgs e)
{
//最初の一回
if (this.session == null)
{
if (!startWatch()) { return; }
if (!auditSelf()) { return; }
}
//侵入の形跡があったらアラートを流す
if (intruder)
{
this.alert();
return;
}
message = $"tested({this.test_count})\n OK({this.ok_count})";
}
//メッセージ表示
string message
{
set
{
//マルチスレッドに必要
this.Dispatcher.Invoke(() =>
{
this.text_box.Text = value;
});
}
}
//実行に必要なファイル/環境が揃っているかセルフテスト。
bool auditSelf()
{
if (true)
{
if (!speech("ビットセンサー。テスト放送"))
{
message = "スピーチができません";
}
}
if (true)
{
message = "テストメール";
if (!sendMail("テスト", "これはテストメールです"))
{
message = "メールが送れません";
}
}
return true;
}
//監視スレッド
bool startWatch()
{
//「侵入されたログ」がある
if (System.IO.File.Exists(intruder_dat))
this.intruder = true;
try
{
#if DEBUG
TraceEventSession.SetDebugPrivilege();
#endif
this.thread = new Thread(() =>
{
session = new TraceEventSession(KernelTraceEventParser.KernelSessionName, TraceEventSessionOptions.NoRestartOnCreate) { };
session.EnableKernelProvider(KernelTraceEventParser.Keywords.All
);
this.parser = new KernelTraceEventParser(session.Source);
parser.FileIORead += onFileIORead;
parser.DiskIODriverMajorFunctionCall += Parser_DiskIODriverMajorFunctionCall;
session.Source.Process();
}); ;
thread.Priority = ThreadPriority.Lowest;
thread.IsBackground = true;
thread.Start();
return true;
}
catch (System.Exception)
{
message = "管理者権限で動かしてください";
return false;
}
}
//FileReadが発生したときに呼び出される
private void onFileIORead(FileIOReadWriteTraceData obj)
{
onFile("FileIORead", obj.FileName, obj.ProcessName,obj.ToString());
}
//ファイルコピーが発生したときに呼び出される
private void Parser_DiskIODriverMajorFunctionCall(DriverMajorFunctionCallTraceData obj)
{
onFile("DiskIO", obj.FileName, obj.ProcessName,obj.ToString());
}
//コマンドを判定
void onFile(string command, string filename,string processname,string info)
{
if (filename.Length <= 0) return;
test_count++;
filename = filename.ToLower();
//ファイル名に反応
if (filename.Contains(REACT_FILENAME))
{
var content = $"{command}\n{filename}\n{processname}\n{info}";
message = content;
Debug.WriteLine(content);
//自分自身でテスト侵入 → 検知に成功した。
if (processname == "BitSensor")
{
speech("テスト検知・成功");
return;
}
logIntruded(content);
intruder = true;
this.ok_count = 0;
this.test_count=0;
}
else
{
this.ok_count++;
}
}
// 侵入者を検知したときに実行する
void alert()
{
var message = "侵入を検知しました";
//警告音を鳴らす
speech(message);
//メールを送る
if (!mail_sent)
{
sendMail("侵入を検知", "侵入を検知しました");
mail_sent = true;
}
shutdown();
}
//「侵入された」とログる。
void logIntruded(string content)
{
System.IO.File.WriteAllText(intruder_dat, content);
}
void shutdown()
{
//強制シャットダウン
bool force_shutdown = true;
bool hibernate = false;
bool disable_wake = true;
SetSuspendState(hibernate, force_shutdown, disable_wake);
}
//textをスピーチ。ただしスピーカーがミュート状態の可能性もあるので過信は禁物
static public bool speech(string text)
{
var speech = new SpeechSynthesizer();
speech.Volume = 100;
speech.Rate = 0;
var voices = speech.GetInstalledVoices();
if (voices.Count <= 0) return false;
speech.SelectVoice(voices[0].VoiceInfo.Name);
speech.Speak(text);
return true;
}
//メールを送信する
bool sendMail(string subject,string message)
{
var _content = System.IO.File.ReadAllText(MAIL_TXT);
var content = decrypt(_content);
content = content.Remove('\r');
var lines = content.Split('\n');
var host = lines[0];
var username = lines[1];
var pw = lines[2];
var to = lines[3];
var mm = new MailMessage();
mm.From = new MailAddress(to);
mm.To.Add(new MailAddress(to));
mm.Subject = "ビットセンサー : " + subject;
mm.Body = message;
var client = new SmtpClient();
client.Host =lines[0];
client.Port = MAIL_PORT;
client.UseDefaultCredentials = false;
client.Credentials = new NetworkCredential(username,pw);
// client.EnableSsl = true;
try
{
client.Send(mm);
return true;
}
catch (Exception)
{
return false;
}
}
//メール送信 設定の暗号化
public string encrypt(string text)
{
var b = Encoding.UTF8.GetBytes(text);
var encrypted = getAes().CreateEncryptor().TransformFinalBlock(b, 0, b.Length);
return Convert.ToBase64String(encrypted);
}
//メール送信 設定の復号化
public string decrypt(string text)
{
var b = Convert.FromBase64String(text);
var decrypted = getAes().CreateDecryptor().TransformFinalBlock(b, 0, b.Length);
return Encoding.UTF8.GetString(decrypted);
}
//暗号化・復号化
Aes getAes()
{
var keyBytes = new byte[16];
var skeyBytes = Encoding.UTF8.GetBytes(AES_KEY);
Array.Copy(skeyBytes, keyBytes, Math.Min(keyBytes.Length, skeyBytes.Length));
Aes aes = Aes.Create();
aes.Mode = CipherMode.CBC;
aes.Padding = PaddingMode.PKCS7;
aes.KeySize = 128;
aes.Key = keyBytes;
aes.IV = keyBytes;
return aes;
}
//PCの電源コマンド
[System.Runtime.InteropServices.DllImport("Powrprof.dll", SetLastError = true)]
public static extern bool SetSuspendState(bool hibernate, bool forceCritical, bool disableWakeEvent);
}
/// <summary>
/// ビット・センサーが正しく動いてるか確かめるためのダミー侵入者
/// </summary>
public partial class GhostIntruder
{
const string path = "見張りたいフォルダ/!bs.txt";
public GhostIntruder()
{
//24hに一回、侵入する。
var timer = new System.Timers.Timer();
timer.Interval = new TimeSpan(24, 0, 0).TotalMilliseconds;
timer.Elapsed += Timer_Elapsed;
timer.Start();
}
private void Timer_Elapsed(object? sender, System.Timers.ElapsedEventArgs e)
{
test();
}
bool test()
{
MainWindow.speech("ゴーストがいまから侵入します");
Thread.Sleep(10*1000);
var content = System.IO.File.ReadAllText(path);
//意味はない。最適化によってコードが削除されないよう適当に何かしている。
return content == "content";
}
}
}
このビット・センサーは、古典的な:
・ログイン&パスワード認証 ← パスワードが漏れたら突破される
・TrueCryptやBitLockerなどの暗号化系 ← 「マウント中」は普通に読まれる
とは全く違う。
わかりやすく言えば、
「家の中に侵入された後」を想定し。
『トラバサミ』を設置。
知らない人がそれを踏んだらアラームが鳴るシステムです。
この「ビット・センサー」は:
PC内の全てのファイル行動を、管理者権限で監視する。
極めてプライベート、かつセンシティブな内容となるので
バイナリ(.exe)は公開しません。
だから代わりにソースコードを公開します。
ユーザーはこのソースの意味を読み解いて。
安全だと思ったら、自分で:
・コンパイル
・exeを生成
・管理者権限で動かし、24時間常駐
してください。
自分でコンパイルしたものなら、
怪しいコードの入る余地はありませんから。
・「バックアップソフト」や「セキュリティソフト」に対しても
容赦なく反応します。
対策:
A. !bs.txtはスキップするようにアクセス側を設定する
B.ビット・センサーのonFile()部分を改変して
特例を作る。
※あまりオススメはしません。
例外を許可するとそもそもセンサーの意味がないです。
あらゆる、サーバーの管理者レベルは。
このビット・センサーをいますぐコンパイル&インストール。
(もしくは類似の働きをするソフトを自分で製作)
今後のサーバー運営には必須になるツールだと思います。