reGeorg简要分析
2022-11-6 00:1:59 Author: 白帽子(查看原文) 阅读量:19 收藏


前言

在某次实战中使用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编程完成流量的转发,再将结果返回,结合实战成功建立了代理,最后两个姿势进行简单免杀处理。

E

N

D

Tide安全团队正式成立于2019年1月,是新潮信息旗下以互联网攻防技术研究为目标的安全团队,团队致力于分享高质量原创文章、开源安全工具、交流安全技术,研究方向覆盖网络攻防、系统安全、Web安全、移动终端、安全开发、物联网/工控安全/AI安全等多个领域。

团队作为“省级等保关键技术实验室”先后与哈工大、齐鲁银行、聊城大学、交通学院等多个高校名企建立联合技术实验室,近三年来在网络安全技术方面开展研发项目60余项,获得各类自主知识产权30余项,省市级科技项目立项20余项,研究成果应用于产品核心技术研究、国家重点科技项目攻关、专业安全服务等。对安全感兴趣的小伙伴可以加入或关注我们。

我知道你在看


文章来源: http://mp.weixin.qq.com/s?__biz=MzAwMDQwNTE5MA==&mid=2650246409&idx=2&sn=b60f76aa0825d47c2d645a5b96e9103d&chksm=82ea56a0b59ddfb6578b338697010aae1e9649db2cc4608daad87b57690c0435fcce990bcd1b#rd
如有侵权请联系:admin#unsafe.sh