Python解析Pcap包类源码学习

0x1、前言

​ 在现场取证遇到分析流量包的情况会比较少,虽然流量类设备原理是把数据都抓出来进行解析,很大一定程度上已经把人可以做的事情交给了机器自动完成。

​ 可用于PCAP包分析的软件比如科来,Wireshark都是很好用的分析软件,找Pcap解析的编程类代码时发现已经有很多大佬写过Python脚本辅助解析Pcap,也有提取将Pcap信息以界面形式展示出来框架。

​ 本文对利用Python里的Scapy库提取协议五元组信息进行学习性总结,没有用于实战,因为实践过程中发现PCAP读包解包查包速度太慢了。

0x2、参考库

Python解析pcap包的常见库有Scapy、dpkt、Pyshark等。可以参考的源码,工具如下

https://github.com/thepacketgeek/cloud-pcap
https://github.com/madpowah/ForensicPCAP
https://github.com/le4f/pcap-analyzer
https://github.com/HatBoy/Pcap-Analyzer
https://github.com/caesar0301/awesome-pcaptools
https://asecuritysite.com/forensics/pcap
https://github.com/DanMcInerney/net-creds

提供解析Pcap包服务的网站https://packettotal.com/、https://www.capanalysis.net/ca/、https://asecuritysite.com/forensics/pcap?infile=smtp.pcap&infile=smtp.pcap、https://app.any.run

0x3、邮件协议

提取发件人的邮箱,就要熟悉SMTP的几个端口进行识别。

25  端口为SMTP(Simple Mail Transfer Protocol,简单邮件传输协议)服务所开放的,是用于发送邮件。
110端口是为POP3(邮件协议3)服务开放的,POP2、POP3都是主要用于接收邮件的,目前POP3使用的比较多,许多服务器都同时支持POP2和POP3。
143端口主要是用于“Internet Message AccessProtocol”v2(Internet消息访问协议,简称IMAP),和POP3一样,是用于电子邮件的接收的协议。
465 端口是SSL/TLS通讯协议的 内容一开始就被保护起来了 是看不到原文的。
465 端口(SMTPS):465端口是为SMTPS(SMTP-over-SSL)协议服务开放的,这是SMTP协议基于SSL安全协议之上的一种变种协议,它继承了SSL安全协议的非对称加密的高度安全可靠性,可防止邮件泄露。
587 端口是STARTTLS协议的 属于TLS通讯协议 只是他是在STARTTLS命令执行后才对之后的原文进行保护的。

SMTP常用命令语法

在Wireshark中SMTP协议数据是在建立TCP三次握手后会出现的,常用的命令如下。

SMTP命令不区分大小写,但参数区分大小写,有关这方面的详细说明请参考RFC821。
HELO <domain> <CRLF>。向服务器标识用户身份发送者能欺骗,说谎,但一般情况下服务器都能检测到。
MAIL FROM: <reverse-path> <CRLF>。<reverse-path>为发送者地址,此命令用来初始化邮件传输,即用来对所有的状态和缓冲区进行初始化。
RCPT TO:<forward-path> <CRLF>。 <forward-path>用来标志邮件接收者的地址,常用在MAIL FROM后,可以有多个RCPT TO。
DATA <CRLF>。将之后的数据作为数据发送,以<CRLF>.<CRLF>标志数据的结尾。
REST <CRLF>。重置会话,当前传输被取消。
NOOP <CRLF>。要求服务器返回OK应答,一般用作测试。
QUIT <CRLF>。结束会话。
VRFY <string> <CRLF>。验证指定的邮箱是否存在,由于安全方面的原因,服务器大多禁止此命令。
EXPN <string> <CRLF>。验证给定的邮箱列表是否存在,由于安全方面的原因,服务器大多禁止此命令。
HELP <CRLF>。查询服务器支持什么命令。

代码

forensicPCAP 可以根据解析PCAP包,然后利用CMD模块循环交互界面,输入命令调出对应函数。核心原理为

基于Scapy找到源端口或目的端口为110、143端口的数据记录保存作为邮件数据。

关键代码代码如下:

