对某端口转发工具的一次分析_

admin 2024年10月6日10:10:02评论10 views字数 7887阅读26分17秒阅读模式

工具名是 rtcp.py ,是2009年知道创宇当时写的一个端口转发工具,工具主要是利用python的socket端口转发,代码的整体编写思路非常值得学习。

我一开始是从模块开始分析的,忘记了从运行流程上来分析,所以出现卡壳了。

完整代码:

import socketimport sysimport threadingimport timestreams = [None, None]  # 存放需要进行数据转发的两个数据流(都是SocketObj对象)debug = 1  # 调试状态 0 or 1def _usage(): # 提示用法函数    print 'Usage: ./rtcp.py stream1 stream2nstream : l:port  or c:host:port'def _get_another_stream(num):    '''    从streams获取另外一个流对象,如果当前为空,则等待    '''    if num == 0:        num = 1    elif num == 1:        num = 0    else:        raise "ERROR"    while True:        if streams[num] == 'quit': # 判断列表里是否 quit            print("can't connect to the target, quit now!")            sys.exit(1)        if streams[num] != None:            return streams[num]        else:            time.sleep(1)def _xstream(num, s1, s2):    '''    交换两个流的数据    num为当前流编号,主要用于调试目的,区分两个回路状态用。    '''    try:        while True:            # 注意,recv函数会阻塞,直到对端完全关闭(close后还需要一定时间才能关闭,最快关闭方法是shutdow)            buff = s1.recv(1024)            if debug > 0:                print num,"recv"            if len(buff) == 0: # 对端关闭连接,读不到数据                print num,"one closed"                break            s2.sendall(buff)            if debug > 0:                print num,"sendall"    except :        print num,"one connect closed."    try:        s1.shutdown(socket.SHUT_RDWR)        s1.close()    except:        pass    try:        s2.shutdown(socket.SHUT_RDWR)        s2.close()    except:        pass    streams[0] = None    streams[1] = None    print num, "CLOSED"def _server(port, num):    '''    处理服务情况,num为流编号(第0号还是第1号)    '''    srv = socket.socket(socket.AF_INET, socket.SOCK_STREAM)    srv.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)    srv.bind(('0.0.0.0', port))    srv.listen(1)    while True:        conn, addr = srv.accept()        print "connected from:", addr        streams[num] = conn  # 放入本端流对象        s2 = _get_another_stream(num)  # 获取另一端流对象        _xstream(num, conn, s2)def _connect(host, port, num):    '''  处理连接,num为流编号(第0号还是第1号)    @note: 如果连接不到远程,会sleep 36s,最多尝试200(即两小时)    '''    not_connet_time = 0    wait_time = 36    try_cnt = 199    while True:        if not_connet_time > try_cnt:            streams[num] = 'quit'            print('not connected')            return None        conn = socket.socket(socket.AF_INET, socket.SOCK_STREAM)        try:            conn.connect((host, port))        except Exception, e:            print ('can not connect %s:%s!' % (host, port))            not_connet_time += 1            time.sleep(wait_time)            continue        print "connected to %s:%i" % (host, port)        streams[num] = conn  #放入本端流对象        s2 = _get_another_stream(num) #获取另一端流对象        _xstream(num, conn, s2)if __name__ == '__main__':    if len(sys.argv) != 3:        _usage()        sys.exit(1)    tlist = []  # 线程列表,最终存放两个线程对象    targv = [sys.argv[1], sys.argv[2] ]    for i in [0, 1]:        s = targv[i]  # stream描述 c:ip:port 或 l:port        sl = s.split(':')        if len(sl) == 2 and (sl[0] == 'l' or sl[0] == 'L'):  # l:port            t = threading.Thread(target=_server, args=(int(sl[1]), i))            tlist.append(t)        elif len(sl) == 3 and (sl[0] == 'c' or sl[0] == 'C'):  # c:host:port            t = threading.Thread(target=_connect, args=(sl[1], int(sl[2]), i))            tlist.append(t)        else:            _usage()            sys.exit(1)    for t in tlist:        t.start()    for t in tlist:        t.join()sys.exit(0)

我们首先看一下用法:

Usage: ./rtcp.py stream1 stream2stream : l:port  or c:host:port

那以 python rtcp.py l:9999 c:192.168.1.1:8022 这样的形式全部分析一下代码的运行流程。

工具主要使用了 socket ,sys,threading,time,这四个模块。

streams = [None, None] debug = 1  

这两个分别是存放数据转发的数据流和调试的开关,关于debug这个很值得学习。

分析的话,肯定是从 if __name__ == '__main__': 这里开始的,因为是逆向分析流程,肯定是不能从功能模块直接开始,这样很容易懵逼的。

if len(sys.argv) != 3:    _usage()    sys.exit(1)tlist = []  # 线程列表,最终存放两个线程对象targv = [sys.argv[1], sys.argv[2] ]

先是判断参数是否为三位,如果不是就直接退出,如果是三位就继续进入下面的流程,

在 targv = [sys.argv[1], sys.argv[2] ] 这里其实是把sys.argv[1] = l:9999sys.argv[2] = c:192.168.1.1:8022分别放进 targ 这个列表里。

