Три чукотских мудреца
Твердят, твердят мне без конца,
Мол, PoSh не принесет плода,
Игра не стоит свеч,
А результат — труда.
Но я сажаю powershell'овские огрурцы, а-а-а,
На DotNet'овском поле...
Те, кто довольно часто общается с WinDbg, знают о команде !vadump, отображающей весь диапазон виртуальной памяти процесса с соответствующими уровнями защиты, — более подробную информацию можно почерпнуть в руководстве WinDbg. В сущности данное расширение всего лишь обертка над апишной функцией VirtualQueryEx, которая в свою очередь задействует Native API, а именно NtQueryVirtualMemory — и так далее. Впрочем, если не лезть в дебри, а также учитывая опыт использования рефлексии в купе с обобщенными делегатами, можно вполне написать !vadump на PowerShell самостоятельно. И если кто-то сейчас захотел открыть свой васистдас и сказать, дескать, на кол оно нужно, то для начала задайтесь вопросом: а для чего, собственно, вообще все делается?
Что нам понадобится? GetSystemInfo, OpenProcess, VirtualQueryEx, — это что касается WinAPI. CloseHandle для OpenProcess не нужен, так как возвращаемый тип вызываемой рефлекторно OpenProcess — SafeProcessHandle, следовательно, Dispose и Close нам в руки. Для начала получим указатель на так называемый старший адрес памяти, доступный приложениям и dll'кам.
$GetSystemInfo = [Object].Assembly.GetType(
'Microsoft.Win32.Win32Native'
).GetMethod(
'GetSystemInfo', [Reflection.BindingFlags]40
)
$si = [Activator]::CreateInstance([Object].Assembly.GetType(
($GetSystemInfo.GetParameters() | ForEach-Object {
$_.ParameterType.FullName.Trim('&')
})
))
$si — это структура SYSTEM_INFO.
$GetSystemInfo.Invoke($null, ($par = [Object[]]@($si)))
$max = $par[0].GetType().GetField(
'lpMaximumApplicationAddress', [Reflection.BindingFlags]36
).GetValue($par[0])
$max — это и есть искомый указатель. Получаем хэндл процесса (относительно ID):
$OpenProcess = [Regex].Assembly.GetType(
'Microsoft.Win32.NativeMethods'
).GetMethod('OpenProcess')
if (($sph = $OpenProcess.Invoke($null, @(0x400, $false, $Id))).IsInvalid) {
break
}
$ptr = $sph.DangerousGetHandle()
VirtualQueryEx вызываем посредством обобщенного делегата Func:
$VirtualQueryEx = Set-Delegate kernel32 VirtualQueryEx `
'[Func[IntPtr, IntPtr, [Byte[]], UInt32, Int32]]'
$mbi = New-Object Byte[] 28 #MEMORY_BASIC_INFORMATION
if ($VirtualQueryEx.Invoke($ptr, [IntPtr]::Zero, $mbi, $mbi.Length)) {
$res = @()
$read = Read-Bytes $mbi
$res += $read
while (($adr = [Int32]$read.BaseAddress + [Int32]$read.RegionSize) -lt $max.ToInt32()) {
$mbi = New-Object Byte[] 28
if (!$VirtualQueryEx.Invoke($ptr, [IntPtr]$adr, $mbi, $mbi.Length)) { break }
$read = Read-Bytes $mbi
$res += $read
}
$res | Format-Table -AutoSize
}
Что из себя представляет функция Read-Bytes? По идее третьим параметром VirtualQueryEx является указатель на структуру MEMORY_BASIC_INFORMATION, но так как обобщенные делегаты не поддерживают refout, мы пошли на небольшую хитрость, записывая данные в массив байтов. Read-Bytes приводит данные из массива к простому для восприятия виду.
function private:Read-Bytes([Byte[]]$bytes) {
New-Object PSObject -Property @{
BaseAddress = [BitConverter]::ToUInt32($bytes[0..3], 0)
AllocationBase = [BitConverter]::ToUInt32($bytes[4..7], 0)
AllocationProtect = (
($MEM_PROTECT.Keys | ForEach-Object {
$$ = [BitConverter]::ToUint32($bytes[8..11], 0)
}{
if (($$ -band $MEM_PROTECT[$_]) -eq $MEM_PROTECT[$_]) {$_}
}) -join ', '
)
RegionSize = [BitConverter]::ToUInt32($bytes[12..15], 0)
State = (
($MEM_STATE.Keys | ForEach-Object {
$$ = [BitConverter]::ToUInt32($bytes[16..19], 0)
}{
if (($$ -band $MEM_STATE[$_]) -eq $MEM_STATE[$_]) {$_}
}) -join ', '
)
Protect = (
($MEM_PROTECT.Keys | ForEach-Object {
$$ = [BitConverter]::ToUInt32($bytes[20..23], 0)
}{
if (($$ -band $MEM_PROTECT[$_]) -eq $MEM_PROTECT[$_]) {$_}
}) -join ', '
)
Type = (
($MEM_TYPE.Keys | ForEach-Object {
$$ = [BitConverter]::ToUInt32($bytes[24..27], 0)
}{
if (($$ -band $MEM_TYPE[$_]) -eq $MEM_TYPE[$_]) {$_}
}) -join ', '
)
} | Select-Object @{N='BaseAddress';E={
'0x{0:X8}' -f $_.BaseAddress
}}, @{N='AllocationBase';E={
'0x{0:X8}' -f $_.AllocationBase
}}, AllocationProtect, @{N='RegionSize';E={
'0x{0:X8}' -f $_.RegionSize
}}, State, Protect, Type
}
Где $MEM_PROTECT, $MEM_STATE и $MEM_TYPE:
$MEM_PROTECT = @{
PAGE_NOACCESS = 0x00000001
PAGE_READONLY = 0x00000002
PAGE_READWRITE = 0x00000004
PAGE_WRITECOPY = 0x00000008
PAGE_EXECUTE = 0x00000010
PAGE_EXECUTE_READ = 0x00000020
PAGE_EXECUTE_READWRITE = 0x00000040
PAGE_EXECUTE_WRITECOPY = 0x00000080
PAGE_GUARD = 0x00000100
PAGE_NOCACHE = 0x00000200
PAGE_WRITECOMBINE = 0x00000400
}
$MEM_STATE = @{
MEM_COMMIT = 0x00001000
MEM_RESERVE = 0x00002000
MEM_FREE = 0x00010000
}
$MEM_TYPE = @{
MEM_PRIVATE = 0x00020000
MEM_MAPPED = 0x00040000
MEM_IMAGE = 0x01000000
}
Пример того, как будет выглядеть вывод для некоторого процесса:
BaseAddress AllocationBase AllocationProtect RegionSize State Protect Type
----------- -------------- ----------------- ---------- ----- ------- ----
0x00000000 0x00000000 0x00010000 MEM_FREE PAGE_NOACCESS
0x00010000 0x00010000 PAGE_READWRITE 0x00001000 MEM_COMMIT PAGE_READWRITE MEM_PRIVATE
0x00011000 0x00000000 0x0000F000 MEM_FREE PAGE_NOACCESS
0x00020000 0x00020000 PAGE_READWRITE 0x00001000 MEM_COMMIT PAGE_READWRITE MEM_PRIVATE
0x00021000 0x00000000 0x0000F000 MEM_FREE PAGE_NOACCESS
0x00030000 0x00030000 PAGE_READWRITE 0x000F2000 MEM_RESERVE MEM_PRIVATE
0x00122000 0x00030000 PAGE_READWRITE 0x00001000 MEM_COMMIT PAGE_READWRITE, PAGE_GUARD MEM_PRIVATE
0x00123000 0x00030000 PAGE_READWRITE 0x0000D000 MEM_COMMIT PAGE_READWRITE MEM_PRIVATE
...
Если не пихать предварительно данные в итоговый массив для последующего форматирования, сценарий будет работать, понятное дело, быстрее. Еще быстрее — если написать командлет целиком на C#, чай, отправная точка есть, так что дело за малым.
Автор: GrigoriZakharov