W jednym z ostatnich postów (Jeśli nie chcesz zarobić na swoim programie…) jako jeden z punktów umieściłem niewykrywanie debuggera. Noe dobrze, łatwo napisać, ale jak tak na prawdę można wykryć, że ktoś podgląda nasz kod w C#?? Jest kilka metod:
Debugger.IsAttached
Pierwszą metodę udostępnia nam sam framework. W namespace’ie System.Diagnostic jest klasa Debugger. Wystarczy, że napiszemy:
Debugger.IsAttached
Niestety wraz z prostotą tego rozwiązania przychodzi także jej ograniczenie. Wykryjemy nią (chyba) tylko uruchomienia programu pod Debuggerem VS.
API – IsDebuggerPresent()
W bibliotece Kernel32.dll możemy odnaleźć coś więcej. Wystarczy więc przy użyciu P/Invoke’a dodać definicję tej metody do naszego kodu. Zrobić to możemy tak:
[DllImport(“Kernel32.dll”)]
public static extern bool IsDebuggerPresent();
Metoda wykrywa WinDbg ale nie wykrywa debugger’a VS (??).
NtQueryInformationProcess
Istnieje jesszcze 3 metoda, wykorzystująca metodę NtQueryInformationProcess. Za jej pomocą możemy uzyskać informacje o procesie, który jest właśnie wykonywany. Jedną z informacji, która się tam znajduje jest wskaźnik na strukturę PEB, w której jest zawarta flaga czy program jest uruchomiony pod kontrolą debuggera. Kod troszkę bardziej używający P/Invoke’a.
Deklaracja niezbędnych struktur:
public enum PROCESSINFOCLASS : int
{
ProcessBasicInformation = 0,
ProcessQuotaLimits,
ProcessIoCounters,
ProcessVmCounters,
ProcessTimes,
ProcessBasePriority,
ProcessRaisePriority,
ProcessDebugPort,
ProcessExceptionPort,
ProcessAccessToken,
ProcessLdtInformation,
ProcessLdtSize,
ProcessDefaultHardErrorMode,
ProcessIoPortHandlers, // Note: this is kernel mode only
ProcessPooledUsageAndLimits,
ProcessWorkingSetWatch,
ProcessUserModeIOPL,
ProcessEnableAlignmentFaultFixup,
ProcessPriorityClass,
ProcessWx86Information,
ProcessHandleCount,
ProcessAffinityMask,
ProcessPriorityBoost,
MaxProcessInfoClass,
ProcessWow64Information = 26
};
[StructLayout(LayoutKind.Sequential)]
public struct PROCESS_BASIC_INFORMATION
{
public long ExitStatus;
public long PebBaseAddress;
public long AffinityMask;
public long BasePriority;
public long UniqueProcessId;
public long InheritedFromUniqueProcessId;
public static int Size
{
get { return (6 * sizeof(long)); }
}
}
[StructLayout(LayoutKind.Sequential)]
public class _PEB
{
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)]
public byte[] Reserved1;
public byte BeingDebugged;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 1)]
public byte[] Reserved2;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)]
public IntPtr[] Reserved3;
public IntPtr Ldr;
public IntPtr ProcessParameters;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 104)]
public byte[] Reserved4;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 52)]
public IntPtr[] Reserved5;
public IntPtr PostProcessInitRoutine;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 128)]
public byte[] Reserved6;
public IntPtr Reserved7;
public ulong SessionId;
}
public struct _PEB64
{
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)]
public byte[] Reserved1;
public byte BeingDebugged;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 21)]
public byte[] Reserved2;
public IntPtr LoaderData;
public IntPtr ProcessParameters;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 520)]
public byte[] Reserved3;
public IntPtr PostProcessInitRoutine;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 136)]
public byte[] Reserved4;
public ulong SessionId;
}
oraz import odpowiedniej metody z Ntdll.dll
[DllImport(“NTDLL.DLL”, SetLastError = true)]
static extern int NtQueryInformationProcess(IntPtr hProcess, PROCESSINFOCLASS pic,
ref PROCESS_BASIC_INFORMATION pbi, int cb, out int pSize);
Pozostaje tylko wywołać to co zaimportowaliśmy:
var pbi = new PROCESS_BASIC_INFORMATION();
int size;
var handle = Process.GetCurrentProcess().Handle;
NtQueryInformationProcess(handle, PROCESSINFOCLASS.ProcessBasicInformation,
ref pbi, PROCESS_BASIC_INFORMATION.Size, out size);
var p64 = (_PEB64) Marshal.PtrToStructure(new IntPtr(pbi.PebBaseAddress),
typeof (_PEB64));
Sprawdzenie to już drobnostka:
p64.BeingDebugged != 0
Oczywiście w przypadku 32-bitów należy użyć struktury _PEB zamiast _PEB32. Jak wykryć system można znaleźć w poście – 32 czy 64-bity? Oto jest pytanie?
Founder of Octal Solutions a .NET software house.
Passionate dev, blogger, occasionally speaker, one of the leaders of Wroc.NET user group. Microsoft MVP. Podcaster – Ostrapila.pl