はじめに
英語ドキュメントを読むのはつらい。
DeepLでもGoogle翻訳でも翻訳文章長のリミットがあったり、なんか日本語的に違和感があったりするものです。
そういう制限を解消しつつ、文書検索を簡単に「意図を理解」して提案してくれる環境をVSCode上に作ろう というお話をやっていきます
この記事では触れませんが、セキュリティ文書って結構Red Teaming的なことを書くと、オンラインのAIエージェントは「回答拒否」するので、ローカルLLMを使って回答拒否しないものを作っています。
LLMをうまく選定すれば作れるのでやりたい人はやってみてください。
とはいえ、絶対的にローカルLLMである必要性はないのでオンラインでもよいです。
あくまでも回答拒否しないモデルを使うという点においてローカルLLMが適しているというだけです
環境準備
- EVO X2の128GBモデルを買う(or 96GB以上のVRAMがあるグラボを買う)
- Pythonをインストールする(できればuvがあると楽)
- Obsidianをインストールする
- ブラウザにObsidian Web Clipperを入れる
- VSCodeをインストールして、RooCodeプラグインをインストールする
ドキュメントの収集
このあたりのドキュメントをObsidian Web Clipperで収集していく
原本からの翻訳
ChatGPT謹製の翻訳スクリプトはこれ
ドデカいマークダウンファイルを渡すと後半の翻訳がゴミみたいになるので、セクション単位で文章を区切って渡すことで精度を保ったまま翻訳をすることを目論んでいます。
# -*- coding: utf-8 -*-
import os,re
import requests
from pathlib import Path
from typing import List
# LMStudioに向けているが、OpenAI互換APIを持っているならOllamaでもOpenAIでもOK
OPENAI_COMPATIBLE_URL = "http://localhost:1234/v1/chat/completions"
MODEL = "gpt-oss-120b"
EXCLUDE_DIRS = {".git", ".venv", "resources"}
SYSTEM_PROMPT = """
あなたは技術文書翻訳者です。
Markdownを日本語に翻訳してください。
やること:
- 日本語のMarkdownを作る
制約:
- Markdown**構文**は一切変更しない
- コードブロックは翻訳しない
- リンクURLは変更しない
- ファイル構造や見出しレベルを保持する
"""
def extract_markdown_content(text: str) -> str:
"""
```markdown ... ``` があれば中身を抽出し、
なければそのまま返す
"""
# ```markdown ... ``` を丸ごとキャプチャ
pattern = re.compile(
r"```markdown\s*\n(?P<content>.*?)\n```",
re.DOTALL
)
match = pattern.search(text)
if match:
# markdownブロックがある場合は中身だけ
return match.group("content").strip()
else:
# 中身だけのパターンはそのまま
return text.strip()
def _scan_existing_files(dst_dir):
"""
dst_dir 以下にすでに存在するファイル名(相対パス)を set にして返す。
例: {'README.md', 'subdir/chapter1.md'}
"""
existing = set()
for root, _, files in os.walk(dst_dir):
rel_root = Path(root).relative_to(dst_dir)
for f in files:
# dst_dir の相対パスだけ保存すれば src_dir と比較しやすい
existing.add(str(rel_root / f))
return existing
def _needs_translation(src_path, dst_path):
"""
翻訳が必要か判定するヘルパー。
- dst が無ければ True
- dst の更新時刻が src より古ければ True
- それ以外は False(スキップ)
"""
if not dst_path.exists():
return True
# タイムスタンプ比較 (src newer -> 再翻訳)
# return src_path.stat().st_mtime > dst_path.stat().st_mtime
return False
# Markdownをセクション単位で分割する
def split_by_heading(md: str) -> List[str]:
"""
Markdownを見出し単位 (#〜######) で分割する。
見出し行は各ブロックの先頭に含まれる。
"""
blocks = re.split(
r'(?=^#{1,6} )',
md,
flags=re.MULTILINE
)
# 空ブロック除去(念のため)
return [b for b in blocks if b.strip()]
def do_translate(text: str) -> str:
payload = {
"model": MODEL,
"messages": [
{"role": "system", "content": SYSTEM_PROMPT},
{"role": "user", "content": text}
],
"temperature": 0.0
}
r = requests.post(OPENAI_COMPATIBLE_URL, json=payload)
return r.json()["choices"][0]["message"]["content"]
def translate_markdown(md: str) -> str:
"""
Markdown全体を分割 → 翻訳 → 結合
"""
blocks = split_by_heading(md)
translated_blocks = []
for i, block in enumerate(blocks, 1):
print(f"Translating block {i}/{len(blocks)}...")
translated = extract_markdown_content(do_translate(block))
translated_blocks.append(translated)
# 改行を保ったまま結合
return "\n\n".join(translated_blocks)
def process_dir(src_dir):
src_dir = Path(src_dir) # 入力は文字列でもPathでも OK
dst_dir = Path(f"{src_dir}(JA)") # 例: Document(JA)
# --------------------------------------------------------------
# (1) すでに翻訳済みのファイルをスキャンしておく
# --------------------------------------------------------------
existing_files = _scan_existing_files(dst_dir)
# ここでは `existing_files` はデバッグやログ出力にも使える
# print("Already translated:", sorted(existing_files))
# --------------------------------------------------------------
# (2) src ディレクトリを走査し、必要なら翻訳 or コピー
# --------------------------------------------------------------
for root, dirs, files in os.walk(src_dir):
# 除外ディレクトリがあれば除く(元コードと同様)
dirs[:] = [d for d in dirs if d not in EXCLUDE_DIRS]
rel_root = Path(root).relative_to(src_dir) # src からの相対パス
out_root = dst_dir / rel_root # 同じ構造で出力先を作る
out_root.mkdir(parents=True, exist_ok=True)
for f in files:
src_path = Path(root) / f
dst_path = out_root / f
# ------------------------------------------------------
# (A) 翻訳対象かどうか判定
# ------------------------------------------------------
if f.lower().endswith(".md"):
# ① ファイルがすでに存在するか?
# → 存在し、かつ src が古い場合はスキップ
if not _needs_translation(src_path, dst_path):
continue
# ② 翻訳実行
print(f"Translating: {src_path}")
text = src_path.read_text(encoding="utf-8")
ja = translate_markdown(text)
dst_path.write_text(ja, encoding="utf-8")
if __name__ == "__main__":
# 翻訳対象のディレクトリを相対パスで指定する
process_dir("DocumentDirectory")
RooCodeで概要をまとめてもらう
プロンプト ドキュメント群の概要をまとめてください
RooCode(Askモード)の回答
ドキュメント群の概要
| ファイル | 主な章・セクション | 要点 |
|---|---|---|
Adventures in Shellcode Obfuscation Part 8 - Shellcode as UUIDs.md |
Converting Shellcode to UUIDs Converting UUIDs to Shellcode Additional Reading / Try It Yourself |
シェルコードを 16 バイト単位の UUID に変換し、UuidFromStringA を使って逆変換する手順とサンプルコードを解説。UUID が 128‑bit ラベルであることから、EDR のヒューリスティック検知を回避しやすい点が強調されている。 |
EDR Evasion How Attackers Evade EDR & How to Detect Them.md |
Attacker tactics (Blinding, Blending, Hiding) CISA red‑team assessment Impact on threat detection Corelight Open NDR |
EDR の 3 大回避手法を概観し、実際の CISA 評価で「EDR に過度に依存」した組織が検知失敗した事例を紹介。ネットワーク可視化 (NDR) が EDR 補完として有効と結論付けている。 |
Ghosts in the Endpoint: How Attackers Evade Modern EDR Solutions.md |
Major EDR evasion techniques Living‑off‑the‑Land (LOLBins) Code injection & process hollowing User‑mode hook bypass In‑memory evasion & obfuscation EDR tampering / disabling |
各回避カテゴリを実例とともに詳細解説。LOLBins の利用やプロセスハローイング、フック除去・直接システムコール、メモリ上だけで完結するファイルレス手法などが網羅されている。 |
How Attackers Bypass EDR: Techniques and Countermeasures.md |
Obfuscation (Recompiling, Encoding/Encrypting) Malicious Action Avoidance / Living‑off‑the‑Land Bypassing detection (Unhooked processes, Kernel‑level ops) Key takeaways |
攻撃側がまず「見た目」を変える(リコンパイル・暗号化)ことから始め、次に正規ツールの濫用やフック回避手法へと移行する流れを示す。Unhooked プロセス生成やカーネルレベルでの実装が高度な回避策として位置付けられる。 |
Introduction to EDR Evasion: API Hooking.md |
API hooking の仕組み Direct system‑call 回避 APC Queue Injection Shellcode loader 手順 |
EDR が API フックで監視する対象と、攻撃側がフックを回避するために ntdll のシステムコールを直接呼び出す手法を解説。さらに APC キュー注入やシェルコードローダーの 4 ステップ(メモリ確保・書き込み・保護変更・実行)を具体例で示す。 |
全体的なまとめ
-
回避の大枠は「Blinding / Tampering」「Blending / Living‑off‑the‑Land」そして「Hiding / In‑memory」という 3 軸に整理できる(
EDR Evasion How Attackers Evade EDR & How to Detect Them.md)。 -
技術的手段は
-
コード・バイナリの変形(再コンパイル、暗号化、UUID 化)← Obfuscation 系列(
Adventures in Shellcode Obfuscation Part 8 - Shellcode as UUIDs.md) -
正規ツールの濫用(LOLBins、PowerShell、WMI 等)← Living‑off‑the‑Land 系列(
Ghosts in the Endpoint…md) -
フック回避(Unhooking、Direct syscalls、APC Queue Injection)← API Hook 系列(
Introduction to EDR Evasion: API Hooking.md) -
メモリ上だけで完結(File‑less、Obfuscation、Shellcode loader)← In‑memory 系列(
Ghosts in the Endpoint…md)。
-
コード・バイナリの変形(再コンパイル、暗号化、UUID 化)← Obfuscation 系列(
-
防御側の提案は、EDR 単体に頼らず ネットワーク可視化 (NDR) と マルチレイヤー検知 を組み合わせること(
EDR Evasion How Attackers Evade EDR & How to Detect Them.md)。 -
実装例としては、UUID → シェルコード変換スニペット(
Adventures in Shellcode Obfuscation Part 8 - Shellcode as UUIDs.md)や APC キュー注入コード(Introduction to EDR Evasion: API Hooking.md)が示されている。
以上で、6 件のマークダウン文書の主要構成と相互関係をまとめました。これにより、各ドキュメントがどの回避技術を扱い、全体としては「コード変形 → 正規ツール濫用 → フック回避 → メモリ上実行」の流れで EDR を迂回していることが把握できます。
概要を理解したうえで翻訳先を読む
Obsidianで読んでみる
まとめ
- 翻訳自動化(translate.py): 全マークダウンをオフラインで日本語化できます
- 知識蓄積・拡張:翻訳済み文書をベースに自分だけのナレッジをまとめた「ノート」を作成できます
- この流れで、英語原典の読解コストを削減しつつ、VS Code とローカル LLM を組み合わせた「翻訳+検索」環境が構築できます。
おわりに
この方法であれば基本的にトークン数の縛りや翻訳回数の上限がありません。
なので、検索や要約を何回も繰り返したり、自動的にレポートを書かせること、検索結果を利用して次は何をすべきかなどのTODOリストを作らせる、RooCodeの本来の使い道であるコード(PoC)を書かせる ということも可能です。
それじゃ今回はここまで。
