|
本帖最后由 gclome 于 2020-9-3 20:36 编辑
原文链接:reGeorg简要分析
前言
在某次实战中使用reGeorg代理工具时,出现了很多问题,带着这些疑问我打算简单分析一下reGeorg。
环境搭建
我这里先以虚拟机phpstudy环境为靶机,用proxifier设置代理程序,pycharm设置reGeorgSocksProxy.py客户端来更好分析reGeorg项目。
这里简要说一下tunnel.nosocket.php和tunnel.php的区别。查看源代码发现tunnel.php多了dl("php_sockets.dll");这行代码。作用是加载socket这个模块,但dl自php 5.3起默认情况下处于禁用状态,所以php 5.3版本以下才可以使用tunnel.php。如果启用,则需配置php.ini。
reGeorgSocksProxy.py
客户端代码分析程序入口
我们从程序的入口__main__来一点点看。
使用argparse模块来接收、解析参数,总共有五个参数。
- -l 本地监听的地址,默认127.0.0.1
- -p 本地监听的端口,默认8888
- -r 每次发送的数据量,默认1024
- -u 目标隧道的地址,需要指定
- -v 详细输出,默认info
复制代码
askGeorg方法
配置好相应的参数,脚本先通过askGeorg方法检测远程代理服务器是否正常。
查看askGeorg方法是怎么判断的?
先看一下使用的是http还是https协议,再connect连接到tunnel地址是否为200。如果响应码为200,则提示Georg says, 'All seems fine'
Socket编程
再往下,socket编程用来接收本地转发的流量。
bind默认绑定到本地127.0.0.1,8888端口上。servSock.accept() 等待TCP的连接(即Proxifier的流量)。
将接收到的数据和远程代理服务器建立session连接。
session类
session类除了初始化还有7个方法。从字面意思大概了解到
- parseSocks5 解析socks5协议
- parseSocks4 解析socks4协议
- handleSocks 使用socks代理
- setupRemoteSession 设置session
- closeRemoteSession 关闭session
- reader 读数据
- writer 写数据
- run 运行程序
复制代码
这是这几个方法依次执行的顺序,我们一个个看。
初始化
使用urlparse库解析tunnel地址获取各个字段。判断是HTTP还是HTTPS协议。
urllib3.HTTPConnectionPool用来创建一个连接池。
run方法
初始化完成后,运行run方法,首先判断使用的什么socks,即handleSocks方法。
通过sock.recv()接收一字节的数据。判断是socks4还是socks5连接。
(可以通过wireshark抓取判断是socks4还是socks5)
判断为socks5,就调用parseSocks5方法。判断socks4,就执行parseSocks4方法。
parseSocks5方法
这里proxifier配置的是socks5。
我们就看parseSocks5方法,首先看到log.debug输出SocksVersion5 detected,提示我们检测到使用的是socks5代理。
然后sock.recv(1)方法又接收一个字节,然后根据atyp判断target是IPV4还是hostname还是IPV6。
这里手动输出atype值查看。即print(str(ord(atype)))
然后sock.recv(4)再接收4个字节为IP地址,ord()函数转化为ASCII码拼接为标准的IP地址。即target。接收2个字节,即targetPort。
targetPort也是通过ord()函数返回十进制数,输出标准端口。
再往下来到connect连接,将target赋值给serverIp,并使用gethostbyname返回主机名IPV4地址。
将serverIp重新变成字节,等待下一步用socks.sendall()发送。在发送之前还需要先设置session。即setupRemoteSession方法。
追踪setupRemoteSession方法。
可以看到是发送POST数据包,并且在headers头设置了{"X-CMD": "CONNECT", "X-TARGET": target, "X-PORT": port} ,以CONNECT为标识,代表和target进行连接。发送该数据包,如果响应码为200,并且响应头x-status为ok,则获取响应头的set-cookie赋值给cookie。
返回cookie进行connect,然后sock.sendall()发送数据。
之后return到handleSocks方法,再到run方法。
至此连接建立,开始session会话,来保存服务端和客户端的socket会话状态。
下面就是关键的reader和write。
reader方法
创建PoolManager对象,发送HTTP数据包。和CONNECT标志相类似。这次使用READ为标识。在headers头添加{"X-CMD": "READ", "Cookie": self.cookie, "Connection": "Keep-Alive"}
POST发送该数据包。如果响应码为200且x-status为ok,则将响应的data数据以socks发送。
writer方法
writer方法实现数据包forward转发。和READ方法类似,不过在POST发送数据包时,直接发送了data数据包。标识为forward。
转发给tunnel.nosocks.php
最后关闭session。即closeRemoteSession方法。
closeRemoteSession方法
还是POST发送数据包,headers添加disconnect标识,{"X-CMD": "DISCONNECT", "Cookie": self.cookie}
响应码为200,则Connection Terminated连接终止。
tunnel.nosocket.php 服务端CONNECT
当接收到客户端的CONNECT连接请求,fsockopen()方法去连接目标地址和端口,如果存在返回X-STATUS: OK,反之则返回X-STATUS: FAIL。
然后进入while循环,fwrite()函数将$writeBuff写进$res,而后一直fgets()读取,将读到的结果赋值给$readBuff,然后输出。
DISCONNECT
当接收到客户端的DISCONNECT连接请求时,只需要将$_SESSION["run"]设置为false,关闭session即可。
READ
当接收到客户端的READ连接请求时,判断$_SESSION["run"],输出$_SESSION["readbuf"]
FORWARD
当接收到客户端的FORWARD连接请求时,使用file_get_contents获取php://input内容,并进行输出。
然后将$rawPostData保存在$_SESSION["writebuf"],通过之前的while循环中fgets()读取数据,追加到$readBuff,再用READ请求输出出来。
debug模式
以debug模式重新回顾reGeorgSocksProxy.py客户端和tunnel.nosocket.php服务端的代码执行的过程。
为了更方便总结该过程,做了小小调试。
设置好profile,代理浏览器访问10.211.55.4的8080端口。
reGeorgSocksProy.py首先访问tunnel.nosocket.php是否响应正常,然后判断来源协议是socks5还是socks4。浏览器访问10.211.55.4的8080端口,reGeorgSocksProy.py接收到的target就是10.211.55.4,port端口就是8080。并设置session。
接着就是read方法和write方法即forward。
writer()方法携带请求10.211.55.4的8080端口的数据包存在data中然后forward发送,data数据存放在全局变量$_SESSION["writebuf"],通过while循环先fwrite()、fgets()方法获取返回的结果,read()方法再从$_SESSION["readbuf"]结果中打印给客户端。
最后返回closeRemoteSession()方法,关闭session。
wireshark抓包
wireshark抓取数据包更直观的可以看到整个过程。
connect连接到指定访问的10.211.55.4和8080端口。
forward 转发10.211.55.4:8080的GET数据包。
read读取返回的内容给客户端。
补充:socks5是怎么判断的?
因为socks5代理启用在本地的环回地址上,即127.0.0.1。且socks协议工作在TCP链路层。所以wireshark应该抓本地lo网卡
过滤规则为
tcp.port == 8888
只看发送到8888的这两个数据包就行。
第一个数据包,leng长度为4。数据内容为十六进制的05020002
第一个字节05即判断使用socks5协议,对应handleSocks方法的if ver == "\x05":
第二、三个字节0200对应方法的parseSocks5nmethods, methods = (sock.recv(1), sock.recv(1))
第四个字节02对应parseSocks5方法的if ver == "\x02":
再看第二个数据包,leng长度10,数据内容十六进制的050100010ad337040050
跟进源代码的parseSocks5方法,继续sock.recv()
接收4个字节长度。即05010001。也对应if atyp == "\x01":
然后再接收4个字节0ad33704为target,2个字节0050为targetPort。
再用ord()函数从target依次拼接返回十进制数。
我们可以在console控制台判断一下
实战调试
大概分析之后,大家应该对整个流程有了一定了解。
下面结合实战对存在的两个问题进行解决。
1. proxifier 代理时为什么出现许多莫名其妙的IP?
其实打开firfox浏览器的一瞬间,firefox就会有多个域名发出请求。
而在reGeorgSocksProxy.py终端里就会解析出多个莫名其妙的IP。
2.什么时候才可以判断确实代理成功?
从代码层上看,访问远程代理服务器tunnel.nosocket.php返回Georg says, 'All seems fine'就可以代理成功的。因为tunnel.nosocket.php只起到一个中转的作用,转发流量到内网。
而这次error的原因是目标地址的80端口没有开放,其实不能说明代理是不能使用的。
比如访问8180端口
最后选择在kali的proxychains和自己编写的端口扫描成功代理。
tunnel.nosocket.php 免杀
最后还测试某盾查杀了tunnel.nosocket.php,调试发现主要查杀两个位置。
一个fsockopen()函数,对该函数提前引用即可绕过。
一个php输入流,即php://input,使用str_rot13()函数和一些混淆即可绕过。
- $php = str_rot13('c!c!cuc');
- $str = explode('!',$php)[2];
- $pql = $str."://";
- $phpinput = $pql."input";
复制代码
也可以结合webshell免杀的思路,使用简单的编码思路将脚本base64编码之后再解码。
也可成功免杀。
总结
大致分析了reGeorg的工作流程,明白是socks编程完成流量的转发,再将结果返回,结合实战成功建立了代理,最后两个姿势进行简单免杀处理。
|
|