diff --git a/.gitignore b/.gitignore index 163d183..f36892d 100644 --- a/.gitignore +++ b/.gitignore @@ -42,3 +42,11 @@ memory/* # Allow tracking of specific SOPs !memory/web_setup_sop.md !memory/autonomous_operation_sop.md + +# ljqCtrl related tools +!memory/ljqCtrl.py +!memory/ljqCtrl_sop.md + +# mem_scanner related tools +!memory/mem_scanner.py +!memory/mem_scanner_sop.md \ No newline at end of file diff --git a/memory/mem_scanner.py b/memory/mem_scanner.py new file mode 100644 index 0000000..f1e1836 --- /dev/null +++ b/memory/mem_scanner.py @@ -0,0 +1,117 @@ +import ctypes +import ctypes.wintypes +import argparse +import yara +import sys +import os +import json + +# Define WinAPI Types for 64-bit compatibility +PHANDLE = ctypes.wintypes.HANDLE +LPCVOID = ctypes.c_void_p +LPVOID = ctypes.c_void_p +SIZE_T = ctypes.c_size_t + +class MEMORY_BASIC_INFORMATION(ctypes.Structure): + _fields_ = [ + ("BaseAddress", LPVOID), + ("AllocationBase", LPVOID), + ("AllocationProtect", ctypes.wintypes.DWORD), + ("RegionSize", SIZE_T), + ("State", ctypes.wintypes.DWORD), + ("Protect", ctypes.wintypes.DWORD), + ("Type", ctypes.wintypes.DWORD), + ] + +# Explicitly setup kernel32 functions with precise types +k32 = ctypes.windll.kernel32 +k32.OpenProcess.argtypes = [ctypes.wintypes.DWORD, ctypes.wintypes.BOOL, ctypes.wintypes.DWORD] +k32.OpenProcess.restype = PHANDLE + +k32.VirtualQueryEx.argtypes = [PHANDLE, LPCVOID, ctypes.POINTER(MEMORY_BASIC_INFORMATION), SIZE_T] +k32.VirtualQueryEx.restype = SIZE_T + +k32.ReadProcessMemory.argtypes = [PHANDLE, LPCVOID, LPVOID, SIZE_T, ctypes.POINTER(SIZE_T)] +k32.ReadProcessMemory.restype = ctypes.wintypes.BOOL + +def is_hex_pattern(pattern): + clean = pattern.replace(" ", "").replace("??", "") + return all(c in "0123456789abcdefABCDEF" for c in clean) and (len(clean) % 2 == 0 or "??" in pattern) + +def build_rules(pattern, mode='auto'): + use_hex = (mode == 'hex') or (mode == 'auto' and is_hex_pattern(pattern)) + if use_hex: + rule_text = f'rule CustomSearch {{ strings: $h = {{ {pattern.strip()} }} condition: $h }}' + else: + escaped = pattern.replace('\\', '\\\\').replace('"', '\\"') + rule_text = f'rule CustomSearch {{ strings: $s = "{escaped}" ascii wide condition: $s }}' + return yara.compile(source=rule_text) + +def format_llm_context(data, offset, base_addr, length=64): + start = max(0, offset - length) + end = min(len(data), offset + length + 16) + chunk = data[start:end] + abs_addr = (base_addr if base_addr else 0) + offset + return { + "address": hex(abs_addr), + "offset": hex(offset), + "hex": chunk.hex(), + "ascii": "".join(chr(b) if 32 <= b <= 126 else "." for b in chunk), + "hit_pos": offset - start + } + +def scan_memory(pid, pattern, context_size=256, mode='auto', llm_mode=False): + rules = build_rules(pattern, mode) + h_proc = k32.OpenProcess(0x0400 | 0x0010, False, pid) + if not h_proc: + # OpenProcess failed: might be system process or higher integrity level + return [f"Error: Cannot open process {pid}. (ErrorCode: {k32.GetLastError()})"] + + results = [] + curr_addr = 0 + mbi = MEMORY_BASIC_INFORMATION() + + # Range for 64-bit user space + max_addr = 0x7FFFFFFFFFFF + + while curr_addr < max_addr: + # Use cast to ensure pointer type is correct for 64-bit + res = k32.VirtualQueryEx(h_proc, ctypes.cast(curr_addr, LPCVOID), ctypes.byref(mbi), ctypes.sizeof(mbi)) + if res == 0: break + + # MEM_COMMIT = 0x1000, PAGE_READABLE bitmask + if mbi.State == 0x1000 and (mbi.Protect & 0xEE): # 0xEE covers common readable flags + buf = ctypes.create_string_buffer(mbi.RegionSize) + read = SIZE_T(0) + if k32.ReadProcessMemory(h_proc, ctypes.cast(mbi.BaseAddress, LPCVOID), buf, mbi.RegionSize, ctypes.byref(read)): + data = buf.raw[:read.value] + for match in rules.match(data=data): + for inst in match.strings: + offset = inst.instances[0].offset + matched_data = inst.instances[0].matched_data + base = mbi.BaseAddress if mbi.BaseAddress else 0 + if llm_mode: + results.append(format_llm_context(data, offset, base)) + else: + results.append(f"Addr: {hex(base+offset)}\nHex: {data[max(0,offset-16):offset+32].hex()}") + + # Update address using the region size + next_addr = (mbi.BaseAddress if mbi.BaseAddress else 0) + mbi.RegionSize + if next_addr <= curr_addr: break + curr_addr = next_addr + + k32.CloseHandle(h_proc) + return results + +if __name__ == "__main__": + parser = argparse.ArgumentParser() + parser.add_argument("pid", type=int) + parser.add_argument("pattern", type=str) + parser.add_argument("--mode", default='auto') + parser.add_argument("--llm", action="store_true") + args = parser.parse_args() + try: + res = scan_memory(args.pid, args.pattern, mode=args.mode, llm_mode=args.llm) + print(json.dumps(res, indent=2) if args.llm else f"Matches: {len(res)}") + except Exception as e: + print(f"Error: {e}") \ No newline at end of file diff --git a/memory/mem_scanner_sop.md b/memory/mem_scanner_sop.md new file mode 100644 index 0000000..4aad196 --- /dev/null +++ b/memory/mem_scanner_sop.md @@ -0,0 +1,33 @@ +# Memory Scanner SOP + +## 1. 快速开始 +内存特征搜索工具,支持 Hex (CE 风格) 和 字符串匹配。特别提供 LLM 模式,方便大模型分析内存上下文。 + +**Python 调用方式:** +```python +import sys +sys.path.append('../memory') # 直接挂载工具目录 +from mem_scanner import scan_memory + +# 示例:搜索特定 Hex 特征码,开启 llm_mode 以获取上下文 +results = scan_memory(pid, "48 8b ?? ?? 00", mode="hex", llm_mode=True) +``` + +**CLI:** +```powershell +# 基础搜索 +python ../memory/mem_scanner.py "pattern" --mode string + +# LLM 增强模式(输出包含上下文的 JSON,推荐) +python ../memory/mem_scanner.py "pattern" --llm +``` + +## 2. 典型场景:结构体或关键数据定位 +1. 确定目标数据的前导特征或已知常量(如特定的 Header 或 Magic Number)。 +2. 在目标进程中搜索该特征: + `scan_memory(pid, "4D 5A 90 00", mode="hex", llm_mode=True)` +3. 分析返回的 JSON 中 `context` 字段,查看目标地址前后的原始字节及 ASCII 预览。 + +## 3. 注意事项 +- **权限**: 并非强制要求管理员权限,但需具备对目标进程的 `PROCESS_QUERY_INFORMATION` 和 `PROCESS_VM_READ` 权限。 +- **效率**: 搜索大块内存时,尽量提供更唯一的特征码以减少误报。 \ No newline at end of file