YY的音频数据传输是P2P协议,音频的编码为AAC,下面抓去的音频编码的信息和频谱信息。
音频编码为AAC,采样为44K,码率24kb/s。音频编码在24kb/s码率能达到15K的音质。值得大家学习啊。
1.准备工具
procexp.exe 分析YY的进程信息
Procmon.exe 分析YY的网络数据包
wireshark.exe 分析网络包的内容
2.分析YY的进程信息
使用procexp分析YY的大致信息,比如进程号,网络连接等
3.分析YY的网络传输信息
使用procmon分析YY的网络数据,根据上面的得到的进程ID设置过滤,只接受YY的UDP数据包
过滤后得到数据包如下:
从上面的数据可以看到端口为8456的UDP接受数据最多,可以看出这个端口接受的就是P2P音频数据。
4.使用wireshark抓取P2P音频数据包
设置wireshark的过滤器,只抓去端口为8456的UDP数据包
抓去的数据如下:
查看UDP数据流,这里面存的就是YY的音频数据。从下面的数据看不出来具体的音频编码。
不急,我们多看几个数据包就会发现,他们都有固定的数据头,紧接着都刚好是0xff|0xf1(这个刚好是aac ADTS的同步头)。所以我们可以按照这个思路分析下去。
5. 使用代码分析pcap抓去的数据包
详细分析参考代码:
#include <stdio.h> #include <stdlib.h> #include <WinSock2.h> #include <assert.h> /// typedef unsigned int bpf_u_int32; typedef unsigned char u_char; typedef unsigned short u_short; typedef unsigned int u_int; typedef int bpf_int32; /* Pcap文件分析如下: +-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+ |Pcap Header |Packet Header |Packet Body |Packet Header |Packet Body | +-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+ */ /* Pcap文件头 +-------+-------+-------+-------+ | Magic 4B | +-------+-------+-------+-------+ | Major 2B | Minor 2B | +-------+-------+-------+-------+ | ThisZone 4B | +-------+-------+-------+-------+ | SigFigs 4B | +-------+-------+-------+-------+ | SnapLen 4B | +-------+-------+-------+-------+ | LinkType 4B | +-------+-------+-------+-------+ Pcap文件头24B各字段说明: Magic:4B:0x1A 2B 3C 4D:用来标示文件的开始 Major:2B,0x02 00:当前文件主要的版本号 Minor:2B,0x04 00当前文件次要的版本号 ThisZone:4B当地的标准时间;全零 SigFigs:4B时间戳的精度;全零 SnapLen:4B最大的存储长度 LinkType:4B链路类型 常用类型: 0 BSD loopback devices, except for later OpenBSD 1 Ethernet, and Linux loopback devices 6 802.5 Token Ring 7 ARCnet 8 SLIP 9 PPP */ typedef struct pcap_file_header { bpf_u_int32 magic; u_short version_major; u_short version_minor; bpf_int32 thiszone; bpf_u_int32 sigfigs; bpf_u_int32 snaplen; bpf_u_int32 linktype; }pcap_file_header; /* Packet 包头和Packet数据组成 +-------+-------+ |Packet Header | +-------+-------+ |Packet Body | +-------+-------+ Paket 包头 +-------+-------+-------+-------+ | Timestamp 4B | +-------+-------+-------+-------+ | Timestamp 4B | +-------+-------+-------+-------+ | Caplen 4B | +-------+-------+-------+-------+ | Len 4B | +-------+-------+-------+-------+ 字段说明: Timestamp:时间戳高位,精确到seconds Timestamp:时间戳低位,精确到microseconds Caplen:当前数据区的长度,即抓取到的数据帧长度,由此可以得到下一个数据帧的位置。 Len:离线数据长度:网络中实际数据帧的长度,一般不大于caplen,多数情况下和Caplen数值相等。 Packet 数据:即 Packet(通常就是链路层的数据帧)具体内容,长度就是Caplen,这个长度的后面,就是当前PCAP文件中存放的下一个Packet数据包,也就 是说:PCAP文件里面并没有规定捕获的Packet数据包之间有什么间隔字符串,下一组数据在文件中的起始位置。我们需要靠第一个Packet包确定。 */ typedef struct timestamp{ bpf_u_int32 timestamp_s; bpf_u_int32 timestamp_ms; }timestamp; typedef struct pcap_header{ timestamp ts; bpf_u_int32 capture_len; bpf_u_int32 len; }pcap_header; //help funtion void printPcapHeader(pcap_file_header * ph) { printf("=====================\n" "magic:0x%0x\n" "version_major:%u\n" "version_minor:%u\n" "thiszone:%d\n" "sigfigs:%u\n" "snaplen:%u\n" "linktype:%u\n" "=====================\n", ph->magic, ph->version_major, ph->version_minor, ph->thiszone, ph->sigfigs, ph->snaplen, ph->linktype); } void printPacketHeader(pcap_header * ph) { printf("=====================\n" "ts.timestamp_s:%u\n" "ts.timestamp_ms:%u\n" "capture_len:%u\n" "len:%d\n" "=====================\n", ph->ts.timestamp_s, ph->ts.timestamp_ms, ph->capture_len, ph->len); } ////////////////////////////////////////////////////////////////////////// /* UDP包分析 */ /* 4字节的IP地址 */ typedef struct ip_address{ u_char byte1; u_char byte2; u_char byte3; u_char byte4; }ip_address; /* IPv4头 */ typedef struct ip_header{ u_char ver_ihl; // 版本 (4 bits) + 首部长度 (4 bits) /// 首部长度 u_char tos; // 服务类型(Type of service) u_short tlen; // 总长(Total length) u_short identification; // 标识(Identification) u_short flags_fo; // 标志位(Flags) (3 bits) + 段偏移量(Fragment offset) (13 bits) u_char ttl; // 存活时间(Time to live) u_char proto; // 协议(Protocol) u_short crc; // 首部校验和(Header checksum) ip_address saddr; // 源地址(Source address) ip_address daddr; // 目的地址(Destination address) u_int op_pad; // 选项与填充(Option + Padding) }ip_header; /* UDP头 */ typedef struct udp_header{ u_short sport; // 源端口(Source port) u_short dport; // 目的端口(Destination port) u_short len; // UDP数据包长度(Datagram length) /// UDP总长度 u_short crc; // 校验和(Checksum) }udp_header; u_char pkt_data[65536]; int main() { FILE * fp = fopen("yy.p2p.packet_long.pcap", "rb"); if (!fp) { fprintf(stderr, "open file error\n"); return -1; } FILE * aacfp = fopen("yy.p2p.packet_long.pcap.aac", "wb"); if (!fp) { fprintf(stderr, "open file error\n"); return -1; } //1. Read pcap file header pcap_file_header pfh; fread(&pfh, 1, sizeof(pfh), fp); printPcapHeader(&pfh); //2. Read pcap packet while (!feof(fp)) { pcap_header ph; if (fread(&ph, 1, sizeof(ph), fp) != sizeof(ph)) break; printPacketHeader(&ph); if (fread(pkt_data, 1, ph.capture_len, fp) != ph.capture_len) break; //3. 分析消息包内容 //fseek(fp, ph.capture_len, 1); // 获取IP数据包头的位置 ip_header *ih = (ip_header *)(pkt_data + 14); // 14以太网头部长度 // 只处理UDP包 if (ih->proto != 0x11) continue; // 获取UDP首部的位置 u_int ip_len = (ih->ver_ihl & 0x0f) * 4; udp_header * uh = (udp_header *)((u_char *)ih + ip_len); // u_short sport = ntohs(uh->sport); u_short dport = ntohs(uh->dport); u_short udplen = ntohs(uh->len); if(sport != 8455) // yy源端口为8455 continue; /* 打印IP地址和UDP端口 */ printf("%d.%d.%d.%d.%d -> %d.%d.%d.%d.%d\n", ih->saddr.byte1, ih->saddr.byte2, ih->saddr.byte3, ih->saddr.byte4, sport, ih->daddr.byte1, ih->daddr.byte2, ih->daddr.byte3, ih->daddr.byte4, dport); // u_char * udp_data = (u_char *)uh + 8; //u_char * aac_data = udp_data + 34; //u_short aac_len = udplen - 8 - 34; u_char * aac_data = pkt_data + 14 + ip_len + 8 + 34; int aac_len = ph.capture_len - (14 + ip_len + 8 + 34); //assert(aac_len > 0); if (aac_len <= 0) continue; /// 只有 yy头,没有aac数据 assert(aac_len < ph.capture_len); printf("aac len = %d pkt_len = %d\n", aac_len, ph.capture_len); assert(aac_data[0] == 0xff && aac_data[1] == 0xf1); fwrite(aac_data, 1, aac_len, aacfp); } fclose(fp); fclose(aacfp); return 0; }