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中,我們可以通過ifconfigiwconfig配置網絡接口的信息。

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

3

通過命令

ifconfig eth0 promisc

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

4

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

通過ifconfig eth0 -promisc

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

5

ifconfig同樣適用於無線網卡。

4.1.3 無線網卡的監聽模式

對於無線網卡,我們可以使用iwconfigmode參數來配置混雜模式,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_SOCKETIPPROTO_TCPIPPROTO_IPIPPROTO_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_RCVALLRCVALL_ONRCVALL_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結構體字段的值。

下面我們看如何使用FLAGSifreq類。

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

 

 

 

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

 

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

 

玄魂工作室-精彩不斷

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM