PowerShell 技能连载 - 通过 Azure 认知服务使用人工智能

现在的云服务不仅提供虚拟机和存储,而且提供全新且令人兴奋的服务,例如认知服务。如果您需要直接访问这些服务,您需要一个 Azure 订阅密钥(可以在以下网站免费获得)。否则,您也可以使用这里提供的免费的互动 DEMO:
https://azure.microsoft.com/en-us/services/cognitive-services/computer-vision/#analyze

以下是一个发送图片文件到 Azure 图片分析的脚本,您将获得关于照片内容的详细描述,包括面部的坐标、性别,以及估计的年龄:

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
# MAKE SURE YOU SPECIFY YOUR FREE OR PAID AZURE SUBSCRIPTION ID HERE:
$subscription = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"


Add-Type -AssemblyName System.Net.Http
# MAKE SURE YOU SPECIFY A PATH TO A VALID IMAGE ON YOUR COMPUTER
$image = "C:\sadjoey.jpg"
$uriBase = "https://westcentralus.api.cognitive.microsoft.com/vision/v2.0/analyze";
$requestParameters = "visualFeatures=Categories,Tags,Faces,ImageType,Description,Color,Adult"
$uri = $uriBase + "?" + $requestParameters

# get image file as a byte array
$imageData = Get-Content $image -Encoding Byte

# wrap image into byte array content
$content = [System.Net.Http.ByteArrayContent]::new($imageData)
$content.Headers.ContentType = [System.Net.Http.Headers.MediaTypeHeaderValue]::new("application/octet-stream")

# get a webclient ready
$webclient = [System.Net.Http.HttpClient]::new()
$webclient.DefaultRequestHeaders.Add("Ocp-Apim-Subscription-Key",$subscription)

# post the request to Azure Cognitive Services
$response = $webclient.PostAsync($uri, $content).Result
$result = $response.Content.ReadAsStringAsync().Result

# convert information from JSON into objects
$data = $result | ConvertFrom-Json

# get image detail information
$data.description.captions
$data.Faces | Out-String
$data.description.Tags

结果类似这样:

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
PS C:\> $data.description.captions

text confidence
---- ----------
a man and a woman standing in a room 0,94136500176759652



PS C:\> $data.faces

age gender faceRectangle
--- ------ -------------
23 Female @{top=114; left=229; width=47; height=47}



PS C:\> $data.description.tags
person
indoor
man
holding
woman
standing
window
table
room
front
living
young
video
computer
kitchen
playing
remote
wii
people
white
game

