2023年12月22日 星期五

Bat/PowerShell 提權執行的指令

Bat/PowerShell 提權執行的指令



PowerShell

啟動一個新的進程並以管理員權限執行指定的命令

Start-Process 'cmd.exe' -ArgumentList "/c $cmdStr" -Verb RunAs



Bat 通常解法

批處理檔(.bat 檔)中實現類似的功能,事情就有些不同了,因為批處理檔沒有內建的提升權限命令。

一種解決方案是創建一個臨時的 VBScript 檔,這個腳本會觸發 UAC 提示以提升權限,然後執行您的批處理檔。這裡有一個例子:

  • 1. 創建一個 VBScript 檔案:
保存以下代碼到一個 .vbs 檔案,例如 elevate.vbs。這段代碼會以管理員身份執行一個指定的命令。
If WScript.Arguments.Count >= 1 Then
    Command = WScript.Arguments(0)
    Set Shell = CreateObject("Shell.Application")
    Shell.ShellExecute "cmd.exe", "/c " & Command, "", "runas", 1
End If


  • 2. 從批處理檔中調用這個 VBScript:
在您的 .bat 檔案中,使用以下命令來調用 elevate.vbs,並傳遞您想要以管理員權限執行的命令作為參數。
cscript //nologo elevate.vbs "您的命令"




進階手段

先利用執行內建需要管理員的程式來判斷是不是管理員

  1. 如果不是則委託給 PowerShell 重新讀取自己再次執行,並結束(並免重複執行自己)
  2. 如果已經是則繼續執行底下 bat

只需要將這行貼在首行即可

fltmc >nul || (set Admin=/x /d /c call "%~f0" %* & powershell -nop -c start cmd $env:Admin -verb runas; & exit /b)





2023年12月21日 星期四

PowerShell 如何讀取解析 Env/Properties 檔案

PowerShell 如何讀取解析 Env/Properties 檔案

實作做的功能包含了

  1. 自動讀取 “.env”, “.env.development”, “.env.production” 三個檔案,優先順序是後面覆蓋前面,同檔案間優先順序也是後面覆蓋前面
  2. 支援擴充功能使用斜線換行
  3. 支援擴充功能雙引號解析 PowerShell 變數


為什麼要拆分這麼多env檔案主要是兩個觀點

  1. 把連接資訊放在“.env.production”中避免部署設置時不小心動到連接資訊。
  2. 拆分development跟production,僅共用.env檔案這樣可以很好的區分開發機跟生產環境


範例 .env

A1="  AAA  
A2=  AAA  "
B1=" AAA "
B2=' AAA '
C1= \
arr1,\
arr2
D1="$($env:USERNAME)"
D2='$($env:USERNAME)'
Z=\
A\
B\
C\
D\


讀取用函式
ConvertFrom-Env.ps1

# 轉換 Env 文件
function ConvertFrom-Env {
    param (
        [string[]]$EnvFiles = @(".env", ".env.development", ".env.production"),
        [System.Text.Encoding]$Encoding = [System.Text.Encoding]::Default
    )

    # 使用有序字典保持讀取的順序
    $envVariables = [Ordered]@{}

    function ProcessLine {
        param (
            [string]$line
        )

        # 忽略註釋行
        if ($line -and -not $line.StartsWith("#")) {
            if ($line -match "^\s*([^#=]+?)\s*=\s*(.*)$") {
                $key = $matches[1].Trim()
                $value = $matches[2]
                if ($value -match '^"(.+)"$') {
                    # 處理雙引號包圍的值並展開字符串
                    $value = $matches[1] -replace '\\n', "`n" -replace '\\r', "`r" -replace '\\t', "`t"
                    $value = $ExecutionContext.InvokeCommand.ExpandString($value)
                } elseif ($value -match "^'(.+)'$") {
                    # 處理單引號包圍的值
                    $value = $matches[1]
                } else {
                    $value = $value.Trim()
                }

                # 將解析後的鍵值對加入字典
                $envVariables[$key] = $value
            }
        }
    }

    # 遍歷所有指定的文件
    foreach ($file in $EnvFiles) {
        if (Test-Path $file) {
            try {
                $reader = New-Object System.IO.StreamReader -ArgumentList $file, $Encoding
                try {
                    $currentLine = ''
                    while ($null -ne ($line = $reader.ReadLine())) {
                        $trimmedLine = $line.Trim()
                        if ($trimmedLine.EndsWith('\')) {
                            # 跨行值的處理
                            $currentLine += $trimmedLine.TrimEnd('\').TrimEnd()
                            continue
                        } else {
                            $currentLine += $trimmedLine
                        }
                        ProcessLine -line $currentLine
                        $currentLine = ''
                    }
                    # 處理跨行結尾在最一行時沒被處理的剩餘值
                    if ($currentLine) { ProcessLine -line $currentLine }
                } finally {
                    $reader.Close()
                }
            } catch {
                Write-Warning "無法讀取文件 '$file': $_"
            }
        }
    } # $envVariables = [PSCustomObject]$envVariables
    return $envVariables
} ConvertFrom-Env


代碼開源於此: hunandy14/ConvertFrom-Env (github.com)






2023年11月26日 星期日

PowerShell 如何測試IP的特定連接埠Port 與 反查HostName

PowerShell 如何測試IP的特定連接埠Port 與 反查HostName

經常需要確認IP每次都查來查去太麻煩,直接一次統整出來常用的指令

查尋該 HostName 在 DNS 上有哪些IP

Resolve-DnsName Kurisu-VM


反查該 IP 的 Host 名

Resolve-DnsName -Type PTR -Name 192.168.3.49


查詢該 IP 在 DNS 上的域名 (不一定能查到 Host 名)

nslookup 8.8.8.8


查詢該IP特定的 Port 是否可連接

Test-NetConnection Kurisu-VM -Port 22


查詢當前網路組態(私人/公開)

Get-NetConnectionProfile


剩下的有想到再慢慢補充

2023年11月19日 星期日

在Windoiws上手動安裝 OpenSSH Server

在Windows上安裝 OpenSSH Server

當前版本的Windwos10其實已經有內建 OpenSSH 了,不過實際版本可能沒那麼新建議是從 Github 上下載最新的版的安裝。


總共提供三個方法選一個執行就可以了 (別全做了擇一就好)

  1. 使用Windwos內建的版本
  2. 從Gihub安裝最新版本的OpenSSH
  3. 懶人包自動爬蟲安裝最新版本



方式1 使用Windwos內建的版本

如果懶得安裝直接到設定 “選用功能” 中打開OpenSSH功能就會自動裝了

快速打開方法

  1. 按下 Windows 鍵 + R 打開運行對話框。
  2. 輸入 ms-settings:optionalfeatures 命令。
  3. 按下 Enter 鍵或點擊「確定」。


然後打勾按下安裝就會自動裝好了





方式2 從Github安裝最新版本的OpenSSH

目前的Windwos10預設是有自動安裝客戶端的,可以不宜除只要在環境變數追加的時候把想要的版本在前面即可,後面的會被忽略掉。

微軟的安裝說明頁面
開始使用 OpenSSH for Windows | Microsoft Learn

具體來說需要做的事情有

  1. 安裝檔案
  2. 追加環境變數
  3. 通過內付的ps1腳本安裝 OpenSSH Server
  4. 設置防火牆打開 22 連接埠


安裝檔案

最新版本載點
Releases · PowerShell/Win32-OpenSSH (github.com)

點擊 OpenSSH-Win64.zip 下載檔案



直接解壓縮到C曹



使用管理員打開 PowerShell 執行命令追加環境變數

# 備份環境變數
$env:Path > C:\EnvPath.txt

# 設置環境變數
$newEnvPath = 'C:\OpenSSH-Win64;'+$env:Path
[Environment]::SetEnvironmentVariable('Path', $newEnvPath, 'Machine')
$env:Path = $newEnvPath

# 確認環境變數
[Environment]::GetEnvironmentVariable('Path', 'Machine') -split ';'


要編輯環境變數可以輸入 rundll32 sysdm.cpl,EditEnvironmentVariables 快速打開環境變數編輯的圖形視窗



測試一下 SSH-Client 有沒有裝好,版要跟自己下載的一致

Get-Command ssh,sshd


Win10內建的是8.1版,要是看到這版本檢查一下環境變數,把自己裝的移到最上方就會優先使用了;或是乾脆從方法1中移除內建的也行。


再來執行內付的腳本安裝 SSH-Server

cd 'C:\OpenSSH-Win64'
PowerShell -Exec Bypass -File install-sshd.ps1



再來初始化 OpenSSH Server 的設定

# 啟動服務
Start-Service sshd

# 設置為開機自動啟動
Set-Service -Name sshd -StartupType 'Automatic'

# 設置防火牆
if (!(Get-NetFirewallRule -Name "OpenSSH-Server-In-TCP" -ErrorAction SilentlyContinue | Select-Object Name, Enabled)) {
    Write-Output "Firewall Rule 'OpenSSH-Server-In-TCP' does not exist, creating it..."
    New-NetFirewallRule -Name 'OpenSSH-Server-In-TCP' -DisplayName 'OpenSSH Server (sshd)' -Enabled True -Direction Inbound -Protocol TCP -Action Allow -LocalPort 22
} else {
    Write-Output "Firewall rule 'OpenSSH-Server-In-TCP' has been created and exists."
}



至此就完成了,接下來自己連接自己試試看

ssh localhost


第一次連接會有一個對話出現需要輸入 yes 並按下 enter,再來輸入密碼即可登入


移除的話自行參考底下 [2] 中的指令移除
PowerShell -Exec Bypass -File C:\OpenSSH-Win64\uninstall-sshd.ps1
防火牆的部分到防火牆設定中砍掉 Port 22 的進站規則就好




方式3 懶人包自動爬蟲安裝最新版本

代碼開源在這裡:自動爬蟲抓取最新版本OpenSSH並安裝 · GitHub

使用方法

irm bit.ly/3uosqf4|iex; Install-OpenSSH 'C:\Program Files\OpenSSH' -IncludeServer -OpenFirewall



移除的代碼(不包含防火牆設置)

PowerShell -Exec Bypass -File "$(Split-Path(gcm sshd).Source)\uninstall-sshd.ps1"




參考文獻

  1. 開始使用 OpenSSH for Windows | Microsoft Learn
  2. Install Win32 OpenSSH · PowerShell/Win32-OpenSSH Wiki · GitHub







PowerShell如何把 高級函式ps1 當作字串執行

PowerShell如何把 高級函式ps1 當作字串執行

一般來說如果在ps1檔案開頭加上 [CmdletBinding()] 可以把檔案升級成高函式,也就是那個檔案本身會當作是一個函式來看待

比如說這樣一個檔案

TestFunction.ps1

[CmdletBinding()]
param(
    [Parameter(Mandatory=$true)]
    [string]$InputString
)
Write-Host by PSVersion:: $PSVersionTable.PSVersion
Write-Output "输入的字符串是: $InputString"

可以在終端機如此使用該檔案

.\TestFunction.ps1 Test

這樣就能直接呼叫這個檔案了並執行了



那如果不把它當作當按執行而是要當作文字呢,考慮到下面的範例文字

$PwshScript = @'
[CmdletBinding()]
param(
    [Parameter(Mandatory=$true)]
    [string]$InputString
)
Write-Host by PSVersion:: $PSVersionTable.PSVersion
Write-Output "输入的字符串是: $InputString"
'@

該如何執行這串代碼並帶入參數的方法是

Invoke-Expression "&{ $PwshScript } AAA"

如此一來就可以不把它當作檔案執行,而是用讀取的方式執行字串了




這個有個例子是 PowerShell 的線上安裝腳本,蠻精妙的方式
官方說明網址是:PowerShell/tools/install-powershell.ps1-README.md · GitHub

實際應用的案例是 (PowerShell自動更新的ps1腳本)

iex "& { $(irm aka.ms/install-powershell.ps1) } -UseMSI"

如此就能把參數帶進去執行了,這會自動下在最新版本 msi 到暫存並自動打開



2023年11月12日 星期日

Java中如何委託執行 PowerShell 代碼

Java中如何委託執行 PowerShell 代碼

遇到的情形是PowerShell中有現成的函式庫可以用Java中似乎沒有,不想自己實現直接套過去用的話可以暫時先這樣擋著。

廢話不多說直接上代碼

package com.excucmd;

import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class App {
    public static void main(String[] args) {
        System.out.println("Hello executeCommand!");
        int exitCode = -1;

        // 執行 PowerShell 測試1
        String program = "PowerShell";
        String[] params = {"-nop", "(Get-Date); 1..3 | ForEach-Object { Write-Host Counting down: $_; Start-Sleep -Seconds 1 }; 0/0"};
        exitCode = executeCommand(program, params);
        System.out.println("Exit code: " + exitCode);

        // 執行 PowerShell 測試2
        String[] params2 = {"powershell", "-nop", "Write-Error 'Pwsh:: throw a Exception' -EA 1; Write-Host Pwsh:: After the Exception"};
        try {
            exitCode = executeCommand2(params2);
        } catch (Exception e) { e.printStackTrace(); }
        System.out.println("Exit code: " + exitCode);
    }

    /**
     * 執行指定的命令並返回結果
     * 
     * @param program 要執行的程序名稱,例如 "powershell"
     * @param params  可變參數,傳遞給程序的參數
     *                例如,對於 PowerShell 命令,可以是 "-nop", "您的 PowerShell 命令"
     * @return        程序執行後的退出碼。通常,0 表示正常結束,非0 表示有錯誤發生
     * 
     * @throws IllegalArgumentException 如果程序名稱為空或 null,或者參數為空或 null。
     */
    private static int executeCommand(String program, String... params) {
        // 檢查參數
        if (program == null || program.trim().isEmpty()) {
            throw new IllegalArgumentException("Program cannot be null or empty.");
        }
        if (params == null || params.length == 0) {
            throw new IllegalArgumentException("Arguments cannot be null or empty.");
        }

        // 設定基本參數
        int exitCode = -1;
        final String encoding = "BIG5"; /* [UTF-8, Shift_JIS, BIG5] */
        final String workDir = System.getProperty("user.dir"); /* 獲取Java當前工作路徑 */

        // 構建輸入參數
        List<String> commandList = new ArrayList<>();
        commandList.add(program);
        Collections.addAll(commandList, params);

        // 構建處理程序
        ProcessBuilder processBuilder = new ProcessBuilder(commandList);
        processBuilder.environment().remove("PSModulePath");
        processBuilder.directory(new File(workDir));

        try {
            // 執行命令
            Process process = processBuilder.start();
            // 處理輸出流
            try (
                BufferedReader stdoutReader = new BufferedReader(
                    new InputStreamReader(process.getInputStream(), encoding));
                BufferedReader stderrReader = new BufferedReader(
                    new InputStreamReader(process.getErrorStream(), encoding));
            ) {
                String line;
                // 讀取標準輸出流
                while ((line = stdoutReader.readLine()) != null) {
                    System.out.println(line);
                }
                // 讀取錯誤輸出流
                while ((line = stderrReader.readLine()) != null) {
                    final String ANSI_RESET = "\u001B[0m";
                    final String ANSI_RED = "\u001B[31m";
                    System.out.println(ANSI_RED + line + ANSI_RESET);
                }
            }
            // 獲取錯誤代碼
            exitCode = process.waitFor();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (InterruptedException e) {
            e.printStackTrace();
            Thread.currentThread().interrupt();
        }

        return exitCode;
    }

    /**
     * 執行指定的命令並返回結果
     * 這是一個簡化版本的方法,將錯誤輸出重定向至標準輸出,只用一個進程監控打印的內容
     * 
     * @param command 命令和其參數,作為可變長度參數傳入。例如:"cmd.exe", "/c", "dir"
     * @return 命令執行後的退出碼。通常,0 表示正常結束,非0 表示有錯誤發生。
     * @throws Exception 如果退出碼非0,則拋出異常。
     */
    private static int executeCommand2(String... command)
            throws Exception
    {
        int exitCode = -1;
        final String encoding = "BIG5"; /* [UTF-8, Shift_JIS, BIG5] */

        ProcessBuilder processBuilder = new ProcessBuilder(command);
        processBuilder.redirectErrorStream(true); // 將錯誤輸出重定向至標準輸出

        try { // 啟動進程
            Process process = processBuilder.start();
            try (BufferedReader reader = new BufferedReader(
                    new InputStreamReader(process.getInputStream(), encoding))) {
                String line;// 使用 BufferedReader 逐行讀取進程的輸出並打印到控制台
                while ((line = reader.readLine()) != null) {
                    System.out.println(line);
                }
            }
            exitCode = process.waitFor();
        } catch (IOException | InterruptedException e) {
            e.printStackTrace();
        }

        // 退出代碼不為零則拋出例外
        if (exitCode != 0) {
            throw new Exception("PowerShell command execution failed with exit code " + exitCode);
        }

        return exitCode;
    }
}

這裡面解決的一個問題是 PowerShell 被這樣委託執行貌似會出錯PS環境變數的問題,解決辦法就是清空他自己會重新讀取

參考這邊獲得的解答
Cert:\ PSDrive is unavailable on PowerShell 5.1 launched by cmd when cmd is launched on PowerShell 7.3 · Issue #18530 · PowerShell/PowerShell · GitHub


這個問題我記得我在別的情況下也遇過,具體錯誤大概會長這樣子

ConvertTo-SecureString : 已在模組 'Microsoft.PowerShell.Security' 上找到 'ConvertTo-SecureString' 
命令,但無法載入該模組。如需詳細資訊,請執行 'Import-Module Microsoft.PowerShell.Security'。
位於 線路:1 字元:22
+ Get-ExecutionPolicy; ConvertTo-SecureString $password -AsPlainText -F ...
+                      ~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : ObjectNotFound: (ConvertTo-SecureString:String) [], CommandNotFoundE 
   xception
    + FullyQualifiedErrorId : CouldNotAutoloadMatchingModule


寫得很不清不楚就是了,總的來說就是很多內建的函式變得不可使用,但明明就有找到模組