powershell简单入门

powershell简单入门

前言

powershell是微软出品的一款shell语言工具,它的出现给运维人员提供了一种更高效的系统管理方式。同样,正因为其高效,强大,天然免杀(现在会拦截)等特性也被广泛地用于后渗透中。本文记录了一些在入门powershell中的心得。

本文主要参考了 Powershell攻击指南黑客后渗透之道系列 这个系列的文章中的一些案例

https://www.anquanke.com/post/id/87976

本文的操作环境在windows 10虚拟机和osx 14.6上,powershell版本为5.1.18362.752。

基本的语法

  1. 灵活使用Help,获取命令帮助手册,例如Help Process获取名字中带有Process的所有cmdlet和function。
    image.png

    比较重要的就是直接使用-example
    来看运行示例来快速学习。正因为这点的存在,个人觉得上手powershell不是特别困难。
    image.png

    或者还可以直接使用Get-Command -Noun 要查找的东西 也能达到一样的效果。

  2. Powershell提供程序,本质上是一个适配器,包括接收一系列存储的数据,然后以一种统一的方式使用他们,值得一提的是powershell就是通过这个机制,可以通过和操作文件系统类似的方式操作环境变量,注册表等系统。

    • Get-PSProvider 获取提供程序的类型

      image.png

      • Get-PSDrive 获取当前session中的驱动
  3. 通过 item等方法操作适配器中的内容

    • 支持使用Get-Item Env:/PATH的方式获取变量名称

    • Set-ItemProperty -Path . -Name xxx -Value 1来设置 当前目录(也可以是注册表)下xxx的值

      • 一条cmdlit中并不是全部的属性都是可以用的 例如可以在文件系统中使用Dir -filter,但是在注册表中就不能,必须使用Dir -include,例如在文件系统中的文件就不能使用set-item来设置值。

      image.png

  4. powershell array

    创建方法

    • $item=1,2,3,4 1,2,3,4

    • $tem=1,,2 1,2

    • $tem=1..3 1,2,3

    • $array=1,”2017”,([System.Guid]::NewGuid()),(get-date)

      image.png

    • @(1..2) 1,2

      常见方法

    • $a.count计数

    • $a.length 长度

    • $a[0..2] $a[0] $a[1] $a[2] [-3.-1]也支持这样的反序

      这里还有个特殊案例:

    • 有连接 slice的作用

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18

      $a = 0..9

      $a[+0..2+4..6+8]

      0

      1

      2

      4

      5

      6

      8
    • rank 数组的维度

    • $null 空对象

    • clear 清空方法

    • ForEach

      1
      2
      3
      4

      $a = @(0 .. 3)

      $a.ForEach({ $_ * $_})
    • $a=ipconfig $a[0]第一行输出

    • 数组判断 $test -is [array]

    • 数组追加 $test+=1

  5. hashtable

    1
    2
    3
    4
    5
    6

    $hashtable=@{} #创建

    $hashtable["name"]=123 #赋值

    $hashtable.remove("name") # 删除
  1. Get-Member,select-Object, where-Object(遍历对象) $_当前管道中的对象

    Get-Member允许列出成员属性并使用属性名称进行筛选

    1
    2

    Get-Process | Get-Member -Name Hand*

    image.png

    select-Object

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10

    Get-CimInstance -Class Win32_LogicalDisk |

    Select-Object -Property Name, @{

    label='FreeSpace'

    expression={($_.FreeSpace/1GB).ToString('F2')}

    }

    image.png

  2. 判断符号
    需要注意的是 powershell为了不和一些特殊符号冲突,一般不使用>,=这些符号。

    -eq :等于

    -ne :不等于

    -gt :大于

    -ge :大于等于

    -lt :小于

    -le :小于等于

    -contains :包含

    $array -contains something

-notcontains :不包含

!($a): 求反

-and :和

-or :或

-xor :异或

-not :非
  1. ForEach-Object

    -Process {} 执行的过程脚本块 -Begin 初始化脚本块 -End 结束

    输出 $_.Id 大于 1000的 进程

    1
    2
    3
    4

    Get-Process| ForEach-Object -Process {if($_.Id -gt 1000){$_

    .Name+','+$_.Id}}
  2. 函数

    1
    2
    3
    4
    5
    6
    7
    8
    function test($strings){
    $strings
    }

    function test1{
    param($strings)
    $strings
    }
  3. if elseif else 一样的

  4. powershell 管理员模式

Start-Process powershell -Verb runAs 需要过uac

  1. 创建对象
    powershell最强大的一点是直接调用.net对象,这点非常爽。
    new-object 新建对象

  2. 多线程
    一种做法是使用start-job来控制,另外中是采用创建powershell对象,采取线程池的形式控制,具体如何做到的下文会给个例子。

