各个模块的设计和实现

4.1  种子解析模块的设计和实现

解析种子文件主要在parse_metafile.h和parse_metafile.c中完成。parse_metafile.h文件的内容为:

parse_metafile.h

#ifndef PARSE_METAFILE

#define PARSE_METAFILE

// 保存从种子文件中获取的tracker的URL

typedef struct _Announce_list {

char    announce[128];

struct  _Announce_list  *next;

} Announce_list;

// 保存各个待下载文件的路径和长度

typedef struct _Files {

char    path[256];

long    length;

struct  _Files  *next;

} Files;

int read_metafile(char *metafile_name);            // 读取种子文件

int find_keyword(char *keyword,long *position);      // 在种子文件中查找某个关键词

int read_announce_list();                              // 获取各个tracker服务器的地址

int add_an_announce(char* url);                      // 向tracker列表添加一个URL

int get_piece_length();     // 获取每个piece的长度,一般为256KB

int get_pieces();               // 读取各个piece的哈希值

int is_multi_files();       // 判断下载的是单个文件还是多个文件

int get_file_name();            // 获取文件名,对于多文件,获取的是目录名

int get_file_length();        // 获取待下载文件的总长度

int get_files_length_path();    // 获取文件的路径和长度,对多文件种子有效

int get_info_hash();              // 由info关键词对应的值计算info_hash

int get_peer_id();            // 生成peer_id,每个peer都有一个20字节的peer_id

void release_memory_in_parse_metafile();// 释放parse_metafile.c中动态分配的内存

int  parse_metafile(char *metafile);       // 调用本文件中定义的函数,完成解析种子文件

#endif

以下是parse_metafile.c文件的头部,主要是包含了一些头文件和定义一些全局变量,各个函数的定义将在后面列出。

parse_metafile.c

#include <stdio.h>

#include <ctype.h>

#include <malloc.h>

#include <stdlib.h>

#include <string.h>

#include <time.h>

#include "parse_metafile.h"

#include "sha1.h"

char    *metafile_content = NULL;   // 保存种子文件的内容

long     filesize;                    // 种子文件的长度

int       piece_length   = 0;    // 每个piece的长度,通常为256KB即262144字节

char     *pieces = NULL;     // 保存每个pieces的哈希值,每个哈希值为20字节

int       pieces_length = 0;     // 缓冲区pieces的长度

int      multi_file = 0;          // 指明是单文件还是多文件

char    *file_name = NULL;      // 对于单文件,存放文件名;对于多文件,存放目录名

long long file_length = 0;           // 存放待下载文件的总长度

Files    *files_head = NULL;     // 只对多文件种子有效,存放各个文件的路径和长度

unsigned    char info_hash[20];          // 保存info_hash的值,连接tracker和peer时使用

unsigned    char peer_id[20];            // 保存peer_id的值,连接peer时使用

Announce_list *announce_list_head = NULL;   // 用于保存所有tracker服务器的URL

下面对解析种子文件中用到函数功能解释如下。

ul  int read_metafile(char *metafile_name)

参数:metafile_name是种子文件名。

返回:处理成功返回0,否则返回−1

附注:将种子文件的内容读入到全局变量metafile_content所指向的缓冲区中以方便处理。该函数的实现代码为:

int read_metafile(char *metafile_name)

{

long  i;

// 以二进制、只读的方式打开文件

FILE *fp = fopen(metafile_name,"rb");

if(fp == NULL) {

printf("%s:%d can not open file\n",__FILE__,__LINE__);

return -1;

}

// 获取种子文件的长度,filesize为全局变量,在parse_metafile.c头部定义

fseek(fp,0,SEEK_END);

filesize = ftell(fp);

if(filesize == -1) {

printf("%s:%d fseek failed\n",__FILE__,__LINE__);

return -1;

}

metafile_content = (char *)malloc(filesize+1);

if(metafile_content == NULL) {

printf("%s:%d malloc failed\n",__FILE__,__LINE__);

return -1;

}

// 读取种子文件的内容到metafile_content缓冲区中

fseek(fp,0,SEEK_SET);

for(i = 0; i < filesize; i++)

metafile_content[i] = fgetc(fp);

metafile_content[i] = '\0';

fclose(fp);

#ifdef DEBUG

printf("metafile size is: %ld\n",filesize);

#endif

return 0;

}

函数代码说明。

(1)编译器预定义的宏__FILE__和__LINE__在程序中可以直接使用。__FILE__代表该宏所在的源文件的文件名,在源文件parse_metafile.c中该宏的值等于“parse_metafile.c”,宏__LINE__的值为__LINE__所在行的行号。

(2)种子文件必须以二进制的方式打开,否则如果以字符方式打开可能无法读取整个文件的内容。无法读取的原因在于piece的hash值中可能含有字符0x00,若文件以字符形式打开,遇到该字符,库函数就认为文件已经结束。

(3)增加“#ifdef DEBUG  #endif ”,主要是为了方便调试。如果在parse_metafile.c文件的头部增加宏定义语句“#define DEBUG”,则程序运行时将执行“#ifdef DEBUG”和“#endif”之间的语句。在软件开发阶段,可以使用“#define DEBUG”来打印和查看某些关键的值,开发完毕,去掉该宏,则打印语句不会执行。

ul int find_keyword(char *keyword,long *position)

参数:keyword为要查找的关键字,position用于返回关键字第一个字符所在的下标。

返回:成功执行并找到关键字返回1,未找到返回0,执行失败返回−1。函数实现代码如下所示:

int find_keyword(char *keyword,long *position)

{

long i;

*position = -1;

if(keyword == NULL)  return 0;

for(i = 0; i < filesize-strlen(keyword); i++) {

if( memcmp(&metafile_content[i], keyword, strlen(keyword)) == 0 ) {

*position = i;

return 1;

}

}

return 0;

}

函数代码说明。

该函数在种子文件解析模块的源文件parse_metafile.c中被频繁使用,用于查找某些关键字。例如,关键字“8:announce”和“13:announce-list”之后都是Tracker服务器的地址,找到该关键字后,便可以获取Tracker的地址。

ul read_announce_list()

功能:获取Tracker地址,并将获取的地址保存到全局变量announce_list_head指向的链表中。该函数实现代码如下:

int read_announce_list()

{

Announce_list  *node = NULL;

Announce_list  *p    = NULL;

int            len   = 0;

long           i;

if( find_keyword("13:announce-list",&i) == 0 ) {

if( find_keyword("8:announce",&i) == 1 ) {

i = i + strlen("8:announce");

while( isdigit(metafile_content[i]) ) {

len = len * 10 + (metafile_content[i] - '0');

i++;

}

i++;  // 跳过 ':'

node = (Announce_list *)malloc(sizeof(Announce_list));

strncpy(node->announce,&metafile_content[i],len);

node->announce[len] = '\0';

node->next = NULL;

announce_list_head = node;

}

}

else {  // 如果有13:announce-list关键词就不用处理8:announce关键词

i = i + strlen("13:announce-list");

i++;         // 跳过 'l'

while(metafile_content[i] != 'e') {

i++;     // 跳过 'l'

while( isdigit(metafile_content[i]) ) {

len = len * 10 + (metafile_content[i] - '0');

i++;

}

if( metafile_content[i] == ':' )  i++;

else  return -1;

// 只处理以http开头的tracker地址,不处理以udp开头的地址

if( memcmp(&metafile_content[i],"http",4) == 0 ) {

node = (Announce_list *)malloc(sizeof(Announce_list));

strncpy(node->announce,&metafile_content[i],len);

node->announce[len] = '\0';

node->next = NULL;

if(announce_list_head == NULL)

announce_list_head = node;

else {

p = announce_list_head;

while( p->next != NULL) p = p->next; // 使p指针指向最后个结点

p->next = node; // node成为tracker列表的最后一个结点

}

}

i = i + len;

len = 0;

i++;    // 跳过 'e'

if(i >= filesize)  return -1;

} // while 循环结束

}

#ifdef DEBUG

p = announce_list_head;

while(p != NULL) {

printf("%s\n",p->announce);

p = p->next;

}

#endif

return 0;

}

程序说明。

(1)下面是某种子文件开头的一部分,请对照它来理解read_announce_list函数

d8:announce32:http://tk.greedland.net/announce13:announce-listll32:http://tk.greedland.net/announceel33:http://tk2.greedland.net/announceee...

第一个字符‘d’是B编码中字典的起始符,接着是关键字“8:announce”,该关键字是一个长度为8的字符串,其对应的值为长度为32的字符串“32:http://tk.greedland.net/announce”,它是一个Tracker服务器的URL,接着是关键字“13:announce-list”,该关键字对应的值是一个列表,因为关键字“13:announce-list”之后的第一个字符为列表的起始字符‘l’,该列表中含有两个元素,这两个元素的类型也都是列表。

如果有关键字“13:announce-list”就不用处理关键字“8:announce”的原因在于,前者对应的值中必定包含后者对应的值。

(2)“#ifdef DEBUG”和“#endif”之间的语句用于打印各个Tracker的URL。

ul int add_an_announce(char *url)

功能:连接某些Tracker时会返回一个重定向的URL,需要连接该URL才能获取peer。函数实现代码如下:

int add_an_announce(char *url)

{

Announce_list *p = announce_list_head, *q;

// 若参数指定的URL在Tracker列表中已存在,则无需添加

while(p != NULL) {

if(strcmp(p->announce,url) == 0)  break;

p = p->next;

}

if(p != NULL)  return 0;

q = (Announce_list *)malloc(sizeof(Announce_list));

strcpy(q->announce,url);

q->next = NULL;

p = announce_list_head;

if(p == NULL)  { announce_list_head = q; return 1; }

while(p->next != NULL)  p = p->next;

p->next = q;

return 1;

}

ul  int is_multi_files()

功能:判断是下载多个文件还是单文件,若含有关键字“5:files”则说明下载的是多个文件。函数实现的代码如下:

int is_multi_files()

{

long i;

if( find_keyword("5:files",&i) == 1 ) {

multi_file = 1;

return 1;

}

#ifdef DEBUG

printf("is_multi_files:%d\n",multi_file);

#endif

return 0;

}

ul int get_piece_length()

功能:获取piece的长度。函数实现的代码如下:

int get_piece_length()

{

long i;

if( find_keyword("12:piece length",&i) == 1 ) {

i = i + strlen("12:piece length");  // 跳过 "12:piece length"

i++;  // 跳过 'i'

while(metafile_content[i] != 'e') {

piece_length = piece_length * 10 + (metafile_content[i] - '0');

i++;

}

} else {

return -1;

}

#ifdef DEBUG

printf("piece length:%d\n",piece_length);

#endif

return 0;

}

程序说明。

以下是某种子文件的一部分:12:piece lengthi262144e6:pieces16900:...

从中可以看到,关键字“12:piece length”后面跟一个B编码的整型数(以i作为起始字符,以e作为终结字符)。262144(256K),说明每个piece的长度都是256KB(最后一个piece除外)。接着是关键字“6:pieces”,它对应的值是一个B编码的字符串,存放各个piece的hash值,16900是字符串的长度,该字符串长度除以20即为piece数,因为每个piece的hash值为固定的20字节。

ul get_pieces()

功能:获取每个piece的hash值,并保存到pieces所指向的缓冲区中。函数实现的代码如下:

int get_pieces()

{

long i;

if( find_keyword("6:pieces", &i) == 1 ) {

i = i + 8;              // 跳过 "6:pieces"

while(metafile_content[i] != ':') {

pieces_length = pieces_length * 10 + (metafile_content[i] - '0');

i++;

}

i++;                    // 跳过 ':'

pieces = (char *)malloc(pieces_length+1);

memcpy(pieces,&metafile_content[i],pieces_length);

pieces[pieces_length] = '\0';

} else {

return -1;

}

#ifdef DEBUG

printf("get_pieces ok\n");

#endif

return 0;

}

ul  get_file_name()

功能:获取待下载的文件的文件名,如果下载的是多个文件,则获取的是目录名。函数实现的代码如下:

int get_file_name()

{

long  i;

int   count = 0;

if( find_keyword("4:name", &i) == 1 ) {

i = i + 6;  // 跳过 "4:name"

while(metafile_content[i] != ':') {

count = count * 10 + (metafile_content[i] - '0');

i++;

}

i++;        // 跳过 ':'

file_name = (char *)malloc(count+1);

memcpy(file_name,&metafile_content[i],count);

file_name[count] = '\0';

} else {

return -1;

}

#ifdef DEBUG

printf("file_name:%s\n",file_name);

#endif

return 0;

}

程序说明。

以下是一个完整的较为简单的种子文件:

d8:announce32:http://tk.greedland.net/announce13:announce-listll32:http://tk.greedland.net/annou

nceel33:http://tk2.greedland.net/announceee13:creation datei1187968874e4:infod6:lengthi119861

306e4:name31:[ymer][naruto][246][jp_cn].rmvb10:name.utf-831:[ymer][naruto][246][jp_cn].rmv

b12:piece lengthi262144e6:pieces9160:...ee

关键字“13:creation date”之前的部分已经在介绍read_announce_list函数时分析过了,此处不再赘述。关键字“13:creation date”及其对应的值“i1187968874e”,它指明了创建种子文件的时间。我们注意到时间是一个整数,它是自1970年1月1日到种子文件创建时所经过的秒数,Linux中有专门的库函数处理这种表示类型的时间。

关键字“4:info”对应的值是一个字典,因为该关键字之后的第一个字符是B编码中字典的起始符‘d’,与该起始符对应的终止符是文件末尾的倒数第二个‘e’。计算info_hash时,就是以关键字“4:info”对应的值作为输入,计算其hash值,将得到的值作为info_hash。文件末尾最后一个字符‘e’与文件开头的’d’对应,因此整个种子文件就是一个B编码的字典。

关键字“6:length”对应的值是待下载文件的长度,以字节为单位,可以大致地确定待下载文件的长度为119MB。

关键字“4:name”对应的值为待下载的文件的文件名,在这个种子文件中没有关键字“5:files”说明待下载的是单文件。

关键字“10:name.utf-8”对应的值也是待下载文件的文件名,只不过以UTF-8的形式表示,UTF-8的形式可以表示宽字符,即中文、日文、朝鲜文等字符。

ul int get_file_length()

功能:获取待下载文件的长度。函数实现的代码如下:

int get_file_length()

{

long i;

if(is_multi_files() == 1)  {

if(files_head == NULL)  get_files_length_path();

Files *p = files_head;

while(p != NULL) { file_length += p->length; p = p->next; }

} else {

if( find_keyword("6:length",&i) == 1 ) {

i = i + 8;  // 跳过 "6:length"

i++;        // 跳过 'i'

while(metafile_content[i] != 'e') {

file_length = file_length * 10 + (metafile_content[i] - '0');

i++;

}

}

}

#ifdef DEBUG

printf("file_length:%lld\n",file_length);

#endif

return 0;

}

ul get_files_length_path()

功能:对于多文件,获取各个文件的路径以及长度。函数实现的代码如下:

int get_files_length_path()

{

long   i;

int    length;

int    count;

Files  *node  = NULL;

Files  *p     = NULL;

if(is_multi_files() != 1) {

return 0;

}

for(i = 0; i < filesize-8; i++) {

if( memcmp(&metafile_content[i],"6:length",8) == 0 )

{

i = i + 8;  // 跳过 "6:length"

i++;        // 跳过 'i'

length = 0;

while(metafile_content[i] != 'e') {

length = length * 10 + (metafile_content[i] - '0');

i++;

}

node = (Files *)malloc(sizeof(Files));

node->length = length;

node->next = NULL;

if(files_head == NULL)

files_head = node;

else {

p = files_head;

while(p->next != NULL) p = p->next;

p->next = node;

}

}

if( memcmp(&metafile_content[i],"4:path",6) == 0 )

{

i = i + 6;  // 跳过 "4:path"

i++;        // 跳过 'l'

count = 0;

while(metafile_content[i] != ':') {

count = count * 10 + (metafile_content[i] - '0');

i++;

}

i++;        // 跳过 ':'

p = files_head;

while(p->next != NULL) p = p->next;

memcpy(p->path,&metafile_content[i],count);

*(p->path + count) = '\0';

}

}

return 0;

}

