原书的代码主要考虑的是如何实现功能,在字符编码,socket 阻塞和数据交互,异常处理等方面存在一些问题,造成了程序功能不完善,逻辑出差和退出等情况。

本篇笔记记录用 Python3 实现原书的 netcat, 脚本功能和步骤主要是参照原书的实现思路,会对部分代码的逻辑进行更合理的调整,并学习字符编码,异常处理,调试日志记录等知识点。

功能和实现思路见上一篇笔记。

Python3 代码

#!/usr/bin/env python3
# -*- code: utf-8 -*-import sys
import getopt
import socket
import subprocess
import threading
import logginglogging.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 = 0def 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 backtry:# 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 clientreturn outputdef client_handler(client_socket):"""the thread function of handling the client. :param client_socket: :return: """global uploadglobal executeglobal command# upload fileif len(upload_destination):# read in all of the bytes and write to our destinationfile_buffer = ""# keep reading data until none is availablewhile 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 outtry: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 fileif len(execute):# run the commandoutput = run_command(execute)client_socket.send(output)# now we go into another loop if a command shell was requestedif command:# receive command from client, execute it, and send the result data.try:while True:# show a simple promptclient_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 resultsresponse = run_command(cmd_buffer)# send back the responseclient_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 targetglobal port# if no target is defined we listen on all interfacesif 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 clientclient_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 targetclient.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 backrecv_len = 1response = ""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:breakprint(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 connectionclient.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 listenglobal portglobal executeglobal commandglobal upload_destinationglobal targetif not len(sys.argv[1:]):usage()# read the commandline optionstry: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 = Trueelif o in ("-e", "--execute"):execute = aelif o in ("-c", "--commandshell"):command = Trueelif o in ("-u", "--upload"):upload_destination = aelif o in ("-t", "--target"):target = aelif o in ("-p", "--port"):port = int(a)else:assert False, "Unhandled Option"usage()# are we going to listen or just send data from stdinif 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 offclient_sender(buffer)# we are going to listen and potentially# upload things, execute commands and drop a shell back# depending on our command line options aboveif 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 logginglogging.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')

参考:https://www.cnblogs.com/liujiacai/p/7804848.html

编码问题

实现命令行 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 编码进行转换。

参考:https://blog.csdn.net/chixujohnny/article/details/51782826

所以,在将 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 黑帽子》学习笔记 - Python3 实现 netcat - Day 8相关推荐

  1. python白帽子学习笔记(整合)

    python白帽子学习笔记(整合) 学习笔记目录 python白帽子学习笔记(整合) 前言 一.基础篇 1.正则表达式 2.列表 3.元组带上了枷锁的列表 4.奇葩的内置方法 5.格式化字符 6.序列 ...

  2. scapy python3_【Python3黑帽子学习笔记 on Mac】第四章 Scapy:网络的掌控者

    Scapy:网络掌控者? 哇咔咔,我喜欢!可是我的问题来了,貌似Scapy只支持Python2,那哥哥这Python3咋办呢? [最新更新:目前scapy目前已经支持Python3了,就不用折腾这个了 ...

  3. 《Python 黑帽子》学习笔记 - 命令行选项和参数处理 - Day 4

    在学习书中 netcat 代码的时候,发现其命令行选项和参数的处理存在一些小问题,由于调用 getopt 模块的 getopt() 函数时参数设置不当,会引起代码执行时获取不到参数值或引发异常.该问题 ...

  4. 关于《Python黑帽子:黑客与渗透测试编程之道》的学习笔记

    本篇文章是学习<Python黑帽子:黑客与渗透测试编程之道>的笔记,会持续地将书上的代码自己敲一遍,从而让自己对Python的安全编程有更多的了解,同时希望各位可以给给建议,不足之处太多了 ...

  5. 《Python 黑帽子》学习笔记 - 准备 - Day 1

    信息安全是一个有意思的方向,也是自己的爱好,从零开始,想在工作之余把这个爱好培养为自己的技术能力,而 web 安全相对来说容易入门些,于是选择 web 渗透测试作为学习的起点,并选择同样是容易入门的 ...

  6. 《Python黑帽子》python3代码实现(第二章)

    <Python黑帽子>代码实现 本篇笔记全部用python3实现,本人大一在校生第一次写博客,有错误之处希望大家积极指出. 参考大佬博客: https://www.cnblogs.com/ ...

  7. 《Python黑帽子:黑客与渗透测试编程之道》读书笔记(四):web攻击

    目录 前言 1.urllib2 2.开源web应用安装 3.破解目录和文件位置 4.破解HTML表格认证 结语 前言 <Python黑帽子:黑客与渗透测试编程之道>的读书笔记,会包括书中源 ...

  8. 《Python黑帽子:黑客与渗透测试编程之道》读书笔记(三):scapy——网络的掌控者

    目录 前言 1.窃取email认证 2.ARP缓存投毒 3.PCAP文件处理 结语 前言 <Python黑帽子:黑客与渗透测试编程之道>的读书笔记,会包括书中源码,并自己将其中一些改写成P ...

  9. 《Python黑帽子:黑客与渗透测试编程之道》读书笔记(九):自动化攻击取证

    目录 前言 1.Volatility配置 2.抓取口令的哈希值 3.直接代码注入 4.插入shellcode 结语 前言 <Python黑帽子:黑客与渗透测试编程之道>的读书笔记,会包括书 ...

最新文章

  1. Feedback about (Blockchain OR ML) AND (logistics)
  2. Java-InnerClass内部类
  3. mysql 清空表的两种方法
  4. linux下golang编译环境搭建
  5. chrome ninja 文件_ninja和gn
  6. python连接数据库oracle_python 连接oracle数据库:cx_Oracle
  7. mysql防注入方法_防止SQL注入的六种方法
  8. Android控件 TextView属性大全
  9. SOIC 和 SOP区别
  10. java创新创业比赛项目教程_java毕业设计_springboot框架的大学生创新创业项目管理...
  11. 当C++遇上AUTOSAR编码规范,你的安全我来护航
  12. linux卸载lightdm,Ubuntu安装LightDM
  13. Python课程第六周笔记及作业练习
  14. Android消息机制(Handler机制) - 线程的等待和唤醒
  15. python3.7 安装 scrapy, pip 升级
  16. 【正则表达式验证邮箱】
  17. Hadoop基础-10-YARN
  18. 我投资失败的 7 个项目,都是这样死掉的!|徐小平
  19. 高科技企业:投资创新企业是高科技公司的必选
  20. 英语2017年6月听力

热门文章

  1. MATLAB repmat函数的使用
  2. Asp.Net Core WebApi 身份验证、注册、用户管理
  3. VMware环境部署vFW虚拟防火墙
  4. STM8 fHSI和 fCPU分频
  5. 基于Vision Transformer的图像去雾算法研究与实现(附源码)
  6. MATLAB plot绘图颜色及配色
  7. 数据结构(10)广义表的介绍与代码实现(c语言)
  8. java语言【#106. 求绝对值】(已通过)
  9. DC-DC升压变换器 直流隔离 高压稳压输出 电源模块
  10. Nim游戏 —— 巴什博弈