PowerShell 技能连载 - 为输出编号(第 1 部分)

如果你想增加一个递增的数字到你的输出,这里有一个简单的方法:

1
2
3
4
5
6
7
Get-Process |
Select-Object -Property '#', ProcessName, CPU -First 10 |
ForEach-Object -begin { $i = 0} -process {
$i++
$_.'#' = $i
$_
} -end {}

Select-Object 添加了一个名为 “#“ 的新属性,ForEach-Object 添加了一个自动递增的数字。结果如下:

 # ProcessName               CPU
 - -----------               ---
 1 AdobeCollabSync       65,5625
 2 AdobeCollabSync           0,5
 3 AGMService
...

PowerShell 技能连载 - 接受屏蔽的密码

如果曾经写过需要接受密码等敏感输入的 PowerShell 函数,请确保允许用户传入 SecureString。如果您通过明文接受密码,则存在很大的风险,即其他人可能在输入密码时看到密码,或者(更糟的是)密码已被记录,稍后可以在转储文件中找到。

下面是一个简单的框架,说明如何实现安全输入:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function Enter-Secret
{
param
(
[Parameter(Mandatory)]
[SecureString]
$SafeInput
)

$PlainText = [Management.Automation.PSCredential]::
new('x',$SafeInput).GetNetworkCredential().Password

"User entered $PlainText"

}

当用户运行 Enter-Secret,可以以屏蔽的方式输入密码。在内部,函数将安全字符串转换为纯文本。这样,秘密密码就永远不可见,也永远不会被记录下来。

从 SecureString 到 String 的转换是通过创建一个临时凭证对象来执行的。凭据对象有一个内置方法 (GetNetworkCredential()),用于将SecureString 转换为字符串。

PowerShell 技能连载 - 查找隐藏的 PowerShell 应用

最广为人知的 PowerShell 宿主当然是 PowerShell.exe 和 powershell_ise.exe,因为它们是开箱即用的。但是,运行 PowerShell 的宿主可能更多(而且是隐藏的)。任何实例化 PowerShell 引擎的软件都是一个 PowerShell 宿主。这可以是 Visual Studio Code(安装了 PowerShell扩展)、Visual Studio 或任何其它类似的软件。

要找出所有当前运行 PowerShell 的宿主,请运行以下命令:

1
2
3
4
5
6
7
8
Get-ChildItem -Path "\\.\pipe\" -Filter '*pshost*' |
ForEach-Object {
$id = $_.Name.Split('.')[2]
if ($id -ne $pid)
{
Get-Process -ID $id
}
}

结果看起来类似这样:

Handles  NPM(K)    PM(K)      WS(K)     CPU(s)     Id  SI ProcessName
-------  ------    -----      -----     ------     --  -- -----------
   1131     101   628520      42440             11216   0 SupportAssistAgent
   1011      82   269920     299208      85,30  17420   1 powershell_ise
    520      29    68012      75880       1,23  33532   1 powershell
    590      31    69508      77712       2,02  36636   1 powershell
    545      27    67952      76668       1,14  37584   1 powershell
   4114     654   801136     965032     129,69  28968   1 devenv

“SupportAssistAgent” 是由 Visual Studio Code 打开的,而 “devenv” 代表由 Visual Studio 启动的内部 PowerShell 宿主。

PowerShell 技能连载 - 控制处理器关联性

大多数现代计算机都有不止一个处理器,无论是物理处理器还是逻辑处理器。如果你想知道处理器的数量,这里有一段 PowerShell 代码:

1
2
Get-WmiObject -Class Win32_Processor |
Select-Object -Property Caption, NumberOfLogicalProcessors

结果看起来类似这样:

Caption                              NumberOfLogicalProcessors
-------                              -------------------------
Intel64 Family 6 Model 78 Stepping 3                         4

当您在这样的机器上运行一个进程时,它通常没有特定的处理器关联性,因此 Windows 决定该进程将运行在哪个处理器上。

如果愿意,可以为每个进程声明一个特定的处理器关联。这是很有用的,例如,如果你想控制一个程序可以使用的处理器,也就是防止一个进程使用所有的处理器。

处理器关联性由位标志控制。要找出当前处理器与进程的关联关系,请使用以下代码:

1
2
$process = Get-Process -Id $PID
[Convert]::ToString([int]$process.ProcessorAffinity, 2)

在本例中,用的是当前的 PowerShell 进程,但是您可以指定任何进程。典型的结果是:

1111

在 4 处理器的机器上,进程没有特定的关联性,可以使用任何处理器。要设置新的关联,最简单的方法是使用自己的位掩码并更改属性。例如,要将当前 PowerShell 进程仅锁定到第一个处理器,可以这样做:

