《Python 黑帽子》學習筆記
原書的程式碼主要考慮的是如何實現功能,在字元編碼,socket 阻塞和資料互動,異常處理等方面存在一些問題,造成了程式功能不完善,邏輯出差和退出等情況。
本篇筆記記錄用 Python3 實現原書的 netcat, 指令碼功能和步驟主要是參照原書的實現思路,會對部分程式碼的邏輯進行更合理的調整,並學習字元編碼,異常處理,除錯日誌記錄等知識點。
功能和實現思路見上一篇筆記。
Python3 程式碼
#!/usr/bin/env python3
# -*- code: utf-8 -*-
import sys
import getopt
import socket
import subprocess
import threading
import logging
logging.basicConfig(level=logging.DEBUG,
format='%(filename)s[line:%(lineno)d] %(levelname)s: %(message)s',
# format='%(asctime)s %(filename)s[line:%(lineno)d] %(levelname)s: %(message)s',
# datefmt='%Y/%m/%d %H:%M:%S',
# filename='myapp.log',
filemode='a')
# define some global variables
listen = False
command = False
upload = False
execute = ""
target = ""
upload_destination = ""
port = 0
def run_command(command):
"""
execute the shell command, or file received from client.
:param command:
:return: output: shell command result.
"""
# trim the newline.(delete the characters of the string end.)
command = command.rstrip()
# run the command and get the output back
try:
# run command with arguments and return its output.
output = subprocess.check_output(command, stderr=subprocess.STDOUT, shell=True)
logging.debug(output)
except Exception as e:
logging.error(e)
output = b"Failed to execute command.\r\n"
# send the output back to the client
return output
def client_handler(client_socket):
"""
the thread function of handling the client.
:param client_socket:
:return:
"""
global upload
global execute
global command
# upload file
if len(upload_destination):
# read in all of the bytes and write to our destination
file_buffer = ""
# keep reading data until none is available
while True:
data = client_socket.recv(1024)
file_buffer += data.decode("utf-8")
logging.debug(data)
# "#EOF#" tell the server, file is end.
if "#EOF#" in file_buffer:
file_buffer = file_buffer[:-6]
break
# for interaciton, like heart packet.
client_socket.send(b"#")
# now we take these bytes and try to write them out
try:
with open(upload_destination, "wb") as fw:
fw.write(file_buffer.encode("utf-8"))
client_socket.send(b"save file successed.\n")
except Exception as err:
logging.error(err)
client_socket.send(b"save file failed.\n")
finally:
client_socket.close()
# execute the given file
if len(execute):
# run the command
output = run_command(execute)
client_socket.send(output)
# now we go into another loop if a command shell was requested
if command:
# receive command from client, execute it, and send the result data.
try:
while True:
# show a simple prompt
client_socket.send(b"<BHP:#>")
# now we receive until we see a linefeed (enter key)
cmd_buffer = ""
while "\n" not in cmd_buffer:
try:
cmd_buffer += client_socket.recv(1024).decode("utf-8")
except Exception as err:
logging.error(err)
client_socket.close()
break
# we have a valid command so execute it and send back the results
response = run_command(cmd_buffer)
# send back the response
client_socket.send(response)
except Exception as err:
logging.error(err)
client_socket.close()
def server_loop():
"""
the server listen. create a thread to handle client's connection.
:return:
"""
global target
global port
# if no target is defined we listen on all interfaces
if not len(target):
target = "0.0.0.0"
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.bind((target, port))
logging.info("Listen %s:%d" % (target, port))
server.listen(5)
while True:
client_socket, addr = server.accept()
# spin off a thread to handle our new client
client_thread = threading.Thread(target=client_handler, args=(client_socket,))
client_thread.start()
def client_sender(buffer):
"""
the client send datas to the server, and receive datas from server.
:param buffer: datas from the stdin
:return:
"""
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
try:
# conncet to target
client.connect((target, port))
logging.debug('server is %s:%d' % (target, port))
# if we detect input from stdin then send the datas.
# if not we are going to wait for the user to input.
if len(buffer):
# send the datas with utf-8 endecode.
client.send(buffer.encode("utf-8"))
while True:
# now wait for datas back
recv_len = 1
response = ""
while recv_len:
data = client.recv(4096)
logging.debug("receive datas : %s" % data)
try:
response += data.decode("utf-8")
except Exception as e:
logging.error(e)
response += data.decode("gbk")
if recv_len < 4096:
break
print(response + " ")
# wait for more input
# Python2 is raw_input(), and Python3 is input()
buffer = input("")
buffer += "\n"
client.send(buffer.encode("utf-8"))
# logging.info("send datas: %s" % buffer)
except Exception as e:
logging.error(e)
finally:
# teardown the connection
client.close()
def usage():
"""
print the info of help
:return:
"""
print("Usage: netcat.py -t target_host -p port")
print("\t-l --listen - listen on [host]:[port] for incoming connections")
print("\t-e --execute=file_to_run - execute the given file upon receiving a connection")
print("\t-c --command - initialize a command shell")
print("\t-u --upload=destination - upon receiving connection upload a file and write to [destination]")
print("Examples: ")
print("\tnetcat.py -t 192.168.1.3 -p 5555 -l -c")
print("\tnetcat.py -t 192.168.1.3 -p 5555 -l -u=c:\\target.exe")
print("\tnetcat.py -t 192.168.1.3 -p 5555 -l -e=\"cat /etc/passwd\"")
print("\techo 'ABCDEFGHI' | ./netcat.py.py -t192.168.1.7 -p80")
sys.exit(0)
def main():
"""
parse shell option and parameters, and set the vars.
call listen function or connect function.
:return:
"""
global listen
global port
global execute
global command
global upload_destination
global target
if not len(sys.argv[1:]):
usage()
# read the commandline options
try:
opts, args = getopt.getopt(sys.argv[1:], "hle:t:p:cu:",
["help", "listen", "execute=", "target=", "port=", "command", "upload="])
except getopt.GetoptError as err:
logging.error("%s", err)
usage()
for o, a in opts:
if o in ("-h", "--help"):
usage()
elif o in ("-l", "--listen"):
listen = True
elif o in ("-e", "--execute"):
execute = a
elif o in ("-c", "--commandshell"):
command = True
elif o in ("-u", "--upload"):
upload_destination = a
elif o in ("-t", "--target"):
target = a
elif o in ("-p", "--port"):
port = int(a)
else:
assert False, "Unhandled Option"
usage()
# are we going to listen or just send data from stdin
if not listen and len(target) and port > 0:
# read in the buffer from the commandline
# this will block, so send CTRL-D if not sending input to stdin
# Windows is Ctrl-Z
# buffer = sys.stdin.read()
buffer = input() + '\n'
# send data off
client_sender(buffer)
# we are going to listen and potentially
# upload things, execute commands and drop a shell back
# depending on our command line options above
if listen:
server_loop()
main()
測試
命令列 shell 功能:
帶 debug 的很亂。
logging 的 level 設定為 ERROR,不會輸出 debug 資訊。
upload 功能
我修改為當客戶端傳送 #EOF#
後,作為檔案傳輸的結束,並在服務端傳送一個反饋資料 #
, 以保障雙方能資料互動,不然 socket.recv() 將一直阻塞,也可以考慮修改 socket 的超時設定。
命令執行功能
異常和除錯
在編寫程式碼的時候,用 logging 除錯資料通訊和異常錯誤,幫我解決了很多問題。簡單記錄下,更詳細的知識使用到時再去查閱。
異常處理程式碼結構如下,把可能會引發異常的程式碼放在 try 後執行,引發異常會執行 except 裡的程式碼,最後會執行 finall 裡的程式碼,可以把關閉套接字,退出程式等善後的程式碼放在這裡。
try:
...
except Exception as e:
logging.error(e)
finally:
...
用 Python 自帶的 logging 模組,可以直觀的在終端看到除錯資訊,或把除錯資訊存到檔案裡,比 print() 函式要方便很多,能夠顯示出除錯資訊出自程式的哪一行程式碼,可以通過設定不同的日誌等級(level)來輸出不同日誌資訊,設定高等級的日誌等級後,低等級的日誌資訊不會輸出。
level 值的說明:
- FATAL 致命錯誤
- CRITICAL 特別糟糕的事情,如記憶體耗盡、磁碟空間為空,一般很少使用
- ERROR 發生錯誤時,如 IO 操作失敗或者連線問題
- WARNING 發生很重要的事件,但是並不是錯誤時,如使用者登入密碼錯誤
- INFO 處理請求或者狀態變化等日常事務
- DEBUG 除錯過程中使用 DEBUG 等級,如演算法中每個迴圈的中間狀態
format 說明:
- %(levelno)s:列印日誌級別的數值
- %(levelname)s:列印日誌級別的名稱
- %(pathname)s:列印當前執行程式的路徑,其實就是sys.argv[0]
- %(filename)s:列印當前執行程式名
- %(funcName)s:列印日誌的當前函式
- %(lineno)d:列印日誌的當前行號
- %(asctime)s:列印日誌的時間
- %(thread)d:列印執行緒ID
- %(threadName)s:列印執行緒名稱
- %(process)d:列印程序ID
- %(message)s:列印日誌資訊
import logging
logging.basicConfig(level=logging.DEBUG,
format='%(asctime)s %(filename)s[line:%(lineno)d] %(levelname)s: %(message)s',
datefmt='%Y/%m/%d %H:%M:%S',
# filename='myapp.log',
filemode='a')
編碼問題
實現命令列 shell 功能時,在 Win7 中文系統上測試,需要傳輸中文字元,出現 UnicodeDecodeError 錯誤。即:
netcat-p3.py[line:168] DEBUG: receive datas : b'\r\nMicrosoft Windows [\xb0\xe6\xb1\xbe 6.1.7601]\r\n<BHP:#>'
netcat-p3.py[line:173] ERROR: 'utf-8' codec can't decode byte 0xb0 in position 21: invalid start byte
對應程式碼是:
try:
response += data.decode("utf-8") # 異常的地方
except Exception as e:
logging.error(e)
response += data.decode("gbk")
來分析下這個異常的原因。異常出在要把
b'\r\nMicrosoft Windows [\xb0\xe6\xb1\xbe 6.1.7601]\r\n<BHP:#>'
這段資料進行 decode(“utf-8”) 解碼。這段資料的來源是建立 shell 子程序後,執行 ver 命令後的結果,即中文的
Microsoft Windows [版本 6.1.7601]
shell 子程序輸出結果資料的編碼是跟隨執行 shell 的系統的,或者說是跟隨當前啟動 shell 的終端的資料編碼。而當前終端資料的編碼是 cp936, 近似於 gbk 編碼。實際上中文 Win7 系統內部都是 cp936.
>>> import locale
>>> locale.getdefaultlocale() # 獲取系統當前的編碼
('zh_CN', 'cp936')
可以理解為,這段資料的編碼是 gbk 編碼,而 utf-8 和 gbk 編碼之間是不能直接轉換的,所有的 utf-8 和 gbk 編碼都得通過 unicode 編碼進行轉換。
所以,在將 shell 子程序的結果資料,直接進行 decode(“utf-8”) 解碼,會引發 UnicodeDecodeError 異常。我在修改程式碼時,添加了一個異常處理,如果 utf-8 解碼失敗,會修改為 gbk 解碼。這樣能保證程式不會因為異常而退出。
再說明下,為什麼要先進行 utf-8 解碼?因為要保證 socket 通訊使用 byte 流傳輸,我對大多數要通訊的資料(基本都是 str)用 utf-8 進行了編碼,編碼後即為 byte 流,傳送前 encode, 接收後 decode. 由於建立 shell 子程序後,其輸出結果直接就是 byte 流,所以沒對其進行編碼轉換,直接通過 socket.send() 傳送。
執行 shell 的程式碼如下,用 logging.debug(output), 可以看到輸出資料為 byte 流。
# run the command and get the output back
try:
# run command with arguments and return its output.
output = subprocess.check_output(command, stderr=subprocess.STDOUT, shell=True)
logging.debug(output)
except Exception as e:
logging.error(e)
output = b"Failed to execute command.\r\n"
netcat-p3.py[line:40] DEBUG: b'\r\nMicrosoft Windows [\xb0\xe6\xb1\xbe 6.1.7601]\r\n'
檢視系統編碼
用 Python 自帶的 locale 模組可以檢測命令列的預設編碼(也是系統的編碼),和設定命令列編碼。
我的 Kali 英文系統,編碼為 utf-8.
>>> import locale
>>> locale.getdefaultlocale()
('en_US', 'UTF-8')
我的 Win7 中文系統,編碼為 cp936.
>>> import locale
>>> locale.getdefaultlocale()
('zh_CN', 'cp936')
總結
關於 netcat 的實現,主要是解決了一些異常和邏輯的問題,還可以有很多完善的地方,考慮加快下學習進度,下步的筆記將主要記錄程式碼的實現。
要想把一個知識點用文字清楚地表達出來,哪怕是簡單的知識點,也要花費很多精力。
相關推薦
Python黑帽子學習筆記-----第三章
#-*- coding:utf8 -*- import socket import os import struct import threading import time import sys from netaddr import IPNetwork,IPAddress
python黑帽子學習筆記(二)——反向ssh
1.反向ssh。 通常情況下,在使用SSH的時候,你可以使用SSH客戶端連線SSH伺服器,但是由於Windows本身不一定裝有SSH服務端,所以我們需要反向將命令從SSH服務端傳送給SSH客戶端。 2.程式碼。 #!/usr/bin/python # -*- c
《Python 黑帽子》學習筆記
原書的程式碼主要考慮的是如何實現功能,在字元編碼,socket 阻塞和資料互動,異常處理等方面存在一些問題,造成了程式功能不完善,邏輯出差和退出等情況。 本篇筆記記錄用 Python3 實現原書的 netcat, 指令碼功能和步驟主要是參照原書的實現思路,會對
《Python黑帽子》學習筆記
作業系統:kail32位 第二章:網路程式設計 tcp(傳輸控制協議):面向連線,通訊時創造一條連線,提供順序的可靠地,不重複的資料傳輸,不會被加上資料邊界,每個傳送的訊息,可能會拆成很多份,每一份會不多不少的到達目的地,然後按照一定的順序拼接起來,傳給正在等待的應
讀書筆記 ~ Python黑帽子 黑客與滲透測試編程之道
alt nbsp too 管理 return cps 工具 transfer args Python黑帽子 黑客與滲透測試編程之道 <<< 持續更新中>>> 第一章: 設置python 環境 1、python軟件包管理工具安裝
Python 3.6學習筆記(一)
示例 ror 功能 put -m 但是 對象 初始化 absolut 開始之前 基礎示例 Python語法基礎,python語法比較簡單,采用縮緊方式。 # print absolute value of a integer a = 100 if a >= 0:
流暢的python和cookbook學習筆記(一)
構造函數 推導 笛卡爾 expr 列表推導 叠代 建立 笛卡兒 imp 1.數據結構 1.1 內置序列類型 四種序列類型: 1.容器序列:list、tuple和collections.deque 2.扁平序列:str、bytes、bytearray、memory
流暢的python和cookbook學習筆記(五)
pytho col () 學習 util 學習筆記 取出 minute python 1.隨機選擇 python中生成隨機數使用random模塊。 1.從序列中隨機挑選元素,使用random.choice() >>> import random
流暢的python和cookbook學習筆記(八)
不可變 pri 列表 改變 如果 book 影響 color print 1.函數的默認參數必須不可變 如果函數的默認參數為可變的對象,那麽默認參數在函數外被修改也會影響到函數本身的。 >>> def spam(a, b=None): # b要為不
Python 進階學習筆記
def 進階學習 學習 blog 私有屬性 屬性和方法 .get line person 把函數作為參數 import math def add(x, y, f): return f(x) + f(y) print add(9, 16, math.sqr
Python(Head First)學習筆記:四
raise b- before hat contents -- supported between data loss 4 持久存儲:文件存儲、讀寫 數據保存到文件:在學習的過程中出現了一個問題,老是報一個錯:SyntaxError: invalid syntax;
python requests庫學習筆記(下)
mail 接收 緩存 nbsp 0.10 基本 eat agen 維基百科 1.請求異常處理 請求異常類型: 請求超時處理(timeout): 實現代碼: import requestsfrom requests import exceptions #引
python入門教程學習筆記#2
tab 下載 body 中文 穩定 出現 包含 圖1 ret 2.1 python3.6 工具使用 運行python 自帶的idle後,輸入python命令,如print(‘hello world‘),回車後輸出 hello world 其中mac系統會出現一段warn
python入門教程學習筆記#1
ext game 2.7 功能 sublime wxpython 程序 免費 圖形界面 下載地址:https://www.python.org/,版本可選擇3.6或2.7 1.2 編譯環境pycharm 下載地址:https://www.jetbrains.com/p
python自動化測試學習筆記-2-字典、元組、字符串方法
ima weight ict 常用 分享圖片 def 刪除列 設置 統計 一、字典 Python字典是另一種可變容器模型,且可存儲任意類型對象,如字符串、數字、元組等其他容器模型。 字典的每個鍵值(key=>value)對用冒號(:)分割,每個對之間用逗號(,)分割,
python 3.x 學習筆記9 (面向對象)
表現 技術發展 計算 多種實現 類方法 run spa col 對數 1.面向對象 面向對象是一種對現實世界理解和抽象的方法,是計算機編程技術發展到一定階段後的產物。 2.類(class): 一個類即是對一類擁有相同屬性的對象的抽象、藍圖、原型。在類中定義了這些
python 3.x 學習筆記13 (socket_ssh and socket_文件傳輸)
粘包問題 問題 取出 nec imp 傳輸文件 ket color md5 ssh服務端 import socket,os server = socket.socket() server.bind((‘localhost‘,6666)) server.listen()
python 3.x 學習筆記18 (mysql 未完 )
offset targe name屬性 表結構 不用 創建用戶 ant 書籍 主鍵 1.數據庫(Database)是按照數據結構來組織、存儲和管理數據的倉庫 2.RDBMS即關系數據庫管理系統(Relational Database Management System)的特
python+selenium個人學習筆記10-調用JavaScript和截圖
end post style keys driver quit fin send IT 調用JavaScript和截圖 一、調用JavaScript 1、調整瀏覽器滾動條位置 window.scrollTo(0,500); #左邊距,上邊距 2、用ex
Python第一周 學習筆記(2)
學習筆記習題解析 0.打印10以內偶數:位運算 for i in range(10): if not i & 0x01: print(i) 1.給定一個不超過5位的正整數,判斷其有幾位(使用input函數) 方法一:正常邏輯處理 a = int(input("Please e