self.pcap是构造函数self.pcap = rdpcap(namefile)提前读取,然后enumerate()遍历。指定目标端口和源端口是110、143的数据筛选出来。

    def do_mail(self, arg, opts=None):
        """Print the number of mail's requests and store its
Usage :
- mail"""
        sys.stdout.write(bcolors.TXT + "## Searching mail's request ... ")
        sys.stdout.flush()
        con = []
        mailpkts = []
        for i,packet in enumerate(self.pcap):
            # TCP的包
            if TCP in packet:
                # 获取源端口或目的端口为110、143端口的数据记录
                if packet.getlayer('TCP').dport == 110 or packet.getlayer('TCP').sport == 110 or packet.getlayer('TCP').dport == 143 or packet.getlayer('TCP').sport == 143 :
                    if packet.getlayer('TCP').flags == 2:
                        con.append(i)
                    mailpkts.append(packet)
        sys.stdout.write("OK.\n")
        print "## Result : Mail's request : " + str(len(con))  
        sys.stdout.write(bcolors.TXT + "## Saving mails ... ")
        sys.stdout.flush()
        res = ""
        for packet in mailpkts:
                if packet.getlayer('TCP').flags == 24:
                    res = res + packet.getlayer('Raw').load
                    sys.stdout.write(".")
                    sys.stdout.flush()
        sys.stdout.write("OK\n")
        sys.stdout.flush()          
        self.cmd = "mail"           
        self.last = res

0x4、DNS解析

forensicPCAP 解析DNS部分代码。bcolors.TXT是设定了一个字体显示的颜色,res = packet.getlayer('DNS').qd.qname是获取DNS域名解析记录。

############# do_dns() ###########
    def do_dns(self, arg, opts=None):
        """Print all DNS requests in the PCAP file
Usage :
- dns"""
        sys.stdout.write(bcolors.TXT + "## Listing all DNS requests ...")
        sys.stdout.flush()
        dns = []
        dns.append([])
        
        # 枚举PCAP包的数据
        for i,packet in enumerate(self.pcap):
            if DNS in packet:
                # 获取DNS域名解析记录
                res = packet.getlayer('DNS').qd.qname
                if res[len(res) - 1] == '.':
                    res = res[:-1]
                # 保存域名
                dns.append([i, res])

        sys.stdout.write("OK.\n")
        # 统计DNS数目
        print bcolors.TXT + "## Result : " + str(len(dns) - 1) + " DNS request(s)" + bcolors.ENDC
        self.last = dns
        self.cmd = "dns"

pcap-analyzer核心的代码是借鉴了forensicPCAP,获取解析为DNS的请求记录显示的时候会统计出次数最多的IP的前十名。

代码

def get_dns(file):
    dns = []
    # 打开PCAP文件
    pcap = rdpcap(UPLOAD_FOLDER+file)
    for packet in pcap:
        if DNS in packet:
                # 核心代码
                res = packet.getlayer('DNS').qd.qname
                if res[len(res) - 1] == '.':
                    res = res[:-1]
                dns.append(res)
    # 统计DNS协议,出现次数最多的IP前十名            
    dns = Counter(dns).most_common(10)

0x5、密码

net-creds 以Scapy为基础,解析PCAP中含有密码信息的一个脚本。

代码

主要代码由other_parser()函数实现,分割每个包中的HTTP等内容,然后搜索身份验证相关的关键字筛选出账户、密码。

def other_parser(src_ip_port, dst_ip_port, full_load, ack, seq, pkt, verbose):
    '''
    Pull out pertinent info from the parsed HTTP packet data
    '''
    user_passwd = None
    http_url_req = None
    method = None
    http_methods = ['GET ', 'POST ', 'CONNECT ', 'TRACE ', 'TRACK ', 'PUT ', 'DELETE ', 'HEAD ']
    http_line, header_lines, body = parse_http_load(full_load, http_methods)
    headers = headers_to_dict(header_lines)
    if 'host' in headers:
        host = headers['host']
    else:
        host = ''

    if http_line != None:
        method, path = parse_http_line(http_line, http_methods)
        http_url_req = get_http_url(method, host, path, headers)
        if http_url_req != None:
            if verbose == False:
                if len(http_url_req) > 98:
                    http_url_req = http_url_req[:99] + '...'
            printer(src_ip_port, None, http_url_req)

    # Print search terms
    searched = get_http_searches(http_url_req, body, host)
    if searched:
        printer(src_ip_port, dst_ip_port, searched)

    # Print user/pwds
    if body != '':
        user_passwd = get_login_pass(body)
        if user_passwd != None:
            try:
                http_user = user_passwd[0].decode('utf8')
                http_pass = user_passwd[1].decode('utf8')
                # Set a limit on how long they can be prevent false+
                if len(http_user) > 75 or len(http_pass) > 75:
                    return
                user_msg = 'HTTP username: %s' % http_user
                printer(src_ip_port, dst_ip_port, user_msg)
                pass_msg = 'HTTP password: %s' % http_pass
                printer(src_ip_port, dst_ip_port, pass_msg)
            except UnicodeDecodeError:
                pass

    # Print POST loads
    # ocsp is a common SSL post load that's never interesting
    if method == 'POST' and 'ocsp.' not in host:
        try:
            if verbose == False and len(body) > 99:
                # If it can't decode to utf8 we're probably not interested in it
                msg = 'POST load: %s...' % body[:99].encode('utf8')
            else:
                msg = 'POST load: %s' % body.encode('utf8')
            printer(src_ip_port, None, msg)
        except UnicodeDecodeError:
            pass

    # Kerberos over TCP
    decoded = Decode_Ip_Packet(str(pkt)[14:])
    kerb_hash = ParseMSKerbv5TCP(decoded['data'][20:])
    if kerb_hash:
        printer(src_ip_port, dst_ip_port, kerb_hash)

    # Non-NETNTLM NTLM hashes (MSSQL, DCE-RPC,SMBv1/2,LDAP, MSSQL)
    NTLMSSP2 = re.search(NTLMSSP2_re, full_load, re.DOTALL)
    NTLMSSP3 = re.search(NTLMSSP3_re, full_load, re.DOTALL)
    if NTLMSSP2:
        parse_ntlm_chal(NTLMSSP2.group(), ack)
    if NTLMSSP3:
        ntlm_resp_found = parse_ntlm_resp(NTLMSSP3.group(), seq)
        if ntlm_resp_found != None:
            printer(src_ip_port, dst_ip_port, ntlm_resp_found)

    # Look for authentication headers
    if len(headers) == 0:
        authenticate_header = None
        authorization_header = None
    for header in headers:
        authenticate_header = re.match(authenticate_re, header)
        authorization_header = re.match(authorization_re, header)
        if authenticate_header or authorization_header:
            break

    if authorization_header or authenticate_header:
        # NETNTLM
        netntlm_found = parse_netntlm(authenticate_header, authorization_header, headers, ack, seq)
        if netntlm_found != None:
            printer(src_ip_port, dst_ip_port, netntlm_found)

        # Basic Auth
        parse_basic_auth(src_ip_port, dst_ip_port, headers, authorization_header)

关键字列表:

# Regexs
authenticate_re = '(www-|proxy-)?authenticate'
authorization_re = '(www-|proxy-)?authorization'
ftp_user_re = r'USER (.+)\r\n'
ftp_pw_re = r'PASS (.+)\r\n'
irc_user_re = r'NICK (.+?)((\r)?\n|\s)'
irc_pw_re = r'NS IDENTIFY (.+)'
irc_pw_re2 = 'nickserv :identify (.+)'
mail_auth_re = '(\d+ )?(auth|authenticate) (login|plain)'
mail_auth_re1 =  '(\d+ )?login '
NTLMSSP2_re = 'NTLMSSP\x00\x02\x00\x00\x00.+'
NTLMSSP3_re = 'NTLMSSP\x00\x03\x00\x00\x00.+'
# Prone to false+ but prefer that to false-
http_search_re = '((search|query|&q|\?q|search\?p|searchterm|keywords|keyword|command|terms|keys|question|kwd|searchPhrase)=([^&][^&]*))'

0x6、提取数据需要关注的元素

  • 源IP、目的IP、源端口、目的端口、协议、数据包大小
关联出受害者,攻击者控制的跳板
  • 协议:HTTP、FTP、邮件协议
- 先查看HTTP、HTTPS类,然后查看数据包长度。
- 提取相关IP信息。
- 判断是否是木马远控、密码账户、可疑IP
  • 账户密码字段
- 渗透测试,嗅探回来的数据包分析含有账户密码信息
- 暴力破解IP

0x7、参考

SMTP协议分析 https://yq.aliyun.com/wenji/262429

scapy 解析pcap文件总结 https://www.cnblogs.com/14061216chen/p/8093441.html

python之字符串格式化(format) https://www.cnblogs.com/benric/p/4965224.html

猜你喜欢

转载自www.cnblogs.com/17bdw/p/10562213.html
今日推荐