!vadump або перший крок на шляху перетворення PowerShell в WinDbg

Три чукотських мудреця
Твердять, твердять мені без кінця,
Мовляв, PoSh не принесе плоду,
Гра не варта свічок,
А результат — праці.
Але я саджу powershell'івські огрурцы, а-а-а,
На DotNet'вському полі...


Ті, хто досить часто спілкується з WinDbg, знають про команду !vadump, що відображає весь діапазон віртуальної пам'яті процесу з відповідними рівнями захисту, — більш детальну інформацію можна почерпнути в керівництві WinDbg. В сутності це розширення всього лише обгортка над апишной функцією VirtualQueryEx, яка в свою чергу задіює Native API, а саме NtQueryVirtualMemory — і так далі. Втім, якщо не лізти в нетрі, а також враховуючи досвід використання рефлексії в купе з узагальненими делегатами, можна цілком написати !vadump на PowerShell самостійно. І якщо хтось зараз захотів відкрити свій васистдас і сказати, мовляв, на кількість воно потрібно, то для початку поставте собі питання: а для чого, власне, взагалі все робиться?

Що нам знадобиться? GetSystemInfo, OpenProcess, VirtualQueryEx, — це що стосується WinAPI. CloseHandle для OpenProcess не потрібен, так як зворотний тип викликається рефлекторно OpenProcess — SafeProcessHandle, отже, Розпоряджатися і 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-Представник 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, але так як узагальнені делегати не підтримують ref\out, ми пішли на невелику хитрість, записуючи дані в масив байтів. 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#, чай, відправна точка є, так що справа за малим.
Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.