SSL/TLS协议是网络安全通信的基石,它通过在客户端和服务器之间建立一个加密的通道,确保数据传输的安全性和完整性。SSL(Secure Sockets Layer)最初由Netscape公司开发,而TLS(Transport Layer Security)则是SSL的后续版本,由IETF标准化。通常,我们提到的SSL/TLS协议是指这两种协议的集合。
SSL/TLS协议的工作原理
SSL/TLS协议的工作原理主要包括握手阶段和数据传输阶段。在握手阶段,客户端和服务器通过交换信息来协商加密算法、验证身份,并生成用于数据加密的对话密钥。这个过程包括客户端问候、服务器问候、证书交换、密钥交换等步骤。一旦握手完成,双方就会使用协商的密钥来加密和解密传输的数据。
TLS记录的相关原理包括:
-  分片(Fragmentation):应用层数据被分割成多个TLS记录,每个记录都有自己的头部和有效载荷。这样做可以确保数据块的大小适合网络传输,并且可以独立处理。 
-  头部(Header):每个TLS记录的头部包含了一些重要信息,如内容类型(例如,握手消息、应用数据等)、版本号、长度等。 
-  有效载荷(Payload):记录的有效载荷部分包含了实际要传输的数据。在传输之前,数据会被加密,以确保数据的机密性和完整性。 
-  加密:TLS记录在传输前会被加密,以防止数据在传输过程中被窃听。加密过程使用了在TLS握手阶段协商的加密算法和密钥。 
-  消息认证码(MAC):为了提供数据完整性,每个TLS记录都会计算一个MAC。这个MAC会在解密后验证,以确保数据在传输过程中没有被篡改。 
-  压缩:在加密之前,TLS记录的有效载荷可能会被压缩,以减少传输数据的大小。 
-  序列号:每个TLS记录都有一个序列号,用于确保记录的顺序和防止重放攻击。 
-  握手协议:在TLS握手阶段,通信双方会协商加密算法、密钥交换参数等,为后续的TLS记录加密和认证提供基础。 
TLS记录的设计使得它能够适应不同的网络环境和应用需求,同时提供了强大的安全保障。RFC 8446是TLS 1.3的官方文档,它详细描述了TLS协议的规范,包括TLS记录的处理方式。如果你需要查看RFC 8446的具体内容,可以访问RFC 8446。如果链接无法访问,可能是因为网络问题或链接本身的问题,请检查链接的有效性或稍后再试。
在实际应用中,SSL/TLS协议广泛应用于HTTPS、SMTPS、FTPS等安全通信场景。通过使用SSL/TLS协议,可以确保网站和用户之间的通信安全,防止数据被窃取或篡改。
为了分析SSL/TLS协议的数据帧,可以使用像Wireshark这样的网络抓包工具。Wireshark能够捕获网络数据包,并展示SSL/TLS协议的握手过程、加密算法、证书信息等。通过Wireshark的抓包分析,可以直观地了解SSL/TLS协议的实际执行过程,包括客户端和服务器之间的消息交换、加密套件的选择、证书的验证以及最终的密钥交换。
在进行SSL/TLS协议分析时,需要注意的是,随着网络安全威胁的不断演变,SSL/TLS协议也面临着一些挑战和漏洞。因此,我们需要不断关注协议的发展动态,并采取相应的措施来保护数据安全。同时,证书管理、服务器和客户端的配置、定期更新以及安全审计等方面也是确保SSL/TLS协议有效性的关键因素。
什么是TLS record?
传输层安全性(TLS),也称为安全套接层(SSL),是通信协议中广泛使用的标准之一,支持应用层(如HTTP、SMTP等)数据传输的加密保护隧道。TLS通过将消息分割成一系列名为TLS记录的可管理块,在两个通信对等体之间提供安全通道。一旦两台主机成功协商了加密密钥和保密参数,每条记录都会在选定的密钥材料下受到独立保护。加密对高层(即应用层)的有效载荷检查施加了限制;然而,在其他方面,具有讽刺意味的是,记录提供了一些新的重要信息,如密码套件、证书、服务器名称指示(SNI)等的偏好。在这方面,记录流被认为反映了通信方的独特应用性质或行为特征。

一个快速简单的工具来分析SSL/TLS协议数据帧 (C/C++代码实现)
...
/* Flow status */
#define F_SAW_SYN              0x1
#define F_SAW_SYNACK           0x2
#define F_END_SYN_HS           0x4
#define F_END_FIN_HS           0x8
#define F_BASE_SEQ_SET        0x10
#define F_LOST_HELLO          0x20
#define F_FRAME_OVERLAP       0x40
/* TCP packet status */
#define TCP_A_ACK_LOST_PACKET                0x1
#define TCP_A_DUPLICATE_ACK                  0x2
#define TCP_A_KEEP_ALIVE                     0x4
#define TCP_A_KEEP_ALIVE_ACK                 0x8
#define TCP_A_LOST_PACKET                   0x10
#define TCP_A_FAST_RETRANSMISSION           0x20
#define TCP_A_OUT_OF_ORDER                  0x40
#define TCP_A_SPURIOUS_RETRANSMISSION       0x80
#define TCP_A_RETRANSMISSION               0x100
#define TCP_A_WINDOW_FULL                  0x200
#define TCP_A_WINDOW_UPDATE                0x400
#define TCP_A_ZERO_WINDOW                  0x800
#define TCP_A_ZERO_WINDOW_PROBE           0x1000
#define TCP_A_ZERO_WINDOW_PROBE_ACK       0x2000
#define TCP_A_NON_RECORD                  0x4000
#define MAX_RECORD_LEN       0x4800
#define MAX_QUEUE_CAPACITY       50
static const std::map<uint8_t, std::pair<std::string, uint8_t>> recordType = {
    { 20, { "Change Cipher Spec", 18 } },
    { 21, { "Alert", 5 } },
    { 22, { "Handshake", 9 } },
    { 23, { "Application Data", 16 } }
};
static const std::map<uint8_t, std::pair<std::string, uint8_t>> handshakeType = {
    { 0,  { "(hello request)", 15} },
    { 1,  { "(client hello)", 14} },
    { 2,  { "(server hello)", 14} },
    { 3,  { "(hello verify request)", 22} },
    { 4,  { "(new session ticket)", 20} },
    { 5,  { "(end of early data)", 19} },
    { 6,  { "(hello retry request)", 21} },
    { 8,  { "(encrypted extensions)", 22} },
    { 11, { "(certificate)", 13} },
    { 12, { "(server key exchange)", 21} },
    { 13, { "(certificate request)", 21} },
    { 14, { "(server hello done)", 19} },
    { 15, { "(certificate verify)", 20} },
    { 16, { "(client key exchange)", 21} },
    { 21, { "(certificate url)", 17} },
    { 22, { "(certificate status)", 20} },
    { 23, { "(supplemental data)", 19} },
    { 24, { "(key update)", 12} },
    { 25, { "(compressed certificate)", 24} }
};
namespace pump
{
    /* 用于保存捕获偏好的数据结构 */
    struct CaptureConfig
    {
        uint32_t maxPacket;         
        uint32_t maxTime;           
        uint32_t maxRcd;            
        uint32_t maxRcdpf;          
        bool outputTypeHex;         
        bool quitemode;             
        std::string outputFileTo;   
    };
    /* 用于解决记录解析例程的数据结构 */
    struct RecordPointer{
        uint16_t rcd_len;           
        uint16_t rcd_pos;           
        uint16_t hs_len;            
        uint16_t hs_pos;            
        uint8_t prev_rcd_type;      
        uint8_t hd[9];              
    };
    /* 此结构包含段边界信息 */
    struct SegInfo{
        uint32_t seq = 0;           
        uint16_t seglen = 0;        
        bool is_newrcd = false;    
        bool operator<(const SegInfo& other) const
        {
            return (seq < other.seq);
        }
        bool operator==(const SegInfo& other) const
        {
            return (seq == other.seq);
        }
    };
    /* 保存流数据的数据结构 */
    struct Flow {
        uint32_t ip = 0;
        uint16_t port = 0;
        uint32_t win = 0xFFFFFFFF;
        uint32_t baseseq = 0;
        uint16_t flags = 0;
        uint16_t a_flags = 0;
        uint32_t nextseq = 0;
        uint32_t lastack = 0;
        uint16_t rcd_cnt = 0;
        uint16_t rcd_idx = 0;
        RecordPointer rcd_pt = {0,0,0,0,0,{}};
        std::set<SegInfo> reserved_seq = {};
    };
    /* 保持双向流信息的数据结构 */
    struct Stream {
        Flow client;
        Flow server;
    };
    uint32_t hashStream(pump::Packet* packet);
    bool isTcpSyn(pump::Packet* packet);
    bool isClient(pump::Packet* packet, Stream* ss);
    bool isTLSrecord(uint8_t* data, uint32_t seglen);
    bool isSSLv2record(uint8_t* data, uint32_t seglen);
    bool isUnencryptedHS(uint8_t curr_rcd_type, uint8_t prev_rcd_type);
    class Assembly
    {
        private:
            uint32_t ab_pkt_cnt;
            uint32_t ab_flow_cnt;
            uint32_t ab_rcd_cnt;
            uint64_t ab_totalbytes;
            bool ab_stop;
            struct timeval ab_init_tv, ab_print_tv;
            std::map<uint32_t, int> ab_flowtable;
            std::map<uint32_t, bool> ab_initiated;
            std::map<uint32_t, Stream> ab_smap;
            int addNewStream(pump::Packet* packet);
            int getStreamNumber(pump::Packet* packet);
            void writeTLSrecord(int idx, bool peer);
            void displayTLSrecord(Stream* ss, bool peer, uint8_t rcd_type, uint8_t hs_type);
            void cleanOldPacket(int idx, bool peer, Flow* fwd, CaptureConfig* config);
            void parseReservedPacket(int idx, bool peer, uint32_t seq, CaptureConfig* config);
        public:
            Assembly(timeval tv);
            ~Assembly();
            void registerEvent();
            uint32_t getTotalPacket() { return ab_pkt_cnt; };
            uint32_t getTotalStream() { return ab_flow_cnt; }
            uint32_t getTotalRecord() { return ab_rcd_cnt; }
            uint64_t getTotalByteLen() { return ab_totalbytes; }
            bool isTerminated() {return ab_stop; }
            void parsePacket(pump::Packet* packet, CaptureConfig* config);
            void managePacket(pump::Packet* packet, CaptureConfig* config);
            void mergeRecord(CaptureConfig* config);
            void close();
    };
}
...
namespace pump
{
    std::string currTime();
    void clearTLSniff();
    class EventHandler
    {
        public:
            typedef void (*EventHandlerCallback)(void* cookie);
            static EventHandler& getInstance()
            {
                static EventHandler instance;
                return instance;
            }
            void onInterrupted(EventHandlerCallback handler, void* cookie);
        private:
            EventHandlerCallback h_interrupt_handler;
            void* h_interrupt_cookie;
            static void handlerRoutine(int signum);
    };
}
...
namespace pump
{
    class Packet
    {
        protected:
            
            uint8_t* pk_data;
            uint16_t pk_datalen;
            bool pk_delete_data;
            uint16_t pk_linktype;
            uint8_t pk_proto_types;
            Layer* pk_firstlayer;
            Layer* pk_lastlayer;
            void Init();
            Layer* initLayer(uint16_t linktype);
        public:
            Packet();
            Packet(const uint8_t* data, uint16_t datalen, bool delete_rawdata, uint16_t layertype = LINKTYPE_ETHERNET);
            ~Packet() { clearData(); }
            bool setData(const uint8_t* data, uint16_t datalen, uint16_t layertype = LINKTYPE_ETHERNET);
            template<class TLayer> TLayer* getLayer() const;
            template<class TLayer> TLayer* getNextLayer(Layer* layertype) const;
            const uint8_t* getData() const { return pk_data; }
            uint16_t getDataLen() const { return pk_datalen; }
            uint8_t getProtocolTypes() const { return pk_proto_types; }
            bool isTypeOf(uint8_t protocol) const { return pk_proto_types & protocol; }
            void clearData();
    };
    template<class T> T* Packet::getLayer() const
    {
        if (dynamic_cast<T*>(pk_firstlayer) != NULL)
            return (T*)pk_firstlayer;
        return getNextLayer<T>(pk_firstlayer);
    }
    template<class T> T* Packet::getNextLayer(Layer* layertype) const
    {
        if (layertype == NULL)
            return NULL;
        Layer* curr_layer = layertype->getNextLayer();
        while ((curr_layer != NULL) && (dynamic_cast<T*>(curr_layer) == NULL))
        {
            curr_layer = curr_layer->getNextLayer();
        }
        return (T*)curr_layer;
    }
}
...
namespace pump
{
    class LiveReader;
    typedef void (*OnPacketArrival)(Packet* packet, LiveReader* lrdr, void* cookie);
    class Reader
    {
        protected:
            bool rdr_on;
            pcap_t* rdr_descriptor;
            Reader(): rdr_on(false), rdr_descriptor(NULL) {}
        
        public:
            virtual ~Reader() {}
            virtual bool open() = 0;
            virtual void close() = 0;
    };
    class PcapReader : public Reader
    {
        protected:
            char* prdr_datasrc;
            uint16_t prdr_linktype;
        public:
            PcapReader(const char* pcapfile);
            ~PcapReader() { close(); }
            static PcapReader* getReader(const char* pcapfile);
            bool open();
            bool getNextPacket(Packet& packet);
            void close();
    };
    class LiveReader : public Reader
    {
        protected:
            char* lrdr_datasrc;
            uint16_t lrdr_linktype;
            bool lrdr_on_capture;
            bool lrdr_on_stop;
            OnPacketArrival lrdr_pkt_arrival;
            void* lrdr_pkt_arrival_cookie;
            pthread_t lrdr_thread_capture;
            pcap_t* LiveInit();
            static void* captureThreadMain(void* ptr);
            static void onPacketArrival(uint8_t* user, const struct pcap_pkthdr* pkt_hdr, const uint8_t* packet);
        public:
            LiveReader(pcap_if_t* pInterface, bool calculateMTU, bool calculateMacAddress, bool calculateDefaultGateway);
            ~LiveReader() { close(); }
            bool open();
            void startCapture(OnPacketArrival onPacketArrival, void* onPacketArrivalCookie);
            void stopCapture();
            const char* getName() const { return lrdr_datasrc; }
            uint16_t getLinkType() const { return lrdr_linktype; }
            void close();
    };
    class LiveInterfaces
    {
        private:
            std::vector<LiveReader*> li_ifacelist;
            LiveInterfaces();
        public:
            static LiveInterfaces& getInstance()
            {
                static LiveInterfaces instance;
                return instance;
            }
            LiveReader* getLiveReader(const std::string& name) const;
    };
}
...
static struct option TLSniffOptions[] =
{
    {"count",  required_argument, 0, 'c'},
    {"duration",  required_argument, 0, 'd'},
    {"interface",  required_argument, 0, 'i'},
    {"rcd-count", required_argument, 0, 'm'},
    {"rcd-count-perflow",  required_argument, 0, 'l'},
    {"input-file",  required_argument, 0, 'r'},
    {"output-file", required_argument, 0, 'w'},
    {"quite-mode", no_argument, 0, 'q'},
    {"byte-type", no_argument, 0, 'x'},
    {"help", no_argument, 0, 'h'},
    {0, 0, 0, 0}
};
/* 处理数据包转储的结构 */
struct PacketArrivedData
{
    pump::Assembly* assembly;
    struct pump::CaptureConfig* config;
};
...
/* 开始从发现的网络接口收集记录信息 */
void doTLSniffOnLive(pump::LiveReader* rdr, struct pump::CaptureConfig* config)
{
    // 打开网络接口进行捕获
    if (!rdr->open())
        EXIT_WITH_CONFERROR("###ERROR : Could not open the device");
    PacketArrivedData data;
    pump::Assembly assembly(init_tv);
    data.assembly = &assembly;
    data.config = config;
    rdr->startCapture(packetArrive, &data);
    while(!assembly.isTerminated())
        sleep(1);
    rdr->stopCapture();
    if(!(config->quitemode)) printf("\n");
    pump::print_progressM(assembly.getTotalPacket());
    printf(" **%lu Bytes**\n", assembly.getTotalByteLen());
    if(config->outputFileTo != "")
    {
        assembly.registerEvent();
        assembly.mergeRecord(config);
    }
    // Close the capture pipe
    assembly.close();
    delete rdr;
}
/* 开始从发现的网络接口收集记录信息 */
void doTLSniffOnPcap(std::string pcapFile, struct pump::CaptureConfig* config)
{
    pump::PcapReader* rdr = pump::PcapReader::getReader(pcapFile.c_str());
    
    // 打开pcap文件进行捕获
    if (!rdr->open())
        EXIT_WITH_CONFERROR("###ERROR : Could not open input pcap file");
    pump::Assembly assembly(init_tv);
    pump::Packet packet;
    // 以无休止的循环运行,直到用户按下Ctrl+C
    // 或者程序遇到文件末尾
    while(rdr->getNextPacket(packet) && !assembly.isTerminated())
    {
        assembly.managePacket(&packet, config);
    }
    if(!(config->quitemode)) printf("\n");
    pump::print_progressM(assembly.getTotalPacket());
    printf(" **%lu Bytes**\n", assembly.getTotalByteLen());
    // 将所有捕获的记录写入指定文件
    if(config->outputFileTo != "")
    {
        assembly.registerEvent();
        assembly.mergeRecord(config);
    }
    assembly.close();
    delete rdr;
}
int main(int argc, char* argv[])
{
...
    // 使用命令行选项中的值设置首选项
    while((opt = getopt_long (argc, argv, "c:d:i:l:m:r:w:qxh", TLSniffOptions, &optionIndex)) != -1)
    {
        switch (opt)
        {
            case 0:
                break;
            case 'c':
                maxPacket = atoi(optarg);
                break;
            case 'd':
                maxTime = atoi(optarg);
                break;
            case 'i':
                readPacketsFromInterface = optarg;
                break;
            case 'l':
                maxRcdpf = atoi(optarg);
                break;
            case 'm':
                maxRcd = atoi(optarg);
                break;
            case 'r':
                readPacketsFromPcap = optarg;
                break;
            case 'w':
                outputFileTo = optarg;
                break;
            case 'q':
                quitemode = true;
                break;
            case 'x':
                outputTypeHex = true;
                break;
            case 'h':
                printUsage();
                break;
            default:
                printUsage();
                exit(-1);
        }
    }
    // 如果没有提供输入pcap文件或网络接口,则退出并出错
    if (readPacketsFromPcap == "" && readPacketsFromInterface == "")
        EXIT_WITH_OPTERROR("###ERROR : Neither interface nor input pcap file were provided");
    // 应只选择一个选项:pcap或接口-带错误退出
    if (readPacketsFromPcap != "" && readPacketsFromInterface != "")
        EXIT_WITH_OPTERROR("###ERROR : Choose only one option, pcap or interface");
    // 不允许出现负值
    if (maxPacket <= 0)
        EXIT_WITH_OPTERROR("###ERROR : #Packet can't be a non-positive integer");
    if (maxTime <= 0)
        EXIT_WITH_OPTERROR("###ERROR : Duration can't be a non-positive integer");
    if (maxRcd <= 0)
        EXIT_WITH_OPTERROR("###ERROR : #Record can't be a non-positive integer");
    if (maxRcdpf <= 0)
        EXIT_WITH_OPTERROR("###ERROR : #Record per flow can't be a non-positive integer");
    struct pump::CaptureConfig config = {
        .maxPacket = maxPacket,
        .maxTime = maxTime,
        .maxRcd = maxRcd,
        .maxRcdpf = maxRcdpf,
        .outputTypeHex = outputTypeHex,
        .quitemode = quitemode,
        .outputFileTo = outputFileTo
    };
...
    if (readPacketsFromPcap != "")
    {
        doTLSniffOnPcap(readPacketsFromPcap, &config);
    }
    else
    {
        pump::LiveReader* rdr = pump::LiveInterfaces::getInstance().getLiveReader(readPacketsFromInterface);
        if (rdr == NULL)
            EXIT_WITH_CONFERROR("###ERROR : Couldn't find interface by provided name");
        doTLSniffOnLive(rdr, &config);
    }
    // 清除临时目录中的内容
    pump::clearTLSniff();
    printf("**All Done**\n");
    WRITE_LOG("===Process Finished");
    return 0;
}
If you need the complete source code, please add the WeChat number (c17865354792)
使用Pcap文件提取TLS记录:
sudo tlsniff -r example.pcap
它将打印两台主机之间传输的记录消息及其方向
 
 在wireshark
 
 实时网络接口上的TLS记录提取:
