Python黑帽子-实现netcat基本功能(改进版)

前言

一个好的渗透测试人员,应该拥有强大的编程能力,而python就是一个很好的工具,我最近也再研究如何用python开发属于自己的小工具,《python黑帽子》是一本很不错的书籍。本系列博文是我在学习过程中,做的一些总结与拓展。
####前置知识
netcat我就不过多介绍了,一句网络中的瑞士军刀已经说明一切了,这里也有一篇关于netcat基本应用的·博文:
https://blog.csdn.net/chengtong222/article/details/64131358
我们还需要用到一些python中的库:
socket、sys、getopt
接下来,我来初略的介绍这几个包:

socket

看到这个包的名字,我想大家应该都知道它是用来干什么大的了,socket我们通常称为套接字,是用来建立网络连接的利器。我们可以使用它很方便地创建一个tcp或者udp连接,下面是一个tcp客户端的实例:

#! /usr/bin/env python
#-*- coding:utf-8 -*-

import socket

def client():
    HOST = '127.0.0.1' #远程主机ip
    PORT = 9998 #远程主机端口
    #创建一个tcp套接字
    s = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
    #连接远程主机
    s.connect((HOST,PORT))
    #向远程主机发送数据
    s.send('Hello, server')
    #接受来自远程主机的数据,数据大小为1024字节
    print s.recv(1024)
    for message in range(10):
        s.send(str(message)+'meesage have been send!')
        print s.recv(1024)
    s.close()#关闭该连接

if __name__ == '__main__':
    client()

我觉的就是创建套接字的时候,里面的两个参数有必要说明一下socket.AF_INET表示协议族为ipv4,socket.SOCK_STREAM表示创建的是一个tcp连接

官方文档:
socket.socket([family[, type[, proto]]])
Create a new socket using the given address family, socket type and protocol number. The address family should be AF_INET (the default), AF_INET6 or AF_UNIX. The socket type should be SOCK_STREAM (the default), SOCK_DGRAM or perhaps one of the other SOCK_ constants. The protocol number is usually zero and may be omitted in that case.
译:
socket.socket([协议族[,连接类型[,协议号]]])
使用给定的地址簇,socket类型和协议号创建一个新的socket。地址簇应该是以下之一:AF_INET(默认),AF_INET6或者AF_UNIX。连接类型应该是SOCK_STREAM(默认),SOCK_DGRAM或者其他的SOCK_constants。协议号通常为0(通常省略不写)。

我们可以从上述实例中总结一下创建tcp客户端的套路:
**1.**创建套接字:client_sk = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
**2.**连接远程主机:client_sk.connect((ip,port))注意这里传入的是一个元组
**3.**接受数据或者发送数据
**4.**关闭连接
我们再来创建一个tcp服务端,tcp服务端比起客户端稍微复杂一点:

#! /usr/bin/env python
#-*- coding:utf-8 -*-

import time
import socket


def server():
    HOST = '127.0.0.1'#监听的ip地址
    PORT = 9998#监听的端口号
    #创建一个套接字
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    #绑定端口号与ip
    s.bind((HOST,PORT))
    #设置最大连接数为5
    s.listen(5)
    while True:
		#接受连接
        cs,addr = s.accept()
        #不停的接受数据
        while True:
            message = cs.recv(1024)
            print message
            if not message:
                break
            message = time.ctime()+message
            cs.send(message)
        cs.close()#关闭套接字

if __name__ == '__main__':
    server()

同样的我们来总结一下,创建tcp服务端的“套路”:
**1.**创建套接字
**2.**绑定监听端口、ip
**3.**设置最大连接数量
**4.**等待连接
**5.**接收数据或者发送数据
**6.**关闭连接

sys

官方文档在此:
https://docs.python.org/2/library/sys.html?highlight=sys#module-sys
这个模块总常用的可能就是:
sys.exit()
sys.path
sys.stdin
sys.stdout
sys.stderr
sys.argv

getopt

官方手册:
https://docs.python.org/2/library/getopt.html?highlight=getopt#module-getopt
getopt是专门用来处理命令行参数的,还是挺方便。我这里就简单的介绍一下它的用法:

getopt.getopt(args, options[, long_options])
Parses command line options and parameter list. args is the argument list to be parsed, without the leading reference to the running program. Typically, this means sys.argv[1:]. options is the string of option letters that the script wants to recognize, with options that require an argument followed by a colon (’:’; i.e., the same format that Unix getopt() uses).

Note Unlike GNU getopt(), after a non-option argument, all further arguments are considered also non-options. This is similar to the way non-GNU Unix systems work.
long_options, if specified, must be a list of strings with the names of the long options which should be supported. The leading ‘–’ characters should not be included in the option name. Long options which require an argument should be followed by an equal sign (’=’). Optional arguments are not supported. To accept only long options, options should be an empty string. Long options on the command line can be recognized so long as they provide a prefix of the option name that matches exactly one of the accepted options. For example, if long_options is [‘foo’, ‘frob’], the option --fo will match as --foo, but --f will not match uniquely, so GetoptError will be raised.

The return value consists of two elements: the first is a list of (option, value) pairs; the second is the list of program arguments left after the option list was stripped (this is a trailing slice of args). Each option-and-value pair returned has the option as its first element, prefixed with a hyphen for short options (e.g., ‘-x’) or two hyphens for long options (e.g., ‘–long-option’), and the option argument as its second element, or an empty string if the option has no argument. The options occur in the list in the same order in which they were found, thus allowing multiple occurrences. Long and short options may be mixed.

getopt.getopt(参数列表,短格式参数字符串,长格式参数序列)

看一个例子:

import getopt
>>> args = '-a -b -cfoo -d bar a1 a2'.split()
>>> args
['-a', '-b', '-cfoo', '-d', 'bar', 'a1', 'a2']
>>> optlist, args = getopt.getopt(args, 'abc:d:')
>>> optlist
[('-a', ''), ('-b', ''), ('-c', 'foo'), ('-d', 'bar')]
>>> args
['a1', 'a2']

如果某一个选项后面有参数,那么它的后面就会带一个冒号。
再看一个长格式参数的例子:

s = '--condition=foo --testing --output-file abc.def -x a1 a2'

>>> args = s.split()
>>> args
['--condition=foo', '--testing', '--output-file', 'abc.def', '-x', 'a1', 'a2']
>>> optlist, args = getopt.getopt(args, 'x', [
...     'condition=', 'output-file=', 'testing'])
>>> optlist
[('--condition', 'foo'), ('--testing', ''), ('--output-file', 'abc.def'), ('-x', '')]
>>> args
['a1', 'a2']

可以看到长格式的如果某一个选项后面带了参数的话,那么它的后面会带一个等号
#####threading
官方文档:
https://docs.python.org/2/library/threading.html?highlight=threading#module-threading
这个模块主要是用于创建多线程的,还是通过官方文档来看看基本用法。

class threading.Thread(group=None, target=None, name=None, args=(), kwargs={})
This constructor should always be called with keyword arguments. Arguments are:

group should be None; reserved for future extension when a ThreadGroup
class is implemented.

target is the callable object to be invoked by the run() method.
Defaults to None, meaning nothing is called.

name is the thread name. By default, a unique name is constructed of
the form “Thread-N” where N is a small decimal number.

args is the argument tuple for the target invocation. Defaults to ().

kwargs is a dictionary of keyword arguments for the target invocation.
Defaults to {}.

If the subclass overrides the constructor, it must make sure to invoke
the base class constructor (Thread.init()) before doing anything
else to the thread.
我们可以调用这个函数来创建一个线程threading.Thread(group=None, target=None, name=None, args=(), kwargs={})
虽然这个函数给了这么多的参数,但是其实我们一般只用的到三个吧,最多四个,target是我们创建一个线程必须要用到的关键字参数,它是我们这个线程需要执行的函数,也就是线程需要做的事,而args参数则是target函数需要的普通参数,而kwargs则是target需要的关键字参数。

我们通过一个实例来看一下吧:

import threading
import time
 
def sayhi(num): #定义每个线程要运行的函数
 
    print("running on number:%s" %num)
 
    time.sleep(3)
 
if __name__ == '__main__':
 
    t1 = threading.Thread(target=sayhi,args=(1,)) #生成一个线程实例
    t2 = threading.Thread(target=sayhi,args=(2,)) #生成另一个线程实例
 
    t1.start() #启动线程
    t2.start() #启动另一个线程
 
    print(t1.getName()) #获取线程名
    print(t2.getName())

很简单吧,创建一个线程,启动它,这样就完成了一个线程的创建。
#####subprocess
subprocess也是系统编程的一部分,它是创建进程的,并用来取代一些旧的方法的。这里我就不细讲了,推荐一篇博文吧:
https://www.cnblogs.com/breezey/p/6673901.html
这里再推荐一个包:multiporcessing,和多线程多进程有关,挺强大的
####开始写代码
netcat的主要功能其实就是将网络上的数据显示到屏幕上,这也是它名字的来源,“net”“cat”,所以我们需要做的就是创建一个tcp客户端和服务端,然后他们之间发送数据,并把这些发送的数据根据我们的需要显示在屏幕上,我们自己写的这个小型netcat只是实现了:文件上传、文件下载、命令执行、获取shell的功能。《python黑帽子》书上的代码,我觉得其实并不算实现了文件上传与下载的功能,而且使用起来感觉很不方便,它需要执某种功能时,必须通过调整服务端的参数才行,我觉得这样有点不方便,于是我改进了一下,只需要客户端指定参数,服务端的任务只是监听连接,然后执行命令,一旦运行起来了就不再需要手动调整,我觉得这样更加人性话,虽然代码可能有点冗余。

我还是先贴上书里的代码吧,需要的请自取:

#! /usr/bin/env python
# -*- coding:utf-8 -*-

import sys
import os
import socket
import getopt
import subprocess
import threading

listen = False
command = False
upload = False
execute = ""
target = ""
upload_destination = ""
port = 0


#工具使用方法概述
def useage():
	print "BHP Net Tool\n"
	print "useage: netcat.py -t target_host -p port"
	print "-l  --listen"
	print "-e --execute=file_to_run"
	print "-c --command"
	print "-u --upload=destination"
	sys.exit(0)


def main():
	global listen
	global port 
	global execute
	global command
	global upload_destination
	global target

	if not len(sys.argv[1:]):
		useage()

	try:
		opts,args = getopt.getopt(sys.argv[1:],"hle:t:p:cu:",
			["help","listen","execute","target","port","commandshell","upload"])
	except getopt.GetoptError as err:
		print str(err)
		useage()
	print opts
	for opt,value in opts:
			if opt in ("-h","--help"):
				useage()
			elif opt in ("-l","--listen"):
				listen = True
			elif opt in ("-e","--execute"):
				execute = value
			elif opt in("-c","--commandshell"): 
				command = True
			elif opt in ("-u","--upload"):
				upload_destination = value
			elif opt in ("-t","--target"):
				target = value
			elif opt in ("-p","--port"):
				port = int(value)
			else:
				assert False,"Unhandled Option"

	if not listen and len(target) and port > 0:
		buffer = sys.stdin.read()
		client_sender(buffer)

	if listen:
		server_loop()

def client_sender(buffer):
	cs = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
	try:
		cs.connect((target,port))

		if len(buffer):
			cs.send(buffer)

		while True:
			recv_len = 1
			response = ""
			while recv_len:
				res = cs.recv(4096)
				recv_len = len(res)
				response += res
				if recv_len < 4096:
					break

			print response

			buffer = raw_input("")
			buffer += '\n'

			cs.send(buffer)
	except:
		print "[*] Exception! Exiting."
	cs.close()



def server_loop():
	global target

	if not len(target):
		target = "0.0.0.0"

	server = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
	server.bind((target,port))
	server.listen(5)

	while True:
		client_socket,addr = server.accept()
		client_thread = threading.Thread(target = client_handler,args=(client_socket,))
		client_thread.start()

def run_command(command):
	command = command.rstrip()
	try:
		output = subprocess.check_output(command,stderr=subprocess.STDOUT,shell = True)
	except:
		output = "Failed to execute command.\r\n"

	return output