然后开始使用一个for循环去分离出自己想要的参数。

for i in [0, 1]:  开始两次循环。# 这里第一次循环    s = targv[i]  # s = targv[0] = sys.argv[1] = l:9999    sl = s.split(':') # 以:号进行分割  '''  sl[0] = l  sl[1] = 9999  '''    if len(sl) == 2 and (sl[0] == 'l' or sl[0] == 'L'):      # 这里判断分割后的sl长度等于2并且sl[0]等于'l'或者等于'L'的话就进入以下代码        t = threading.Thread(target=_server, args=(int(sl[1]), i))        #启动线程,线程目标为:_server,传入的参数为sl[1] = 9999,i = 0        tlist.append(t) #将线程对象放入tlist中    elif len(sl) == 3 and (sl[0] == 'c' or sl[0] == 'C'):      # 这里因为循环第一次sl长度等于2,因此表达式不成立。        t = threading.Thread(target=_connect, args=(sl[1], int(sl[2]), i))        tlist.append(t)    else:        _usage()        sys.exit(1)# 这里第一次循环# 这里第二次循环    s = targv[i]  # s = targv[1] = sys.argv[2] = c:192.168.1.1:8022    sl = s.split(':') # 以:号进行分割  '''  sl[0] = c  sl[1] = 192.168.1.1    sl[2] = 8022  '''    if len(sl) == 2 and (sl[0] == 'l' or sl[0] == 'L'):          # 这里循环第二次sl长度等于3,因此不成立        t = threading.Thread(target=_server, args=(int(sl[1]), i))        #启动线程,线程目标为:_server,传入的参数为sl[1],与i        tlist.append(t) #将线程对象放入tlist中    elif len(sl) == 3 and (sl[0] == 'c' or sl[0] == 'C'):          # 这里判断分割后的sl长度等于3并且sl[0]等于'c'或者等于'C'的话就进入以下代码        t = threading.Thread(target=_connect, args=(sl[1], int(sl[2]), i))        #启动线程,线程目标为:_connect,传入的参数为sl[1] = 192.168.1.1,int(sl[2]) = 8022,i = 1        tlist.append(t)#将线程对象放入tlist中    else:        _usage()        sys.exit(1)# 这里第二次循环# 模块结束

这里比较简单就以注释的方法解释一遍。

在多线程那里,首先是进入了_server这个模块,从上面的例子过来,我们知道它传入的是监听端口号,我们先看一下代码:

def _server(port, num):    '''    处理服务情况,num为流编号(第0号还是第1号)    '''    srv = socket.socket(socket.AF_INET, socket.SOCK_STREAM)    srv.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)    srv.bind(('0.0.0.0', port))    srv.listen(1)    while True:        conn, addr = srv.accept()        print "connected from:", addr        streams[num] = conn  # 放入本端流对象        s2 = _get_another_stream(num)  # 获取另一端流对象        _xstream(num, conn, s2)

传入的是两个参数一个是端口号,一个是流编号,也是就是一开始的第一次循环和第二次循环的那个[0,1],conn, addr = srv.accept()这里的conn是套接字对象,addr是IP地址,streams[num] = conn这个也就是把这个流的套接字放入streams[0]中。

下面那两行代码一会再分析,继续回归上面那个第二个循环代码。

第二个就是循环的到_connect,也就是客户端,先看下整体代码:

def _connect(host, port, num):    '''  处理连接,num为流编号(第0号还是第1号)    @note: 如果连接不到远程,会sleep 36s,最多尝试200(即两小时)    '''    not_connet_time = 0    wait_time = 36    try_cnt = 199    while True:        if not_connet_time > try_cnt:            streams[num] = 'quit'            print('not connected')            return None        conn = socket.socket(socket.AF_INET, socket.SOCK_STREAM)        try:            conn.connect((host, port))        except Exception, e:            print ('can not connect %s:%s!' % (host, port))            not_connet_time += 1            time.sleep(wait_time)            continue        print "connected to %s:%i" % (host, port)        streams[num] = conn  #放入本端流对象        s2 = _get_another_stream(num) #获取另一端流对象        _xstream(num, conn, s2)

参数主要为三个,分别是host,port,num,也就是IP地址,端口号,流编码,顺着咱们的预设下来,这三个应该分别是

host = 192.168.1.1port = 8022num = 1

函数中的not_connet_time,wait_time,try_cnt,这三个变量,主要是用来判别是否存在数据流,和挂载延迟时间,来重复连接的。

if not_connet_time > try_cnt:    streams[num] = 'quit'    print('not connected')    return None

从这几行就可以看到,如果没有连接的时间大于挂载的这个时间,就会把quit这个字符串添加到streams[1]里,然后并输出无法连接,然后退出该函数,不再执行下面的代码。

下面用了异常函数来处理端口IP绑定之间的错误,如果无法访问就会自动挂载sleep(36)秒。

连接成功的话,就会输出连接成功,并且print出IP地址和端口号,然后这个客户端的数据流对象会放到streams[1]中。

都进行到这里后,就会进入下面两个函数中:

s2 = _get_another_stream(num)  # 获取另一端流对象_xstream(num, conn, s2)

_get_another_stream的后面的注释中,可以知道这是个一个获取另一端流对象的函数,具体是什么流程还是得看代码。

def _get_another_stream(num):    '''    从streams获取另外一个流对象,如果当前为空,则等待    '''    if num == 0:        num = 1    elif num == 1:        num = 0    else:        raise "ERROR"    while True:        if streams[num] == 'quit': # 判断列表里是否 quit            print("can't connect to the target, quit now!")            sys.exit(1)        if streams[num] != None:            return streams[num]        else:            time.sleep(1)

num就是一开始我们的 0,1这两个流编号,前面几个判断就是替换下编号,把1改成0,把0改成1,如果都不是就爆异常,raise这个用法执行后,后面的代码将不会继续执行了。

下面的死循环,显示判断数据流中是否存在 quit ,如果存在就退出,不存在执行下面的代码,如果流对象不为空,就返回交换后的那个流对象。

_xstream(num, s1, s2)这个函数,我们可以看到入口处有三个参数:

num = 传进来的num值[0,1]s1 = 传入者自己的流量s2 = 对方的流量

其实在上面传入的参数中是_xstream(num, conn, s2)这样的,因为s2是交换数据流以后的函数,所以也就是对方的流量对象。

下面看一下整体函数:

def _xstream(num, s1, s2):    '''    交换两个流的数据    num为当前流编号,主要用于调试目的,区分两个回路状态用。    '''    try:        while True:            # 注意,recv函数会阻塞,直到对端完全关闭(close后还需要一定时间才能关闭,最快关闭方法是shutdow)            buff = s1.recv(1024)            if debug > 0:                print num,"recv"            if len(buff) == 0: # 对端关闭连接,读不到数据                print num,"one closed"                break            s2.sendall(buff)            if debug > 0:                print num,"sendall"    except :        print num,"one connect closed."    try:        s1.shutdown(socket.SHUT_RDWR)        s1.close()    except:        pass    try:        s2.shutdown(socket.SHUT_RDWR)        s2.close()    except:        pass    streams[0] = None    streams[1] = None    print num, "CLOSED"

到这里的时候代码就很简单明了了,传入者自己的流量赋值给变量 buff,然后通过s2.sendall(buff)来互相通信流量。

剩下的都是关闭数据流关闭端口,然后重置下数据,但是我个人太菜了,看这一部分的时候还是有点懵逼,估计我只是细看的原因吧。

整体分析一遍流程,通过外接参数,然后分析使用服务端函数还是使用客户端函数,然后加入到多线程中,如果进入服务端就先创建一个套接字,然后绑定下端口,把本端的流放入到一开始存在的那个列表里,然后使用交换两端的流函数,交换一下, 最后使用通信函数,互相交流流文件。如果是选择的客户端函数,就会先判断流文件中是否存在quit这个退出关键字,如果存在就退出,不存在就绑定IP地址和端口号,继续和上面的服务端函数差不多,互相通信流文件。最后退出时,关闭端口,清空存在流文件的列表。

这个端口转发脚本,真的值得去学习,感觉对我感触很多,在开发其他一些工具的时候,关于构造的思考。

参考链接

https://sh1yan.top/2018/07/09/Port-Forwarding-Tool-Analysis/#0x00

真心感觉自己要学习的知识好多,也有好多大神卧虎藏龙、开源分享。作为初学者,我们可能有差距,不论你之前是什么方向,是什么工作,是什么学历,是大学大专中专,亦或是高中初中,只要你喜欢安全,喜欢渗透,就朝着这个目标去努力吧!有差距不可怕,我们需要的是去缩小差距,去战斗,况且这个学习的历程真的很美,安全真的有意思。但切勿去做坏事,我们需要的是白帽子,是维护我们的网络,安全路上共勉。

本文版权归作者和微信公众号平台共有,重在学习交流,不以任何盈利为目的,欢迎转载。

由于传播、利用此文所提供的信息而造成的任何直接或者间接的后果及损失,均由使用者本人负责,文章作者不为此承担任何责任。公众号内容中部分攻防技巧等只允许在目标授权的情况下进行使用,大部分文章来自各大安全社区,个人博客,如有侵权请立即联系公众号进行删除。若不同意以上警告信息请立即退出浏览!!!

敲敲小黑板:《刑法》第二百八十五条 【非法侵入计算机信息系统罪;非法获取计算机信息系统数据、非法控制计算机信息系统罪】违反国家规定,侵入国家事务、国防建设、尖端科学技术领域的计算机信息系统的,处三年以下有期徒刑或者拘役。违反国家规定,侵入前款规定以外的计算机信息系统或者采用其他技术手段,获取该计算机信息系统中存储、处理或者传输的数据,或者对该计算机信息系统实施非法控制,情节严重的,处三年以下有期徒刑或者拘役,并处或者单处罚金;情节特别严重的,处三年以上七年以下有期徒刑,并处罚金。

原文始发于微信公众号(巢安实验室):对某端口转发工具的一次分析_

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年10月6日10:10:02
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   对某端口转发工具的一次分析_https://cn-sec.com/archives/3234636.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息