Інлайн асемблер в PowerShell

Сьогодні вночі мені не давала спокою думка чи можливо якось змусити PowerShell виконувати ассеблерные інструкції або це щось з області фантастики. Концепт був готовий через двадцять хвилин.

Ті, хто не перший рік на «ти» з C#, знають, про що піде далі мова: все вірно — подання ассемблерних інструкцій у вигляді масиву байтів. Єдине, що може здатися неймовірним у всій це історії, як цей масив вдалося перетворити в функцію. Вважаю, хто вже здогадався, буде бурчати, мовляв, знову узагальнені делегати. Адже коли я про них тільки упомянал, багато хто скептично поставилися до всього цього, бо саму сіль, а саме відсутність необхідності генерування динамічної збірки при використанні таких делегатів, мало хто вловив.

Отже, концепт являє собою отримання вендора CPU.

Набір байтів для архітектури x86 буде виглядати так:

[Byte[]]$x86 = @(
0x55, #push ebp
0x8B, 0xEC, #mov ebp, esp
0x53, #push ebx
0x57, #push edi
0x8B, 0x45, 0x08, #mov eax, dword ptr[ebp+8]
0x0F, 0xA2, #cpuid
0x8B, 0x7D, 0x0C, #mov edi, dword ptr[ebp+12]
0x89, 0x07, #mov dword ptr[edi+0], eax
0x89, 0x5F, 0x04, #mov dword ptr[edi+4], ebx
0x89, 0x4F, 0x08, #mov dword ptr[edi+8], ecx
0x89, 0x57, 0x0C, #mov dword ptr[edi+12], edx
0x5F, #pop edi
0x5B, #pop ebx
0x8B, 0xE5, #mov esp, ebp
0x5D, #pop ebp
0xC3 #ret
)

Для x64:

[Byte[]]$x64 = @(
0x53, #push rbx
0x49, 0x89, 0xD0, #mov r8, rdx
0x89, 0xC8, #mov eax, ecx
0x0F, 0xA2, #cpuid
0x41, 0x89, 0x40, 0x00, #mov dword ptr[r8+0], eax
0x41, 0x89, 0x58, 0x04, #mov dword ptr[r8+4], ebx
0x41, 0x89, 0x48, 0x08, #mov dword ptr[r8+8], ecx
0x41, 0x89, 0x50, 0x0C, #mov dword ptr[r8+12], edx
0x5B, #pop rbx
0xC3 #ret
)

Далі нам потрібно виділити ділянку пам'яті у віртуальному адресному просторі і запхати в отриманий покажчик один з вищеозначених масивів.

#у цій збірці можна розжитися VirtualAlloc і VirtualFree
Add-Type -AssemblyName System.ServiceModel
#витягаємо потрібні нам функції і заносимо їх в однойменні змінні
([AppDomain]::CurrentDomain.GetAssemblies() |
Where-Object {
$_.ManifestModule.ScopeName.Equals(
'System.ServiceModel.dll'
)
}).GetType(
'System.ServiceModel.Channels.UnsafeNativeMethods'
).GetMethods([Reflection.BindingFlags]40) |
Where-Object {
$_.Name -cmatch '\AVirtual(Alloc|Free)'
} | ForEach-Object { Set-Variable $_.Name $_ }
#який блок потрібно запхати
[Byte[]]$bytes = switch ([IntPtr]::Size) {
4 { $x86 }
8 { $x64 }
}

try {
#виділяємо пам'ять дорівнює розміру масиву байтів
$ptr = $VirtualAlloc.Invoke($null @(
[IntPtr]::Zero, [UIntPtr](New-Object UIntPtr($bytes.Length)),
[UInt32](0x1000 -bor 0x2000), [UInt32]0x40
))
#запихаємо дані покажчик
[Marshal]::Copy($bytes, 0, $ptr, $bytes.Length)
...

Далі використовуємо вже відому нам техніку створення функції через узагальнений делегат.

...
$proto = [Action[Int32 [Byte []]]]
$method = $proto.GetMethod('Invoke')

$returntype = $method.ReturnType
$paramtypes = $method.GetParameters() |
Select-Object -ExpandProperty ParameterType

$holder = New Object Reflection.Emit.DynamicMethod(
'Invoke', $returntype, $paramtypes, $proto
)
$il = $holder.GetILGenerator()
0..($paramtypes.Length - 1) | ForEach-Object {
$il.Emit([OpCodes]::Ldarg, $_)
}

switch ([IntPtr]::Size) {
4 { $il.Emit([OpCodes]::Ldc_I4, $ptr.ToInt32()) }
8 { $il.Emit([OpCodes]::Ldc_I8, $ptr.ToInt64()) }
}
$il.EmitCalli(
[OpCodes]::Calli, [CallingConvention]::Cdecl, $returntype, $paramtypes
)
$il.Emit([OpCodes]::Ret)

$cpuid = $holder.CreateDelegate($proto)
...

Залишається тільки прочитати вендора і звільнити покажчик.

...
[Byte[]]$buf = New Object Byte[] 16
$gch = [GCHandle]::Alloc($buf, 'Pinned')
$cpuid.Invoke(0, $buf)
$gch.Free()

"$(-join [Char[]]$buf[4..7])$(
-join [Char[]]$buf[12..15]
)$(-join [Char[]]$buf[8..11])"
}
catch { $_.Exception }
finally {
if ($ptr) {
[void]$VirtualFree.Invoke($null @($ptr, [UIntPtr]::Zero, [UInt32]0x8000))
}
}

