فحص ذاكرة العملية واكتشاف عناوين المتغيرات المختلفة. (C#)
سنتناول في هذه المقالة كيفية الحصول على تفريغ dump الذاكرة لعملية ما ، عن طريق التحقق تقريبًا من جميع عناوين الذاكرة التي يمكنها تخزين البيانات.
نظرًا لأن C# هي لغة برمجة عالية المستوى ، أعتقد أن هذه هي الطريقة الوحيدة المتاحة للقيام بذلك.
وبما أن أحد ما قد يسأل عن كيفية البحث عن سلسلة string في ذاكرة العملية – حسنًا ، فإن أسهل طريقة هي البحث في تفريغ الذاكرة dump الذي تم إنشاؤها. هناك أيضًا طرق أخرى تشير ضمنيًا إلى المؤشرات pointers والإزاحات offsets والتجميع Assembly أو حقن injecting بعض dll في التطبيق الهدف ، ولكن … هذا الشرح هو C#.
في هذا البرنامج التعليمي ، سأحاول إخراج كل الذاكرة المخصصة memory allocated بواسطة برنامج Notepad ، أوصيك باستهداف العمليات التي لا تستهلك الكثير من ذاكرة RAM. يخصص برنامج Notepad حوالي 1-2 ميغا بايت من الذاكرة وملف التفريغ dump file الذي تم إنشاؤه به حوالي 38 ميغا بايت (ومع ذلك ، أقوم أيضًا بتضمين عنوان الذاكرة memory address لكل بايت والأسطر الجديدة).
هذه صورة صغيرة توضح النتيجة:
المسافات بين الأحرف (بايت فارغ) ناتجة عن استخدام برنامج Notepad لتشفير Unicode.
الدوال البرمجية المطلوبة
عندما تبدأ إحدى العمليات ، يخصص النظام ذاكرة كافية للكومات heap, stack ومناطقها regions – ولكن Windows لن يخصص “كتلة كاملة” ‘entire block من الذاكرة. ييحاول تخصيص أي ذاكرة خالية متاحة لوضع المستخدم – لذلك لن تكون الذاكرة المخصصة متجاورة. بشكل أساسي ، لن يخبرنا Windows بمجموعة من العناوين حيث يمكننا العثور على بيانات البرنامج.
لذا ، فإن الحل المتبقي هو فحص كل عنوان ممكن تقريبًا (نحصل عليه باستخدام GetSystemInfo ()) ونتحقق مما إذا كان ينتمي إلى العملية المستهدفة (مع VirtualQueryEx () ): إذا كان الأمر كذلك ، فنحن سنقرأ القيم من هناك بواسطة الدالة البرمجية (ReadProcessMemory ( )).
الدوال البرمجية التي ستكون مطلوبة (بما في ذلك تلك المذكورة أعلاه):
GetSystemInfo ()
[DllImport("kernel32.dll")]
static extern void GetSystemInfo(out SYSTEM_INFO lpSystemInfo);
يسترجع معلومات عشوائية حول النظام في بنية تسمى SYSTEM_INFO.
تحتوي هذه البنية أيضًا على متغيرين:
الحد الأدنى لعنوان التطبيق والحد الأقصى للعنوان الذي يخزن الحد الأدنى والحد الأقصى للعنوان حيث يمكن للنظام تخصيص الذاكرة لتطبيقات وضع المستخدم.
SYSTEM_INFO يبدو كالتالي:
public struct SYSTEM_INFO
{
public ushort processorArchitecture;
ushort reserved;
public uint pageSize;
public IntPtr minimumApplicationAddress; // minimum address
public IntPtr maximumApplicationAddress; // maximum address
public IntPtr activeProcessorMask;
public uint numberOfProcessors;
public uint processorType;
public uint allocationGranularity;
public ushort processorLevel;
public ushort processorRevision;
}
VirtualQueryEx()
[DllImport("kernel32.dll", SetLastError=true)]
static extern int VirtualQueryEx(IntPtr hProcess, IntPtr lpAddress, out MEMORY_BASIC_INFORMATION lpBuffer, uint dwLength);
تحصل هذه الدالة البرمجية على معلومات حول مجموعة من عناوين الذاكرة وتعيدها إلى بنية تسمى MEMORY_BASIC_INFORMATION. بالنظر إلى الحد الأدنى من العنوان ، نستخدم هذا لمعرفة ما إذا كانت هناك منطقة من الذاكرة تم تخصيصها بواسطة هذا البرنامج (بهذه الطريقة نقوم بتقليل نطاق البحث عن طريق القفز والتجاوز مباشرة فوق أجزاء الذاكرة). تخبرنا هذه الطريقة أساسًا بمدى جزء الذاكرة الذي يبدأ من العنوان المحدد: من أجل الوصول إلى مقطع الذاكرة التالي ، نضيف طول هذه المنطقة إلى عنوان الذاكرة الحالي (المجموع).
يتطلب PROCESS_QUERY_INFORMATION.
يجب تحديد MEMORY_BASIC_INFORMATION بهذه الطريقة:
public struct MEMORY_BASIC_INFORMATION
{
public int BaseAddress;
public int AllocationBase;
public int AllocationProtect;
public int RegionSize; // size of the region allocated by the program
public int State; // check if allocated (MEM_COMMIT)
public int Protect; // page protection (must be PAGE_READWRITE)
public int lType;
}
ReadProcessMemory()
[DllImport("kernel32.dll")]
public static extern bool ReadProcessMemory(int hProcess, int lpBaseAddress, byte[] lpBuffer, int dwSize, ref int lpNumberOfBytesRead);
تُستخدم لقراءة عدد من البايتات بدءًا من عنوان ذاكرة محدد.
يتطلب PROCESS_WM_READ.
OpenProcess()
[DllImport("kernel32.dll")]
public static extern IntPtr OpenProcess(int dwDesiredAccess, bool bInheritHandle, int dwProcessId);
إرجاع مؤشر إلى عملية محددة – يجب فتح العملية باستخدام PROCESS_QUERY_INFORMATION و PROCESS_WM_READ.
نص التعليمات البرمجية المصدري
بمجرد أن تفهم ما يحدث أعلاه ، يمكننا الانتقال إلى بعض التعليمات البرمجية – ولكن نظرًا لعدم وجود المزيد لشرحها ، سأقدم نص التعليمات البرمجية المصدري بالكامل وأغطي ما تبقى باستخدام التعليقات.
using System;
using System.Diagnostics;
using System.IO;
using System.Runtime.InteropServices;
namespace MemoryScanner
{
class Program
{
// REQUIRED CONSTS
const int PROCESS_QUERY_INFORMATION = 0x0400;
const int MEM_COMMIT = 0x00001000;
const int PAGE_READWRITE = 0x04;
const int PROCESS_WM_READ = 0x0010;
// REQUIRED METHODS
[DllImport("kernel32.dll")]
public static extern IntPtr OpenProcess(int dwDesiredAccess, bool bInheritHandle, int dwProcessId);
[DllImport("kernel32.dll")]
public static extern bool ReadProcessMemory(int hProcess, int lpBaseAddress, byte[] lpBuffer, int dwSize, ref int lpNumberOfBytesRead);
[DllImport("kernel32.dll")]
static extern void GetSystemInfo(out SYSTEM_INFO lpSystemInfo);
[DllImport("kernel32.dll", SetLastError=true)]
static extern int VirtualQueryEx(IntPtr hProcess, IntPtr lpAddress, out MEMORY_BASIC_INFORMATION lpBuffer, uint dwLength);
// REQUIRED STRUCTS
public struct MEMORY_BASIC_INFORMATION
{
public int BaseAddress;
public int AllocationBase;
public int AllocationProtect;
public int RegionSize;
public int State;
public int Protect;
public int lType;
}
public struct SYSTEM_INFO
{
public ushort processorArchitecture;
ushort reserved;
public uint pageSize;
public IntPtr minimumApplicationAddress;
public IntPtr maximumApplicationAddress;
public IntPtr activeProcessorMask;
public uint numberOfProcessors;
public uint processorType;
public uint allocationGranularity;
public ushort processorLevel;
public ushort processorRevision;
}
// finally...
public static void Main()
{
// getting minimum & maximum address
SYSTEM_INFO sys_info = new SYSTEM_INFO();
GetSystemInfo(out sys_info);
IntPtr proc_min_address = sys_info.minimumApplicationAddress;
IntPtr proc_max_address = sys_info.maximumApplicationAddress;
// saving the values as long ints so I won't have to do a lot of casts later
long proc_min_address_l = (long)proc_min_address;
long proc_max_address_l = (long)proc_max_address;
// notepad better be runnin'
Process process = Process.GetProcessesByName("notepad")[0];
// opening the process with desired access level
IntPtr processHandle = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_WM_READ, false, process.Id);
StreamWriter sw = new StreamWriter("dump.txt");
// this will store any information we get from VirtualQueryEx()
MEMORY_BASIC_INFORMATION mem_basic_info = new MEMORY_BASIC_INFORMATION();
int bytesRead = 0; // number of bytes read with ReadProcessMemory
while (proc_min_address_l < proc_max_address_l)
{
// 28 = sizeof(MEMORY_BASIC_INFORMATION)
VirtualQueryEx(processHandle, proc_min_address, out mem_basic_info, 28);
// if this memory chunk is accessible
if (mem_basic_info.Protect == PAGE_READWRITE && mem_basic_info.State == MEM_COMMIT)
{
byte[] buffer = new byte[mem_basic_info.RegionSize];
// read everything in the buffer above
ReadProcessMemory((int)processHandle, mem_basic_info.BaseAddress, buffer, mem_basic_info.RegionSize, ref bytesRead);
// then output this in the file
for (int i = 0; i < mem_basic_info.RegionSize; i++)
sw.WriteLine("0x{0} : {1}", (mem_basic_info.BaseAddress+i).ToString("X"), (char)buffer[i]);
}
// move to the next memory chunk
proc_min_address_l += mem_basic_info.RegionSize;
proc_min_address = new IntPtr(proc_min_address_l);
}
sw.Close();
Console.ReadLine();
}
}
}
المرجع : https://codingvision.net/c-how-to-scan-a-process-memory