Dneska jsem během náhodného procházení internetu narazil (z mého pohledu) zajímavou věc – jak udělat to, co umí např. proces CSRSS, tj. že když ho někdo nebo něco killne nebo umře přirozenou programovou smrtí, tak s sebou vezme i celý systém – Windows hodí BSOD (KeBugCheckEx) s tím, že mu (volně řečeno) umřel kritický proces a bez něj už nemá smysl dál existovat :)
Kód Bluescreenu se liší podle toho jestli hlídaný program skončil v pořádku (vrátil 0), pak je to CRITICAL_PROCESS_DIED (0xEF), nebo jestli skončil s chybou, pak se zjeví BSOD CRIITICAL_OBJECT_TERMINATION (0xF4).
Jak se tedy takováhle věc nastavuje? V podstatě jsou 2 cesty, kdy první používá interně tu druhou. V obou dvou případech je třeba, aby proces měl seDebugPrivilege (v .NETu to lze zařídit pomocí fcí Process.EnterDebugMode a Process.LeaveDebugMode) a nejspíš jsou potřeba i admin práva (bez nich mi to nefungovalo). Nejjednodušší je asi použít nedokumentovanou API funkci RtlSetProcessIsCritical, které se (mj.) předá BOOL vyjadřující, jestli tento proces je pro systém kritický nebo není. Zásadní nevýhoda je, že se toto nedá použít pro nastavování jiných procesů. A protože jsem člověk zvědavý, tak jsem se podíval do referenčních zdrojáků NTOSKRNL (část Windowsího kernelu) a implementaci té funkce tam ke své spokojenosti našel – je to víceméně jen obálka nad dvojicí (zase nedokumentovaných) funkcí NtSetInformationProcess a NtQueryInformationProcess, sloužících pro nastavování či čtení nejrůznějších parametrů procesů.
Implementace
Nejdřív definice těch 3 funkcí :
[DllImport("ntdll.dll", SetLastError = true)]
extern static unsafe UInt32 NtSetInformationProcess(IntPtr ProcessHandle, int ProcessInformationClass, void* ProcessInformation, uint ProcessInformationLength);
[DllImport("ntdll.dll", SetLastError = true)]
extern static unsafe UInt32 NtQueryInformationProcess(IntPtr ProcessHandle, int ProcessInformationClass, void* ProcessInformation, uint ProcessInformationLength, uint* ReturnLength);
[DllImport("ntdll.dll")]
extern static unsafe void RtlSetProcessIsCritical(bool NewValue, bool* OldValue,bool CheckFlag);
ProcessInformationClass je enum vyjadřující, co vlastně chceme zjistit nebo nastavit za informaci, na internetu lze nalézt definici, my potřebujeme ProcessBreakOnTermination, což je číselně 29, ProcessInformation je pak daná informace. Pomocí toho pak už kód je jednoduchý:
unsafe void SetCritical(Process proc, bool enable)
{
Process.EnterDebugMode();
uint ienable = enable ? 1u : 0u;
try
{
UInt32 status = NtSetInformationProcess(proc.Handle, ProcessBreakOnTermination, &ienable, sizeof(uint));
if (status < 0) MessageBox.Show("Error " + status);
}
catch (Win32Exception e)
{ MessageBox.Show("Error " + e.Message); }
Process.LeaveDebugMode();
}
unsafe bool GetCritical(Process proc)
{
uint ienable = 0;
Process.EnterDebugMode();
try
{
UInt32 status = NtQueryInformationProcess(proc.Handle, ProcessBreakOnTermination, &ienable, sizeof(uint), (uint*)IntPtr.Zero.ToPointer());
if (status < 0) MessageBox.Show("Error " + status);
}
catch (Win32Exception e)
{ MessageBox.Show("Error " + e.Message); }
Process.LeaveDebugMode();
return ienable != 0;
}
Užitečnost
je v tomto případě celkem diskutabilní :) Napadá mě použítí do nějakých ochranných systémů a případně na hraní, kdy takto odděláte ochranu důležitým systémovým procesům a sledujete, co se stane po killu. Nejspíše by se to také dalo zneužít na nějaký pěkný kanadský žertík. Mě osobně se na tom líbí to, že ony systémové procesy nejsou “něco víc” než ty moje a liší se jen voláním jedné API funkce. Případné nápady na využití můžete psát do diskuze pod článkem.
Zdrojový kód aplikace si můžete stáhnout na http://dl.jcermak.cz/clanky/CriticalSysProcessExample.zip