您也可以查看一下通过 Web Service 返回的 JSON 数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$result
{"categories":[{"name":"indoor_","score":0.42578125},{"name":"others_","score":0
.00390625}],"tags":[{"name":"person","confidence":0.999438464641571},{"name":"in
door","confidence":0.99413836002349854},{"name":"wall","confidence":0.9905883073
8067627}],"description":{"tags":["person","indoor","man","holding","woman","stan
ding","window","table","room","front","living","young","video","computer","kitch
en","playing","remote","wii","people","white","game"],"captions":[{"text":"a man
and a woman standing in a room","confidence":0.94136500176759652}]},"faces":[{"
age":23,"gender":"Female","faceRectangle":{"top":114,"left":229,"width":47,"heig
ht":47}}],"adult":{"isAdultContent":false,"adultScore":0.023264557123184204,"isR
acyContent":false,"racyScore":0.042826898396015167},"color":{"dominantColorForeg
round":"Brown","dominantColorBackground":"Black","dominantColors":["Brown","Blac
k","Grey"],"accentColor":"6E432C","isBwImg":false},"imageType":{"clipArtType":0,
"lineDrawingType":0},"requestId":"8aebed85-68eb-4b9f-b6f9-5243cd20e4d7","metadat
a":{"height":306,"width":408,"format":"Jpeg"}}

PowerShell 技能连载 - 将所有脚本备份到 ZIP 中

PowerShell 5 终于支持 ZIP 文件了,所以如果您希望备份所有 PowerShell 脚本到一个 ZIP 文件中,以下是一个单行代码:

1
2
Get-ChildItem -Path $Home -Filter *.ps1 -Recurse -ErrorAction SilentlyContinue |
Compress-Archive -DestinationPath "$home\desktop\backupAllScripts.zip" -CompressionLevel Optimal

请注意在 Windows 10 上,所有文件写入 ZIP 之前都需要通过反病毒引擎。如果您的反病毒引擎检测到一段可疑的代码,可能会产生异常,并且不会生成 ZIP 文件。

PowerShell 技能连载 - 以其他用户身份运行 PowerShell 代码

本地管理员权限十分强大,您需要使用类似 JEA 等技术来尽可能减少本地管理员的数量。为什么?请看以下示例。如果您有某台机器上的本地管理员特权,而且启用了 PowerShell 远程操作,那么您可以发送任意的 PowerShell 代码到那台机器上,并且以登录到那台机器上的用户的上下文执行该代码。

如果一个企业管理员正坐在该机器前,您作为一个本地管理员也可以发送一行 PowerShell 代码,并以企业管理员的身份执行。

在操作之前:请知道自己在做什么。这个示例演示了计划任务和本地管理员权限的技术可能性。这些与 PowerShell 和 PowerShell 远程操作都没有关系。我们只是使用 PowerShell 作为工具。

您可以在没有 PowerShell 和 PowerShell 远程操作的情况下做相同的事情,只是使用纯 cmd 以及 psexec 等工具。

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
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
function Invoke-PowerShellAsInteractiveUser
{
param(
[Parameter(Mandatory)]
[ScriptBlock]
$ScriptCode,

[Parameter(Mandatory)]
[String[]]
$Computername
)

# this runs on the target computer
$code = {

param($ScriptCode)

# turn PowerShell code into base64 stream
$bytes = [System.Text.Encoding]::Unicode.GetBytes($ScriptCode)
$encodedCommand = [Convert]::ToBase64String($bytes)

# find out who is physically logged on
$os = Get-WmiObject -Class Win32_ComputerSystem
$username = $os.UserName

# define a scheduled task in the interactive user context
# with highest privileges
$xml = @"
<?xml version="1.0" encoding="UTF-16"?>
<Task version="1.2" xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task">
<RegistrationInfo />
<Triggers />
<Settings>
<MultipleInstancesPolicy>IgnoreNew</MultipleInstancesPolicy>
<DisallowStartIfOnBatteries>false</DisallowStartIfOnBatteries>
<StopIfGoingOnBatteries>false</StopIfGoingOnBatteries>
<AllowHardTerminate>true</AllowHardTerminate>
<StartWhenAvailable>false</StartWhenAvailable>
<RunOnlyIfNetworkAvailable>false</RunOnlyIfNetworkAvailable>
<IdleSettings />
<AllowStartOnDemand>true</AllowStartOnDemand>
<Enabled>true</Enabled>
<Hidden>false</Hidden>
<RunOnlyIfIdle>false</RunOnlyIfIdle>
<WakeToRun>false</WakeToRun>
<ExecutionTimeLimit>PT72H</ExecutionTimeLimit>
<Priority>7</Priority>
</Settings>
<Actions Context="Author">
<Exec>
<Command>powershell.exe</Command>
<Arguments>-windowstyle minimized -encodedCommand $EncodedCommand</Arguments>
</Exec>
</Actions>
<Principals>
<Principal id="Author">
<UserId>$username</UserId>
<LogonType>InteractiveToken</LogonType>
<RunLevel>HighestAvailable</RunLevel>
</Principal>
</Principals>
</Task>
"@

# define, run, then delete the scheduled job
$jobname = 'remotejob' + (Get-Random)
$xml | Out-File -FilePath "$env:temp\tj1.xml"
$null = schtasks.exe /CREATE /TN $jobname /XML $env:temp\tj1.xml
Start-Sleep -Seconds 1
$null = schtasks.exe /RUN /TN $jobname
$null = schtasks.exe /DELETE /TN $jobname /F
}

# run the code on the target machine, and submit the PowerShell code to execute
Invoke-Command -ScriptBlock $code -ComputerName $computername -ArgumentList $ScriptCode
}

要将恶意代码发送到另一台机器,例如打开一个可见的浏览器页面,或用这段代码通过文字转语音和用户“聊天”:

1
2
3
4
5
6
7
8
9
10
11
$ComputerName = 'ENTER THE COMPUTER NAME'

$pirateCode = {
Start-Process -FilePath www.microsoft.com

$sapi = New-Object -ComObject Sapi.SpVoice
$sapi.Speak("You are hacked...!")
Start-Sleep -Seconds 6
}

Invoke-PowerShellAsInteractiveUser -ScriptCode $pirateCode -Computername $ComputerName

显然,需要调整 $ComputerName 对应到您拥有本地管理员特权,并且启用了 PowerShell 远程操作系统上。并且,这段代码需要用户物理登录。如果没有物理登录的用户,那么将没有可以进入的用户回话,这段代码将会执行失败。

PowerShell 技能连载 - 强化脚本块日志

默认情况下,脚本块日志数据对所有人都可见,不仅是管理员。当启用了脚本块日志后,所有用户都可以访问日志并读取它的内容。最简单的方式是用这行代码下载工具:

1
2
Install-Module -Name scriptblocklogginganalyzer -Scope CurrentUser
Get-SBLEvent | Out-GridView

有一些方法可以强化脚本块日志,并确保只有管理员才能读取这些日志。请运行以下代码将存取权限改为仅允许管理员存取:

1
2
3
4
5
6
7
8
9
10
11
12
13
#requires -RunAsAdministrator

$Path = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\winevt\Channels\Microsoft-Windows-PowerShell/Operational"
# get the default access permission for the standard security log...
$sddlSecurity = ((wevtutil gl security) -like 'channelAccess*').Split(' ')[-1]
# get the current permissions
$sddlPowerShell = (Get-ItemProperty -Path $Path).ChannelAccess
# make a backup of the current permissions
New-ItemProperty -Path $Path -Name ChannelAccessBackup -Value $sddlPowerShell -ErrorAction Ignore
# apply the hardened permissions
Set-ItemProperty -Path $Path -Name ChannelAccess -Value $sddlSecurity
# restart service to take effect
Restart-Service -Name EventLog -Force

现在,当一个普通用户尝试读取脚本块日志的记录时,什么信息也不会返回。

PowerShell 技能连载 - 启用脚本块日志

在前一个技能中,我们深入了解了 PowerShell 5 脚本块日志的工作方式:简而言之,启用了以后,机器上运行的所有 PowerShell 代码在机器上的执行过程都将记录日志,这样您可以浏览源代码并查看机器上运行了什么 PowerShell 代码。

我们将它封装为一个免费的 PowerShell 模块,可以从 PowerShell Gallery 上下载,所以要启用脚本块日志,您只需要一个以管理员特权运行的 PowerShell 5.x 控制台,以及以下代码:

1
2
3
Install-Module -Name scriptblocklogginganalyzer -Scope CurrentUser
Set-SBLLogSize -MaxSizeMB 1000
Enable-SBL

一旦启用了脚本块日志,您可以转储日志并且像这样查看记录的脚本代码:

1
Get-SBLEvent | Out-GridView

PowerShell 技能连载 - 查找内存中的密码

有些脚本执行后可能会留下敏感信息。这可能是偶然发生,当使用全局作用域,或是用户通过 “dot-sourced” 调用函数和命令。其中一些变量可能包含对于黑客十分感兴趣的数据,比如用户名和密码。

以下是一个快速的测试,检查内存中的所有变量并查找凭据,然后返回变量和用户名,以及明文形式的密码:

1
2
3
4
5
6
7
8
9
Get-Variable |
Where-Object Value -is [System.Management.Automation.PSCredential] |
ForEach-Object {
[PSCustomObject]@{
Variable = '$' + $_.Name
User = $_.Value.UserName
Password = $_.Value.GetNetworkCredential().Password
}
}

要测试执行,请使用凭据创建一个变量:

1
2
3
PS> $test = Get-Credential
cmdlet Get-Credential at command pipeline position 1
Supply values for the following parameters:

然后,运行以上代码在内存中查找变量。

如果您想最小化风险,请确使用 Remove-Variable 命令手工保移除了所有变量。通常情况下,您可以信任自动垃圾收集,但是当包含敏感数据是,攻击者可能会使用多种方法防止变量被自动回收。当您人工移除后,就安全了。

PowerShell 技能连载 - 将数据输出为 HTML 报告

以下是一个超级简单和有用的 PowerShell 函数,名为 Out-HTML

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
function Out-HTML
{

param
(

[String]
$Path = "$env:temp\report$(Get-Date -format yyyy-MM-dd-HH-mm-ss).html",

[String]
$Title = "PowerShell Output",

[Switch]
$Open
)

$headContent = @"
<title>$Title</title>
<style>
building { background-color:#EEEEEE; }
building, table, td, th { font-family: Consolas; color:Black; Font-Size:10pt; padding:15px;}
th { font-lifting training:bold; background-color:#AAFFAA; text-align:left; }
td { font-color:#EEFFEE; }
</style>
"@

$input |
ConvertTo-Html -Head $headContent |
Set-Content -Path $Path


if ($Open)
{
Invoke-Item -Path $Path
}
}

您所需要的只是用管道将数据输出到 Out-HTML 命令来生成一个简单的 HTML 报告。请试试这段:

1
2
3
PS C:\> Get-Service | Out-HTML -Open

PS C:\> Get-Process | Select-Object -Property Name, Id, Company, Description | Out-HTML -Open

PowerShell 技能连载 - 从 PowerShell 函数中窃取敏感数据

PowerShell 函数常常处理敏感的信息,例如包含密码的登录信息,并且将这些信息存储在变量中。让我们假设有这样一个函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
function Connect-Server
{
param
(
[Parameter(Mandatory)]
[pscredential]
[System.Management.Automation.Credential()]
$Credential
)

"You entered a credential for {0}." -f $Credential.UserName
# now you could do something with $Credential
}

当运行这个函数时,会弹出提示输入凭据和密码。您的信息在函数中体现在 $Credential 变量中,并且当函数完成时,内部信息自动从内存中移除:

1
2
3
4
5
6
PS> Connect-Server -Credential tobias
You entered a credential for Tobias.

PS> $Credential

PS>

然而,任何攻击者都可以通过 dot-sourced 方式运行该函数。当函数执行完毕之后,所有内部变量都还在内存中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
PS> . Connect-Server -Credential tobias
You entered a credential for Tobias.

PS> $Credential

UserName Password
-------- --------
Tobias System.Security.SecureString



PS> $Credential.GetNetworkCredential().Password
test

PS>

如您所见,攻击者现在可以访问凭据对象,并且也可以看到原始的密码。

要防止这种情况,您可以再次从内存中手动移除敏感的数据。您可以用 Remove-Variable 移除变量:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function Connect-Server
{
param
(
[Parameter(Mandatory)]
[pscredential]
[System.Management.Automation.Credential()]
$Credential
)

"You entered a credential for {0}." -f $Credential.UserName
# now you could do something with $Credential, i.e. submit it to
# other cmdlets that support the -Credential parameter
# i.e.
# Get-WmiObject -Class Win32_BIOS -ComputerName SomeComputer -Credential $Credential

Remove-Variable -Name Credential
}

现在不再能获取到变量:

1
2
3
4
5
6
7
8
PS> Remove-Variable -Name Credential

PS> . Connect-Server -Credential tobias
You entered a credential for Tobias.

PS> $credential

PS>

PowerShell 技能连载 - 用参数的方式解决凭据

凭据是包含用户名和加密的密码的对象。如果您的 PowerShell 函数可以接受凭据,那么加上 PSCredential 类型:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function Connect-Server
{
param
(
[Parameter(Mandatory)]
[pscredential]
$Credential
)

"You entered a credential for {0}." -f $Credential.UserName
# now you could do something with $Credential, i.e. submit it to
# other cmdlets that support the -Credential parameter
# i.e.
# Get-WmiObject -Class Win32_BIOS -ComputerName SomeComputer -Credential $Credential

}

当您运行以上代码后,再调用您的函数,将会显示一个对话框,显示用户名和密码。当指定一个用户名时,情况也是一样:也是打开一个对话框提示输入密码,然后将您的输入转换为一个合适的 PSCredential 对象:

1
PS> Connect-Server -Credential tobias

这个自动转换过程只在 PowerShell 5.1 及以上的版本中有效。在之前的 PowerShell 版本中,您可以先传入一个凭据对象。要在旧版的 PowerShell 中也启用该转换过程,您可以可以增加一个额外的转换属性:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function Connect-Server
{
param
(
[Parameter(Mandatory)]
[pscredential]
[System.Management.Automation.Credential()]
$Credential
)

"You entered a credential for {0}." -f $Credential.UserName
# now you could do something with $Credential, i.e. submit it to
# other cmdlets that support the -Credential parameter
# i.e.
# Get-WmiObject -Class Win32_BIOS -ComputerName SomeComputer -Credential $Credential

}

PowerShell 技能连载 - 解析映射驱动器

是否想知道网络驱动器背后的原始 URL?以下是一个简单的 PowerShell 方法:

1
2
3
4
5
6
7
8
9
# make sure the below drive is a mapped network drive
# on your computer
$mappedDrive = 'z'


$result = Get-PSDrive -Name $mappedDrive |
Select-Object -ExpandProperty DisplayRoot

"$mappedDrive -> $result"