├── .gitattributes ├── bin ├── dotnet.x64.bin ├── dotnet.x86.bin ├── peload.x64.bin └── peload.x86.bin ├── clematis.py ├── images ├── clematis.png └── zsxq.jpg ├── readme.md └── readme_ch.md /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /bin/dotnet.x64.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TianNaYa/Clematis/a6a98ea5787e39cfbbbdc8da32a8e67ab67c6b30/bin/dotnet.x64.bin -------------------------------------------------------------------------------- /bin/dotnet.x86.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TianNaYa/Clematis/a6a98ea5787e39cfbbbdc8da32a8e67ab67c6b30/bin/dotnet.x86.bin -------------------------------------------------------------------------------- /bin/peload.x64.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TianNaYa/Clematis/a6a98ea5787e39cfbbbdc8da32a8e67ab67c6b30/bin/peload.x64.bin -------------------------------------------------------------------------------- /bin/peload.x86.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TianNaYa/Clematis/a6a98ea5787e39cfbbbdc8da32a8e67ab67c6b30/bin/peload.x86.bin -------------------------------------------------------------------------------- /clematis.py: -------------------------------------------------------------------------------- 1 | import secrets 2 | import struct 3 | import argparse 4 | import random 5 | import pefile 6 | import os 7 | import lznt1 8 | import time 9 | 10 | def exception( func ): 11 | def capture( *args, **kwargs ): 12 | try: 13 | return func( *args, **kwargs ) 14 | except Exception as e: 15 | print( f'[-] error: { e }' ) 16 | exit( 1 ) 17 | 18 | return capture 19 | 20 | class Pack( object ): 21 | def __init__( self ): 22 | self.__data = bytes() 23 | self.__endian = "<" 24 | 25 | def long( self, value ): 26 | self.__data += struct.pack( f"{self.__endian}I", value ) 27 | 28 | def bool( self, value ): 29 | if value: 30 | self.long( 1 ) 31 | else: 32 | self.long( 0 ) 33 | 34 | def byts( self, value ): 35 | self.long( len( value ) ) 36 | self.__data += value 37 | 38 | def strs( self, value ): 39 | self.long( len( value ) ) 40 | self.__data += value.encode( 'utf-8' ) 41 | 42 | def join( self, value ): 43 | if isinstance( value, str ): 44 | self.__data += value.encode( 'utf-8' ) 45 | elif isinstance( value, bytes ): 46 | self.__data += value 47 | elif isinstance( value, bytearray ): 48 | self.__data += bytes( value ) 49 | else: 50 | raise TypeError( 'value must be str or bytes or bytearray' ) 51 | 52 | def build( self ) -> bytes: 53 | return self.__data 54 | 55 | def enable( value ) -> bool: 56 | return value.lower() == 'true' 57 | 58 | def arch( pe ) -> str: 59 | return pe.FILE_HEADER.Machine == 0x8664 and 'x64' or 'x86' 60 | 61 | def is_assembly( pe : pefile.PE ) -> bool: 62 | if len( pe.OPTIONAL_HEADER.DATA_DIRECTORY ) >= 14: 63 | return pe.OPTIONAL_HEADER.DATA_DIRECTORY[ 14 ].VirtualAddress != 0 and pe.OPTIONAL_HEADER.DATA_DIRECTORY[ 14 ].Size != 0 64 | 65 | return False 66 | 67 | def boot( pe ) -> bytes: 68 | if is_assembly( pe ): 69 | with open( f'bin/dotnet.{ arch( pe ) }.bin', 'rb' ) as f: 70 | return f.read() 71 | else: 72 | with open( f'bin/peload.{ arch( pe ) }.bin', 'rb' ) as f: 73 | return f.read() 74 | 75 | def file( args ) -> bytes: 76 | with open( args.file, 'rb' ) as f: 77 | return f.read() 78 | 79 | def keys( args ) -> bytes: 80 | if not enable( args.garble ): 81 | return bytes( 0 ) 82 | 83 | return secrets.token_bytes( random.randrange( 256, 2048 ) ) 84 | 85 | def argv( args ) -> str: 86 | return f'{ os.path.basename( args.file ) } { " ".join( args.parameter ) }' + '\x00' 87 | 88 | def compre( args, data : bytes ) -> bytes: 89 | if not enable( args.compress ): 90 | return data 91 | 92 | return lznt1.compress( data ) 93 | 94 | def garble( args, data : bytes, key : bytes ) -> bytes: 95 | if not enable( args.garble ): 96 | return data 97 | 98 | data = bytearray( data ) 99 | size = len( key ) 100 | 101 | for i in range( len( data ) ): 102 | data[ i ] ^= key[ i % size ] 103 | 104 | return bytes( data ) 105 | 106 | @exception 107 | def main(): 108 | parser : argparse.ArgumentParser = argparse.ArgumentParser( description = 'Convert PE file to shellcode' ) 109 | parser.add_argument( '-f', '--file', required = True, help = 'PE file to be converted into shellcode' ) 110 | parser.add_argument( '-o', '--output', required = True, help = 'Output file name' ) 111 | parser.add_argument( '-g', '--garble', default = 'true', help = 'Specify whether to enable obfuscation [ obfuscate PE and parameters ]' ) 112 | parser.add_argument( '-c', '--compress', default = 'true', help = 'Specify whether to enable compression, which can significantly reduce the size' ) 113 | parser.add_argument( '-p', '--parameter', default = '', help = 'Execution parameters to be passed to the PE file', type = str, nargs = argparse.REMAINDER ) 114 | 115 | # parse arguments 116 | stat = time.time() 117 | args = parser.parse_args() 118 | key = keys( args ) 119 | para = Pack() 120 | data = Pack() 121 | comp = Pack() 122 | pe = pefile.PE( args.file ) 123 | 124 | # output prompt message 125 | print( f'[+] file: { args.file }' ) 126 | print( f'[+] parameter: { argv( args ) }' ) 127 | print( f'[+] output: { args.output }' ) 128 | print( f'[+] arch: { arch( pe ) }' ) 129 | print( f'[+] dotnet: { is_assembly( pe ) }' ) 130 | 131 | # build payload arguments 132 | para.strs( argv( args ) ) 133 | para.byts( file( args ) ) 134 | 135 | # process payload 136 | buff = compre( args, para.build() ) 137 | buff = garble( args, buff, key ) 138 | 139 | # build compressed payload 140 | comp.bool( enable( args.compress ) ) 141 | comp.long( len( para.build() ) ) 142 | comp.byts( buff ) 143 | comp.byts( key ) 144 | 145 | # compress payload 146 | data.join( boot( pe ) ) 147 | data.byts( comp.build() ) 148 | 149 | # write shellcode 150 | with open( args.output, 'wb' ) as f: 151 | f.write( data.build() ) 152 | 153 | print( f'[+] compress: { enable( args.compress ) }' ) 154 | print( f'[+] garble: { enable( args.garble ) }' ) 155 | print( f'[+] save: { args.output } | size: { len( data.build() ) }' ) 156 | print( f'[+] time: { int( time.time() ) - int( stat ) }s' ) 157 | 158 | if __name__ == "__main__": 159 | main() 160 | -------------------------------------------------------------------------------- /images/clematis.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TianNaYa/Clematis/a6a98ea5787e39cfbbbdc8da32a8e67ab67c6b30/images/clematis.png -------------------------------------------------------------------------------- /images/zsxq.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TianNaYa/Clematis/a6a98ea5787e39cfbbbdc8da32a8e67ab67c6b30/images/zsxq.jpg -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Clematis 2 | 3 | ![ clematis ](images/clematis.png) 4 | 5 | [ [中文](https://github.com/CBLabresearch/clematis/blob/main/readme_ch.md) | [English](https://github.com/CBLabresearch/clematis/blob/main/readme.md) ] 6 | 7 | 🛠️ A powerful tool for converting PE files (EXE/DLL) into position-independent shellcode. 8 | 9 | ## ✨ Key Features 10 | 11 | - Support for converting PE files (EXE/DLL) to shellcode 12 | - Compatible with both x86 and x64 architectures 13 | - Command-line argument support 14 | - Built-in LZNT1 compression algorithm for significant output file size reduction 15 | - Optional obfuscation for enhanced stealth 16 | 17 | ## 📦 Installation 18 | 19 | ### Dependencies 20 | ```bash 21 | pip install pefile lznt1 22 | ``` 23 | 24 | ## 🚀 Usage 25 | 26 | ```bash 27 | python clematis.py -f -o [-g ] [-c ] [-p ] 28 | ``` 29 | 30 | ### Parameters 31 | 32 | | | | desc | default | required | 33 | |--------|-----------------|---------------------------------------------|---------|----------| 34 | | **-f** | **--file** | Path to the PE file to convert | | true | 35 | | **-o** | **--output** | Output filename | | true | 36 | | **-g** | **--garble** | Enable obfuscation | true | false | 37 | | **-c** | **--compress** | Enable compression | true | false | 38 | | **-p** | **--parameter** | Execution parameters to pass to the PE file | | false | 39 | 40 | ### Examples 41 | 42 | ```bash 43 | # Show help information 44 | python clematis.py -h 45 | 46 | # Basic usage 47 | python clematis.py -f target.exe -o output.bin 48 | 49 | # Disable obfuscation and compression 50 | python clematis.py -f target.exe -o output.bin -g false -c false 51 | 52 | # Pass arguments to target program 53 | python clematis.py -f target.exe -o output.bin -p arg1 arg2 54 | 55 | python clematis.py -f target.exe -o output.bin -p "arg1 arg2" 56 | ``` 57 | 58 | ## 💪 Our Advantages 59 | 60 | - 🎯 Support for DOT NET 61 | - 🗜️ Compression support 62 | - 🎭 Obfuscation support 63 | - 🔄 Parameter passing support 64 | - 🚀 Full support for golang 65 | - 💪 Generated shellcode is powerful and stable 66 | 67 | ## 💡 Design Philosophy 68 | 69 | ##### In certain special environments, we may encounter the following challenges: 70 | 71 | ```text 72 | - 🛡️ Unable to perform process injection (AV/EDR/XDR blocking) 73 | - 🔄 Executing golang programs in current process may cause blocking 74 | - 💾 Memory leaks may occur after golang program execution 75 | - ⚠️ Threads created by golang cannot be released! 76 | ``` 77 | 78 | ##### To address these issues, we developed clematis: 79 | 80 | ```text 81 | - ✨ Convert golang programs to shellcode 82 | - 🎯 Direct execution in current process 83 | - ♻️ Automatic memory release after execution 84 | - 🚀 Completely avoid golang-related memory issues 85 | - 🔄 Reclaim all threads created by golang 86 | ``` 87 | 88 | ## 📝 Notes 89 | 90 | - Ensure sufficient permissions to read source PE files and write target files 91 | - Compression is recommended when converting large files 92 | - Obfuscation may add some performance overhead but provides better stealth 93 | 94 | ## ⚠️ Known Issues 95 | 96 | - Parts of an application (exe) built with mingw | gcc may fail to load, it may be caused by relocation? ( Not implemented ) 97 | - DOT NET illegal memory access ( Fixed ) 98 | 99 | ## 🗓️ Planned Features 100 | 101 | - Advanced encryption options for better security 102 | - GUI interface for easier operation 103 | - Real-time conversion progress monitoring 104 | - Processing of resources in PE 105 | - Enhanced evasion capabilities, such as [ProxyDll, Syscall, ...] 106 | 107 | ## 🔄 Recent Updates 108 | 109 | - 2024-12-27 110 | - Support for DOT NET (x64 | x86) 111 | 112 | - 2024-12-28 113 | - Fixed potential DOT NET program crashes (May not occur) 114 | - Added handling for IMAGE_DIRECTORY_ENTRY_EXCEPTION ( x64 ) 115 | - Updated APIs to use NTAPI 116 | 117 | | before | now | 118 | | --- | --- | 119 | | `VirtualAlloc` | `NtAllocateVirtualMemory` | 120 | | `VirtualProtect` | `NtProtectVirtualMemory` | 121 | | `VirtualFree` | `NtFreeVirtualMemory` | 122 | | `LoadLibrary` | `LdrLoadDll` | 123 | | `GetProcAddress` | `LdrGetProcedureAddress` | 124 | | `WaitForMultipleObjects` | `NtWaitForMultipleObjects` | 125 | | `CreateEvent` | `NtCreateEvent` | 126 | | `CloseHandle` | `NtClose` | 127 | | `SignalObjectAndWait` | `NtSignalAndWaitForSingleObject` | 128 | | `TerminateThread` | `NtTerminateThread` | 129 | | `SuspendThread` | `NtSuspendThread` | 130 | | `OpenThread` | `NtOpenThread` | 131 | | `ResumeThread` | `NtResumeThread` | 132 | | `GetContextThread` | `NtGetContextThread` | 133 | | `SetContextThread` | `NtSetContextThread` | 134 | | ... | 135 | 136 | - 2025-1-1 137 | - Added thread safety for managing thread lists and memory lists 138 | - Fix some functional issues 139 | - Remove parameter processing in payload and change to patch 140 | - Support for upx 141 | - Add architecture check in the payload to check whether the architecture matches 142 | - Some implementations have been changed to provide better compatibility 143 | 144 | --- 145 | 146 | ## ⚠️ Warning 147 | - We are not planning to release this update for free, we will put it in our paid circle. 148 | - This project continues to be available and free, but may not be updated in the future, thank you! 149 | 150 | ![ clematis ](images/zsxq.jpg) 151 | 152 | --- 153 | 154 | ## 🔍 How It Works 155 | 156 | Clematis converts PE files to shellcode through the following steps: 157 | 158 | 1. Read and parse target PE file 159 | 2. Process command line arguments (if any) 160 | 3. Optional LZNT1 compression 161 | 4. Optional obfuscation processing 162 | 5. Generate final position-independent shellcode 163 | 164 | ```mermaid 165 | flowchart TD 166 | A[START] --> B[Read PE file] 167 | B --> C[Parse PE structure] 168 | C --> D{Is there a command line argument?} 169 | D -- TRUE --> E[Process command line arguments] 170 | D -- FALSE --> F{Enable compression?} 171 | E --> F 172 | F -- TRUE --> G[LZNT1 compression] 173 | F -- FALSE --> H{Enable obfuscation?} 174 | G --> H 175 | H -- TRUE --> I[Execute obfuscation processing] 176 | H -- FALSE --> J[Generate shellcode] 177 | I --> J 178 | J --> K[Output result] 179 | K --> L[END] 180 | ``` 181 | 182 | --- 183 | 184 | ## 🤝 Contributing 185 | 186 | Issues and Pull Requests are welcome! 187 | -------------------------------------------------------------------------------- /readme_ch.md: -------------------------------------------------------------------------------- 1 | # Clematis 2 | 3 | ![ clematis ](images/clematis.png) 4 | 5 | [ [中文](https://github.com/CBLabresearch/clematis/blob/main/readme_ch.md) | [English](https://github.com/CBLabresearch/clematis/blob/main/readme.md) ] 6 | 7 | 🛠️ 一个强大的工具,用于将PE文件(EXE/DLL)转换为与位置无关的shellcode。 8 | 9 | ## ✨ 主要特性 10 | 11 | - 支持将PE文件(EXE/DLL)转换为shellcode 12 | - 同时兼容x86和x64架构 13 | - 支持命令行参数 14 | - 内置LZNT1压缩算法,显著减小输出文件大小 15 | - 可选的混淆功能,增强隐蔽性 16 | 17 | ## 📦 安装 18 | 19 | ### 依赖项 20 | ```bash 21 | pip install pefile lznt1 22 | ``` 23 | 24 | ## 🚀 使用方法 25 | 26 | ```bash 27 | python clematis.py -f -o <输出文件> [-g ] [-c ] [-p <参数>] 28 | ``` 29 | 30 | ### 参数说明 31 | 32 | | | | 描述 | 默认值 | 是否必需 | 33 | |--|--|------|---------|---------| 34 | | **-f** | **--file** | 需要转换的PE文件路径 | | 是 | 35 | | **-o** | **--output** | 输出文件名 | | 是 | 36 | | **-g** | **--garble** | 启用混淆 | true | 否 | 37 | | **-c** | **--compress** | 启用压缩 | true | 否 | 38 | | **-p** | **--parameter** | 传递给PE文件的执行参数 | | 否 | 39 | 40 | ### 使用示例 41 | 42 | ```bash 43 | # 显示帮助信息 44 | python clematis.py -h 45 | 46 | # 基本用法 47 | python clematis.py -f target.exe -o output.bin 48 | 49 | # 禁用混淆和压缩 50 | python clematis.py -f target.exe -o output.bin -g false -c false 51 | 52 | # 传递参数给目标程序 53 | python clematis.py -f target.exe -o output.bin -p arg1 arg2 54 | 55 | python clematis.py -f target.exe -o output.bin -p "arg1 arg2" 56 | ``` 57 | 58 | ## 💪 我们的优势 59 | 60 | - 🎯 支持 DOT NET 61 | - 🗜️ 支持压缩 62 | - 🎭 支持混淆 63 | - 🔄 支持参数传递 64 | - 🚀 对golang进行完全的支持 65 | - 💪 构造的shellcode非常的强大且稳定 66 | 67 | ## 💡 设计理念 68 | 69 | ##### 在某些特殊环境中,我们可能会遇到以下挑战: 70 | 71 | ```text 72 | - 🛡️ 无法进行进程注入(AV/EDR/XDR拦截) 73 | - 🔄 当前进程中执行golang程序可能导致阻塞 74 | - 💾 golang程序执行后可能存在内存泄漏 75 | - ⚠️ golang创建的线程无法释放! 76 | ``` 77 | 78 | ##### 为解决这些问题,我们开发了clematis: 79 | 80 | ``` 81 | - ✨ 将golang程序转换为shellcode 82 | - 🎯 在当前进程中直接执行 83 | - ♻️ 执行完毕后自动释放内存 84 | - 🚀 完全避免golang相关的内存问题 85 | - 🔄 回收golang创建的所有线程 86 | ``` 87 | 88 | ## 📝 注意事项 89 | 90 | - 确保有足够的权限读取源PE文件和写入目标文件 91 | - 转换大文件时建议启用压缩功能 92 | - 混淆可能会增加一些性能开销,但能提供更好的隐蔽性 93 | 94 | ## ⚠️ 已知问题 95 | 96 | - 使用mingw | gcc编译的应用程序(exe)的部分内容可能无法加载,这可能是由重定位导致的?(未实现) 97 | - DOT NET 出现非法内存访问(已修复) 98 | 99 | ## 🗓️ 计划功能 100 | 101 | - 更高级的加密选项以提升安全性 102 | - 图形界面支持,便于操作 103 | - 实时转换进度监控 104 | - 处理PE中的资源 105 | - 增加规避能力,比如 [ProxyDll, Syscall, ...] 106 | 107 | ## 🔄 最近更新 108 | 109 | - 2024-12-27 110 | - 支持 DOT NET(x64 | x86) 111 | 112 | - 2024-12-28 113 | - 修复了潜在的 DOT NET 程序崩溃问题(可能不会发生) 114 | - 添加了 IMAGE_DIRECTORY_ENTRY_EXCEPTION 的处理(x64) 115 | - 更新API使用NTAPI 116 | 117 | | 原API | 新API | 118 | | --- | --- | 119 | | `VirtualAlloc` | `NtAllocateVirtualMemory` | 120 | | `VirtualProtect` | `NtProtectVirtualMemory` | 121 | | `VirtualFree` | `NtFreeVirtualMemory` | 122 | | `LoadLibrary` | `LdrLoadDll` | 123 | | `GetProcAddress` | `LdrGetProcedureAddress` | 124 | | `WaitForMultipleObjects` | `NtWaitForMultipleObjects` | 125 | | `CreateEvent` | `NtCreateEvent` | 126 | | `CloseHandle` | `NtClose` | 127 | | `SignalObjectAndWait` | `NtSignalAndWaitForSingleObject` | 128 | | `TerminateThread` | `NtTerminateThread` | 129 | | `SuspendThread` | `NtSuspendThread` | 130 | | `OpenThread` | `NtOpenThread` | 131 | | `ResumeThread` | `NtResumeThread` | 132 | | `GetContextThread` | `NtGetContextThread` | 133 | | `SetContextThread` | `NtSetContextThread` | 134 | | ... | 135 | 136 | - 2025-1-1 137 | - 增加了线程列表和内存列表管理的线程安全性 138 | - 修复了一些功能性问题 139 | - 移除了payload中的参数处理并改为patch方式 140 | - 支持upx 141 | - 在payload中添加架构检查以验证架构是否匹配 142 | - 更改了一些实现方式以提供更好的兼容性 143 | 144 | --- 145 | 146 | ## ⚠️ 警告 147 | - 我们不打算免费发布此更新,我们会将其放在付费圈子中。 148 | - 该项目将继续保持可用和免费,但未来可能不会更新,谢谢! 149 | 150 | ![ clematis ](images/zsxq.jpg) 151 | 152 | --- 153 | 154 | ## 🔍 工作原理 155 | 156 | Clematis通过以下步骤将PE文件转换为shellcode: 157 | 158 | 1. 读取并解析目标PE文件 159 | 2. 处理命令行参数(如果有) 160 | 3. 可选的LZNT1压缩 161 | 4. 可选的混淆处理 162 | 5. 生成最终的位置无关shellcode 163 | 164 | ```mermaid 165 | flowchart TD 166 | A[开始] --> B[读取PE文件] 167 | B --> C[解析PE结构] 168 | C --> D{是否有命令行参数?} 169 | D -- 是 --> E[处理命令行参数] 170 | D -- 否 --> F{是否启用压缩?} 171 | E --> F 172 | F -- 是 --> G[LZNT1压缩] 173 | F -- 否 --> H{是否启用混淆?} 174 | G --> H 175 | H -- 是 --> I[执行混淆处理] 176 | H -- 否 --> J[生成shellcode] 177 | I --> J 178 | J --> K[输出结果] 179 | K --> L[结束] 180 | ``` 181 | 182 | --- 183 | 184 | ## 🤝 贡献 185 | 186 | 欢迎提交Issues和Pull Requests! 187 | 188 | --- 189 | --------------------------------------------------------------------------------