def client_handler(client_socket):
	global upload
	global execute
	global command

	if len(upload_destination):
		file_buffer = ""
		while True:
			data = client_socket.recv(1024)

			if not data:
				break
			else:
				file_buffer += data

			try:
				file_descriptor = open(upload_destination,"wb")
				file_descriptor.write(file_buffer)
				file_descriptor.close()

				client_socket.send("Success saved file to %s \r\n"%upload_destination)
			except:
				client_socket.send("Failed to save file to %s\r\n"%upload_destination)

	if len(execute):
		output = run_command(execute)
		client_socket.send(output)


	if command:
		while True:
			client_socket.send("")

			cmd_buffer = ""
			while "\n" not in cmd_buffer:
				cmd_buffer += client_socket.recv(1024)
			response = run_command(cmd_buffer)
			client_socket.send(response)

if __name__ == '__main__':
	main()

下面是我改进过后的代码:

#! /usr/bin/env python
# -*- coding:utf-8 -*-

'''
You can use this tool to do somethind interesing!!!
'''

import socket
import getopt
import threading
import subprocess
import sys
import time

listen = False
shell = False
upload_des = ""
upload_src = ""
execute = ""
target = ""
port = 0

def help_message():
	print 
	print "You can connect a host like this:mytool.py -t target_ip -p port"
	print "[*]example:mytool.py -t 127.0.0.1 -p 7777"
	print 
	print "-t    specify the ip you wanna connect or listen"
	print
	print "-p    specify the port you wanna connect or listen"
	print
	print "-l    start to listen the connection"
	print
	print "-c    get a shell"
	print 
	print "-e 	 execute command from user"
	print "[*]example: mytool.py -t 127.0.0.1 -p 7777 -e ipconfig"
	print
	print "-u    upload files"
	print "[*]example:mytool.py -t 127.0.0.1 -p 7777 -u c:/test.txt->d:/test.txt"


def main():
	global listen
	global shell
	global port
	global execute
	global target
	global upload_src
	global upload_des
	#解析参数
	try:
		opts,args = getopt.getopt(sys.argv[1:],"t:p:lce:u:h",
			["target=","port=","listen","commandshell","execute","upload","help"])
	except Exception as e:
		print str(e)
		help_message()
		sys.exit(0)

	for opt,value in opts:
		if opt in ["-h","--help"]:
			help_message()
		elif opt in ["-t","--target"]:
			target = value
		elif opt in ["-p","--port"]:
			port = int(value)
		elif opt in ["-c","--commandshell"]:
			shell = True
		elif opt in ["-e","--execute"]:
			execute = value
		elif opt in ["-u","--upload"]:
			upload_src = value.split(">")[0]
			print upload_src
			upload_des = value.split(">")[1]
		elif opt in ["-l","--listen"]:
			listen = True
	#判断以服务端运行还是以客户端运行
	if listen:
		server()
	elif not listen and len(target) and port > 0:
		client()
#客户端逻辑
def client():
	global shell
	global port
	global execute
	global upload_src
	global target
	data = ""
	client_sk = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
	try:
		client_sk.connect((target,port))
	except:
		print "[*]Connecting error!"
	#将客户端的参数发往服务端
	params = " ".join(sys.argv[1:])
	client_sk.send(params)
	#print params
	#是否上传文件
	if upload_src:
		time.sleep(5)
		data = file_read(upload_src)
		client_sk.send(data)
		data = ""
		while True:
			data_tmp = client_sk.recv(1024)
			data += data_tmp
			if len(data_tmp) < 1024:
				break
		print data
	#是否直接执行命令
	if execute:
		while True:
			data_tmp = client_sk.recv(1024)
			data += data_tmp
			if len(data_tmp) < 1024:
				break
		print data
	#是否获得一个shell
	if shell:
		print client_sk.recv(1024)
		while True:
			data = ""
			command = raw_input()
			command += '\n'
			client_sk.send(command)
			while True:
				data_tmp = client_sk.recv(1024)
				data += data_tmp
				if len(data_tmp) < 1024:
					print data
					break
			print client_sk.recv(1024)
	client_sk.close()

#服务端逻辑
def server():
	global target
	global port
	#如果未指定监听,则监听所有接口
	if not len(target):
		target = "0.0.0.0"

	s_socket = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
	s_socket.bind((target,port))
	s_socket.listen(5)

	while True:
		client_sk, addr =	s_socket.accept()
		client_thread = threading.Thread(target=client_handler,args = (client_sk,addr))
		client_thread.start()