1
2
3
4
5
6
7
# calculate the bit mask
$mask = '1000'
$bits = [Convert]::ToInt32($mask, 2)

# assign new affinity to current PowerShell process
$process = Get-Process -Id $PID
$process.ProcessorAffinity = $bits

PowerShell 技能连载 - 转储 Chrome 的所有密码

在前面的技能中,我们演示了如何从个人 Windows 密码库转储所有密码。基本上任何密码管理器都是如此,因为这些程序的目的是返回它们为您存储的密码。

谷歌 Chrome 浏览器将您的个人密码(和网站历史记录)存储在 SQLLite 数据库中。PowerShell 可以轻松地访问这个数据库并为您转储信息。但是,要做到这一点,PowerShell 需要不属于 Windows 的特定 SQLLite 程序集。

下面的代码展示了如何 (a) 通过一行程序下载大量代码,(b) 在下载的代码中嵌入一个二进制 .net 程序集。

警告:只能在演示机器上运行此代码,或者确保先下载代码,然后在运行之前仔细检查。代码是从第三方下载的,您永远不知道这些代码是否包含恶意内容。

如果你希望转储 Chrome 的隐私信息,我们建议你把代码下载到一个安全的机器上,隔离并检查它,然后在需要的时候从本地保存的副本中使用它。永远不要从不可信的来源下载和执行代码,除非仔细检查代码的实际功能:

1
2
3
4
5
6
7
8
# download the code from GitHub
$url = 'https://raw.githubusercontent.com/adaptivethreat/Empire/master/data/module_source/collection/Get-ChromeDump.ps1'
$code = Invoke-RestMethod -Uri $url -UseBasicParsing
# run the code
Invoke-Expression $code

# now you have a new function called Get-ChromeDump
Get-ChromeDump

注意,当Chrome运行时,SQLLite数据库被锁定。你需要关闭Chrome才能转储它的隐私数据。

代码来自未知或不可信源的事实并不意味着代码不好。事实上,当您查看代码时,您会发现一些有趣的技术:

1
2
3
4
5
6
# download the code from GitHub
$url = 'https://raw.githubusercontent.com/adaptivethreat/Empire/master/data/module_source/collection/Get-ChromeDump.ps1'
# and copy it to the clipboard
Invoke-RestMethod -Uri $url -UseBasicParsing | Set-ClipBoard

# now paste the code into your editor of choice and inspect it!

您将看到,代码附带了访问 SQLLite 数据库所需的二进制 .net 程序集。二进制文件以 base64 编码为字符串。这是在你的电脑上恢复二进制的部分:

1
2
3
$content = [System.Convert]::FromBase64String($assembly)
$assemblyPath = "$($env:LOCALAPPDATA)\System.Data.SQLite.dll"
Add-Type -Path $assemblyPath

下面是更多与安全相关的PowerShell单行程序:https://chrishales.wordpress.com/2018/01/03/powershell-password-one-liners/

PowerShell 技能连载 - 从 Windows 中转储个人密码

Windows 有一个受保护的密码库,它可以在其中存储您的密码,因此您不必始终在 InternetExplorer 或 Edge 中手动输入密码。

如果您习惯了自动密码管理器,可能偶尔会忘记原始密码。下面是一个超级简单的 PowerShell 方法来转储存储在 Windows 密码库中的所有密码:

1
2
3
4
5
6
7
8
9
10
11
# important: this is required to load the assembly
[Windows.Security.Credentials.PasswordVault,Windows.Security.Credentials,ContentType=WindowsRuntime]


(New-Object Windows.Security.Credentials.PasswordVault).RetrieveAll() |
ForEach-Object {
$_.RetrievePassword()
$_
} |
Select-Object -Property Username, Password, Resource |
Out-GridView

请注意,如果您从未在 InternetExplorer 或 Edge 中存储凭据,则不会得到任何结果。还请注意,此代码设计的工作方式:只有您只能检索密码,就像您在浏览器中访问网站并要求浏览器为您填写凭据一样。

转储所有存储的密码说明了为什么在您不在时始终锁定您的计算机是如此重要。如果您让您的计算机无人值守的话,任何人都可以运行此 PowerShell 代码来转储您的个人密码。

PowerShell 技能连载 - 通过 PowerShell 安装 Google Chrome

要下载并安装谷歌Chrome浏览器,只需几个常见的 PowerShell 命令组合:

1
2
3
4
5
$Installer = "$env:temp\chrome_installer.exe"
$url = 'http://dl.google.com/chrome/install/375.126/chrome_installer.exe'
Invoke-WebRequest -Uri $url -OutFile $Installer -UseBasicParsing
Start-Process -FilePath $Installer -Args '/silent /install' -Wait
Remove-Item -Path $Installer

PowerShell 技能连载 - 使用始终可见的弹出对话框

在上一个技能中,我们使用了一种古老的 COM 技术来显示带有内置超时的弹出框。除了对话框有时会被覆盖在 PowerShell 窗口下之外,这种方法运行得非常好。

通过一个鲜为人知的技巧,你可以确保对话框总是打开在所有其他窗口的顶部:

1
2
3
4
$shell = New-Object -ComObject WScript.Shell
$value = $shell.Popup("You can't cover me!", 5, 'Example', 17 + 4096)

"Choice: $value"

关键是在参数中添加 4096 来控制按钮和图标。这将该对话框转换为模态对话框:它保证在所有现有窗口之上打开,并且永远不会被覆盖。

使用哈希表来包装所有这些幻数,这又是一个好办法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
$timeoutSeconds = 5
$title = 'Example'
$message = "You can't cover me!"

$buttons = @{
OK = 0
OkCancel = 1
AbortRetryIgnore = 2
YesNoCancel = 3
YesNo = 4
RetryCancel = 5
}

$icon = @{
Stop = 16
Question = 32
Exclamation = 48
Information = 64
}

$clickedButton = @{
-1 = 'Timeout'
1 = 'OK'
2 = 'Cancel'
3 = 'Abort'
4 = 'Retry'
5 = 'Ignore'
6 = 'Yes'
7 = 'No'
}

$ShowOnTop = 4096

$shell = New-Object -ComObject WScript.Shell
$value = $shell.Popup($message, $timeoutSeconds, $title, $buttons.Ok + $icon.Exclamation + $ShowOnTop)

Switch ($clickedButton.$value)
{
'OK' { 'you clicked OK' }
'Timeout'{ 'you did not click anything, timeout occurred' }
}

PowerShell 技能连载 - 用哈希表提高代码可读性

也许你在过去偶然见过这样的代码:

1
2
3
4
$shell = New-Object -ComObject WScript.Shell
$value = $shell.Popup('Restart Computer?', 5, 'Important', 36)

"Choice: $value"

这段代码打开一个对话框,询问用户是否可以重启计算机。弹出对话框有一个内置超时设置,因此即使在无人值守的情况下运行,代码也不会停止。

但是,由于 PowerShell 使用的是一种旧的 COM 方法,它是由加密的 ID 来控制。用户无法理解 “36” 表示一个带有 YesNo 按钮和问号的对话框。那么如何转义返回的值呢?

哈希表可以以一种简单的方法来包装代码数字,并使代码更具可读性。请看:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
$timeoutSeconds = 5
$title = 'Important'
$message = 'Restart Computer?'

$buttons = @{
OK = 0
OkCancel = 1
AbortRetryIgnore = 2
YesNoCancel = 3
YesNo = 4
RetryCancel = 5
}

$icon = @{
Stop = 16
Question = 32
Exclamation = 48
Information = 64
}

$clickedButton = @{
-1 = 'Timeout'
1 = 'OK'
2 = 'Cancel'
3 = 'Abort'
4 = 'Retry'
5 = 'Ignore'
6 = 'Yes'
7 = 'No'
}

$shell = New-Object -ComObject WScript.Shell
$value = $shell.Popup($message, $timeoutSeconds, $title, $buttons.YesNo + $icon.Question)

"Raw result: $value"
"Cooked result: " + $clickedButton.$value

Switch ($clickedButton.$value)
{
'Yes' { 'restarting' }
'No' { 'aborted' }
'Timeout'{ 'you did not make a choice' }
}

多亏了哈希表,代码使用 $buttons.YesNo + $icon.Question 而不是指定 “36”,而且一旦运行了代码(这样就定义了哈希表),甚至可以获得可用选项的智能感知。

同样,通过使用原始返回值作为哈希表的键,可以轻松地将返回代码转换为人类可读的格式。通过这种方式,您可以使用 switch 语句并为用户单击的按钮分配脚本块,而不必知道单个按钮代码。

PowerShell 技能连载 - 提高管道速度

PowerShell 管道在处理大量元素时往往比较慢。可能需要很多时间:

1
2
3
4
$result = 1..15000 |
ForEach-Object {
"Line $_"
}

一种更快的方法是用匿名脚本块代替 ForEach-Object,它会带来 200 倍的速度提升:

1
2
3
4
$result = 1..15000 |
& { process {
"Line $_"
}}