DllInject или обобщенные делегаты часть вторая
Если в CLR 2 количество параметров обощенных делегатов было ограничено пятью, то в CLR 4 это количество стало равно шестнадцати, что в свою очередь открывает больше возможностей для полета фантазии: не составляет особого труда вызывать такие функции как CreateRemoteThread или VirtualAllocEx, — чуете куда ветер дует? Именно, можно вполне себе внедрять Dll’ки в доступные для текущего пользователя процессы.
Для успешного внедрения нам нужны такие функции, как OpenProcess, CloseHandle, VirtualAllocEx, VirtualFreeEx, CreateRemoteThread, LoadLibraryW, WriteProcessMemory и WaitForSingleObject — все экспортируются из kernel32.dll, так что начало нашего сценария будет выглядеть так:
@(
[Runtime.InteropServices.CallingConvention],
[Reflection.Emit.OpCodes]
) | ForEach-Object {
$keys = ($ta = [PSObject].Assembly.GetType(
'System.Management.Automation.TypeAccelerators'
))::Get.Keys
}{
if ($keys -notcontains $_.Name) {
$ta::Add($_.name, $_)
}
}
function private:Get-ProcAddress {
param(
[Parameter(Mandatory=$true)]
[String]$Function
)
[Object].Assembly.GetType(
'Microsoft.Win32.Win32Native'
).GetMethods(
[Reflection.BindingFlags]40
).Where{
$_.Name -cmatch '\AGet(ProcA|ModuleH)'
}.ForEach{
Set-Variable $_.Name $_
}
$GetProcAddress.Invoke($null, @(
$GetModuleHandle.Invoke(
$null, @('kernel32.dll')
), $Function
))
}
function private:Set-Delegate {
param(
[Parameter(Mandatory=$true, Position=0)]
[ValidateScript({$_ -ne [IntPtr]::Zero})]
[IntPtr]$ProcAddress,
[Parameter(Mandatory=$true, Position=1)]
[ValidateNotNullOrEmpty()]
[String]$Delegate
)
$proto = Invoke-Expression $Delegate
$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{
$il.Emit([OpCodes]::Ldarg, $_)
}
switch ([IntPtr]::Size) {
4 { $il.Emit([OpCodes]::Ldc_I4, $ProcAddress.ToInt32()) }
8 { $il.Emit([OpCodes]::Ldc_I8, $ProcAddress.ToInt64()) }
}
$il.EmitCalli(
[OpCodes]::Calli, [CallingConvention]::StdCall,
$returntype, $paramtypes
)
$il.Emit([OpCodes]::Ret)
$holder.CreateDelegate($proto)
}
Все это мы уже проходили, верно?! Разница лишь в «плюшках» синтаксиса PowerShell 5.
Ищем адреса нужных нам функций:
('CloseHandle', 'CreateRemoteThread', 'LoadLibraryW', 'VirtualFreeEx',
'OpenProcess', 'VirtualAllocEx', 'WaitForSingleObject', 'WriteProcessMemory'
).ForEach{
Set-Variable $_ (Get-ProcAddress $_)
}
А какой смысл заморачиваться, ища для каждой функции ее адрес? Прогнали через ForEach, занесли адрес в одноименную переменную. Осталось эти адреса «прикрутить» к делегатам.
$CloseHandle = Set-Delegate $CloseHandle '[Func[IntPtr, Boolean]]'
$OpenProcess = Set-Delegate $OpenProcess '[Func[UInt32, Boolean, Int32, IntPtr]]'
$VirtualAllocEx = Set-Delegate $VirtualAllocEx `
'[Func[IntPtr, IntPtr, Int32, UInt32, Uint32, IntPtr]]'
$VirtualFreeEx = Set-Delegate $VirtualFreeEx `
'[Func[IntPtr, IntPtr, Int32, UInt32, Boolean]]'
$CreateRemoteThread = Set-Delegate $CreateRemoteThread `
'[Func[IntPtr, IntPtr, UInt32, IntPtr, IntPtr, UInt32, [Byte[]], IntPtr]]'
$WaitForSingleObject = Set-Delegate $WaitForSingleObject `
'[Func[IntPtr, UInt32, UInt32]]'
$WriteProcessMemory = Set-Delegate $WriteProcessMemory `
'[Func[IntPtr, IntPtr, [Byte[]], Int32, IntPtr, Boolean]]'
Далее — инжект:
if (($proc = $OpenProcess.Invoke(0x42A, $false, $Id)) -ne [IntPtr]::Zero) {
$sz = ((Get-Item $DllPath).Length + 1) * 2
if (($lrem = $VirtualAllocEx.Invoke($proc, [IntPtr]::Zero, $sz, 0x1000, 0x4)
) -ne [IntPtr]::Zero) {
$bytes = [Text.Encoding]::Unicode.GetBytes($DllPath)
if ($WriteProcessMemory.Invoke($proc, $lrem, $bytes, $sz, [IntPtr]::Zero)) {
$bytes = [Byte[]]@(0, 0, 0, 0)
if (($thrd = $CreateRemoteThread.Invoke(
$proc, [IntPtr]::Zero, 0, $LoadLibraryW, $lrem, 0, $bytes
)) -ne [IntPtr]::Zero) {
[void]$WaitForSingleObject.Invoke($thrd, $INFINITE)
[void]$CloseHandle.Invoke($thrd)
}
}
[void]$VirtualFreeEx.Invoke($proc, $lrem, 0, 0x8000)
}
[void]$CloseHandle.Invoke($proc)
}
Открываем процесс с флагами PROCESS_QUERY_INFORMATION, PROCESS_CREATE_THREAD, PROCESS_VM_OPERATION, PROCESS_VM_WRITE, далее выделяем необходимый блок памяти для нашей Dll (0×1000 = MEM_COMMIT, 0×4 = PAGE_READWRITE); записываем данные о Dll’ке в память и вызываем поток, — все. Ну разве что осталось удалить акселераторы, созданные ранее:
('OpCodes', 'CallingConvention').ForEach{
[void]$ta::Remove($_)
}
Вот теперь действительно все. Остается лишь напомнить, что код писался на 32-битной Win7, так как какой-либо другой виндой автор не располагает.
#requires -version 5
function Invoke-InjectLibrary {
param(
[Parameter(Mandatory=$true, Position=0)]
[ValidateScript({(Get-Process -Id $_ -ea 0) -ne $null})]
[Int32]$Id,
[Parameter(Mandatory=$true, Position=1)]
[ValidateScript({Test-Path $_})]
[String]$DllPath
)
begin {
@(
[Runtime.InteropServices.CallingConvention],
[Reflection.Emit.OpCodes]
) | ForEach-Object {
$keys = ($ta = [PSObject].Assembly.GetType(
'System.Management.Automation.TypeAccelerators'
))::Get.Keys
}{
if ($keys -notcontains $_.Name) {
$ta::Add($_.name, $_)
}
}
function private:Get-ProcAddress {
param(
[Parameter(Mandatory=$true)]
[String]$Function
)
[Object].Assembly.GetType(
'Microsoft.Win32.Win32Native'
).GetMethods(
[Reflection.BindingFlags]40
).Where{
$_.Name -cmatch '\AGet(ProcA|ModuleH)'
}.ForEach{
Set-Variable $_.Name $_
}
$GetProcAddress.Invoke($null, @(
$GetModuleHandle.Invoke(
$null, @('kernel32.dll')
), $Function
))
}
function private:Set-Delegate {
param(
[Parameter(Mandatory=$true, Position=0)]
[ValidateScript({$_ -ne [IntPtr]::Zero})]
[IntPtr]$ProcAddress,
[Parameter(Mandatory=$true, Position=1)]
[ValidateNotNullOrEmpty()]
[String]$Delegate
)
$proto = Invoke-Expression $Delegate
$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{
$il.Emit([OpCodes]::Ldarg, $_)
}
switch ([IntPtr]::Size) {
4 { $il.Emit([OpCodes]::Ldc_I4, $ProcAddress.ToInt32()) }
8 { $il.Emit([OpCodes]::Ldc_I8, $ProcAddress.ToInt64()) }
}
$il.EmitCalli(
[OpCodes]::Calli, [CallingConvention]::StdCall,
$returntype, $paramtypes
)
$il.Emit([OpCodes]::Ret)
$holder.CreateDelegate($proto)
}
('CloseHandle', 'CreateRemoteThread', 'LoadLibraryW', 'VirtualFreeEx',
'OpenProcess', 'VirtualAllocEx', 'WaitForSingleObject', 'WriteProcessMemory'
).ForEach{
Set-Variable $_ (Get-ProcAddress $_)
}
$CloseHandle = Set-Delegate $CloseHandle '[Func[IntPtr, Boolean]]'
$OpenProcess = Set-Delegate $OpenProcess '[Func[UInt32, Boolean, Int32, IntPtr]]'
$VirtualAllocEx = Set-Delegate $VirtualAllocEx `
'[Func[IntPtr, IntPtr, Int32, UInt32, Uint32, IntPtr]]'
$VirtualFreeEx = Set-Delegate $VirtualFreeEx `
'[Func[IntPtr, IntPtr, Int32, UInt32, Boolean]]'
$CreateRemoteThread = Set-Delegate $CreateRemoteThread `
'[Func[IntPtr, IntPtr, UInt32, IntPtr, IntPtr, UInt32, [Byte[]], IntPtr]]'
$WaitForSingleObject = Set-Delegate $WaitForSingleObject `
'[Func[IntPtr, UInt32, UInt32]]'
$WriteProcessMemory = Set-Delegate $WriteProcessMemory `
'[Func[IntPtr, IntPtr, [Byte[]], Int32, IntPtr, Boolean]]'
$DllPath = Resolve-Path $DllPath
$INFINITE = [BitConverter]::ToUInt32([BitConverter]::GetBytes(0xFFFFFFFF), 0)
}
process {
if (($proc = $OpenProcess.Invoke(0x42A, $false, $Id)) -ne [IntPtr]::Zero) {
$sz = ((Get-Item $DllPath).Length + 1) * 2
if (($lrem = $VirtualAllocEx.Invoke($proc, [IntPtr]::Zero, $sz, 0x1000, 0x4)
) -ne [IntPtr]::Zero) {
$bytes = [Text.Encoding]::Unicode.GetBytes($DllPath)
if ($WriteProcessMemory.Invoke($proc, $lrem, $bytes, $sz, [IntPtr]::Zero)) {
$bytes = [Byte[]]@(0, 0, 0, 0)
if (($thrd = $CreateRemoteThread.Invoke(
$proc, [IntPtr]::Zero, 0, $LoadLibraryW, $lrem, 0, $bytes
)) -ne [IntPtr]::Zero) {
[void]$WaitForSingleObject.Invoke($thrd, $INFINITE)
[void]$CloseHandle.Invoke($thrd)
}
}
[void]$VirtualFreeEx.Invoke($proc, $lrem, 0, 0x8000)
}
[void]$CloseHandle.Invoke($proc)
}
}
end {
('OpCodes', 'CallingConvention').ForEach{
[void]$ta::Remove($_)
}
}
}