#处理客户端连接
def client_handler(client_sk,addr):
	global listen
	global shell
	global port
	global execute
	global upload_src
	global listen
	global upload_des

	print "Connected by"+str(addr)
	data = ""

	#接受来自客户端的运行参数
	while True:
		data_tmp = client_sk.recv(4096)
		print data_tmp
		data += data_tmp
		if len(data_tmp) < 4096:
			break
	print data
	data_list = data.split()
	#解析来自客户端的参数
	try:
		opts,args = getopt.getopt(data_list,"t:p:lce:u:h",
			["target=","port=","listen","commandshell","execute=","upload","help"])
		print opts
	except Exception as e:
		print str(e)
		help_message()
		sys.exit(0)

	for opt,value in opts:
		if opt in ["-c","--commandshell"]:
			shell = True
		elif opt in ["-e","--execute"]:
			execute = value
			print execute
		elif opt in ["-u","--upload"]:
			upload_des = value.split(">")[1]
			print upload_des

	if upload_des:
		data = ""
		time.sleep(5)
		while True:
			data_tmp = client_sk.recv(4096)
			data += data_tmp
			if len(data_tmp) < 4096:
				break
		if file_write(upload_des,data):
			client_sk.send("Successfuly upload file to {0}\r\n".format(upload_des))
		else:
			client_sk.send("Failed to upload file to {0}\r\n".format(upload_des))

	if execute:
		output = run_command(execute)
		client_sk.send(output)
				
	if shell:
		symbol = ""
		client_sk.send(symbol)
		while True:
			data = ""
			while "\n" not in data:
				data_tmp = client_sk.recv(1024)
				data += data_tmp
			print data
			output = run_command(data)
			#print output
			client_sk.send(output)#将运行结果回显
			#time.sleep(2)
			client_sk.send(symbol)
	

	


def file_write(path,data):
	with open(path,"wb") as file:
		file.write(data)
	return True

def file_read(path):
	with open(path,"rb") as file:
		data = file.read()
	return data

def run_command(command):
	try:
		#执行命令
		output = subprocess.check_output(command,stderr=subprocess.STDOUT,shell = True)
	except:
		output = "Failed to execute command.\r\n"

	return output





if __name__ == "__main__":
	main()

我觉得main函数没什么好说的
我们先来看一下client函数:

def client():
	global shell
	global port
	global execute
	global upload_src
	global target
	data = ""
	client_sk = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
	try:
		client_sk.connect((target,port))
	except:
		print "[*]Connecting error!"
	#将客户端的参数发往服务端
	params = " ".join(sys.argv[1:])
	client_sk.send(params)
	#print params
	#是否上传文件
	if upload_src:
		time.sleep(5)
		data = file_read(upload_src)
		client_sk.send(data)
		data = ""
		while True:
			data_tmp = client_sk.recv(1024)
			data += data_tmp
			if len(data_tmp) < 1024:
				break
		print data
	#是否直接执行命令
	if execute:
		while True:
			data_tmp = client_sk.recv(1024)
			data += data_tmp
			if len(data_tmp) < 1024:
				break
		print data
	#是否获得一个shell
	if shell:
		print client_sk.recv(1024)
		while True:
			data = ""
			command = raw_input()
			command += '\n'
			client_sk.send(command)
			while True:
				data_tmp = client_sk.recv(1024)
				data += data_tmp
				if len(data_tmp) < 1024:
					print data
					break
			print client_sk.recv(1024)
	client_sk.close()

这里是两个改进的点,由于最开始上传文件的功能实现的很扯淡,居然是自己输入然后上传,这有点名不副实,所以我改动了一下,可以选择本地文件然后上传,具体操作也很简单就是以“rb”打开指定文件,然后将数据发往远端,还有就是我将客户端的参数都传到了服务端,这样服务端就知道客户端想要做什么事了。其它地方改动都不是很大,我也就不一一介绍了,欢迎随时与我交流。

效果展示

Python黑帽子-实现netcat基本功能(改进版)_第1张图片

每天进步一点点

你可能感兴趣的:(计算机基础,安全工具)