1. 程式人生 > >Python黑帽程式設計 4.1 Sniffer(嗅探器)之資料捕獲(上)

Python黑帽程式設計 4.1 Sniffer(嗅探器)之資料捕獲(上)


Python黑帽程式設計 4.1 Sniffer(嗅探器)之資料捕獲(上)

網路嗅探,是監聽流經本機網絡卡資料包的一種技術,嗅探器就是利用這種技術進行資料捕獲和分析的軟體。

編寫嗅探器,捕獲資料是前置功能,資料分析要建立在捕獲的基礎上。本節就資料捕獲的基本原理和程式設計實現做詳細的闡述。

4.1.1 乙太網網絡卡的工作模式

乙太網網絡卡是我們日常生活中見得最多的網絡卡,我們的電腦通過網線或者wifi接入網路,使用的都是乙太網網絡卡。

圖2

常用的乙太網卡支援以下工作模式:廣播模式、多播模式、直接模式和混雜模式。

1.廣播模式(Broad Cast Model):它的實體地址(MAC)地址是 0Xffffff 的幀為廣播幀,工作在廣播模式的網絡卡接收廣播幀。它將會接收所有目的地址為廣播地址的資料包,一般所有的網絡卡都會設定為這個模式。

2.多播傳送(MultiCast Model):多播傳送地址作為目的實體地址的幀可以被組內的其它主機同時接收,而組外主機卻接收不到。但是,如果將網絡卡設定為多播傳送模式,它可以接收所有的多播傳送幀,而不論它是不是組內成員。當資料包的目的地址為多播地址,而且網絡卡地址是屬於那個多播地址所代表的多播組時,網絡卡將接納此資料包,即使一個網絡卡並不是一個多播組的成員,程式也可以將網絡卡設定為多播模式而接收那些多播的資料包。

3.直接模式(Direct Model):工作在直接模式下的網絡卡只接收目地址是自己Mac地址的幀。只有當資料包的目的地址為網絡卡自己的地址時,網絡卡才接收它。

4.混雜模式(Promiscuous Model):工作在混雜模式下的網絡卡接收所有的流過網絡卡的幀,信包捕獲程式就是在這種模式下執行的。網絡卡的預設工作模式包含廣播模式和直接模式,即它只接收廣播幀和發給自己的幀。如果採用混雜模式,網絡卡將接受同一網路內所有主機發送的資料包。

利用網絡卡混雜模式的特性,就可以到達對於網路資訊監聽捕獲的目的。

需要注意的是,並不是任何情況下,網路中的資料都會流經你的網絡卡,比如交換機網路,交換機會繫結埠和MAC,此時就需要上一章講到的ARP欺騙了。

4.1.2 設定網絡卡為混雜模式

在Kali Linux中,我們可以通過ifconfig和iwconfig配置網路介面的資訊。

正常情況下輸入ifconfig,虛擬機器中顯示如下:

圖3

通過命令

ifconfig eth0 promisc

可以將eth0設定為混雜模式。

圖4

圖四中圈紅的部分,表示當前網絡卡處於混雜模式。

通過ifconfig eth0 -promisc

可以取消網絡卡的混雜模式。

圖5

ifconfig同樣適用於無線網絡卡。

4.1.3 無線網絡卡的監聽模式

對於無線網絡卡,我們可以使用iwconfig的mode引數來配置混雜模式,mode的選項值如下:

1)      Ad-hoc:不帶AP的點對點無線網路

2)      Managed:通過多個AP組成的網路,無線裝置可以在這個網路中漫遊

3)      Master:設定該無線網絡卡為一個AP

4)      Repeater:設定為無線網路中繼裝置,可以轉發網路包

5)      Secondary:設定為備份的AP/Repeater

6)      Monitor:監聽模式

7)      Auto:由無線網絡卡自動選擇工作模式   

使用如下命令可以設定無線網絡卡為監聽模式:

ifconfig wlan0 down

iwconfig wlan0 mode monitor

ifconfig wlan0 up

在Kali中我們通過iwconfig來設定混雜模式,可能會遇到點困難,無線網絡卡設定成混雜模式後,過幾秒又變成manage模式了。這是由於Network Manage服務造成,我們可以關閉該服務。

監聽模式和上文的混雜模式有什麼區別呢?混雜模式是在wifi連線到指定網路中,監聽子網中的資料傳輸;監聽模式下wifi會斷網,進而監聽某一個通道內所有傳輸流量,因此可以用來掃描wifi熱點,破解wifi密碼等工作。

下面我們來看一下如何程式設計實現Sniffer。

4.1.4 可以在WINDOWS上執行的SNIFFER

Raw Socket是一種較為底層的socket程式設計介面,可以用來獲取IP層以上的資料,所以可以用來編寫Sniffer。一個完整的sniffer程式碼組成,大致分為建立socket物件,接收資料,分析資料三個部分。其中開啟網絡卡的混雜模式,需要配置socket物件的屬性。在開啟混雜模式方面,Linux上要比windows上覆雜一點,我們先從簡單的情況開始。

首先我們定義出程式的基本框架。

 

在上面的程式碼中,我們首先定義了一個類——PromiscuousSocket,這個類負責建立一個繫結到當前主機名繫結的網絡卡上的raw socket物件,並設定啟動混雜模式。PromiscuousSocket類有三個方法,分別為類的建構函式,另外兩個函式是用於with關鍵字的塊作用域的起止函式,不瞭解的同學請翻閱Python的程式設計基礎資料看一下。sniffer函式會建立PromiscuousSocket類的例項,並使用它接收和分析資料。printPacket方法用來顯示捕獲的資料內容。

接下來我們來完善核心的PromiscuousSocket類,在__init__方法中,我們建立socket物件,並繫結到物件的s欄位上。

def __init__(self):

#建立socket

      HOST = socket.gethostbyname(socket.gethostname())

      s = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_IP)

      s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)

      s.bind((HOST, 0))

          s.setsockopt(socket.IPPROTO_IP, socket.IP_HDRINCL, 1)

     s.ioctl(socket.SIO_RCVALL, socket.RCVALL_ON)

      self.s = s

這段程式碼首先建立一個socket物件,第一個欄位family我們選擇ipv4;第二個欄位type,選擇raw socket。(這裡關於socket程式設計的基礎內容,如果你不是很理解,可以先看一看本教程的2.8節。)

setsockopt函式是用來對socket物件進行補充選項的設定,三個引數的分別為level、選項名稱和值。

level支援SOL_SOCKET、IPPROTO_TCP、IPPROTO_IP和IPPROTO_IPV6。

可用的socket層選項名字如下:

協議層        選項名字
SOL_SOCKET    SO_REUSEADDR
SOL_SOCKET    SO_KKEPALIVE
SOL_SOCKET    SO_LINGER
SOL_SOCKET    SO_BROADCAST
SOL_SOCKET    SO_OOBINLINE
SOL_SOCKET    SO_SNDBUF
SOL_SOCKET    SO_RCVBUF
SOL_SOCKET    SO_TYPE
SOL_SOCKET    SO_ERROR

程式碼中我們使用了SOL_SOCKET 的SO_REUSEADDR

選項,該選項可以讓多個socket物件繫結到相同的地址和埠上。

  s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)

設定了該選項之後,我們呼叫bind方法,來繫結socket。

  s.bind((HOST, 0))

接下來我們再次通過setsockopt函式來設定資料保護IP頭部。

    s.setsockopt(socket.IPPROTO_IP, socket.IP_HDRINCL, 1)

最後,通過ioctl函式類設定混雜模式,注意傳入的兩個引數,第一個指定設定的型別為接收所有資料,第二個引數要個第一個對應,使用RCVALL_ON來開啟。

   s.ioctl(socket.SIO_RCVALL, socket.RCVALL_ON)

我們再來看完善的__enter__函式。

def __enter__(self):

    return self.s

程式碼很簡單,返回建立的socket物件。

__exit__方法中,我們呼叫ioctl方法通過RCVALL_OFF來關閉混雜模式。程式碼如下:

def __exit__(self, *args, **kwargs):

    self.s.ioctl(socket.SIO_RCVALL, socket.RCVALL_OFF)

完善的sniff方法如下:

def sniffer(count, bufferSize=65565, showPort=False, showRawData=False):

 

    with PromiscuousSocket() as s:

      for i in range(count):

 

          # receive a package

          package = s.recvfrom(bufferSize)

          printPacket(package, showPort, showRawData)

sniff方法利用PromiscuousSocket的一個例項,接收資料包,然後呼叫printPacket方法列印基本資訊。

def printPacket(package, showPort, showRawData):

 

    dataIndex = 0

    headerIndex = 1

    ipAddressIndex = 0

    portIndex = 1

 

    print('IP:', package[headerIndex][ipAddressIndex], end=' ')

    if(showPort):

        print('Port:', package[headerIndex][portIndex], end=' ')           

    print('') #newline

    if(showRawData):

        print('Data:', package[dataIndex])

printPacket方法接收資料包物件,列印對應資訊。這裡不用過多解釋,傳入的package物件作為二維陣列被解析,通過除錯可以知道資料包裡面的內容,從而進一步調整程式。

4.1.5 解決LINUX上混雜模式問題

至此,一個簡單 的嗅探程式就完成了,在windows上可以執行無誤了。不過在linux上會遇到問題,在設定混雜模式的程式碼:

  s.ioctl(socket.SIO_RCVALL, socket.RCVALL_ON)

Python並沒有將SIO_RCVALL和RCVALL_ON及RCVALL_OFF暴露出來。但是系統底層的C結構體是有這樣的定義的,這裡我們通過fcntl模組的fcntl物件的ioctl方法來配置選項。這裡面涉及一個Python程式設計中python物件和C 型別轉換的知識點,我這裡就不展開了,不太知道的同學請自行查詢資料解決。

這裡我們先將要用到的數值封裝到類FLAGS中。

class FLAGS(object):

  # linux/if_ether.h

  ETH_P_ALL     = 0x0003 # 所有協議

  ETH_P_IP      = 0x0800 # 只處理IP層

  # linux/if.h,混雜模式

  IFF_PROMISC   = 0x100

  # linux/sockios.h

  SIOCGIFFLAGS  = 0x8913 # 獲取標記值

  SIOCSIFFLAGS  = 0x8914 # 設定標記值

然後建立一個ifreq類,如下:

class ifreq(ctypes.Structure):

    _fields_ = [("ifr_ifrn", ctypes.c_char * 16),

                ("ifr_flags", ctypes.c_short)]

該類繼承自ctypes.Structure類,使用它我們可以通過字串中轉c結構體欄位的值。

下面我們看如何使用FLAGS和ifreq類。

在PromiscuousSocket類初始化socket的程式碼部分,我們增加下面的程式碼。

 

if os.name == 'posix':

      import fcntl # posix-only

 

      s = socket.socket(socket.PF_PACKET, socket.SOCK_RAW, socket.htons(FLAGS.ETH_P_ALL))

      ifr = ifreq()

      ifr.ifr_ifrn = b'eth0' #此處注意,這裡寫死了網絡卡名稱,需要根據實際情況修改或者傳入

      fcntl.ioctl(s, FLAGS.SIOCGIFFLAGS, ifr) # 獲取標記欄位的名稱

      ifr.ifr_flags |= FLAGS.IFF_PROMISC # 新增混雜模式的值

      fcntl.ioctl(s, FLAGS.SIOCSIFFLAGS, ifr) # 更新

      self.ifr = ifr

上面的程式碼中,注意幾個地方。htons方法用來將16bit的正數的位元組順序轉換為網路傳輸的順序(所謂的大端,小端,不瞭解的請google之)。我們建立了一個ifreq類的例項 ifr,接下來設定繫結的網絡卡的名字,這裡程式寫死了,需要根據實際情況調整。通過

fcntl.ioctl(s, FLAGS.SIOCGIFFLAGS, ifr) # 獲取標記欄位的名稱

將當前socket已經有的Flag獲取到,然後加上設定混雜模式的數值,在通過

fcntl.ioctl(s, FLAGS.SIOCSIFFLAGS, ifr) # 更新

更新給socket物件,從而使該socket具有獲取所有資料的能力。

在__exit__方法中,取消混雜模式的程式碼我們也要修改一下:

def __exit__(self, *args, **kwargs):

    if os.name == 'posix':

      import fcntl

      self.ifr.ifr_flags ^= FLAGS.IFF_PROMISC

      fcntl.ioctl(self.s, FLAGS.SIOCSIFFLAGS, self.ifr)

    else:

      self.s.ioctl(socket.SIO_RCVALL, socket.RCVALL_OFF)

這段程式碼就不必再解釋了,根據上面的說明應該看得明白。

 

4.1.6 小結

到此為止,我們基於raw socket實現的嗅探器就完成了,實現我們捕獲資料的目的。此種方法,需要大家對作業系統本身對網路協議棧的描述,有較為深入的理解。下一節,我們讓這個過程變得輕鬆一點,使用一些流行的網路庫來實現Sniffer。

 

哦,今天是程式設計師節,給大家送點福利吧,本文的完整的原始碼,當然是人人有份了;另外送一本Python網路程式設計相關的書,只有一本哦。活動參與的方法如下:

1)如果你是在微信中閱讀本文,請選擇打賞或者轉發截圖回覆,我會講原始碼發給你。

2)在打賞的人中,我會隨機抽取一個人,送書一本(只限10月14日當晚有效哦)。

3)如果你不是在微信中閱讀本文,請移步微信,開啟玄魂工作室訂閱號(xuanhun521),回覆“python”,開啟連結,在黑帽欄目下面找到本篇文章(《PYTHON黑帽程式設計 4.1 SNIFFER(嗅探器)之資料捕獲(上)》),回到1)。

 

第4.2節《4.1 Sniffer(嗅探器)之資料捕獲(下)》已經在微信訂閱號搶先發布,心急的同學進入訂閱號(二維碼在下方),從選單“專欄”—>”Python黑帽程式設計”進入即可。

 

檢視完整系列教程,請關注我的微信訂閱號(xuanhun521,下方二維碼),回覆“python”。問題討論請加qq群:Hacking (1群):303242737   Hacking (2群):147098303。

 

玄魂工作室-精彩不斷