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