1-編寫一個埠掃描器

ltoddy發表於2017-07-17

原始碼:https://github.com/LToddy/penetrationtest

任何一個靠譜的網路攻擊都是起步於偵察的。 攻擊者必須在挑選並確定利用目標中的漏洞之前找到目標在哪裡有漏洞。 編寫一個掃描目標主機開放的TCP埠的偵察小指令碼。 為了與TCP埠進行互動,我們要先建立TCP套接字。

Python提供了BSD套接字的介面。 BSD套接字提供了一個應用程式設計介面,使程式設計師能編寫在主機之間進行網路通訊的應用程式。 通過一系列套接字API函式,我們可以建立、繫結監聽、連線,或在TCP/IP套接字上傳送資料。

大多數能訪問網際網路的應用使用的都是TCP協議。 例如,在目標組織中,Web伺服器可能位於TCP80埠,電子郵件伺服器在TCP25埠,FTP伺服器在TCP21埠。 要連線目標組織中的任一伺服器,攻擊者必須知道與伺服器相關聯的IP地址和TCP埠。

所有成功的網路攻擊一般都是從埠掃描來開序幕的。 有一種型別的埠掃描會向一系列常用的埠傳送TCP SYN資料包,並等待TCP ACK響應——這能讓我們確定這個埠是開放的。 與此相反,TCP連線掃描是使用完整的三次握手來確定伺服器或埠是否可用的。

TCP全連線掃描

開始編寫自己用的TCP全連線掃描來識別主機的TCP埠掃描器。 要匯入Python的BSD套接字API實現。 套接字API會為我們提供一些在實現TCP埠掃描程式時有用的函式。 要深入的瞭解請檢視文件.

為了更好的瞭解TCP埠掃描器的工作原理,我們將指令碼分成五個獨立的步驟,分別為它們編寫Python程式碼。 首先輸入一個主機名和用逗號分割的埠列表,並予以掃描。 接下來將主機轉換成IPv4網際網路地址。對列表中的每個埠,我們都會連結目標地址和該埠。 最後,為了確定在該埠上執行什麼服務,我們將傳送垃圾資料並讀取由具體應用發回的Banner。

在第一步中,從使用者那裡獲得主機名和埠。 為了做到這一點,我們在程式中使用optparse庫解析命令列引數。 呼叫optparse.OptionParser([usage message])會生成一個引數解析器(option parser)類的例項。 接著,在parser.add_option中指定這個指令碼具體要解析哪個命令列引數。

e.g. 一個快速解析要掃描的目標主機名和埠的方法

```python import optparse

parser = optparse.OptionParser('usage %prog -H -p ') parser.add_option('-H', dest='tgtHost', type='string', help='specify target host') parser.add_option('-p', dest='tgtPort', type='string', help='specify target port')

(options, args) = parser.parse_args() tgtHost = options.tgtHost tgtPort = options.tgtPort if tgtHost == None or tgtPort == None: print(parser.usage) exit(0) else: print(tgtHost) print(tgtPort) ```

接下來我麼要生成倆個個函式: connScan和portScan. portScan函式以引數的形式接受主機名和目標埠列表。 它首先會嘗試用gethostbyname()函式確定主機名對應的IP地址。 接下來,它會使用connScan函式輸出主機名字(或IP地址),並使用connScan()函式嘗試逐個連線我們要連線的每個埠。 connScan函式接受兩個引數:tgtHost和tgtPort,它會去嘗試建立與目標主機埠的連線。 如果成功,connScan將列印出一個埠開放的訊息。如果不成功,它會列印出埠關閉的訊息。

```python from socket import *

def connScan(tgtHost, tgtPort): try: connSkt = socket(AF_INET, SOCK_STREAM) connSkt.connect((tgtHost, tgtPort)) print('[+] %d/tcp open' % tgtPort) connSkt.close() except Exception: print('[-] %d/tcp closed' % tgtPort)

def portScan(tgtHost, tgtPorts): try: tgtIP = gethostbyname(tgtHost) except: print("[-] Cannot resolve '%s': Unknown host" % tgtHost) return

try:
    tgtName = gethostbyaddr(tgtHost)
    print("\n[+] Scan Results for: " + tgtName[0])
except:
    print("\n[-] Scan Results for: " + tgtIP)

setdefaulttimeout(1)
for tgtPost in tgtPorts:
    print("Scannint port " + tgtPost)
    connScan(tgtHost, tgtPost)

```

抓取應用的Banner

為了抓取目標主機上應用的Banner,必須現在connScan函式中插入一些新增的程式碼。 找到開放埠後,我們向它傳送一個資料傳並等待響應。 根據收集到的響應,就能推斷出目標主機和埠上執行的應用。

```python from socket import *

def connScan(tgtHost, tgtPort): try: connSkt = socket(AF_INET, SOCK_STREAM) connSkt.connect((tgtHost, tgtPort)) connSkt.send('Hello Python\r\n') results = connSkt.recv(1024) print('[+] {}/tcp open'.format(tgtPort)) print('[+] ' + str(results)) connSkt.close() except: print('[-] {}/tcp closed'.format(tgtPort))

def portScan(tgtHost, tgtPorts): try: tgtIP = gethostbyname(tgtHost) except: print("[-] Cannot resolve '{}': Unknown host".format(tgtHost)) return

try:
    tgtName = gethostbyaddr(tgtIP)
    print("\n[+] Scan Results for: " + tgtName[0])
except:
    print('\n[+] Scan Results for: ' + tgtIP)

setdefaulttimeout(1)
for tgtPort in tgtPorts:
    print('Scanning port ' + tgtPort)
    connScan(tgtHost, tgtPort)

def main(): import optparse parser = optparse.OptionParser('usage %prog -H -p ') parser.add_option('-H', dest='tgtHost', type='string', help='specify target host') parser.add_option('-p', dest='tgtPort', type='string', help='specify target port')

(options, args) = parser.parse_args()
tgtHost = options.tgtHost
tgtPorts = (options.tgtPort).split(' ')
if tgtPorts[0] == None or tgtHost == None:
    print('[-] You must specify a target host and port[s].')
    exit(0)
portScan(tgtHost, tgtPorts)

if name == 'main': main() ```

執行緒掃描

根據套接字中timeout變數的值,每掃描一個套接字都會花費幾秒鐘。 這看上去微不足道,但如果我們要掃描多個主機埠,時間總量就會成倍增加。 理想情況下,我們希望能同時掃描多個套接字,而不是一個一個地進行掃描。 這時,我們必須引入Python執行緒,執行緒是一種能提供這類同時執行多項任務的方法。 具體到我們這個掃描器,我們要修改的是portScann()函式中迭代迴圈裡的程式碼。

python from threading import * for tgtPort in tgtPorts: t = Thread(target=connScan, args=(tgtHost, int(tgtPort))) t.start()

這讓我們的速度有了顯著的提升,但這又有一個缺點。connScan()函式會在螢幕上列印一個輸出。 如果多個執行緒同時列印輸出,就可能會出現亂碼和失序。 為了讓一個函式獲得完整的螢幕控制前,我們需要使用一個訊號量(semaphore)。 一個簡單的訊號量就能阻止其他執行緒的執行。 注意,在列印輸出前,我們用screenLock。acquire()執行一個加鎖操作。 如果訊號量還沒被鎖上,執行緒就有權繼續執行,並輸出到螢幕上。 如果訊號量已經被鎖定,我們只能等待,直到持有訊號量的執行緒釋放訊號量。 通過利用訊號量,我們現在能夠確保在任何給定時間上只有一個執行緒可以列印螢幕。 在異常處理程式碼中,位於finally關鍵字的是在終止阻塞(其他執行緒)之前需要執行的程式碼。

```python from threading import * from socket import *

screenLock = Semaphore(value=1) def connScan(tgtHost, tgtPOrt): try: connSkt = socket(AF_INET, SOCK_STREAM) connSkt.connect((tgtHost, tgtPOrt)) connSkt.send('something') results = connSkt.recv(1024) screenLock.acquire() print('[+] {}/tcp open'.format(tgtPOrt)) print('[-] ' + str(results)) except: screenLock.acquire() print('[-] {}/tcp closed'.format(tgtPOrt)) finally: screenLock.release() connSkt.close() ```

把其他所有的函式放入同一個指令碼中,並新增一些引數解析程式碼,就有了最終的埠掃描指令碼。

```python

!/usr/bin/python3

-- coding: utf-8 --

from socket import * from threading import *

screenLock = Semaphore(value=1)

def connScan(tgtHost, tgtPort): try: connSkt = socket(AF_INET, SOCK_STREAM) connSkt.connect((tgtHost, tgtPort)) connSkt.send('some information\r\n') results = connSkt.recv(1024) screenLock.acquire() print('[+] {}/tcp open'.format(tgtPort)) print('[+] ' + str(results)) except: screenLock.acquire() print('[-] {}/tcp closed'.format(tgtPort)) finally: screenLock.release() connSkt.close()

def portScan(tgtHost, tgtPorts): try: tgtIP = gethostbyname(tgtHost) except: print("[-] Cannot resolve '{}': Unknown host".format(tgtHost)) return

try:
    tgtName = gethostbyaddr(tgtIP)
    print("\n[+] Scan Results for: " + tgtName[0])
except:
    print('\n[+] Scan Results for: ' + tgtIP)

setdefaulttimeout(1)
for tgtPort in tgtPorts:
    t = Thread(target=connScan, args=(tgtHost, tgtPort))
    t.start()

def main(): import optparse parser = optparse.OptionParser('usage %prog -H -p ') parser.add_option('-H', dest='tgtHost', type='string', help='specify target host') parser.add_option('-p', dest='tgtPort', type='string', help='specify target port[s] separated by comma')

(options, args) = parser.parse_args()

tgtHost = options.tgtHost
tgtPorts = str(options.tgtPort).split(', ')

if tgtPorts[0] == None or tgtHost == None:
    print(parser.usage)
    exit(0)

portScan(tgtHost, tgtPorts)

if name == 'main': main() ```

使用NMAP埠掃描程式碼

前面的例子是快速編寫能進行TCP連結掃描的一個指令碼。 這可能還不能夠用,因為我們可能還要進行其他型別的掃描,例如,由Nmap工具包提供的ACK、RST、FIN或SYN-ACK掃描等。 實際的工業標準——Namp埠掃描工具包提供了大量功能。 這也引出一個問題,為什麼不直接使用Nmap?這就是Python真正美妙的地方。 雖然Fyodor Vaskovich編寫的Nmap中也能使用C和Lua編寫的指令碼,但是Nmap還能被非常好的很合到Python中。 Nmap可以生成基於XML的輸出。這讓我們能在Python指令碼中使用Nmap的全部功能。在編寫程式碼之前,你必須安裝Python-Nmap。

安裝好Python-Nmap之後,我們就可以將Nmap匯入到現有的指令碼中。 建立一個PortScanner()類物件,這使我們能用這個物件完成掃描操作。 PortScanner類有一個scan()函式,它可將目標和埠的列表作為引數輸入,並對它們進行基本的Nmap掃描。 另外還可以把目標主機的地址/埠放入陣列中備查,並列印出埠狀態。

```python def nmapScan(tgtHost, tgtPort): import nmap nmScan = nmap.PortScanner() nmScan.scan(tgtHost, tgtPort) state = nmScan[tgtHost]['tcp'][int(tgtPort)]['state'] print('[+] ' + tgtHost + ' tcp/' + tgtPort + " " + state)

def main(): import optparse parser = optparse.OptionParser('usage %prog -H -p ') parser.add_option('-H', dest='tgtHost', type='string', help='specify target host') parser.add_option('-p', dest='tgtPort', type='string', help='specify target port[s] separated by comma')

(options, args) = parser.parse_args()

tgtHost = options.tgtHost
tgtPorts = str(options.tgtPort).split(', ')

if tgtPorts[0] == None or tgtHost == None:
    print(parser.usage)
    exit(0)

for tgtPort in tgtPorts:
    nmapScan(tgtHost, tgtPort)

if name == 'main': main() ```

more

  • TCP SYN SCAN —— 也稱為半開放掃描,這種型別的掃描傳送一個SYN包,啟動一個TCP會話,並等待響應的資料包。 如果收到的是一個reset包,表明埠是關閉的,而如果收到的是一個SYN/ACK包,則表示響應的埠是開放的。
  • TCP NULL SCAN —— NULL掃描把TCP頭中的所有標誌位都設為NULL。如果受到的是一個RST包,則表示響應的埠是關閉的。
  • TCP FIN SCAN —— TCP FIN 掃描傳送一個表示拆除一個活動的TCP連線的FIN包,讓對方關閉連線。 如果收到了一個RST包,則表示相應的埠是關閉的。
  • TCP XMAS SCAN —— TCP XMAS掃描傳送PSH、FIN、URG和TCP標誌為被設為1的資料包。 如果受到了一個RST包,則表示響應的埠是關閉的。

相關文章