sudo tlsniff -i eth0 -w example.csv
以不那么冗长的模式编写一个csv文件:
 
 在1分钟(60秒)内捕获TLS记录
sudo tlsniff -d 60 -i eth0 -w example.csv
捕获前100条TLS记录
sudo tlsniff -m 100 -i eth0 -w example.csv
代码用途:
尽管有如此多的网络流量分析器支持对TLS层的检查,但由于包括重传、乱序数据包、数据包丢失等在内的几个问题,它们很难保持记录帧的原始形式和顺序。此外,单个记录的每个部分都可能占据不同数据包有效载荷内的连续比特,因为记录大小可能大于数据包传递所允许的最大传输单元。即使记录的大小小到可以放置在单个数据包中,TLS协议也不喜欢频繁传输数据块。
因此,它将一束连续的记录塞进一个数据包中,剩余的位被截断并传递给下一个数据包包含,以此类推。大多数工具包旨在分析数据包级的传递,而不仅仅是TLS特定的消息。与这些相比,旨在从分段的有效载荷数据中重建原始TLS记录流。此工具从pcap格式文件或实时网络接口读取数据包数据,并在输出文件上写入共享同一对源/目标IP地址和端口号的每个TCP会话的记录序列。
总结
TLS(Transport Layer Security)记录是TLS协议中用于封装和传输数据的基本单元。TLS是一种安全协议,用于在计算机网络上提供加密通信和数据完整性保障。TLS记录层位于TLS协议栈的较低层,它负责将应用层数据(如HTTP、SMTP等)分割成小块,并为这些数据块提供封装、加密、MAC(Message Authentication Code)计算和压缩等功能。
We also undertake the development of program requirements here. If necessary, please follow the WeChat official account 【程序猿编码】and contact me
参考:https://datatracker.ietf.org/doc/html/rfc8446



















