PS:也是照搬的之前的论坛存档,当时写得比较仓促,后来去朋友那边的技术沙龙讲了一次,同时文末附上PPT,写过大大小小类似的工具,也算是对这类技术的一些总结吧

常见边界拓扑

第一种情况

1
Inbound Stream ---> Firewall ---> Target
1
Inbound Stream --->  Load Balance ---> Target

这其中无论负载均衡设备转发或者防火墙的,均有可能存在带来源IP转发或者不带来源IP转发的情况,带来源IP或者端口的方案我们在下文的常见端口复用实现机制中有所介绍,该如何在此种情况下实现端口复用,而不带来源IP或者端口的情况目前网上讨论得不多,下文也将就这类情况进行分析提出解决方案。

第二种情况

1
Inbound Stream ---> Reverse Proxy ---> Target

另外一种较为常见的场景就是类似于Nginx或者Squid之类的反向代理应用了,这种其中绝大部分所代理的协议均为HTTP,通常我们遇到这类场景,在渗透中都会使用一个基于脚本的正向HTTP Tunnel,在权限允许的情况下,本文也给出了针对此种场景的一个端口复用方案。

常见的端口复用实现机制

看完常见的边界拓扑,我们来谈一谈流行的端口复用实现方案,笔者几乎都动手编码实现过如下的端口复用方案。

要实现端口复用,首先需要理解其本质,端口复用的本质实际上就是包的重定向(在特定时机下拿到特定的包并进行转发),在我们熟悉了各类操作系统从内核层到用户层实现的网络协议栈后,就可以轻松的从中挑选出某个环节进行加工对包重定向。在内核层,比如针对 Windows 在不同的NT内核中均提供了不同的方案,在NT6以后提供了 WFP(Windows Filter Platform),针对 Linux 则一直都有 Netfilter;在用户层,Windows 提供了LSP等机制。

故由此可以得出的端口复用方案可以从内核层或者用户层入手。我们先来聊聊用户层,比较通用的方式是Hook Socket的相关函数,而绝大部分Linux的Port Knock类型的Rootkit实现方式都是基于此,比如通过Hook Accept判断来源端口是否为某个Magic Port来重定向包,或者通过Hook Recv函数,同时指定其Flag为MSG_PEEK即可判断收到包是否包含特定特征,而不破坏原来的数据流,最终依条件进行包的重定向;再比如在IIS6的时代,数据包是通过IOCP放到用户层进程w3svc来处理的,此时只需要Hook CreateFile有同样可以拿到发来的数据包;同时针对指定用户层进程的端口复用方式也可以通过程序本身提供的功能,比较流行WEB中间件如Apache、Nginx等均提供了Module的编程接口,在特定的回调函数或事件中即可拿到包进行判断进而转发,还有Socket的Setsockopt函数中关于SOCK_REUSE设定端口可重用的一种实现方式,这在以前也比较场景,可以重新Bind一个自己的工具监听端口到设置了SOCK_REUSE标志的端口上,从而实现端口复用;而在内核层中,如Linux最方便的莫过于Netfilter了,在之前代表也写了一篇文章介绍基于其实现“端口复用”的文章,而Windows则可以通过过滤驱动的方式来重定向包,但多数已有内核方案的用户层接口并不友好,比如仅仅只能实现TCP三层的转发,而无法实现四层的包鉴别,同时开发的成本也不低,作为一个RedTeamer应该有随手拿起身边的工具,为攻击过程提供便利的能力,介于此有了这篇笔记,这或许不是最完美的解决方案,希望所提供的思路能为大家抛砖引玉。

廉价的端口复用方案

基于以上讨论,便有了一个廉价的端口复用方案,能够兼容常见边界拓扑中的种种情况,主要的流程如下:

1
Inbound Stream ---> Kernel Packet Filter ---> Userland Packet Redirect ---> Proxy Server

我们以一个开放在IP为10.1.1.1的WEB服务为例(80端口),假设我们通过前期的渗透获取了10.1.1.1的系统权限(Root或System),但由于只开放了80端口,我们通常的攻击方案是选取对应脚本语言实现的HTTP Tunnel将此台机器作为跳板攻击内网,如常见边界拓扑中所描述的情况一样,10.1.1.1的上层可能是一个反向代理或者一个防火墙

当流量到达需要进行端口复用的机器上时,我们首先通过内核层所提供的包转发机制将入站的数据包依据特定规则,将原本会到达80端口的流量,优先转发到我们用户态的程序进行(下文将以Haproxy为例),同时我们用户态的程序会通过基于协议的识别、或者应用层协议包中的特定数据或者特征再进行一次分流,流程讲变为如下图所示:

1
2
3
4
5
6
7
                                                                        + ----> Proxy Server
|
+
Inbound Stream ---> Kernel Packet Filter ---> Userland Packet Redirect -|
+
|
+ ----> Web Server

Ps: Ascii流程图 http://asciiflow.com/

最终,我们包含特定特征的流量会到达我们的Proxy Server,同时不影响正常业务的使用。下面就不同的操作系统和具体实现方案进一步展开。

For Linux

假设这里的通信接口为eth0,我们将所有目的端口为80的流量转发到9999,通过Iptables进行转发,这里必须得指定接口,以避免回环接口上的地址也进行转发,造成 loop redirect。

1
iptables -t nat -A PREROUTING -i eth0 -p tcp --dport 80 -j REDIRECT --to-port 9999

其他常见操作

1
2
3
iptables -t nat -L -n -v
iptables-save
iptables -L INPUT –line-numbers && iptables -D INPUT 4

For Windows

对于非系统服务,比如重定向Windows上的 Apache的 8080 端口到 1080 端口,我们可以使用 IpNat 进行转发

1
netsh interface portproxy add v4tov4 listenport=8080 connectport=1080 connectaddress=10.1.1.1

其他常见操作

1
2
netsh int portproxy delete v4tov4 listenport=9999
netsh int portproxy show all

对于系统服务,针对IIS的80端口(Http.sys)SMB的445端口(Smb.sys),使用 IpNat 无法直接转发。以SMB为例,首先我们需要通过 sc config smb start= demand将 SMB 这个内核驱动设定为禁止自动运行,然后添加一张本地回环网卡(这里将IP设定为 10.255.255.1),并且将跃点数修改为一个较大值,同时取消 Netbios 协议在此网卡上的注册,为了保证原SMB服务的正常运行,我们需要在计划任务中添加一个SMB的自启动,sc start smb,最后通过添加转发规则即可。

1
netsh interface portproxy add v4tov4 listenaddress=10.1.1.1 listenport=445 connectaddress=10.255.255.1 connectport=44445
  1. Q:是否必须添加回环网卡 A:是,并且需要进行配置以修改445监听
  2. Q:是否必须重启服务 A:实际上从 sc query 可以知道 smb是一个Kernel Driver,所以是无法通过sc restart方式来重启这个驱动的
  3. Q:是否必须重启计算机 A:必须重启计算机以加载他,故为了使原有服务正常,必须再在登陆后重新启动smb这个kernel driver

如果想要不进行重启,那么只能通过加载驱动的方式来进行处理,并且需要自己编写Ring3的部分代码来通过驱动回调增加过滤的条件等,比较流行的方式有基于WFP实现的WIndiver以及基于NDIS的WinpkFilter,可以参考 https://github.com/BarbaTunnelCoder/BarbaTunnel/wiki/Choosing-FilterDriver-(WinDivert-vs-WinpkFilter) 的比较,由于本文旨是廉价版的端口复用,故这里不再赘述如何以驱动方式来进行包转发了。

处理完内核层的包重定向后,接下来需要做的针对到达用户层的包进行分流,这里我们不需要自己实现一个 Load Balance 而是使用比较成熟的方案 Haproxy 来进行TCP四层的包识别与转发,对于

TCP/IP 三层

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
global
log /dev/log local0
log /dev/log local1 notice
chroot /var/lib/haproxy
user haproxy
group haproxy
daemon

defaults
log global
mode tcp
option tcplog
option dontlognull
#maxconn 2000
timeout connect 24h
timeout client 24h
timeout server 24h


frontend main
mode tcp
bind *:9999

tcp-request inspect-delay 3s
#GET POS(T) PUT DEL(ETE) OPT(IONS) HEA(D) CON(NECT) TRA(CE)
acl is_http req.payload(0,3) -m bin 474554 504f53 505554 44454c 4f5054 484541 434f4e 545241
tcp-request content accept if is_http
tcp-request content accept
use_backend http if is_http
use_backend socks5

backend socks5
mode tcp
timeout server 1h
server ss 127.0.0.1:51025

backend http
mode tcp
server ngx 127.0.0.1:80

通过 req.payload 来取包中的前3字节,确认是否为 HTTP 中的 Verb,如果是则判断为HTTP协议并进行转发,如不是则判断为是我们代理程序的协议转发到 51025 中。

TCP/IP 四层

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
global
log /dev/log local0
log /dev/log local1 notice
chroot /var/lib/haproxy
user haproxy
group haproxy
# daemon

defaults
log global
mode tcp
option tcplog
option dontlognull
#maxconn 2000
timeout connect 24h
timeout client 24h
timeout server 24h


frontend main
mode tcp
bind *:9999

option forwardfor except 127.0.0.1
option forwardfor header X-Real-IP

acl is-proxy-now urlp_reg(proxy) ^(http|https|socks5)$
# http-request set-var(req.proxy) urlp(proxy) if is-proxy-now

use_backend socks5 if is-proxy-now
use_backend http

backend socks5
mode tcp
timeout server 1h
server ss 127.0.0.1:51025

backend http
mode tcp
server ngx 127.0.0.1:80

这里通过 urlp_reg 注册了一个变量 proxy,同时使用正则来判断这个 proxy 的Key所有的值的范围为 http\https\socks5,也就是说当访问的url中带了?proxy=socks5时候则进行转发,此时我们的代理程序必须也能够识别 HTTP 的数据包,这里的实现则变成了类似于HTTP Tunnel的一个实现了。而基于高级语言实现的代理程序,在绝大部分情况下的健壮性与效率基本优于基于脚本的HTTP Tunnel。

武器化

将如上过程武器化,只需要一个静态编译的 Haproxy 加上自己实现的 Proxy Server (代理程序)即可,通过一个 Loader 的脚本即可进行调用,但针对Windows这个方案并不算比较便捷,诚然可以通过 Powershell 来编写 Loader,但由于需要重启所以动静还是比较大的,针对Windows后期坏牛会基于 Windivert 编写 一个比较成熟的工具,敬请期待。

最后感谢肥欣哥提供的思路 ;)

以及附件的下载:右键另存为…

参考

http://woshub.com/port-forwarding-in-windows/

https://www.nikhef.nl/~janjust/CifsOverSSH/VistaLoopback.html

https://github.com/TalAloni/SMBLibrary#notes

https://blog.sleeplessbeastie.eu/2018/05/02/how-to-use-variable-to-choose-haproxy-backend/

https://davidhamann.de/2019/06/20/setting-up-portproxy-netsh/