В моєму випадку буде повернено рядок AuthenticAMD.

Приклад дуже простий, щоб розібратися що до чого. Якщо пофантазувати, можна цілком собі писати таким чином на PowerShell більш просунуту малварь, ніж ті вироби, які час від часу засмоктує ханипот. Пара концептів малварі, написана в такому ключі, в лабораторних умовах показує непоганий результат за відсутності детекції антивірусами, що в черговий раз наштовхує на думку про неспроможність останніх справлятися з новими видами загроз.

Код повністю
function Get-CPUVendor {
begin {
@(
[Runtime.InteropServices.CallingConvention],
[Runtime.InteropServices.GCHandle],
[Runtime.InteropServices.Marshal],
[Reflection.Emit.OpCodes]
) | ForEach-Object {
$keys = ($ta = [PSObject].Assembly.GetType(
'System.Management.Automation.TypeAccelerators'
))::Get.Keys
$collect = @()
}{
if ($keys -notcontains $_.Name) {
$ta::Add($_.Name, $_)
$collect += $_.Name
}
}

Add-Type -AssemblyName System.ServiceModel

([AppDomain]::CurrentDomain.GetAssemblies() |
Where-Object {
$_.ManifestModule.ScopeName.Equals(
'System.ServiceModel.dll'
)
}).GetType(
'System.ServiceModel.Channels.UnsafeNativeMethods'
).GetMethods([Reflection.BindingFlags]40) |
Where-Object {
$_.Name -cmatch '\AVirtual(Alloc|Free)'
} | ForEach-Object { Set-Variable $_.Name $_ }

[Byte[]]$x86 = @(
0x55, #push ebp
0x8B, 0xEC, #mov ebp, esp
0x53, #push ebx
0x57, #push edi
0x8B, 0x45, 0x08, #mov eax, dword ptr[ebp+8]
0x0F, 0xA2, #cpuid
0x8B, 0x7D, 0x0C, #mov edi, dword ptr[ebp+12]
0x89, 0x07, #mov dword ptr[edi+0], eax
0x89, 0x5F, 0x04, #mov dword ptr[edi+4], ebx
0x89, 0x4F, 0x08, #mov dword ptr[edi+8], ecx
0x89, 0x57, 0x0C, #mov dword ptr[edi+12], edx
0x5F, #pop edi
0x5B, #pop ebx
0x8B, 0xE5, #mov esp, ebp
0x5D, #pop ebp
0xC3 #ret
)

[Byte[]]$x64 = @(
0x53, #push rbx
0x49, 0x89, 0xD0, #mov r8, rdx
0x89, 0xC8, #mov eax, ecx
0x0F, 0xA2, #cpuid
0x41, 0x89, 0x40, 0x00, #mov dword ptr[r8+0], eax
0x41, 0x89, 0x58, 0x04, #mov dword ptr[r8+4], ebx
0x41, 0x89, 0x48, 0x08, #mov dword ptr[r8+8], ecx
0x41, 0x89, 0x50, 0x0C, #mov dword ptr[r8+12], edx
0x5B, #pop rbx
0xC3 #ret
)

[Byte[]]$bytes = switch ([IntPtr]::Size) {
4 { $x86 }
8 { $x64 }
}
}
process {
try {
$ptr = $VirtualAlloc.Invoke($null @(
[IntPtr]::Zero, [UIntPtr](New-Object UIntPtr($bytes.Length)),
[UInt32](0x1000 -bor 0x2000), [UInt32]0x40
))

[Marshal]::Copy($bytes, 0, $ptr, $bytes.Length)

$proto = [Action[Int32 [Byte []]]]
$method = $proto.GetMethod('Invoke')

$returntype = $method.ReturnType
$paramtypes = $method.GetParameters() |
Select-Object -ExpandProperty ParameterType

$holder = New Object Reflection.Emit.DynamicMethod(
'Invoke', $returntype, $paramtypes, $proto
)
$il = $holder.GetILGenerator()
0..($paramtypes.Length - 1) | ForEach-Object {
$il.Emit([OpCodes]::Ldarg, $_)
}

switch ([IntPtr]::Size) {
4 { $il.Emit([OpCodes]::Ldc_I4, $ptr.ToInt32()) }
8 { $il.Emit([OpCodes]::Ldc_I8, $ptr.ToInt64()) }
}
$il.EmitCalli(
[OpCodes]::Calli, [CallingConvention]::Cdecl, $returntype, $paramtypes
)
$il.Emit([OpCodes]::Ret)

$cpuid = $holder.CreateDelegate($proto)

[Byte[]]$buf = New Object Byte[] 16
$gch = [GCHandle]::Alloc($buf, 'Pinned')
$cpuid.Invoke(0, $buf)
$gch.Free()

"$(-join [Char[]]$buf[4..7])$(
-join [Char[]]$buf[12..15]
)$(-join [Char[]]$buf[8..11])"
}
catch { $_.Exception }
finally {
if ($ptr) {
[void]$VirtualFree.Invoke($null @($ptr, [UIntPtr]::Zero, [UInt32]0x8000))
}
}
}
end {
$collect | ForEach-Object { [void]$ta::Remove($_) }
}
}


Джерело: Хабрахабр

0 коментарів

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