惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

MongoDB | Blog
MongoDB | Blog
博客园 - 聂微东
Attack and Defense Labs
Attack and Defense Labs
WordPress大学
WordPress大学
D
Darknet – Hacking Tools, Hacker News & Cyber Security
Spread Privacy
Spread Privacy
AI
AI
宝玉的分享
宝玉的分享
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
C
Cyber Attacks, Cyber Crime and Cyber Security
爱范儿
爱范儿
Help Net Security
Help Net Security
V
Visual Studio Blog
大猫的无限游戏
大猫的无限游戏
Forbes - Security
Forbes - Security
P
Privacy & Cybersecurity Law Blog
Project Zero
Project Zero
IT之家
IT之家
Hugging Face - Blog
Hugging Face - Blog
博客园 - 三生石上(FineUI控件)
S
SegmentFault 最新的问题
有赞技术团队
有赞技术团队
T
Troy Hunt's Blog
美团技术团队
T
Threatpost
K
Kaspersky official blog
V
V2EX
Scott Helme
Scott Helme
Vercel News
Vercel News
T
The Blog of Author Tim Ferriss
T
Tailwind CSS Blog
V
Vulnerabilities – Threatpost
Last Week in AI
Last Week in AI
PCI Perspectives
PCI Perspectives
Google Online Security Blog
Google Online Security Blog
Apple Machine Learning Research
Apple Machine Learning Research
Engineering at Meta
Engineering at Meta
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
I
InfoQ
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
酷 壳 – CoolShell
酷 壳 – CoolShell
GbyAI
GbyAI
L
LINUX DO - 最新话题
T
The Exploit Database - CXSecurity.com
L
LangChain Blog
S
Security @ Cisco Blogs
The Last Watchdog
The Last Watchdog
H
Hacker News: Front Page
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
TaoSecurity Blog
TaoSecurity Blog

f2h2h1's blog

使用yii3实现一个微框架 claw养殖技术 计算机网络基础知识 定时任务 ACME的使用经验 magento2加上varnish缓存 开发Magento2的模块 在magento2中使用persisted-query socket编程 一些开发笔记 一段CSDN文章主要内容的油猴脚本 电子邮件的不完整总结 git的笔记 在Windows下配置PHP服务器 终端,控制台和外壳 PHP各种运行方式的不完整总结 把网页导出成PDF 和颜色相关的笔记 HTTP认证方式的不完整总结 SEO的经验 密码学入门简明指南 文件的上传和下载 用纯CSS3实现的滑动按钮 在VSCode里调试PHP Linux的GUI 关于字符编码的一些坑 nc的使用和原理 在Windows下安装Magento2 对JS原型链的理解 使用docker-compose部署magento2 浏览器和服务器通讯方式的不完整总结 观察网站性能 一些关于Linux的笔记 telnet的不完整总结 在Windows下安装pear MySQL的时间类型和时间相关的函数 关于 在VSCode里使用Xdebug远程调试PHP 在Windows下搭建git服务 关于环境变量的不完整总结 使用shell实现的kv数据库 如何完成以xx管理系统为选题的毕业设计 数字号码资源 各种标记语言 使用PowerShell实现的http服务器 kind相关经验 DNSSEC简介 nginx+ffmpeg+websocket实现的直播例子 使用Tesseract识别字符验证码 使用docker部署nuxt FirstData后台的设置 paypal,firtdata,支付宝的不完整接入指南 微信支付的不完整接入指南 用docker-compose部署lnmp环境 mongodb分片 练习
Windows下通过PEB读取进程的环境变量
2024-04-11 · via f2h2h1's blog

这篇文章最后更新的时间在六个月之前,文章所叙述的内容可能已经失效,请谨慎参考!

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 是 Process Environment Block 进程 环境 块

笔者在网上搜索了很久才在这个站点里找到 PEB 详细的结构 http://terminus.rewolf.pl/terminus

通过 PEB 读取进程的环境变量的具体流程

  1. 获取进程id
    • 获取进程id的方式有非常多,最简单的方式就是通过任务管理器
  2. 获取进程的句柄(Handle)
    • 可以通过这个 OpenProcess api 来获取
  3. 根据 Handle 获取 PBI (PROCESS_BASIC_INFORMATION)
    • 可以通过这个 NtQueryInformationProcess api 来获取
  4. 从 PBI 中获取 PEB
    • 在结构体 PROCESS_BASIC_INFORMATION 中的 PebBaseAddress
  5. 从 PEB 中获取 ProcessParameters
    • 通过这个 ReadProcessMemory api 来获取
  6. 从 ProcessParameters 中获取 environment
    • 通过这个 ReadProcessMemory api 来获取
    • 在 win10 下,可以通过 ProcessParameters 中的 EnvironmentSize 获取环境变量具体的长度
    • win10 以下的版本只能通过逐个读取字符和空字符判断环境变量的长度
    • 读取的结果是一个连续的字符串,使用 \0 字符来分隔环境变量,使用连续的两个 \0 字符表示结束

相关的 结构体 和 api

  • PEB
  • 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 的语法差异。 要写一段兼容性好的脚本是一件麻烦的事,各种环境下都测试一次,笔者没有这样的条件 (笔者已经理解了大概的原理,没有动力完善了) 笔者在网上找到的脚本大多不能直接使用,就是兼容性的问题。

note

  • 要先确认当前的 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,
                0,
                [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

参考