一些练习的脚本

  1. 端口扫描脚本
    • System.Net.Sockets.TcpClient 建立对象来测试端口扫描
    • System.Net.NetworkInformation.Ping 来扫描ip
    • 多线程扫描
      • 采用RunspacePool 限制线程数量
      • [powershell]::Create()手动创建脚本 $(Function:function_name)指定function
      • BeginInvoke() 启动脚本
      • EndInvoke()获得返回结果,扫描结果统一由PortScan变量存储
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
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
function PortScan {
[CmdletBinding()] Param(
[parameter(Mandatory = $true, Position = 0)]
# [ValidatePattern("bd{1,3}.d{1,3}.d{1,3}.d{1,3}b")]
[string]
$StartAddress,
[parameter(Mandatory = $true, Position = 1)]
# [ValidatePattern("bd{1,3}.d{1,3}.d{1,3}.d{1,3}b")]
[string]
$EndAddress,
[switch]
$GetHost,
[switch]
$ScanPort,
[int[]]
$Ports = @(21, 22, 23, 25, 53, 80, 110, 139, 143, 389, 443, 445, 465, 873, 993, 995, 1080, 1086, 1723, 1433, 1521, 2375, 3128, 3306, 3389, 3690, 5432, 5800, 5900, 6379, 7001, 7002, 7778, 8000, 8001, 8080, 8081, 8089, 8161, 8888, 9000, 9001, 9060, 9200, 9300, 9080, 9090, 9999, 10051, 11211, 27017, 28017, 50030),
[int]
$TimeOut = 100
)
Begin {
# 加载线程池
$throttleLimit = 40
$SessionState = [system.management.automation.runspaces.initialsessionstate]::CreateDefault()
$Pool = [runspacefactory]::CreateRunspacePool(1, $throttleLimit, $SessionState, $Host)
$Pool.Open()
$success_ip = @()
$threads = @()
$handles = @()
}
Process {
function output {
param([string]$title, [array]$result)
Write-Host $title
foreach ($res in $result) {
Write-Host $res 'is open'
}
}

function doIpScan {
param(
[string] $ip,
[string] $timeOut
)

try {
$pingSender = new-object System.Net.NetworkInformation.Ping
$res = $pingSender.Send($ip, $timeOut)
}
catch {
continue
}
if ($res.Status -eq "Success") {
return @($true, $ip)
}
else {
return @($false, $ip)
}
}

function doPortScan {
param([string]$ip, [int]$port)

try {
# 用来消除new-object的输出
$null = new-object System.Net.Sockets.TcpClient($ip, $port)
}
catch {
return @($false, $port)
}
return @($true, $port)


}




#添加ip扫描任务
$handles = foreach ($a in ($StartAddress.Split(".")[0]..$EndAddress.Split(".")[0])) {
foreach ($b in ($StartAddress.Split(".")[1]..$EndAddress.Split(".")[1])) {
foreach ($c in ($StartAddress.Split(".")[2]..$EndAddress.Split(".")[2])) {
foreach ($d in ($StartAddress.Split(".")[3]..$EndAddress.Split(".")[3])) {
$ip = "$a.$b.$c.$d"
$powershell = [powershell]::Create().AddScript(${Function:doIpScan}, $true).AddParameter("ip", $ip).AddParameter("timeOut", $TimeOut)
$powershell.RunspacePool = $Pool
$powershell.BeginInvoke()
$threads += $powershell
}
}
}
}

do {
$i = 0
$done = $true
foreach ($handle in $handles) {
if ($null -ne $handle) {
if ($handle.IsCompleted) {
$result = $threads[$i].EndInvoke($handle)
# 处理返回结果
if ($result[0] -eq $true) {
$success_ip += $result[1]
}
$threads[$i].Dispose()
$handles[$i] = $null
}
else {
$done = $false
}
}
$i++
}
if (-not $done) { Start-Sleep -Milliseconds 500 }
} until ($done)

$handles = @()
$threads = @()

output -title "有效ip" -result $success_ip

if ($ScanPort) {
# 分ip进行端口扫苗
foreach ($ip in $success_ip) {
$success_ports = @()
$handles = @()
$threads = @()
$handles = foreach ($port in $Ports) {
$powershell = [powershell]::Create().AddScript(${Function:doPortScan}, $true).AddParameter("ip", $ip).AddParameter("port", $port)
$powershell.RunspacePool = $Pool
$powershell.BeginInvoke()
$threads += $powershell
}

do {
$i = 0
$done = $true
foreach ($handle in $handles) {
if ($null -ne $handle) {
if ($handle.IsCompleted) {
$result = $threads[$i].EndInvoke($handle)
if ($result[0] -eq $true) {
$success_ports += $result[1]
}
$threads[$i].Dispose()
$handles[$i] = $null
}
else {
$done = $false
}
}
$i++
}
if (-not $done) { Start-Sleep -Milliseconds 500 }
} until ($done)

$title = $ip + "的端口扫描结果"
output -title $title -result $success_ports
}
}
}

End {
}

}

总结

主要踩的坑还是在程序的设计,如何保存端口扫描的结果一开始还是想用全局变量来存储,不过没弄出来。最后采用了返回变量这种方式来实现。以后可以选择分析一下PowerSploit的Invoke-Portscan.ps1来学习一下多线程扫描的实现。