ここでは、本質的には本当のハックである機会を示します。 問題は、なぜこれが必要なのでしょうか? 実際、これには膨大な数の目標があります。 したがって、タスクは
mscorlibライブラリのコードを変更して、それを使用するすべてのプログラムがこれらの変更を受け取るようにすることです。 もちろん、ランタイムではありませんが、最初は(ランタイムの場合、他のことを行う必要があります。ここでは、これらの変更がライブラリの現在の状態を壊さないように予約する必要があります)。 Mscorlibを例に取りました。誰もがコンピューター上に持っているからです。 ただし、他のハッキングは可能です。
「hell dll」を回避するために、Microsoftは、通常のバージョンとライブラリ名に加えて、特定のアセンブリが特定の開発者からではなく「特定の」開発者から「来た」ことを保証する公開鍵を持つアセンブリに署名する機会を与えられたことを知っています別の。 したがって、何らかの完全な理由で既存のライブラリのコードを変更して他のプロセスにロードし、公開キーが同じままにしたい場合、成功しません。 署名できないため、秘密キーはありません。
私たちの小さな目標は、プログラムがコンソールにテキストを表示することです。

まず、このサイクルのHabréに投稿された記事の全リスト 要約:
- はじめに
- ドライバー開発
- ドライバー開発の結論
- Windowsサービスの作成
- mscorlib.dllコードの変更
- 調査結果
はじめに
今日は、GACとNACにあるアセンブリについて説明します。 .NET FrameworkはGAC構造に非常に敏感であることがわかりました。 彼は彼女を非常に信頼しており、実際にバージョン番号を確認せずにそこからアセンブリをロードします。 したがって、置換を行うために、それほど必要はありません。特定のファイルへの呼び出しを別の場所にリダイレクトするために、カーネルレベルのドライバーを記述する必要があります。 ファイルシステムフィルターの可能性を利用しました。 一番下の行は簡単です。ドライバー自体に多くのコードを記述しないために、カーネル空間とユーザー空間の間にプロトコルが記述されています。 サービスアプリケーション-ユーザースペース側のウィンドウから立っています。 ドライバーと通信し、リダイレクト先をコマンドに渡します。 そして、それは順番にリダイレクトされます。 mscorlibなどの既存のライブラリのコードを変更するには、ReflexilまたはMono :: Cecilを使用できます。
手続き
- mscorlibアセンブリのアセンブリ(ReflexilまたはMono :: Cecil)を変更し、変更されたアセンブリを取得します。 たとえば、ログインを埋め込みます。
- 結果をtmpディレクトリに入れます。
- リダイレクト「C:\ Windows \ Microsoft.NET \ assembly \ GAC_64 \ mscorlib \ v4.0_4.0.0.0__b77a5c561934e089 \ mscorlib.dll」を、たとえば「C:\ tmp \ mscorlib64.dll」に変更します。
そして、.netシステムライブラリロギング上のすべてのアプリケーションのその後の起動時に取得します。 順序のために、ドライバーはプロセス番号でフィルタリングする必要があります。プロセス番号は、「左翼」を正確に誰に渡すか、元のライブラリを誰に渡すかです。
ドライバー開発
まず、VirtualBoxをインストールします。 ドライバーをデバッグするために必要です。 ドライバーが間違ったアドレスにアクセスしてエラー(当然、BSOD)でクラッシュするたびに再起動する必要はありません。 Windows XPおよびWindows 7、x86およびx64のイメージを仮想マシンにロールし、スナップショットを削除してロールバックできるようにします。
次に、開発マシンでドライバーを開発するためのVisualDDK、WinDDK、およびその他のインフラストラクチャをインストールします。
次に、ドライバー自体を作成します。 完全なリストは掲載しませんが、重要な部分のみを掲載します。 予約をさせてください、私たちはMinifilter Filesystem Driverを書いています。
1)フィルター登録:
const FLT_OPERATION_REGISTRATION Callbacks[] = { { IRP_MJ_CREATE, 0, PbPreOperationCreateCallback, PbPostOperationCreateCallback }, { IRP_MJ_NETWORK_QUERY_OPEN, 0, PbPreOperationNetworkQueryOpenCallback, NULL }, { IRP_MJ_OPERATION_END } }; CONST FLT_REGISTRATION FilterRegistration = { sizeof( FLT_REGISTRATION ), // Size FLT_REGISTRATION_VERSION, // Version 0, // Flags NULL, // Context Callbacks, // Operation callbacks PtFilterUnload, // FilterUnload PtInstanceSetup, // InstanceSetup PtInstanceQueryTeardown, // InstanceQueryTeardown PtInstanceTeardownStart, // InstanceTeardownStart PtInstanceTeardownComplete, // InstanceTeardownComplete PtGenerateFileName, // GenerateFileName PtNormalizeNameComponent // NormalizeNameComponent };
ここではすべてが簡単です。 フィルタードライバーを登録するための構造を紹介します。 フィルターはすべてのボリュームで機能し、CreateFile操作をインターセプトします(ファイルを作成/開く)
ドライバーの初期化も、経験のある人に質問をしてはいけません。 ここで何が起こるかをリストします。
- ドライバーはシステムに登録されています
- システム内の新しいプロセスを通知する通知が作成されます
- 通信ポートが上昇し、ユーザーレベル(保護の3番目のリング、Windowsアプリケーション)と通信します。ここで、Windowsサービスについて説明します。これについては、以下で説明します。
CPP_DRIVER_ENTRY ( __in PDRIVER_OBJECT DriverObject, __in PUNICODE_STRING RegistryPath ) { NTSTATUS status; OBJECT_ATTRIBUTES attr; UNICODE_STRING portName; PSECURITY_DESCRIPTOR securityDescriptor; UNREFERENCED_PARAMETER( RegistryPath ); __try {
フィルターを終了するのも簡単です。
DeregisterFilterがすべてのリソースのリリースに従事し
ているとだけ言って
おきましょう。 DRIVER_DATA.Initializedの設定フラグに基づいています(上記のコードを参照)
NTSTATUS PtFilterUnload( __in FLT_FILTER_UNLOAD_FLAGS Flags ) { UNREFERENCED_PARAMETER( Flags ); PAGED_CODE(); DeregisterFilter(); DbgPrint("PoliciesSandbox!PtFilterUnload: Entered\n"); return STATUS_SUCCESS; }
次。 次に、何をどこにリダイレクトするかを知る必要があります。 どのファイルと場所。 これを行うために、DriverEntryで作成した通信ポートでカーネルモードとユーザーモードの間に小さなプロトコルを作成しました。
一連のコマンドを定義します。
typedef enum {
すべてのチームの基本構造を決定します。
typedef struct { COMM_COMMAND Command; USHORT Data[]; } COMM_MESSAGE, *PCOMM_MESSAGE;
システムの新しいプロセスを通知するために、Windowsサービスに送信される構造を決定します。
typedef struct { ULONG Pid; } COMM_PROCESS_ATTACHED, *PCOMM_PROCESS_ATTACHED;
応答構造を定義します。
typedef struct { USHORT NeedToMonitor; USHORT IsCompleted;
また、ドライバーにデータを保存するための構造も紹介します。
typedef struct _MAPPING_ENTRY { UNICODE_STRING OldName; UNICODE_STRING NewName; _MAPPING_ENTRY *Next; } MAPPING_ENTRY, *PMAPPING_ENTRY; typedef struct _PROCESSES_MAP_ENTRY { ULONG Pid; PMAPPING_ENTRY entries; _PROCESSES_MAP_ENTRY *Prev, *Next; } PROCESSES_MAP_ENTRY, *PPROCESSES_MAP_ENTRY;
すべてが定義されたので、新しいプロセスを開始するとき(および既にインターセプトしているとき)、これについてサービスに通知する必要があります。これは、回答として、このプロセスに応じてファイルとフォルダーのリダイレクトルールのセットを提供します。
メモリをクリアする関数のコードは含めません。 あまり面白くない。
関数が呼び出されると、その関数は、それを作成したプロセス、作成されたもの、およびプロセスが作成されたか終了するかを示すフラグに渡されます。 つまり この関数は、プロセスの作成と終了を通知します。
その内部で、プロセスが作成されると、Windowsサービスにそのことを通知し、プロセスのPIDを渡します。 そのため、PIDサービスはリダイレクトルールのリストを見つけ、それらを回答として提供します。
VOID PsProcessNotify ( IN HANDLE ParentId, IN HANDLE ProcessId, IN BOOLEAN Create ) { NTSTATUS status; if( HandleToULong( ProcessId ) <= 4) return; if(Create) { LARGE_INTEGER liTimeout; DbgPrint("Process created: %x", ProcessId); liTimeout.QuadPart = -((LONGLONG)1 * 10 * 1000 * 1000); ULONG SenderBufferLength = MESSAGE_BUFFER_SIZE; ULONG ReplyLength = MESSAGE_BUFFER_SIZE; PCOMM_MESSAGE message = (PCOMM_MESSAGE)myNonPagedAlloc(MESSAGE_BUFFER_SIZE); message->Command = ProcessAttached; ((PCOMM_PROCESS_ATTACHED)(&message->Data[0]))->Pid = HandleToULong(ProcessId); status = FltSendMessage(DRIVER_DATA.fltHandle, &DRIVER_DATA.Connection.ClientPort, message, SenderBufferLength, message, &ReplyLength, &liTimeout); if(ReplyLength > 0) { PCOMM_FS_REDIRECTIONS Redirections = (PCOMM_FS_REDIRECTIONS)&message->Data[0]; DbgPrint("Recieved reply from user-mode: NeedToMonitor = %s\n", Redirections->NeedToMonitor ? "TRUE" : "FALSE" ); if(Redirections->NeedToMonitor) { PbRepInitializeMapping(HandleToULong(ProcessId), Redirections); } else { DbgPrint("Thread %d not needed to be monitored. Skipping.", HandleToULong(ProcessId)); } } if(!message) myFree(message); } else { DbgPrint("Process destroyed: %x\n", ProcessId); PPROCESSES_MAP_ENTRY entry = DRIVER_DATA.Mapping; while( entry != NULL ) { if(entry->Pid == HandleToULong(ProcessId)) { PbRepDeleteMapping(entry); break; } } } }
以下のコードは、Windowsサービスから取得したデータに関連する内部構造を単純に初期化します。
NTSTATUS PbRepInitializeMapping( __in ULONG pid, __in PCOMM_FS_REDIRECTIONS Redirections ) { NTSTATUS status = STATUS_SUCCESS; FltAcquirePushLockExclusive( &DRIVER_DATA.Sync ); __try { DbgPrint("PlociesSandbox!PbRepInitializeMapping: redirections count: %d\n", Redirections->PairsCount); PMAPPING_ENTRY current = NULL;
そして最後に、リダイレクトルールの検索機能。 ルールが見つかった場合、STATUS_SUCCESSと変更されたパスを返します。
bool PbIsFolder(PUNICODE_STRING path) { return path->Buffer[path->Length/2 - 1] == L'\\'; } NTSTATUS PbLookupRedirection(__in PUNICODE_STRING FilePath, __out PUNICODE_STRING *FileFound) { // Possible redirections: // Full change: \Device\HarddiskVolume2\InternalPath\Path\To\Some\File.Ext -> \Device\.\Temporary\File.ext // Partial change: \Device\HarddiskVolume2\InternalPath\ -> \Device\HarddiskVolume2\Temporary\ // in this case all pathes, starts with ..\InternalPath should be changed. For ex.: // \Device\HarddiskVolume2\InternalPath\Some\Path\To\File.Ext -> \Device\HarddiskVolume2\Temporary\Some\Path\To\File.Ext ULONG Pid = HandleToULong(PsGetCurrentProcessId()); FltAcquirePushLockShared( &DRIVER_DATA.Sync ); __try { PPROCESSES_MAP_ENTRY currentProcess = DRIVER_DATA.Mapping; while(currentProcess != NULL) { if(currentProcess->Pid == Pid) { PMAPPING_ENTRY current = currentProcess->entries; while(current != NULL) { if(PbIsFolder(¤t->OldName)) { // Folders prefixes are identical, please note that all lengthes are double-sized if(wcsncmp(current->OldName.Buffer, FilePath->Buffer, current->OldName.Length / 2) == NULL) { int newlength = (FilePath->Length - current->OldName.Length) + current->NewName.Length; PUNICODE_STRING ret = PbAllocUnicodeString(newlength + 2); RtlCopyUnicodeString(ret, ¤t->NewName); RtlCopyMemory( Add2Ptr(ret->Buffer, ret->Length), Add2Ptr(FilePath->Buffer, current->OldName.Length), (FilePath->Length - current->OldName.Length) + 2); ret->Length = wcslen(ret->Buffer) * 2; *FileFound = ret; return STATUS_SUCCESS; } } else { if(wcscmp(current->OldName.Buffer, FilePath->Buffer) == NULL) { PUNICODE_STRING ret = PbAllocUnicodeString(current->NewName.Length + 2); RtlCopyUnicodeString(ret, ¤t->NewName); *FileFound = ret; return STATUS_SUCCESS; } } current = current->Next; } } currentProcess = currentProcess->Next; } return STATUS_NOT_FOUND; } __finally { FltReleasePushLock( &DRIVER_DATA.Sync ); } }
ドライバー開発の結論
オペレーティングシステムでの新しいプロセスの作成を外部サービスに通知するドライバーがあります。 サービスがプロセスに関する情報を受信すると、そのサービスで監視を有効にするかどうかを決定します。 その場合、ファイルシステム上のリダイレクトルールのリストも送信します。
Windowsサービスの作成
繰り返しますが、私は特定の詳細には触れません。 これは通常のサービスです。 唯一の機能は、ドライバーからのコマンドを期待し、それらに応答することです。 IoCompletionPortの助けを借りてお待ちしています。 このメカニズムでは、I / Oが完了するのを待っている間にいくつかのスレッドがポートでハングすることが必要です(一般に、理解できる理由です)。 データが到着すると、タスクの1つが起動し、このデータを処理できます。 このタスクでは、リダイレクトのリストを送信します。
コードは少し汚いかもしれませんが、まあまあです。 主なことは、必要なことを行うことです。
- FilterConnectCommunicationPortはドライバーをドライバーに接続します
- CreateIoCompletionPortは、I / O完了ポートを作成します。これについて読む場所のすべてのリンクはコード内にあります
- MessagingManagerThread-ドライバーからのメッセージ処理のフローを制御するクラス
- Stopは、io完了ポートを介してすべてのスレッドに完了コマンドを送信します。そうしないと、すべてがハングします
public unsafe class MessagingManager { private const string FilterCommunicationPortName = "\\PoliciesInjectorCom0"; private IntPtr m_filterPortHandle; private IntPtr m_ioCompletionPortHandle; private IntPtr[] m_buffers; private MessagingManagerThread[] m_threads; private CancellationTokenSource m_cancellationToken; public unsafe MessagingManager(Dictionary<string,string> redirections) { uint hr = WinApi.FilterConnectCommunicationPort(FilterCommunicationPortName, 0, IntPtr.Zero, 0, IntPtr.Zero, out m_filterPortHandle); if(hr != 0x0) { throw WinApi.CreateWin32Exception(String.Format("Cannot connect to driver via '{0}' port", FilterCommunicationPortName)); } Console.WriteLine("Connected to {0}", FilterCommunicationPortName);
最後に、ストリームサービスクラス:
public class MessagingManagerThread { private ManualResetEvent m_resetEvent; private IntPtr m_ioCompletionPortHandle; private IntPtr m_filterPortHandle; private CancellationToken m_cancelToken; private Thread m_thread; private Int32 m_threadId; private Dictionary<string,string> m_redirections; private const int timeoutCompletionStatus = 5000;
構成は、次のように扱いにくい方法で行われます。
var redir = new RedirectionsManager(); redir.Add(typeof(Environment).Assembly.GetName(), "\\temp\\GAC32\\mscorlib.dll");
つまり、GACで
mscorlib.dllアセンブリを呼び出すと、32ビットシステムの「C:\ temp \ GAC32 \ mscorlib.dll」フォルダーにリダイレクトされます。 64ビットの場合、同じことを行いますが、異なるフォルダーに対して行います。
mscorlib.dllを変更する
mscorlibを変更するには、.Net Reflector + Reflexilの古いフリーウェアバージョンを使用できます。
それらの中で、バージョン番号が「5.0.40930.0」になるようにSystem.Environment.get_Version()を置き換えました
結果を確認する
テストでは、.Net Framework 4.0アプリケーションを作成し、
testredirと呼びます。これは、ドライバーがこのプロセス名を予期しているためです。
アプリケーションのテキストは単純に不可能です
using System; namespace testredir { public static class testredir { public static void Main(string[] args) { Console.WriteLine("Ver: {0}!", System.Environment.Version); Console.Write("Press any key to continue . . . "); Console.ReadKey(true); } } }
プログラム出力:
Ver: 5.0.40930.0!
注:
システムライブラリのコードを慎重に変更する必要があります。 たとえば、この例では、実行するバージョン番号をチェックするため、ソフトウェアの一部を停止します。 良い例は、現在行っている内部イベントのロガーの作成です
ソース: