tcpdump是由美國的Lawrence Berkeley National Laboratory開發。使用了libpcap,獨立於系統的接口,它能夠將網絡中傳送的數據包徹底截獲下來提供分析。它支持針對網絡層、協議、主機、網絡或端口的過濾,並提供and、or、not等邏輯語句來幫助你去掉無用的信息。
網絡數據包的傳遞過程:
git
數據包的傳遞主要經過libpcap庫來實現,tcpdump調用libpcap的api函數,由libpcap進入到內核態到鏈路層來抓包,能夠根據用戶設置用於數據包過濾減小應用程序的數據包的包數和字節數從而提升性能。tcpdump底層原理其實就是libpcap的實現原理,如今libpcap成爲獨立的庫和API,來知足網絡嗅探。github
libpcap經常使用函數 | 用途 |
---|---|
pcap_open_offine | 打開一個保存的文件。 |
pcap_setfilter | 設置過濾器。 |
pcap_open_live | 打開選擇的設備。 |
pcap_lookupdev | 若是分組捕獲設備不曾指定(-i命令行選項),該函數選擇一個設備。 |
pcap_next | 接收一個包。 |
pcap_dump | 將包寫入到pcap_dump_t結構體。 |
pcap_loopupnet | 返回分組捕獲設備的網絡地址和子網掩碼,而後在調用pcap_compile時必須指定這個子網掩碼。 |
pcap_compile | 把cmd字符數組中構造的過濾器字符串編譯成一個過濾器程序,存放在fcode中。 |
pcap_setfilter | 把編譯出來的過濾器程序裝載到分組捕獲設備,同時引起用該過濾器選取的分組的捕獲。 |
pcap_datalink | 返回分組捕獲設備的數據鏈路類型。 |
,
根據官網的連接,咱們找到tcpdump的主要源碼tcpdump.c,進行分析。
源碼中引用了不少頭文件,主要分析一下netdissect.h以及interface.h。
在netdissect.h裏定義了一個數據結構struct netdissect_options來描述tcdpump支持的全部參數動做,每個參數有對應的flag, 在tcpdump 的main 裏面, 會根據用戶的傳入的參數來增長相應flag數值, 最後根據這些flag數值來實現特定動做。各個參數含義請參考源代碼註釋。api
struct netdissect_options { int ndo_aflag; /* 解析網絡和廣播地址 */ int ndo_eflag; /* 打印以太網報頭 */ int ndo_fflag; /* 不翻譯「外國」IP地址 */ int ndo_Kflag; /* 不檢查TCP校驗和 */ //不將地址轉換爲名字 int ndo_nflag; /* 丟棄地址及編號 */ int ndo_Nflag; /* 刪除已打印的主機名域 */ int ndo_qflag; /* 快速(更短)輸出 */ int ndo_Rflag; /* AH/ESP中的打印序列#字段*/ int ndo_sflag; /* 使用libsmi解析OID */ int ndo_Sflag; /* 打印原始TCP序列 */ // 報文到達時間 int ndo_tflag; /* 打印數據包到達時間 */ int ndo_Uflag; /* 未緩衝的轉儲文件輸出 */ int ndo_uflag; /* 打印未編碼NFS句柄 */ //詳細信息 int ndo_vflag; /* 冗餘 */ // 十六進制打印報文 int ndo_xflag; /* 十六進制打印報文 */ // 十六進制和ASCII碼打印報文 int ndo_Xflag; /* 十六進制/ASCII碼打印報文 */ //以ASCII碼顯示打印報文 int ndo_Aflag; /* 將TAB、LF、CR和PACE做爲圖形字符,只在ASCII中打印數據包*/ //默認的打印函數 void (*ndo_default_print)(netdissect_options *, register const u_char *bp, register u_int length); void (*ndo_info)(netdissect_options *, int verbose); }
interface.h是接口頭文件,定義了一堆宏方便調用struct netdissect_options裏的成員。數組
#ifndef NETDISSECT_REWORKED extern netdissect_options *gndo; #define nflag gndo->ndo_nflag #define tflag gndo->ndo_tflag #define vflag gndo->ndo_vflag #define xflag gndo->ndo_xflag #define Xflag gndo->ndo_Xflag #endif
源碼還定義了一些信號傳遞的靜態變量,如cookie
extern int dflag; int dflag; /* 打印過濾器 */ static int Gflag; /* 在多少秒以後轉儲文件 */ static int Gflag_count; /* 用Gflag交替建立的文件數 */ static time_t Gflag_time; /* 最後一time_t轉儲文件被交替 */ static int Lflag; /* 列出可用的數據連接類型和退出 */ ...
定義命令選項。
除了g、k、o和P以外,全部的字母都用於短選項。網絡
#define SHORTOPTS "aAb" B_FLAG "c:C:d" D_FLAG "eE:fF:G:hHi:" I_FLAG j_FLAG J_FLAG "KlLm:M:nNOpq" Q_FLAG "r:s:StT:u" U_FLAG "vV:w:W:xXy:Yz:Z:#"
定義了靜態長選項。數據結構
static const struct option longopts[] = { #if defined(HAVE_PCAP_CREATE) || defined(_WIN32) { "buffer-size", required_argument, NULL, 'B' }, #緩衝大小 #endif { "list-interfaces", no_argument, NULL, 'D' }, #接口列表 #ifdef HAVE_PCAP_FINDALLDEVS_EX { "list-remote-interfaces", required_argument, NULL, OPTION_LIST_REMOTE_INTERFACES }, #遠端接口列表 #endif { "help", no_argument, NULL, 'h' }, #幫助 { "interface", required_argument, NULL, 'i' }, #接口 #ifdef HAVE_PCAP_CREATE { "monitor-mode", no_argument, NULL, 'I' }, #監測模式 #endif #ifdef HAVE_PCAP_SET_TSTAMP_TYPE { "time-stamp-type", required_argument, NULL, 'j' }, #時間戳狀態 { "list-time-stamp-types", no_argument, NULL, 'J' }, #時間戳狀態列表 #endif #ifdef HAVE_PCAP_SET_TSTAMP_PRECISION { "micro", no_argument, NULL, OPTION_TSTAMP_MICRO}, { "nano", no_argument, NULL, OPTION_TSTAMP_NANO}, { "time-stamp-precision", required_argument, NULL, OPTION_TSTAMP_PRECISION},#時間戳精度 #endif ...
main函數能夠分爲三個部分:
第一部分是用struct netdissect_options數據結構做爲一個參數集合, 並用getopt框架來處理argv的參數邏輯,如case 'i'表示-i,用來指定網口,-x表示以十六進制打印報文,-X也是一樣的功能。app
while ( (op = getopt_long(argc, argv, SHORTOPTS, longopts, NULL)) != -1) switch (op) { case 'i': device = optarg; break; case 'x': ++ndo->ndo_xflag; ++ndo->ndo_suppress_default_print; break; case 'X': ++ndo->ndo_Xflag; ++ndo->ndo_suppress_default_print; break;
第二部分是使用libpcap庫函數來搭建與底層IPC通道。 其中最重要的API有三個, 第一個是pcap_lookupdev(), 查找可用網口框架
#ifdef HAVE_PCAP_FINDALLDEVS /* 找到接口列表,選擇第一個接口。*/ if (pcap_findalldevs(&devlist, ebuf) == -1) error("%s", ebuf); if (devlist == NULL) error("no interfaces available for capture"); device = strdup(devlist->name); pcap_freealldevs(devlist); #else /*使用pcap_lookupdev()接口選擇網口 */ device = pcap_lookupdev(ebuf); if (device == NULL) error("%s", ebuf); #endif }
第二個是pcap_open_live(),打開指定設備並將其配置爲混雜模式返回句柄:tcp
if (ndo->ndo_snaplen == 0) ndo->ndo_snaplen = MAXIMUM_SNAPLEN; pc = pcap_open_live(device, ndo->ndo_snaplen, !pflag, timeout, ebuf); if (pc == NULL) { /*若是「沒有這樣的設備」失敗,這意味着接口不存在;返回NULL,以便調用者能夠看到設備名稱是否其實是接口索引。*/ if (strstr(ebuf, "No such device") != NULL) return (NULL); error("%s", ebuf); } if (*ebuf) warning("%s", ebuf); #endif /* HAVE_PCAP_CREATE */ return (pc);
第三個是使用pcap_loop()持續獲取報文數據,調用回調函數進行打印處理。
do { status = pcap_loop(pd, cnt, callback, pcap_userdata); if (WFileName == NULL) { /*打印數據包, 刷新打印輸出,所以它不會與錯誤輸出混合。*/ if (status == -2) { putchar('\n'); } (void)fflush(stdout); } if (status == -2) { /*若是正在讀取多個文件(經過-V)和設置這些文件,馬上打斷。*/ VFileName = NULL; ret = NULL; } if (status == -1) { /*發生錯誤,用日誌記錄*/ (void)fprintf(stderr, "%s: pcap_loop: %s\n", program_name, pcap_geterr(pd)); } if (RFileName == NULL) { /* * We're doing a live capture. Report the capture * statistics. */ info(1); } pcap_close(pd); if (VFileName != NULL) { ret = get_next_file(VFile, VFileLine); if (ret) { int new_dlt; RFileName = VFileLine; pd = pcap_open_offline(RFileName, ebuf); if (pd == NULL) error("%s", ebuf); #ifdef HAVE_CAPSICUM cap_rights_init(&rights, CAP_READ); if (cap_rights_limit(fileno(pcap_file(pd)), &rights) < 0 && errno != ENOSYS) { error("unable to limit pcap descriptor"); } #endif new_dlt = pcap_datalink(pd); if (new_dlt != dlt) { /*新文件具備與前一個不一樣的鏈路層頭類型。*/ if (WFileName != NULL) { /*編寫與p cap文件匹配的原始數據包,因爲pcap文件不支持多個不一樣的鏈路層頭類型,因此在這裏失敗了。*/ error("%s: new dlt does not match original", RFileName); } /*打印解碼的數據包,切換到新的DLT,更改打印機,更改DLT名稱,並使用新的DLT從新編譯過濾器。*/ dlt = new_dlt; ndo->ndo_if_printer = get_if_printer(ndo, dlt); if (pcap_compile(pd, &fcode, cmdbuf, Oflag, netmask) < 0) error("%s", pcap_geterr(pd)); } /*在新文件上設置過濾器*/ if (pcap_setfilter(pd, &fcode) < 0) error("%s", pcap_geterr(pd)); /*報告新文件*/ dlt_name = pcap_datalink_val_to_name(dlt); fprintf(stderr, "reading from file %s", RFileName); if (dlt_name == NULL) { fprintf(stderr, ", link-type %u", dlt); } else { fprintf(stderr, ", link-type %s (%s)", dlt_name, pcap_datalink_val_to_description(dlt)); } fprintf(stderr, ", snapshot length %d\n", pcap_snapshot(pd)); } } } while (ret != NULL); if (count_mode && RFileName != NULL) fprintf(stderr, "%u packet%s\n", packets_captured, PLURAL_SUFFIX(packets_captured)); free(cmdbuf); pcap_freecode(&fcode); exit_tcpdump(status == -1 ? 1 : 0); }
第三部分是實現callback 函數,tcpdump.c裏的callback函數只作了一個封裝,並用到第二部分中pcap_loop()持續獲取報文數據,最終調用的是參數pcap_userdata裏提供的特定數據鏈路層的打印函數,
/*CALLBACK函數封裝*/ static void CALLBACK verbose_stats_dump(PVOID param _U_, BOOLEAN timer_fired _U_) { print_packets_captured(); }
u_char *pcap_userdata; ... /*調用參數pcap_userdata進行打印*/ do { status = pcap_loop(pd, cnt, callback, pcap_userdata); if (WFileName == NULL) { /* * We're printing packets. Flush the printed output, * so it doesn't get intermingled with error output. */ if (status == -2) { /* * We got interrupted, so perhaps we didn't * manage to finish a line we were printing. * Print an extra newline, just in case. */ putchar('\n'); } (void)fflush(stdout); ...
-h
顯示幫助信息
-i
選項表示選擇網卡,-vn
表示不把網絡地址轉換成名字,而且輸出一個包含ttl和服務類型的信息
登陸一個比較舊的水木社區,註冊用戶louhao123,密碼65697070Sx!,而後用wireshark監聽HTTP協議POST信息
能夠檢測到從本地發送到論壇IP的數據報中帳號和密碼的信息。
在電腦上開啓網絡共享
在手機中搜索LAPTOP-RIITK5LA 5748
的無線局域網並鏈接。
剛開始查了天涯論壇,數據中收到了cookie的信息,帳號以明文形式傳遞,密碼則無從入手,用在線網站加解密,也沒法恢復有信息的內容,缺失如今前段至少都會進行重要信息的加密。
後來又搜了一些比較小衆的app,其中一個越牛新聞的本地app居然徹底用明文傳遞用戶和密碼,存在明顯的帳號隱患。