【技术分享】基于TCP/IP协议栈的隐写术和隐蔽通道(part 2)

http://p2.qhimg.com/t019dd4a8d2cb25f223.jpg

翻译:shan66

预估稿费:200RMB

投稿方式:发送邮件至linwei#360.cn,或登陆网页版在线投稿


传送门

【技术分享】基于TCP/IP协议栈的隐写术和隐蔽通道(part 1)

隐写术理论基础

在本文的第1篇中,我们提到过隐写术;现在,我们将对其进行深入的介绍。

隐写术有2类: 

我们可以将莎士比亚戏剧文本隐写到一些斑马图像中,并确保没有人能觉察出来,因为对于观看图像的人来说,通常不会检查每个像素的每个字节的LSB。但是如果他们真的这样做的话,就会发现这些文本。 这属于“隐藏在平原地带”的隐写术。 整个第1篇中,我们将纯数据封装在TCP / IP报头中的方法,就属于此类别。

第二类(上述Tanenbaum的例子即属此类)隐写术就要好多了。它使用加密技术来确保即使把图像翻个遍,也看不出莎士比亚戏剧文本的痕迹,从而找不到相应的秘密了。 

为什么说基于加密技术的隐写术要更好呢?因为这两种技术中的任何一种,都能给你提供保护。不同的是,如果你使用加密技术,虽然没有人能理解你说的话(授权的听众除外),但是所有人都知道你跟听众之间有一个通信信道,并且知道你可能会谈论一些秘密(这正是你使用加密技术的原因)。如果你使用隐写术的话,没有人觉察到通信信道。所以,别人也无法知道正在进行交流。换句话说,隐蔽通道就是一个别人不知道的通信通道。 

坏消息是隐写术大部分情况下都会留下痕迹。有些时候是不言自明的。例如,图片中的LSB隐写术会导致色差,这很容易引起人们的怀疑。或者,在第1篇文章中,由于只能在随机字段中推送ASCII字节,这样就会显著降低这些字段中数据的熵——通过这种现象,通信信道甚至通信本身的可能性就会引起取证人员的注意。如果基于隐写术的隐蔽通道被发现了,那么它就降级到了单纯加密层面。 


现在谈点完全不同的事情! 

Pozzo & Lucky

Pozzo&Lucky是戏剧《等待戈多》中的两个关键人物。这部戏是爱尔兰现代主义剧作家塞缪尔·贝克特的两幕悲喜剧,也是它最著名的戏剧,同时也是我最喜欢的一部。

Lucky是一个没有自己的信仰、意见甚至想法的仆人。他盲目地服从Pozzo,Pozzo甚至用一条狗项圈牵着他。每当Pozzo发号命令,他都会跳舞。

在戏中,我们不知道为什么Lucky是如此可悲,任Pozzo摆布,做尽各种恶心的事情。他们之间一定有一个隐蔽通道…

但是,除了是戏剧的两个角色名字之外,Pozzo&Lucky还是一个个人项目。一个以打赌作为开始的项目。 “是否存在这样一种远程命令执行shell,既没有网络设备可以检测到它,也不会留下网络踪迹。我打赌有…

嗯,我赌赢了。这个shell确实存在并被命名为Pozzo&Lucky … 

思路

这里的思路与第1篇中的思路非常接近,我们将通过IP标识和TCP序列(ISN)字段来传递命令。 但是,这一次,我们做得更好…

Pozzo&Lucky shell由2个组件组成。Lucky必须安装到目标机器上(实际上只是在目标机器上面运行),Pozzo则是用于控制安装了Lucky的目标机器。 

功能

执行OS命令(带输出和不带输出)

远程实时执行Shellcode(粘贴并执行)

文件上传/下载

完全绕过.pcap文件分析、防火墙日志分析和常规分析,从而阻碍取证人员从目标机器的操作系统上取证

能够模拟nmap -sS端口扫描或任何类型的SYN扫描,或将SYN Flood转发到特定(或给定)的一个或多个目标端口

可以用于Windows系统和Linux系统。

没有创建连接。同一个会话中的每一个数据包都可以从不同的源IP发送到不同的目标端口。

缺点

太慢! (带宽是5字节/包,所以要格外有耐心)

作为一个进程,它缺乏隐藏自身或持久性的能力。它必须与一个rootkit配合使用。

代理会杀死它(但是不会检测它)。它必须通过端口转发才能正常工作。

具有依赖性… 在Windows上依赖基于Winpcap的Scapv,在Linux上依赖Scapy。两者可能会被Pylnstaller-pv2exe-nuitka会话阻止。 

要求

需要拥有目标主机的root / admin权限(由于数据包的构建和嗅探都需求超级权限)。

要求Pozzo与Lucky必须在同一个子网(如果它们所在主机都具有公共IP的话,只要连接Internet即可)中,至少Pozzo要有一个指向Lucky的TCP端口路由(如果Lucky位于一个只转发TCP的21端口的防火墙之后, Pozzo将数据包发送到<Firewall_IP>:21即可。)

Pozzo不能位于NAT之后。 这是因为出站Pozzo数据包的源端口对Lucky是有意义的,但是NAT会修改这个字段(这是因为,在使用网关的IP转发之前,会将其转换为另一个源端口)。 

概念

前面说过,Lucky运行在目标机器上面,实际上它就是一个数据包嗅探器。它能收到所有到达机器的数据包,通过下文将要介绍的算法来确定哪些数据包是由Pozzo所在的计算机创建的。 