程序说明。

图13-2是一个多文件种子的一部分,可以参照图13-2理解get_files_length_path函数。

多文件种子的关键字“5:files”对应的值是比较复杂的。关键字“5:files”说明这是一个多文件种子,它对应的值是一个列表,列表的每个元素是字典,每个字典代表一个待下载文件。

图13-2  多文件种子示例

“5:filesl”及字符‘d’之后,有一个关键字“6:length”及其值“i127025815e”,然后是关键字“4:path”,其值为一个列表“l34:[BBsee出品][军情观察室08.22].rmvbe”,“rmvbee”中最后一个‘e’与字符‘d’对应。

然后“d6:lengthi76e4:pathl42:综艺 美剧 篮球 足球 尽在迅视XunTv.Net.urlee”又是一个字典。“urleee”中最后一个‘e’与“5:files”后的‘l’构成一个列表。“4:name”所跟的是目录名,然后是“12:piece length”关键字,“6:pieces”关键字。

从中可以总结出:有一个目录名“[BBsee出品][军情观察室08.22]”,其中存放了两个文件“[BBsee出品][军情观察室08.22].rmvb”和“综艺 美剧 篮球 足球 尽在迅视 XunTv. Net.url”,长度分别为127025815字节和76字节。

ul int get_info_hash ()

功能:计算info_hash的值。函数实现代码如下:

int get_info_hash()

{

int   push_pop = 0;

long  i, begin, end;

if(metafile_content == NULL)  return -1;

// begin的值表示的是关键字"4:info"对应值的起始下标

if( find_keyword("4:info",&i) == 1 )  begin = i+6;

else  return -1;

i = i + 6;        // 跳过 "4:info"

for(; i < filesize; )

if(metafile_content[i] == 'd') {

push_pop++;

i++;

} else if(metafile_content[i] == 'l') {

push_pop++;

i++;

} else if(metafile_content[i] == 'i') {

i++;      // 跳过 i

if(i == filesize)  return -1;

while(metafile_content[i] != 'e') {

if((i+1) == filesize)  return -1;

else i++;

}

i++;      // 跳过 e

} else if((metafile_content[i] >= '0') && (metafile_content[i] <= '9')) {

int number = 0;

while((metafile_content[i] >= '0') && (metafile_content[i] <= '9')) {

number = number * 10 + metafile_content[i] - '0';

i++;

}

i++;      // 跳过‘!’:

i = i + number;

} else if(metafile_content[i] == 'e') {

push_pop--;

if(push_pop == 0) { end = i; break; }

else  i++;

} else {

return -1;

}

if(i == filesize)  return -1;

SHA1_CTX context;

SHA1Init(&context);

SHA1Update(&context, &metafile_content[begin], end-begin+1);

SHA1Final(info_hash, &context);

#ifdef DEBUG

printf("info_hash:");

for(i = 0; i < 20; i++)

printf("%.2x ",info_hash[i]);

printf("\n");

#endif

return 0;

}

程序说明。

(1)在种子文件解析模块,由种子文件的内容计算info_hash的值是比较复杂的。前面已经提到,由关键字“4:info”对应的值来计算info_hash,该关键字对应的值是一个B编码的字典,问题的关键在于找到与“4:info”之后的‘d’对应的‘e’。

get_info_hash函数中找到所需要的‘e’的思路是:在“4:info”之后,每当遇到字典的起始符‘d’,则将push_pop的值加1(push_pop初始值为0),遇到列表的起始符‘l’也作相同处理;遇到整数的起始符‘i’则一直扫描直到找到与之对应的终结符‘e’:遇到一个0~9的数字说明接下来是一个字符串,跳过该字符串继续扫描;遇到‘e’则将push_pop值减1,如果减1后,push_pop值为0,说明已经找到了与‘d’匹配的‘e’。其思路类似于使用数据结构中的“栈”进行括号匹配操作。

(2)以“SHA1”开头的变量和函数用于计算一段文本的hash值,这些变量和函数的定义在sha1.h和sha1.c文件中,hash值的计算原理不必深究。计算hash值的这段代码的功能是以metafile_content[begin]~metafile_content[end]这end-begin+1个字符作为输入,计算其hash值,并把结果保存到info_hash所指向的数组中。

ul int get_peer_id()

功能:生成一个惟一的peer id。函数实现代码如下:

int get_peer_id()

{

// 设置产生随机数的种子

srand(time(NULL));

// 使用rand函数生成一个随机数,并使用该随机数来构造peer_id

// peer_id前8位固定为-TT1000-

sprintf(peer_id,"-TT1000-d",rand());

#ifdef DEBUG

printf("peer_id:%s\n",peer_id);

#endif

return 0;

}

ul void release_memory_in_parse_metafile()

功能:释放动态申请的内存。函数实现代码如下:

void release_memory_in_parse_metafile()

{

Announce_list *p;

Files         *q;

if(metafile_content != NULL)     free(metafile_content);

if(file_name != NULL)          free(file_name);

if(pieces != NULL)              free(pieces);

while(announce_list_head != NULL) {

p = announce_list_head;

announce_list_head = announce_list_head->next;

free(p);

}

while(files_head != NULL) {

q = files_head;

files_head = files_head->next;

free(q);

}

}

ul int parse_metafile(char *metafile)

功能:调用parse_metafile.c中定义的函数,完成解析种子文件。该函数由main.c调用。

返回:解析成功返回0,否则返回-1。函数实现代码如下:

int parse_metafile(char *metafile)

{

int ret;

// 读取种子文件

ret = read_metafile(metafile);

if(ret < 0) { printf("%s:%d wrong",__FILE__,__LINE__); return -1; }

// 从种子文件中获取tracker服务器的地址

ret = read_announce_list();

if(ret < 0) { printf("%s:%d wrong",__FILE__,__LINE__); return -1; }

// 判断是否为多文件

ret = is_multi_files();

if(ret < 0) { printf("%s:%d wrong",__FILE__,__LINE__); return -1; }

// 获取每个piece的长度,一般为256KB

ret = get_piece_length();

if(ret < 0) { printf("%s:%d wrong",__FILE__,__LINE__); return -1; }

// 读取各个piece的哈希值

ret = get_pieces();

if(ret < 0) { printf("%s:%d wrong",__FILE__,__LINE__); return -1; }

// 获取要下载的文件名,对于多文件的种子,获取的是目录名

ret = get_file_name();

if(ret < 0) { printf("%s:%d wrong",__FILE__,__LINE__); return -1; }

// 对于多文件的种子,获取各个待下载的文件路径和文件长度

ret = get_files_length_path();

if(ret < 0) { printf("%s:%d wrong",__FILE__,__LINE__); return -1; }

// 获取待下载的文件的总长度

ret = get_file_length();

if(ret < 0) { printf("%s:%d wrong",__FILE__,__LINE__); return -1; }

// 获得info_hash,生成peer_id

ret = get_info_hash();

if(ret < 0) { printf("%s:%d wrong",__FILE__,__LINE__); return -1; }

ret = get_peer_id();

if(ret < 0) { printf("%s:%d wrong",__FILE__,__LINE__); return -1; }

return 0;

}

BitTorrent协议分析三相关推荐

  1. 微信安卓协议分析笔记

    一.查资料 网上没找到SDK可以分析,关于微信安卓协议的文章也比较少,比较有用的是<微信交互协议和加密模式研究>,这篇论文里介绍了微信使用RSA2048与AES-CBC-128结合的加密算 ...

  2. 用wireshark抓包分析TCP协议的三次握手连接、四次握手断开

    用wireshark抓包分析TCP协议的三次握手连接.四次握手断开 一.TCP三次握手图解 二.TCP得四次挥手过程 三.用Fiddler抓包,分析验证一个HTTPS网站的TCP连接过程 一.TCP三 ...

  3. 计算机网络协议分析 第三课 PAP,CHAP,PPPoE

    计算机网络协议分析第三课 第二章 链路层技术 2.3.5 PPP认证 PAP认证 CHAP认证 2.4 PPPoE 第二章 链路层技术 2.3.5 PPP认证 PPP协议支持两种认证协议:PAP和CH ...

  4. ARP协议分析与攻击防护(三)

    ARP协议分析与攻击防护(三) 一.ARP攻击实操 1.1)ARP攻击(断网) 1.2)ARP攻击(窃取)

  5. 最新QQ2008贺岁协议分析第三版

    一. QQ2008 Touch包0x91 a) 0x2aa8 客户端 原始数据: 02 11 5b 00 91 2a a8 38 25 f5 91 5d 4c af 65 54 44 6d 76 7b ...

  6. 实验三 ARP 协议分析实验

    1.ARP 协议分析实验 1.ARP 协议介绍 ARP 是地址解析协议(Reverse  Address   Resolution   Protocol)的缩写,负责实现从IP 地址到物理地址(如以太 ...

  7. 宅男抖音某猫协议分析及应用破解

    " 分析传说中的快x,顺便提供破VIP线路及去启动广告方法." 在当今这个由应用市场主导的网络上,流传着一批应用,它们低调又神秘,依赖口碑与独立网站在地下渠道传播,应用市场中从来都 ...

  8. 从新手到入门,如何进入协议分析的世界

    " 协议分析与还原自学及入门指南." 有部分朋友给我发消息,说对协议还原很感兴趣,但苦于没人指导,希望得到我的帮助,问我如何进行协议分析的学习. 这篇文章从初学者的角度,编列了一个 ...

  9. PYTHON黑帽编程1.5 使用WIRESHARK练习网络协议分析

    Python黑帽编程1.5  使用Wireshark练习网络协议分析 1.5.0.1  本系列教程说明 本系列教程,采用的大纲母本为<Understanding Network Hacks At ...

最新文章

  1. 分布式ID(唯一性)的生成方法汇总
  2. 【Oracle】表空间相关集合
  3. 排序算法的实现(C/C++实现)
  4. muduo之TimerQueue
  5. 在小程序开发路上踩过的小坑
  6. 华为诺亚方舟实验室主任李航:神经符号处理开启自然语言处理新篇章
  7. java 文件指定位置插入_java 中利用io流将一个文件插入到另一个文件中的指定位置,指定位置...
  8. 一个SAP顾问的回忆:我过去很胖!
  9. 得到classpath和当前类的绝对路径的一些方法
  10. python数据分析笔记中panda(2)
  11. C++ opengl 使视野转头移动(站桩看世界)
  12. vSphere ESXi 5.5网络问题两例及解决方法
  13. java 排序 1和1_160308、java排序(形如1.1、1.2.1)
  14. r语言list添加元素_Redis数据结构 List 类型】List 类型生产中的应用 消息队列、排行榜、朋友圈、监控程序的实现...
  15. 人脸检测(十八)--TinyFace(S3FD,SSH,HR,RSA,Face R-CNN,PyramidBox)
  16. RubyGems 镜像 -- 淘宝网
  17. VxLAN技术基本原理
  18. 攻防世界逆向-logmein
  19. OpenChannelSSD之六_从OpenChannelSSD到ZNS
  20. 2020年高考理综试题解析:聚焦关键能力,凸显价值引领

热门文章

  1. [CF538C]Tourist's Notes
  2. linux shell 中函数的返回值问题
  3. 工频交流耐压试验装置中试验变压器有何作用
  4. xp系统无法工作组计算机,xp系统进不去工作组计算机的解决方法
  5. 如何防御DDoS攻击器抓取肉鸡呢?只需要注意这几点即可!
  6. 网页播放器的参数含义
  7. 人不可有傲气,但不可无傲骨。
  8. progressive-generation-master代码记录【下载处理数据】(定义CNNDataset类)
  9. 计算机动画制作初步视频,计算机动画制作初步.docx
  10. cdo收取邮件_使用CDO发送电子邮件