首页
社区
课程
招聘
[原创]pyoneGUI让你实现插件自由
发表于: 2024-1-1 22:41 13322

[原创]pyoneGUI让你实现插件自由

2024-1-1 22:41
13322

https://github.com/wstone0011/pyoneGUI
绿色便携,插件自由,即改即用,几乎可以完美表现Python的能力

曾几何时,明明更习惯于用Python写poc,为啥却是Java的工具用的更顺手?软件依赖、版本冲突,Python虽然做到了跨平台,但其实没那么“绿色”,很多工具做不到开箱即用。Jar包用的很爽,可是也有问题啊,要带一个大大的Java运行时环境,而且还不能做到即改即用,毕竟Java不是脚本语言呀。为了做到即改即用,有些工具会让修改其特有的yaml配置文件,来定义软件的行为,这虽无可厚非,但不得不说这是增加了学习成本的,而且yaml的表达能力是比不上Python这种编程语言的。
2019年的时候,发现Python的exec函数可以把文本当做Python代码执行(https://bbs.kanxue.com/thread-255400.htm) ,隐约觉得这个特性有潜力带来一些变化,也做了一些尝试,但是在实现过程中犯了个小错误导致没能真正领悟exec这句代码的威力,最终只写了个能替代shell的方便运维用的小工具。
2023年,AIGC大爆发,机缘巧合之下问了大模型一个问题,它的回答算不上多准确,但让我在实验之后认识到了那个关于exec的错误,我不应该在main函数里调用exec,而是应该直接在顶层代码里调用exec(或者执行exec(code, globals())也可以),只有这样,那些通过exec执行而申明的类、变量才会直接出现在globals里,才能被全局使用,才能真正自由的的把文本当Python脚本执行。为了充分发挥exec这句代码的威力,打造一个方便的工具,我写了pyoneGUI,个人觉得,这个工具是值得期待的。

对于打包好的程序,使用"代码执行->执行Python代码"功能执行如下代码,可以查看打包到pyoneGUI里的依赖库等信息

这个特点是pyinstaller带来的,将程序和依赖库打包成一个可执行文件,绿色便携,路径无关,方便执行。

这个特点是由exec函数配合技巧性的软件设计带来的。核心逻辑如下:

这个特点是Python本身的能力。

有这个能力加持,如果对某个插件不满意,可以即改即用,比如GBK解码报错,可以立即换成别的编码,甚至更改整个函数逻辑,使用的就是熟悉的Python语言,不需要增加新的学习成本,灵活且强大,除了性能和稳定性,几乎没有槽点。(非常适合写小工具和实验性质的项目,比如PoC)

这个特点是exec函数带来的

每个插件都是Plugin类的子类,只要在plugin目录下随便建一个py文件写个Plugin子类就是一个新的插件。需要注意的是,每个插件类的类名是唯一的,不能重复,否则会丢失之前定义的同名插件。

插件机制的代码逻辑可参见“插件自由”特点介绍、main.py、plugin/base.py等内容。

“字典去重”这个插件是在demo.py中实现的,其类名为RemoveDuplicates(唯一),menu和name分别是菜单名称和插件名称,这两个值决定了插件的显示名称。

图片描述

“json格式化”这个插件是在demo.py中实现的

“场景编排demo”这个插件是在laboratory.py中实现的,这是一个实验性质的插件,其展示了场景编排能力,可以把其他的插件组合在一起,按照新的逻辑处理任务。

图片描述

“命令行”这个插件是在laboratory.py中实现的,可以执行系统命令。这是一个有自定义界面的插件,其type类型为laboratory,通过buildWindow函数绘制了插件界面,并返回了self.output_text,点击“开始”的时候执行onStart函数,点击“查看帮助文档”的时候执行showHelp函数。

图片描述

“http.server”这个插件是在laboratory.py中实现的,点击开始按钮会启动一个简单的HTTP服务,可以用来下载文件。

图片描述

"HTTP访问"这个插件是在poc.py中实现的,可以访问指定的URL地址并返回结果,是PoC功能的一个例子。这个插件没有定义buildWindow函数,而是使用的父类Plugin默认的buildWindow函数完成的界面绘制。

图片描述

[!] 欢迎使用和反馈意见,尤其是不需要大的环境依赖就能实现的有意思的功能点
下载链接:https://pan.baidu.com/s/1etMzrrHGnwA70vOqyR5yNg?pwd=1024

发布了了pyoneGUI_v1.0.3版本,添加了pyinstaller解包、pyc反编译、反弹Shell生成这3个插件,python环境为python-3.7.3-x86

图片描述

图片描述

图片描述

框架代码做了微调,添加了-lp参数和-fp参数,可以在命令行上指定插件路径,也可以指定pyoneGUI运行后第一个运行的插件,进而选择默认插件视图,并且可以把pyoneGUI当做pyone直接在命令行中运行python脚本。内置的python环境为python-3.7.3-x86.exe

用法举例

封装了fscan程序,使用"pyoneGUI.exe -fp Fscan"命令可以默认进入fscan插件视图,以图形化的方法调用fscan程序进行主机存活探测、端口扫描、漏洞扫描、密码爆破。pyoneGUI在这个功能里主要是做一个壳,可以方便的生成一些fscan调用命令,如果有其他常用场景的命令,也很容易添加和修改,主打一个“即改即用”。

图片描述

发布了pyoneGUI_v2.0.0版本,最大的变化是主版本放弃了pyinstaller打包,而是维护并直接打包了整个绿色版Python环境。这意味着主版本放弃了小巧便携,为什么要牺牲这个特性,主要是在打包pwntools库的时候很麻烦,而且以后如果要打包别的强大的库也可能会很麻烦,既然Python本身就比较绿色,那还用pyinstaller打包做什么,直接维护好绿色版Python环境不就好了吗。不过,如果有什么插件是比较有特色的,后面可能会用pyinstaller单独打包,毕竟pyoneGUI中的one本意就是一个程序的意思,小巧便携也是一个好的特性,如果主版本不支持,那就在特别版里支持吧。

随v2.0.0版本一起发布的插件有加载文件为Hex编码、Hex转C数组、Hex转Python数组,这些插件依然是所见即所得,即改即用的,另外还添加了一个插件名为“pwntools模板1”,可以方便的利用pwntools库生成exp代码,然后通过“代码执行->执行Python代码(exec执行)”执行代码,下图是这个插件的一个简单使用说明。

图片描述

另外,因为摆脱了pyinstaller打包,直接使用了整个绿色版Python环境,所以这个环境本身也是个很好用的工具。

其他说明:
1.绿色版Python环境使用的是Python3.7.3-amd64,经过测试,pyoneGUI_v2.0.0支持Win7-64位、Win10、Win11,如果要在Linux下运行,理论上应该不会太难,可以自行尝试,如果需要知道使用了哪些依赖库,版本又是什么,可以参考本文正文相关部分;
2.因为发布时删除了.pyc文件,所以初次运行某个插件的时候可能会稍微卡一下,但是问题不大,后面就好了;
3.虽然主版本放弃了小巧便携这个特性,但依然绿色,路径无关。

这些版本下有效,其他版本未测试(2024.1.2更新)
1.Win10下正常,Win7下报错
Python: 3.12.0 (tags/v3.12.0:0fb18b0, Oct  2 2023, 13:03:39) [MSC v.1935 64 bit (AMD64)]
tkinter.TkVersion: 8.6
pyinstaller: 6.2.0
 
2.Win10下正常,Win7_64位下正常
Python: 3.7.3 (v3.7.3:ef4ec6ed12, Mar 25 2019, 21:26:53) [MSC v.1916 32 bit (Intel)]
tkinter.TkVersion: 8.6
pyinstaller: 5.13.2
这些版本下有效,其他版本未测试(2024.1.2更新)
1.Win10下正常,Win7下报错
Python: 3.12.0 (tags/v3.12.0:0fb18b0, Oct  2 2023, 13:03:39) [MSC v.1935 64 bit (AMD64)]
tkinter.TkVersion: 8.6
pyinstaller: 6.2.0
 
2.Win10下正常,Win7_64位下正常
Python: 3.7.3 (v3.7.3:ef4ec6ed12, Mar 25 2019, 21:26:53) [MSC v.1916 32 bit (Intel)]
tkinter.TkVersion: 8.6
pyinstaller: 5.13.2
打包出来的程序会有命令行窗口,其实留着命令行窗口也挺好,方便看报错日志。
pyinstaller -F main.py --noupx
 
把py脚本后缀改为pyw,再打包,运行时就没有命令行窗口了。
pyinstaller -F main.pyw --noupx
打包出来的程序会有命令行窗口,其实留着命令行窗口也挺好,方便看报错日志。
pyinstaller -F main.py --noupx
 
把py脚本后缀改为pyw,再打包,运行时就没有命令行窗口了。
pyinstaller -F main.pyw --noupx
查看python版本
import sys
print(sys.version)
 
查看操作系统版本
import platform
print(platform.platform())
 
查看依赖库信息
import sys
for module_name in sys.modules:
    print(module_name)
 
查看requests库的信息
import requests
print(requests.__version__)
查看python版本
import sys
print(sys.version)
 
查看操作系统版本
import platform
print(platform.platform())
 
查看依赖库信息
import sys
for module_name in sys.modules:
    print(module_name)
 
查看requests库的信息
import requests
print(requests.__version__)
lst_py=[]
def traverse_directory(path):
    for entry in os.scandir(path):
        if entry.is_file():
            if entry.path.endswith(".py"):
                if entry.name=="base.py":
                    lst_py.insert(0, entry.path)
                else:
                    lst_py.append(entry.path)
        elif entry.is_dir():
            traverse_directory(entry.path)
 
traverse_directory("plugin")
for path in lst_py:
    code=read_file(path)
    exec(code)
 
lst_plugin=[obj for obj in [v() for v in globals().values()  if Plugin in getattr(v, "__mro__", [])] if obj.__class__.__name__!="Plugin" ]
lst_py=[]
def traverse_directory(path):
    for entry in os.scandir(path):
        if entry.is_file():
            if entry.path.endswith(".py"):
                if entry.name=="base.py":
                    lst_py.insert(0, entry.path)
                else:
                    lst_py.append(entry.path)
        elif entry.is_dir():
            traverse_directory(entry.path)
 
traverse_directory("plugin")
for path in lst_py:
    code=read_file(path)
    exec(code)
 
lst_plugin=[obj for obj in [v() for v in globals().values()  if Plugin in getattr(v, "__mro__", [])] if obj.__class__.__name__!="Plugin" ]
class RemoveDuplicates(Plugin):
    menu="文本处理"
    name="字典去重"
    def run(self, text):
        if not text:
            return ""
        words = text.strip().replace("\r\n", "\n").split("\n")
        return "\n".join(list(dict.fromkeys(words)))
class RemoveDuplicates(Plugin):
    menu="文本处理"
    name="字典去重"
    def run(self, text):
        if not text:
            return ""
        words = text.strip().replace("\r\n", "\n").split("\n")
        return "\n".join(list(dict.fromkeys(words)))
class JsonView(Plugin):
    menu="文本处理"
    name="json格式化"
    def run(self, text):
        if not text:
            return ""
        j=json.loads(text)
        #formatted_json = json.dumps(j, indent=4)
        formatted_json = json.dumps(j, indent=4, ensure_ascii=False)
        return formatted_json
class JsonView(Plugin):
    menu="文本处理"
    name="json格式化"
    def run(self, text):
        if not text:
            return ""
        j=json.loads(text)
        #formatted_json = json.dumps(j, indent=4)
        formatted_json = json.dumps(j, indent=4, ensure_ascii=False)
        return formatted_json
class WorkflowOrchestration(Plugin):
    menu="实验室"
    name="场景编排demo"
    type="text"
    def run(self, text):
        tasks=[]
        tasks+=[RemoveDuplicates]
        tasks+=[ToLower]
        tasks+=[SortLines]
         
        for _ in tasks:
            text=_().run(text)
         
        return text
class WorkflowOrchestration(Plugin):
    menu="实验室"
    name="场景编排demo"
    type="text"
    def run(self, text):
        tasks=[]
        tasks+=[RemoveDuplicates]
        tasks+=[ToLower]
        tasks+=[SortLines]
         
        for _ in tasks:
            text=_().run(text)
         
        return text
class CMD(Plugin):
    menu="实验室"
    name="命令行"
    type="laboratory"
    def buildWindow(self):
        tab_laboratory = self.frame_args["tab_laboratory"]
        此处省略一些画界面的代码
 
        # 设置options
        self.options=Options()
        dic={"Name":"CMD", "Current Setting":"", "Required":"yes", "Description":"执行的命令", "obj":cmd_entry }
        self.options.append(dic)
        return self.output_text
         
    def onStart(self, event=None):
        options=self.getOptions()
        cmd=options["CMD"]
         
        self.log(f"[+] {cmd}\n")
        self.executeCommand(cmd, logfunc=self.log)
        # TODO:添加自定义命令,并使自定义命令和系统命令都支持管道
 
    def onStop(self, event=None):
        self.log("[*] onStop\n")
             
    def showHelp(self, event=None):
        super().showHelp()
        help="功能上相当于system(CMD)\n\n"\
             "Windows下常用命令:\n"\
             "netstat -ano | findstr LISTEN\t\t\t查看监听端口\n"\
             "\n"
        self.log(help)
class CMD(Plugin):
    menu="实验室"
    name="命令行"
    type="laboratory"
    def buildWindow(self):
        tab_laboratory = self.frame_args["tab_laboratory"]
        此处省略一些画界面的代码
 
        # 设置options
        self.options=Options()
        dic={"Name":"CMD", "Current Setting":"", "Required":"yes", "Description":"执行的命令", "obj":cmd_entry }
        self.options.append(dic)
        return self.output_text
         
    def onStart(self, event=None):
        options=self.getOptions()
        cmd=options["CMD"]
         
        self.log(f"[+] {cmd}\n")
        self.executeCommand(cmd, logfunc=self.log)
        # TODO:添加自定义命令,并使自定义命令和系统命令都支持管道
 
    def onStop(self, event=None):
        self.log("[*] onStop\n")
             
    def showHelp(self, event=None):
        super().showHelp()
        help="功能上相当于system(CMD)\n\n"\
             "Windows下常用命令:\n"\
             "netstat -ano | findstr LISTEN\t\t\t查看监听端口\n"\
             "\n"
        self.log(help)
class HttpServer(Plugin):
    menu="实验室"
    name="http.server"
    type="laboratory"
    server=None
    def buildWindow(self):
        tab_laboratory = self.frame_args["tab_laboratory"]
        此处省略一些画界面的代码
 
        # 设置options
        self.options=Options()
        dic={"Name":"IP", "Current Setting":"", "Required":"yes", "Description":"监听网卡的IP", "obj":ip_entry }
        self.options.append(dic)
        dic={"Name":"PORT", "Current Setting":"", "Required":"yes", "Description":"监听端口", "obj":port_entry }
        self.options.append(dic)
         
        #MyHandler里会用
        PluginGlobalStorage[self.__class__.__name__]={"class_obj":self}
        return self.output_text
         
    def onStart(self, event=None):
        options=self.getOptions()
        IP=options["IP"]
        PORT=int(options["PORT"])
         
        class MyHandler(http.server.SimpleHTTPRequestHandler):
            def log_message(self, format, *args):
                msg=format%args
                obj=PluginGlobalStorage["HttpServer"]["class_obj"]
                obj.log(msg+"\n")
                 
        def fn():
            with socketserver.TCPServer((IP, PORT), MyHandler) as self.server:
                self.log(f"监听于{IP}:{PORT}\n")
                self.server.serve_forever()
                 
        self.log("[+] 启动Server\n")
        thread = threading.Thread(target=fn)
        thread.start()
 
    def onStop(self, event=None):
        if self.server:
            self.log("[+] 关闭Server\n")
            self.server.shutdown()
            self.server=None
        else:
            self.log("[*] Server未启动\n")
             
    def showHelp(self, event=None):
        super().showHelp()
        self.log("相当于在python3下调用:\n")
        self.log("python -m http.server port\n\n")
        self.log("或者相当于在python2.7下调用:\n")
        self.log("python -m SimpleHTTPServer port\n\n")
class HttpServer(Plugin):
    menu="实验室"
    name="http.server"
    type="laboratory"
    server=None
    def buildWindow(self):
        tab_laboratory = self.frame_args["tab_laboratory"]
        此处省略一些画界面的代码
 
        # 设置options
        self.options=Options()
        dic={"Name":"IP", "Current Setting":"", "Required":"yes", "Description":"监听网卡的IP", "obj":ip_entry }
        self.options.append(dic)
        dic={"Name":"PORT", "Current Setting":"", "Required":"yes", "Description":"监听端口", "obj":port_entry }
        self.options.append(dic)
         
        #MyHandler里会用
        PluginGlobalStorage[self.__class__.__name__]={"class_obj":self}
        return self.output_text
         
    def onStart(self, event=None):
        options=self.getOptions()
        IP=options["IP"]
        PORT=int(options["PORT"])
         
        class MyHandler(http.server.SimpleHTTPRequestHandler):
            def log_message(self, format, *args):
                msg=format%args
                obj=PluginGlobalStorage["HttpServer"]["class_obj"]
                obj.log(msg+"\n")
                 
        def fn():
            with socketserver.TCPServer((IP, PORT), MyHandler) as self.server:
                self.log(f"监听于{IP}:{PORT}\n")

[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)

最后于 2024-2-3 22:05 被Jtian编辑 ,原因:
上传的附件:
收藏
免费 13
支持
分享
最新回复 (10)
雪    币: 3439
活跃值: (2124)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
测试可用
2024-1-2 04:18
0
雪    币: 3573
活跃值: (31026)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
3
感谢分享
2024-1-2 10:12
1
雪    币: 10
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
4
支持
2024-1-2 12:15
0
雪    币: 2350
活跃值: (2240)
能力值: ( LV4,RANK:55 )
在线值:
发帖
回帖
粉丝
5
感谢分享
2024-1-2 22:46
0
雪    币: 2064
活跃值: (4172)
能力值: ( LV7,RANK:100 )
在线值:
发帖
回帖
粉丝
6
2024.1.4更新
发布了了pyoneGUI_v1.0.3版本,添加了pyinstaller解包、pyc反编译、反弹Shell生成这3个插件,python环境为python-3.7.3-x86
2024-1-4 14:32
0
雪    币: 9
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
7
觉得有些功能很好
2024-1-14 08:04
1
雪    币: 2064
活跃值: (4172)
能力值: ( LV7,RANK:100 )
在线值:
发帖
回帖
粉丝
8

2024.1.15更新
发布了了pyoneGUI_v1.1.0版本,框架代码做了微调,添加了-lp参数和-fp参数,可以在命令行上指定插件路径,也可以指定pyoneGUI运行后第一个运行的插件,进而选择默认插件视图,并且可以把pyoneGUI当做pyone直接在命令行中运行python脚本。内置的python环境为python-3.7.3-x86.exe
下载链接:https://pan.baidu.com/s/1etMzrrHGnwA70vOqyR5yNg?pwd=1024

最后于 2024-1-15 12:34 被Jtian编辑 ,原因:
2024-1-15 10:15
0
雪    币: 2064
活跃值: (4172)
能力值: ( LV7,RANK:100 )
在线值:
发帖
回帖
粉丝
9

2024.1.21更新
发布了pyoneGUI_v1.1.1版本,封装了fscan程序,使用"pyoneGUI.exe -fp Fscan"命令可以默认进入fscan插件视图,以图形化的方法调用fscan程序进行主机存活探测、端口扫描、漏洞扫描、密码爆破。pyoneGUI在这个功能里主要是做一个壳,可以方便的生成一些fscan调用命令,如果有其他常用场景的命令,也很容易添加和修改,主打一个“所见即所得,即改即用”。

最后于 2024-1-21 17:30 被Jtian编辑 ,原因:
2024-1-21 17:29
0
雪    币: 2064
活跃值: (4172)
能力值: ( LV7,RANK:100 )
在线值:
发帖
回帖
粉丝
10

2024.2.3更新
发布了pyoneGUI_v2.0.0版本,最大的变化是主版本放弃了pyinstaller打包,而是维护并直接打包了整个绿色版Python环境。这意味着主版本放弃了小巧便携,为什么要牺牲这个特性,主要是在打包pwntools库的时候很麻烦,而且以后如果要打包别的强大的库也可能会很麻烦,既然Python本身就比较绿色,那还用pyinstaller打包做什么,直接维护好绿色版Python环境不就好了吗。不过,如果有什么插件是比较有特色的,后面可能会用pyinstaller单独打包,毕竟pyoneGUI中的one本意就是一个程序的意思,小巧便携也是一个好的特性,如果主版本不支持,那就在特别版里支持吧。

最后于 2024-2-3 17:31 被Jtian编辑 ,原因:
2024-2-3 17:29
0
雪    币: 218
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
11
感谢分享
2024-2-20 12:36
0
游客
登录 | 注册 方可回帖
返回
//