关键在于这些数据包没有建立连接(无论TCP还是UDP连接),它们是TCP SYN数据包,没有以任何方式滥用TCP协议(在本文的第3篇中,我们将详细讲解如何绕过(由安全设备和数据包检查程序进行的)协议完整性检查。同时,它们也是有用的数据包,通常不会被网络拦截(这与与ICMP不同),因为这样做就会破坏网络的运转(网络中不允许连接阻止SYN数据包,否则SQL、Web应用程序、FTP等统统挂掉…)。

这些“Pozzo数据包"的特征在于通过IP标识字段和TCP序列号字段(2字节+ 4字节)以强加密的形式提供6字节的数据,当Lucky遇到这样一个数据包时,会从中抽取6字节的有效负载,以1 + 5字节形式分割,其中第一个字节是用于随后5个字节的命令操作码。然后生成一个RST-ACK数据包(当然也不能违反TCP协议),并且注入(同时加密)到在目标机器上执行的命令的响应中,从而将其发送回Pozzo。 

这种SYN-RST之间的交互,与远程命令执行相比,更像是端口扫描,因此它不会被IDS / IPS阻止,因为使用加密后,就没有匹配的签名了(并且它们很少检测3-4层的报头)。 一个配置良好的防火墙设备,如果考虑到了每个主机的使用情况(这是一个SSH服务器——只允许22端口),可以减轻Pozzo&Lucky带来的影响,但是这么做的并不多见! 


解决面临的问题

在第1篇中,我们曾经提到一些问题,下面给出相应的解决方案。

克服熵问题

熵的问题是,虽然允许在随机字段中的每个字节位置中使用256个字节中的任何一个,但是我们只会使用可打印ASCII列表中相应字节,并且通常不包括大写字母和数字。 这使得随机字段会包含的数据具有较高的可预测性,从而导致数据的熵降低。

对于这个问题,解决方案是加密。但是我们需要以6字节为块单位的加密技术,或流密码。 最重要的是,我们需要”style”…所以我根据明文XOR和SHA512定制了一个一次性密钥加密方案。这样的话,那根本就不缺少”style”! 

OTP方案

你对通行字短语进行SHA512运算,从而得到一个密钥。然后,利用这个密钥对数据进行XOR运算,每次处理6字节的数据。经过异或处理的数据就被安全地加密了,因为密钥是通行字短语的单向函数,同时它是保密的。要加密下一个6字节的数据块,需要通过SHA512处理当前的密钥,并重新进行XOR运算。这样我们就不会使用相同的密钥进行XOR运算了,从而消除了通过已知明文进行密码分析的可能性,同时也消除了预测下一个密钥的可能性,即使我们始终加密相同的6个字节(例如wls -laH),每次可以恢复密钥部分是6个字节。单纯6个字节,无法提供足够的信息来产生下一个密钥,因为整个密钥是512位(64字节)的。

另外,使用这种方法的话,有可能对任何可能的字节进行XOR(SHA512返回包含各种字节的序列),我们得到的加密字节处于整个256字节范围内。而且它们具有相同的可能性…这意味着熵接近1,也就是说数据看上去是随机的。 

克服身份问题

“谁是你的主人?”RCE shell必须知道如何回答这个问题。你可以远程运行命令,这是一件好事,但你必须是唯一可以这么做的人。也就是说,shell必须能够将你的数据包与其他人的数据包区分开来。并且为了在shell代理程序中加入IP检查功能,你必须将自己IP或域名硬编码到代理中。仅靠这一点是不够的,除非你采用了Exploit-Kits中快速更换子域名等技术,以及其他缺乏”style”的东西,否则这些数据包最终会被捕获和分析! 

在本文的第1篇中,我们忘了TCP中的一个可控字段,同时在端口扫描中也无人关心它:源端口。 “好的,你会想,让数据包来自端口xxxx,那么这就是需要解密和执行的数据包”。 嗯,是的,但它也缺乏”style”。 所以需要: 

解决“谁是你的主人”问题

源端口检查的思想在一定程度上是正确的,但是它有个很大的坑。这个想法实现起来很容易,不过同样也很容易被分析人员发现。如果您收到一个.pcap文件,涉及各种目标端口和一个源端口(甚至有多个源IP),这肯定会引起你的怀疑。

为什么端口扫描器需要在多个系统中分配端口23456? 

这是通过硬编码实现的吗?

你了解这样的端口扫描器吗?

这是一种常见的行为吗?

在搜索引擎中搜索端口23456时没有返回结果。

所以这非常可疑。 

在Pozzo&Lucky中,我们会检查数据包的源端口,但我们不指望它始终是一样的。此外,还有一个循环算法,类似于上面的OTP方案。 

源端口字段包含2个字节,即4个十六进制数字。 我们根据给定的通行字短语(它必须大于8——始终得到高端口)来初始化第一个(最高有效位)数字。 然后我们利用SHA512处理该通行字短语,从而得到哈希值的前3个十六进制数字。然后,将它们与最初的十六进制数字组成4个十六进制数字或2个字节。然后我们重复哈希计算过程,也就是通过重新计算其哈希值来生成下一个端口。 

这种技术能够提供不同的端口号,对于没有通行字短语的人来说,这将是一个完全不可预测的序列。只有代理程序(Lucky)和客户端(Pozzo)知道下一个正确的通信端口,并且出现具有正确源端口的“噪音”数据包的可能性为1/65536,所以可以忽略不计。 

克服不一致的状态(或狗项圈)

虽然出现噪音数据包的机会很渺茫,但是代理程序还是可能会接收到具有正确的源端口的噪音数据包(即并非由客户端Shell创建的数据包)。如果发生这种情况,代理将循环到下一个源端口,循环处理加密密钥,尝试对并没有包含隐写内容的数据包进行解密,并将其中的噪音数据当做要处理的内容,这样的话,就乱套了。

这时,局面就会完全失控,因为客户端对发生的密钥循环处理一无所知,因此将继续使用代理无法识别的密钥进行加密,并且从无法继续接收数据的代理源端口发出。

也就是说,我们将失去对肉鸡的远程控制能力。我们必须重新拿下它,并使用另外的漏洞利用代码投送工具。但是,请记住,Pozzo是靠狗项圈来控制Lucky的。他随时都能把他拽回来。 

狗项圈的实现

当然,我们有一个安全机制可以防止这种悲剧。在OTP方案中,我们存储了一个不会参与循环的特殊控制密钥。此外,还有一个控制源端口,代理程序会接收来自该端口的数据包,并使用控制密钥将其解密。如果这种数据包中包含特殊的RST有效载荷,那么OTP密钥和源端口循环机制都会被重置。

这意味着,如果通信出现问题,可以从头开始,并且不会留下任何未加密的踪迹。 

长于5字节即为长有效载荷

有些命令,如“find / -name”flag'2> / dev / null”,其长度会超过单个数据包的5字节限制(+1字节的操作码),这些命令需要进行分块,并通过多个数据包传送,Lucky必须明白,“find”(注意空格 – 1个字节!)不是完整的命令,还必须等待下一个数据包的到达。

还有一种情况是“head -1 /etc/shadow”只能得到root密码的哈希值,这个命令产生一个大于等于100个字节的输出,并且必须将其传送回Pozzo。 而Pozzo必须知道什么时候需要等待更多的输出,什么时候整个有效载荷已经传输完成。此外,Lucky也从不发送那些数据包响应之外的数据包。 

协议中协议

如果您可以使用操作码,那么您可以进行有状态的传输,这意味着您能知道何时需要等待更多的数据。有的操作码指示“还有更多的数据要传送,先不执行”,有些操作码表示“这个数据是命令的一部分”,有的操作码指出“这个数据是命令中的最后一个数据包,现在执行该命令”。它类似于TCP分组算法,只是无需使用数据偏移,因为无论是时间还是带宽方面,都不允许使用偏移量。OTP方案都可以确保,如果数据包发生丢失,那么后续的数据包就无法进行解密,因此不会出现部分执行的问题——状态不一致的问题就被解决了。 

Lucky的应答方式

除非进行应答,否则Lucky从不发送数据包…这意味着它必须通知Pozzo想“谈谈”。然后,Pozzo开始发送随机数据(使用“谈谈”操作码),并且只能接受有意义的应答。Lucky还会声明什么时候需要结束谈话,然后就安静下来(直到下一个命令为止)。 

执行Shellcode结束Lucky

传递shellcode后,在Linux中可以通过上面的ctypes代码片段来执行:

libc = CDLL(‘libc.so.6’)# 加载 libc
sc = c_char_p(shellcode)  # 利用shellcode生成一个C字符串
size=len(shellcode) #计算shellcode的长度(后面会用到)
addr=c_void_p(libc.valloc(size)) # 根据shellcode长度分配堆内存
memmove(addr,sc size)#将shellcode从堆栈变量(指针)sc复制到刚刚分配的堆内存中
libc.mprotect(addr, size, 0x7)# 禁用数据内存的NX保护
run = cast(addr, CFUNCTYPE(c_void_p)) # 将指向堆中shellcode的指针转换为函数指针
run()#运行shellcode

这里首先将其复制到堆内存中,解锁NX内存块的保护,并跳转到相应地址。这样,Lucky就会停止执行,因为EIP现在指向shellcode。Lucky将在shellcode终止时终止… 

变成独立进程! 

进行下列处理:

p = Process(target=run)  #将shellcode作为独立进程p.start()运行

而不是使用:

 run()

花了我半个小时的时间盯着屏幕…

在Windows中,也可以使用CreateThread()。它甚至要更好一些,因为EIP在Windows中是无法跟踪的。在任何给定的时间,没有人可以跟踪EIP,即使其开发者也做不到。


好戏开场了! 

测试

启动Lucky

#Jlucfy.py mypcssphrase

Lucky启动之后,使用通行字短语来创建OTP,然后耐心等待…

连接Pozzo 

# Jpozzapy tai^eCjp mypossphrase

实际感染

cp lucky.py/usr/sbirVX
printf "@reboot/usr/sbin/X -rootless-noresetn" > /etc/crontab

记住,原来的X可执行文件位于/ usr / bin目录…我个人不相信系统管理员会发觉这个进程是个冒牌货。即使乐观地估计,十个管理员中有四个看出来就不错了。如果你不是一个善于观察的人,则需要借助一些工具才能发现问题! 

而这个Lucky实例的通行字短语是(是的,你猜到了!)“-rootless”(argv[1])。 你可以使用所有类似开关的通行字短语。我认为,没有谁了解所有的X开关…同时,也永远不会有人去阅读X的手册(页)! 

(在这里,我们黑的不是电脑,而是人脑。恕我直言,Hacking无处不在。) 

通行字短语也可以硬编码到lucky.py中,但是这种方法更容易被发现!此外,如果通行字短语作为参数传递的话,字符串命令将不会返回任何东西。这种方式很容易暴露。 


开启视频模式 

OS Shell

这里我在Pozzo&Lucky中运行一些linux命令,同时用tcpdump嗅探。

视频链接

Shellcode(ASM)Shell  

这里,我远程运行了网上找到的某shellcode。我第一次尝试投递shellcode时连接断开了,所以我重新启动了Pozzo强制重置数据包,之后就一切正常了。

视频链接

实验证明,终止shellcode后,再次使用操作系统shell时Lucky没有出问题。

视频模式关闭


结束语

目前这个项目是封闭的,因为它是一项尚未完成的个人研究的一部分。总的来说,整体想法具有一些学术性的观点,与"Embedding Covert Channels into TCP/IP”(Murdoch & Lewis, 2005)等论文的观点相仿——我早就说过这个想法不是新的。 

此外,任何人都可以将本文介绍的思路为基础,撰写自己的实现。当然,我的技术不是最好的,因为我相信有些事情还可以做得更好。我在编写Pozzo&Lucky时学到了很多东西,希望大家不要错过这样的机会——不妨自己亲自动手编写自己的实现版本。此外,还有些事情(也许是很多事情!)要做,比如:

用ASM可编译语言(可能是C ++…)编写这样一个工具! 这将是一个威力无比的工具,因为没有依赖关系(如果有的话可以随时使用 – – static)。

使用另一种协议。 ARP如何? ARP是不会被拦截的,除非网络管理员是一个疯子,并已将所有交换机端口绑定到MAC。 即使出现这种情况,LAN中的所有人都可以收到Gratuitous ARP。我觉得这方面很有潜力。

根据上文中给出的伪代码提供其他实现。可以使用隐蔽通道过滤器。可以使用一个分类模型,来提供一个数据包是否包含隐写数据的可能性大小。我的意思是,为什么周围没有这样的东西?

我真的很想看到一个用于隐写术过滤的PF-Sense插件。

列表继续… 


还有第3篇吗?

当然,谢谢关心! 

第3篇将介绍针对这种技术的检测方法和防御措施。这将是一篇针对蓝队的文章!

目前,这种技术还有一些把手可能被抓住!

虽然TCP序列字段的熵与/ dev / urandom的熵具有相同数量字节,但是概率分布呢? 即使使用时间作为“种子”来创建ISN(由操作系统完成),它们也不是完全随机的。 这意味着它们不可避免地具有特定的分布状态。 Pozzo&Lucky是否具有与ISN类似的分布呢? 很可能不是。 

我们可以使用此信息确定数据包流是否包含隐写数据?

如果是,我们需要许多数据包(很多值来识别概率分布)。

具体多少?

在我们抓住坏蛋之前,会有多少数据已经被泄漏?

下一篇会介绍函数和积分,以及防火墙和IDS日志等!更多精彩内容,敬请期待… 


传送门


【技术分享】基于TCP/IP协议栈的隐写术和隐蔽通道(part 1)

(完)