Windows 下没有 api 能直接读取进程的环境变量。
wmi 和 powershell 也没有方式能读取进程的环境变量。
在 powershell 5.1 里确实可以通过象这样的命令读取环境变量,但这并不是进程的环境变量。
([System.Diagnostics.Process]::GetProcessesByName("php-cgi") | Select-Object -First 1 ).StartInfo.EnvironmentVariables
([System.Diagnostics.Process]::GetProcessesByName("php-cgi") | Select-Object -First 1 ).StartInfo.Environment
进程的 PEB
Windows 下只能通过 PEB 来读取环境变量。
PEB 是一个臃肿的结构体,且 msdn 里没有这个结构体的详细信息。
PEB 的具体结构根据操作系统的版本不同而不同,也和进程的位数有关。
PEB 里除了环境变量,还是有执行目录,可执行文件路径,命令行等信息。
笔者在网上搜索了很久才在这个站点里找到 PEB 详细的结构
http://terminus.rewolf.pl/terminus
通过 PEB 读取进程的环境变量的具体流程
获取进程id
- 获取进程id的方式有非常多,最简单的方式就是通过任务管理器
- 获取进程的句柄(Handle)
- 可以通过这个
OpenProcess
api 来获取
- 根据 Handle 获取 PBI (PROCESS_BASIC_INFORMATION)
- 可以通过这个
NtQueryInformationProcess
api 来获取
- 从 PBI 中获取 PEB
- 在结构体 PROCESS_BASIC_INFORMATION 中的 PebBaseAddress
- 从 PEB 中获取 ProcessParameters
- 通过这个
ReadProcessMemory
api 来获取
- 从 ProcessParameters 中获取 environment
- 通过这个
ReadProcessMemory
api 来获取
- 在 win10 下,可以通过 ProcessParameters 中的 EnvironmentSize 获取环境变量具体的长度
- win10 以下的版本只能通过逐个读取字符和空字符判断环境变量的长度
- 读取的结果是一个连续的字符串,使用
\0
字符来分隔环境变量,使用连续的两个 \0
字符表示结束
相关的 结构体 和 api
- PROCESS_BASIC_INFORMATION
- PROCESS_BASIC_INFORMATION_WOW64
- RTL_USER_PROCESS_PARAMETERS
- OpenProcess
- IsWow64Process
- NtQueryInformationProcess
- NtWow64QueryInformationProcess64
- ReadProcessMemory
这些 api 的文档里大多有这样的一句声明。
“在未来的Windows版本中可能会修改”
笔者其实不理解象PEB这么重要的结构微软为什么不公开详细的信息
读取进程环境变量的脚本
这是一段使用 powershell 通过 win32api 读取进程 PEB 中的环境变量的脚本。
这段脚本没有做太多的兼容性处理,估计只能运行在 64 位 win10 上,且只能用 64 位的 powershell 查询 64 位的进程。
这段脚本很容易被安全软件判断为恶意脚本。
PEB 的结构每个版本的 Windows 都有一点差异, 32位和64位也有明显的差异,还有考虑 WoW64 的环境,还要考虑各个版本的 powershell 的语法差异。
要写一段兼容性好的脚本是一件麻烦的事,各种环境下都测试一次,笔者没有这样的条件 (笔者已经理解了大概的原理,没有动力完善了)
笔者在网上找到的脚本大多不能直接使用,就是兼容性的问题。
- 要先确认当前的 Windows 版本,然后再查找对应 PEB 的结构
- 进程的数据要用 ReadProcessMemory 读取,即使是字符串
- 可以用 IsWow64Process 判断是不是 WoW64 的环境。
- WoW64 的环境的 PBI 要用这个结构体 PROCESS_BASIC_INFORMATION_WOW64
function Get-ProcessEnvironmentVariables {
param(
[int]$ProcessId
BEGIN
# Native API Definitions
Add-Type -TypeDefinition @"
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Security.Principal;
[StructLayout(LayoutKind.Sequential)]
public struct UNICODE_STRING
public UInt16 Length;
public UInt16 MaximumLength;
public IntPtr Buffer;
[StructLayout(LayoutKind.Sequential)]
public struct PROCESS_BASIC_INFORMATION
public IntPtr ExitStatus;
public IntPtr PebBaseAddress;
public IntPtr AffinityMask;
public IntPtr BasePriority;
public UIntPtr UniqueProcessId;
public IntPtr InheritedFromUniqueProcessId;
public static class ProcessEnvironmentHelper
[DllImport("kernel32.dll")]
public static extern Boolean ReadProcessMemory(
IntPtr hProcess,
IntPtr lpBaseAddress,
IntPtr lpBuffer,
UInt32 dwSize,
ref UInt32 lpNumberOfBytesRead
[DllImport("ntdll.dll")]
public static extern UInt32 NtQueryInformationProcess(
IntPtr processHandle,
UInt32 processInformationClass,
ref PROCESS_BASIC_INFORMATION processInformation,
UInt32 processInformationLength,
ref UInt32 returnLength
function Get-PBI {
param(
[IntPtr]$hProcess
$PROCESS_BASIC_INFORMATION = New-Object PROCESS_BASIC_INFORMATION
$PROCESS_BASIC_INFORMATION_Size = [System.Runtime.InteropServices.Marshal]::SizeOf($PROCESS_BASIC_INFORMATION)
[UInt32]$RetLen = 0
$CallResult = [ProcessEnvironmentHelper]::NtQueryInformationProcess(
$hProcess,
[ref]$PROCESS_BASIC_INFORMATION,
$PROCESS_BASIC_INFORMATION_Size,
[ref]$RetLen
if ($CallResult -ne 0) {
$false
} else {
$PROCESS_BASIC_INFORMATION
PROCESS
try {
$hProcess = [System.Diagnostics.Process]::GetProcessById($ProcessId).SafeHandle.DangerousGetHandle()
# get process info
# Get-CimInstance -ClassName Win32_Process -Filter "ProcessId = $ProcessId" | Select-Object -First 1 | Format-List *
} catch {
Write-Host "Failed to get process handle"
Write-Host $_
return 1;
Write-Host "[+] ProcessId " $ProcessId
Write-Host "[+] Handle " $hProcess
# Get process basic information
$ProcessBasicInformation = Get-PBI -hProcess $hProcess
Write-Host "[+] PBI " $ProcessBasicInformation
# Get PEBAddress
$PEBAddress = $ProcessBasicInformation.PebBaseAddress.ToInt64()
Write-Host "[+] PEB " $PEBAddress
# Get ProcessParameters
$AddressOffset = $PEBAddress + 0x20 # 64位
$ReadSize = 8
$BytesRead = 0
[IntPtr]$lpBuffer = [System.Runtime.InteropServices.Marshal]::AllocHGlobal($ReadSize)
$CallResult = [ProcessEnvironmentHelper]::ReadProcessMemory(
$hProcess,
$AddressOffset,
$lpBuffer,
$ReadSize,
[ref]$BytesRead
$ProcessParametersAddress = [System.Runtime.InteropServices.Marshal]::ReadInt64($lpBuffer.ToInt64())
# Get EnvironmentSize
$EnvironmentSizeAddress = $ProcessParametersAddress + 0x03F0
$AddressOffset = $EnvironmentSizeAddress
$ReadSize = 8
$BytesRead = 0
[IntPtr]$lpBuffer = [System.Runtime.InteropServices.Marshal]::AllocHGlobal($ReadSize)
$CallResult = [ProcessEnvironmentHelper]::ReadProcessMemory(
$hProcess,
$AddressOffset,
$lpBuffer,
$ReadSize,
[ref]$BytesRead
$EnvironmentSize = [System.Runtime.InteropServices.Marshal]::ReadInt64($lpBuffer.ToInt64())
Write-Host "[+] EnvironmentSize " $EnvironmentSize
# Get Environment
$EnvironmentAddress = $ProcessParametersAddress + 0x0080
$AddressOffset = $EnvironmentAddress
$ReadSize = 8
$BytesRead = 0
[IntPtr]$lpBuffer = [System.Runtime.InteropServices.Marshal]::AllocHGlobal($ReadSize)
$CallResult = [ProcessEnvironmentHelper]::ReadProcessMemory(
$hProcess,
$AddressOffset,
$lpBuffer,
$ReadSize,
[ref]$BytesRead
$AddressOffset = [System.Runtime.InteropServices.Marshal]::ReadInt64($lpBuffer.ToInt64())
$ReadSize = $EnvironmentSize
$BytesRead = 0
[IntPtr]$lpBuffer = [System.Runtime.InteropServices.Marshal]::AllocHGlobal($ReadSize)
$CallResult = [ProcessEnvironmentHelper]::ReadProcessMemory(
$hProcess,
$AddressOffset,
$lpBuffer,
$ReadSize,
[ref]$BytesRead
$ProcessEnvironmentVariables = [System.Runtime.InteropServices.Marshal]::PtrToStringAnsi($lpBuffer, $EnvironmentSize)
# Convert string to hashTable
$ProcessEnvironmentVariables = [System.Convert]::ToString($ProcessEnvironmentVariables)
$ProcessEnvironmentVariables = $ProcessEnvironmentVariables.Replace("`0`0", "`n")
$ProcessEnvironmentVariables = $ProcessEnvironmentVariables.Replace("`0", "")
if ($ProcessEnvironmentVariables[0] -eq '=') {
$ProcessEnvironmentVariables = $ProcessEnvironmentVariables.Split("`n")
$ProcessEnvironmentVariables = [System.Collections.ArrayList]::new($ProcessEnvironmentVariables)
$ProcessEnvironmentVariables.RemoveAt(0)
$ProcessEnvironmentVariables = $ProcessEnvironmentVariables.ToArray()
$ProcessEnvironmentVariables = $ProcessEnvironmentVariables -join "`n"
$ProcessEnvironmentVariables = $ProcessEnvironmentVariables.Replace("\", "\\")
$ProcessEnvironmentVariables = ConvertFrom-StringData -StringData $ProcessEnvironmentVariables
return $ProcessEnvironmentVariables
# $Id = ([System.Diagnostics.Process]::GetProcessesByName("php-cgi") | Select-Object -First 1 ).Id
# Get-ProcessEnvironmentVariables -ProcessId $Id
- https://docs.microsoft.com/en-us/windows/win32/api/winternl/nf-winternl-ntqueryinformationprocess
- https://docs.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-readprocessmemory
- https://docs.microsoft.com/en-us/windows/win32/api/winternl/ns-winternl-peb
- https://docs.microsoft.com/en-us/windows/win32/api/winternl/ns-winternl-rtl_user_process_parameters
- http://terminus.rewolf.pl/terminus/structures/ntdll/_PEB_x64.html
- http://terminus.rewolf.pl/terminus/structures/ntdll/_RTL_USER_PROCESS_PARAMETERS_x64.html
- https://www.powershellgallery.com/packages/PowerSploit/1.0.0.0/Content/ReverseEngineering%5CGet-PEB.ps1
- https://stackoverflow.com/questions/38660262/how-to-get-other-processs-environment-variable-using-c-sharp
- https://gist.github.com/xenoscr/99370ecffb07f629ae74e7808cb91450
- http://blog.rewolf.pl/blog/?cat=19
created at: 2022-08-11T18:24:55+08:00
updated at: 2024-04-11T18:31:09+08:00
如果文章内容有什么错误的地方,恳请各位大佬能通过 issues 或 邮件 的方式告诉作者
本作品采用
知识共享署名 4.0 国际许可协议
进行许可。
- Article list
- nc的使用和原理
- 定时任务
- ACME的使用经验
- 文件的上传和下载
- 终端,控制台和外壳
- 和颜色相关的笔记
- Windows下通过PEB读取进程的环境变量
- 关于环境变量的不完整总结
- 各种标记语言
- 使用PowerShell实现的http服务器
- MySQL的时间类型和时间相关的函数
- nginx+ffmpeg+websocket实现的直播例子
- kind相关经验
- 使用shell实现的kv数据库
- 对JS原型链的理解
- magento2加上varnish缓存
- 数字号码资源
- 使用docker-compose部署magento2
- 如何完成以xx管理系统为选题的毕业设计
- DNSSEC简介
- 在Windows下安装Magento2
- 用纯CSS3实现的滑动按钮
- Linux的GUI
- 使用Tesseract识别字符验证码
- 开发Magento2的模块
- 浏览器和服务器通讯方式的不完整总结
- HTTP认证方式的不完整总结
- PHP各种运行方式的不完整总结
- 把网页导出成PDF
- FirstData后台的设置
- SEO的经验
- paypal,firtdata,支付宝的不完整接入指南
- 微信支付的不完整接入指南
- 观察网站性能
- git的笔记
- 密码学入门简明指南
- 使用docker部署nuxt
- 在Windows下搭建git服务
- 用docker-compose部署lnmp环境
- mongodb分片
- 练习
- 一些开发笔记
- 一些关于Linux的笔记
- 在Windows下安装pear
- 在VSCode里使用Xdebug远程调试PHP
- 在Windows下配置PHP服务器
- 关于字符编码的一些坑
- 在VSCode里调试PHP
- 关于
- 共计 49 篇文章