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
, 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
, 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"
]
此处省略一些画界面的代码
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)
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"
]
此处省略一些画界面的代码
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)
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"
]
此处省略一些画界面的代码
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)
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"
]
此处省略一些画界面的代码
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)
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-2-3 22:05
被Jtian编辑
,原因: