[Python] 纯文本查看 复制代码
#!/usr/bin/env python3
"""ZMX crackme keygen: derive 16-byte password from username.
Algorithm location: sub_140001C20 @ 0x140003262..0x140003362
Default params match normal run (dword_14001A05C=1, timing=0, v98=0).
"""
from __future__ import annotations
import argparse
import ctypes
import json
import struct
import sys
import urllib.error
import urllib.request
from pathlib import Path
N10 = 114514
KEY_LEN = 16
_TABLE_BIN = Path(__file__).with_name("tables_14001A080.bin")
TABLE_BLOB = bytearray(_TABLE_BIN.read_bytes() if _TABLE_BIN.exists() else b"\x00" * 256)
if len(TABLE_BLOB) != 256:
raise RuntimeError(f"expected 256-byte table snapshot: {_TABLE_BIN}")
def c_char(value: int) -> int:
"""Simulate MSVC signed char cast (low 8 bits)."""
return ctypes.c_int8(value & 0xFF).value
def c_mod(dividend: int, divisor: int) -> int:
"""Simulate MSVC signed integer remainder (trunc toward zero)."""
if divisor == 0:
return 0
return int(ctypes.c_int(dividend).value % ctypes.c_int(divisor).value)
def _table_dword(odd: bool, index: int) -> int:
base = 0x80 if odd else 0
offset = base + index * 4
return struct.unpack_from("<I", TABLE_BLOB, offset)[0]
def _table_byte(odd: bool, index: int) -> int:
base = 0x80 if odd else 0
return TABLE_BLOB[base + index * 4]
def _pick_char(value: int, *, second_loop: bool) -> int:
index = c_mod(c_char(value), 26)
odd = bool(value & 1)
n10_sub = (N10 & 0xFF) if second_loop else N10
table_val = (_table_dword(odd, index) - n10_sub) & 0xFFFFFFFF
first_byte = _table_byte(odd, index)
if c_char(first_byte - N10) >= 0:
return table_val & 0xFF
return c_mod(-c_char(table_val), 10) + 48
def derive_password(
username: str,
*,
v93: int = 1,
timing: int = 0,
v98: int = 0,
) -> str:
"""Return the 16-byte password for *username*."""
if not username:
raise ValueError("username must not be empty")
if len(username) > 16:
raise ValueError("username must be at most 16 characters (scanf %16s)")
ulen = len(username)
v97 = timing + v98
out = bytearray(KEY_LEN)
v76 = (N10 * v97) & 0xFFFFFFFF
first_mix = c_char(ord(username[0]) + v93)
v77 = (v76 + first_mix) & 0xFFFFFFFF
denom = c_char(v77)
v78 = (v77 - c_mod(N10, denom)) & 0xFFFFFFFF
out[0] = _pick_char(v78, second_loop=False)
v76 = c_char(v93 + c_char(v76))
for index in range(1, KEY_LEN):
v83 = (v76 + ord(username[index % ulen])) & 0xFFFFFFFF
denom = index + c_char(v83)
v84 = (v83 - c_mod(N10, denom)) & 0xFFFFFFFF
out[index] = _pick_char(v84, second_loop=True)
return out.decode("latin1")
def load_tables_from_debugger(url: str = "http://127.0.0.1:3000/") -> bool:
"""Overwrite TABLE_BLOB with live tables from x64dbg MCP plugin."""
body = json.dumps(
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "memory_read",
"arguments": {
"address": "0x14001A080",
"size": 256,
"encoding": "hex",
},
},
}
).encode()
req = urllib.request.Request(
url,
data=body,
headers={"Content-Type": "application/json"},
)
try:
res = json.loads(urllib.request.urlopen(req, timeout=5).read().decode())
except (urllib.error.URLError, TimeoutError, json.JSONDecodeError):
return False
for item in res.get("result", {}).get("content", []):
text = item.get("text", "")
try:
payload = json.loads(text)
except json.JSONDecodeError:
continue
data = bytes.fromhex(payload["data"])
TABLE_BLOB[: len(data)] = data
return True
return False
def main(argv: list[str] | None = None) -> int:
parser = argparse.ArgumentParser(
description="Generate 16-byte crackme password from username.",
)
parser.add_argument("username", help="username entered in crackme")
parser.add_argument(
"--timing",
type=int,
default=0,
choices=range(0, 11),
metavar="[0-10]",
help="GetTickCount delta capped to 0..10 (default: 0)",
)
parser.add_argument(
"--v93",
type=int,
default=1,
help="dword_14001A05C at derivation (default: 1)",
)
parser.add_argument(
"--v98",
type=int,
default=0,
choices=(0, 1),
help="nonzero if qword_14001A040 was set before derivation (default: 0)",
)
parser.add_argument(
"--live-tables",
action="store_true",
help="read ZMX tables from x64dbg @ 127.0.0.1:3000",
)
parser.add_argument(
"--verify",
metavar="EXPECTED",
help="exit 1 unless generated key equals EXPECTED",
)
args = parser.parse_args(argv)
if args.live_tables:
if not load_tables_from_debugger():
print("warning: could not read live tables, using static defaults", file=sys.stderr)
password = derive_password(
args.username,
v93=args.v93,
timing=args.timing,
v98=args.v98,
)
if args.verify is not None and password != args.verify:
print(f"mismatch: got {password!r}, expected {args.verify!r}", file=sys.stderr)
return 1
print(password)
return 0
if __name__ == "__main__":
raise SystemExit(main())