reGeorg简要分析 – 作者:CSeroad

前言

在某次实战中使用reGeorg代理工具时,出现了很多问题,带着这些疑问我打算简单分析一下reGeorg。

环境搭建

我这里先以虚拟机phpstudy环境为靶机,用proxifier设置代理程序,pycharm设置reGeorgSocksProxy.py客户端来更好分析reGeorg项目。

1599109789.png!small

1599109802.png!small

这里简要说一下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__来一点点看。

1599109819.png!small

使用argparse模块来接收、解析参数,总共有五个参数。

-l 本地监听的地址,默认127.0.0.1
-p 本地监听的端口,默认8888
-r 每次发送的数据量,默认1024
-u 目标隧道的地址,需要指定
-v 详细输出,默认info

askGeorg方法
配置好相应的参数,脚本先通过askGeorg方法检测远程代理服务器是否正常。

1599109839.png!small

查看askGeorg方法是怎么判断的?

1599109854.png!small

先看一下使用的是http还是https协议,再connect连接到tunnel地址是否为200。如果响应码为200,则提示Georg says, 'All seems fine'

Socket编程

再往下,socket编程用来接收本地转发的流量。

1599109880.png!small

bind默认绑定到本地127.0.0.1,8888端口上。servSock.accept() 等待TCP的连接(即Proxifier的流量)。
将接收到的数据和远程代理服务器建立session连接。

session类

1599109895.png!small

session类除了初始化还有7个方法。从字面意思大概了解到

parseSocks5  解析socks5协议
parseSocks4  解析socks4协议
handleSocks  使用socks代理
setupRemoteSession   设置session
closeRemoteSession   关闭session
reader  读数据
writer  写数据
run     运行程序

1599109911.png!small

这是这几个方法依次执行的顺序,我们一个个看。

初始化

1599109926.png!small

使用urlparse库解析tunnel地址获取各个字段。判断是HTTP还是HTTPS协议。
urllib3.HTTPConnectionPool用来创建一个连接池。

run方法

初始化完成后,运行run方法,首先判断使用的什么socks,即handleSocks方法。

1599109939.png!small

通过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)))

1599109953.png!small

然后sock.recv(4)再接收4个字节为IP地址,ord()函数转化为ASCII码拼接为标准的IP地址。即target。接收2个字节,即targetPort。

1599109963.png!small

targetPort也是通过ord()函数返回十进制数,输出标准端口。
再往下来到connect连接,将target赋值给serverIp,并使用gethostbyname返回主机名IPV4地址。

1599109973.png!small

将serverIp重新变成字节,等待下一步用socks.sendall()发送。在发送之前还需要先设置session。即setupRemoteSession方法。

1599109987.png!small

追踪setupRemoteSession方法。

1599109998.png!small

可以看到是发送POST数据包,并且在headers头设置了{“X-CMD”: “CONNECT”, “X-TARGET”: target, “X-PORT”: port} ,以CONNECT为标识,代表和target进行连接。发送该数据包,如果响应吗为200,并且响应头x-status为ok,则获取响应头的set-cookie赋值给cookie。

1599110013.png!small

1599110032.png!small

返回cookie进行connect,然后sock.sendall()发送数据。

1599110049.png!small

之后return到handleSocks方法,再到run方法。

1599110061.png!small

1599110076.png!small

至此连接建立,开始session会话,来保存服务端和客户端的socket会话状态。
下面就是关键的reader和write。

reader方法

1599110087.png!small

创建PoolManager对象,发送HTTP数据包。和CONNECT标志相类似。这次使用READ为标识。在headers头添加{“X-CMD”: “READ”, “Cookie”: self.cookie, “Connection”: “Keep-Alive”}

1599110099.png!small

POST发送该数据包。如果响应吗为200且x-status为ok,则将响应的data数据以socks发送。

1599110113.png!small

writer方法

writer方法实现数据包forward转发。和READ方法类似,不过在POST发送数据包时,直接发送了data数据包。标识为forward。

1599110128.png!small

转发给tunnel.nosocks.php
最后关闭session。即closeRemoteSession方法。

closeRemoteSession方法

1599110140.png!small

还是POST发送数据包,headers添加disconnect标识,{“X-CMD”: “DISCONNECT”, “Cookie”: self.cookie}
响应码为200,则Connection Terminated连接终止。

tunnel.nosocket.php 服务端

CONNECT

当接收到客户端的CONNECT连接请求,fsockopen()方法去连接目标地址和端口,如果存在返回X-STATUS: OK,反之则返回X-STATUS: FAIL。

1599110155.png!small

然后进入while循环,fwrite()函数将$writeBuff写进$res,而后一直fgets()读取,将读到的结果赋值给$readBuff,然后输出。

1599110171.png!small

DISCONNECT

当接收到客户端的DISCONNECT连接请求时,只需要将$_SESSION["run"]致为false,关闭session即可。

1599110184.png!small

READ

当接收到客户端的READ连接请求时,判断$_SESSION["run"],输出$_SESSION["readbuf"]

1599110217.png!small

FORWARD

当接收到客户端的FORWARD连接请求时,使用file_get_contents获取php://input内容,并进行输出。

1599110234.png!small

然后将$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。

1599110251.png!small

接着就是read方法和write方法即forward。

1599110261.png!small

writer()方法携带请求10.211.55.4的8080端口的数据包存在data中然后forward发送,data数据存放在全局变量$_SESSION["writebuf"],通过while循环先fwrite()、fgets()方法获取返回的结果,read()方法再从$_SESSION["readbuf"]结果中打印给客户端。

1599110272.png!small

1599110293.png!small

最后返回closeRemoteSession()方法,关闭session。

1599110312.png!small

wireshark抓包

wireshark抓取数据包更直观的可以看到整个过程。

1599110326.png!small

connect连接到指定访问的10.211.55.4和8080端口。
forward 转发10.211.55.4:8080的GET数据包。

1599110368.png!small

read读取返回的内容给客户端。

1599110383.png!small

补充:socks5是怎么判断的?
因为socks5代理启用在本地的环回地址上,即127.0.0.1。且socks协议工作在TCP链路层。所以wireshark应该抓本地lo网卡
过滤规则为

tcp.port == 8888

1599110397.png!small

只看发送到8888的这两个数据包就行。
第一个数据包,leng长度为4。数据内容为十六进制的05020002

1599110408.png!small

第一个字节05即判断使用socks5协议,对应handleSocks方法的if ver == "\x05":
第二、三个字节0200对应方法的parseSocks5nmethods, methods = (sock.recv(1), sock.recv(1))
第四个字节02对应parseSocks5方法的if ver == "\x02":

再看第二个数据包,leng长度10,数据内容十六进制的050100010ad337040050

1599110425.png!small

跟进源代码的parseSocks5方法,继续sock.recv()

1599110439.png!small

接收4个字节长度。即05010001。也对应if atyp == "\x01":

1599110454.png!small

然后再接收4个字节0ad33704为target,2个字节0050为targetPort。
再用ord()函数从target依次拼接返回十进制数。
我们可以在console控制台判断一下

1599110466.png!small

1599110477.png!small

实战调试

大概分析之后,大家应该对整个流程有了一定了解。
下面结合实战对存在的两个问题进行解决。
1. proxifier 代理时为什么出现许多莫名其妙的IP?
其实打开firfox浏览器的一瞬间,firefox就会有多个域名发出请求。

1599110488.png!small

而在reGeorgSocksProxy.py终端里就会解析出多个莫名其妙的IP。

1599110501.png!small

2.什么时候才可以判断确实代理成功?
从代码层上看,访问远程代理服务器tunnel.nosocket.php返回Georg says, 'All seems fine'就可以代理成功的。因为tunnel.nosocket.php只起到一个中转的作用,转发流量到内网。
而这次error的原因是目标地址的80端口没有开放,其实不能说明代理是不能使用的。
比如访问8180端口

1599110519.png!small

最后选择在kali的proxychains和自己编写的端口扫描成功代理。

1599110542.png!small

tunnel.nosocket.php 免杀

最后还测试某盾查杀了tunnel.nosocket.php,调式发现主要查杀两个位置。

1599110556.png!small

一个fsockopen()函数,对该函数提前引用即可绕过。

1599110566.png!small

一个php输入流,即php://input,使用str_rot13()函数和一些混淆即可绕过。

$php = str_rot13('c!c!cuc');
$str = explode('!',$php)[2];
$pql = $str."://";
$phpinput = $pql."input"; 

也可以结合webshell免杀的思路,使用简单的编码思路将脚本base64编码之后再解码。

1599110580.png!small

也可成功免杀。

总结

大致分析了reGeorg的工作流程,明白是socks编程完成流量的转发,再将结果返回,结合实战成功建立了代理,最后两个姿势进行简单免杀处理。

来源:freebuf.com 2020-09-03 13:58:41 by: CSeroad

© 版权声明
THE END
喜欢就支持一下吧
点赞0
分享
评论 抢沙发

请登录后发表评论