Files
NeoTachyon/tachyon/files/usr/lib/subscription/crypt4.uc
T

356 lines
13 KiB
Ucode

#!/usr/bin/env ucode
let fs = require("fs");
let common = require("core.common");
let as_string = common.as_string;
let shell_quote = common.shell_quote;
function trim(str) {
return replace(as_string(str), /^[\s\r\n]+|[\s\r\n]+$/g, "");
}
function is_crypt4(value) {
value = lc(trim(value));
return match(value, /^(happ:\/\/)?crypt4\//) != null;
}
function strip_prefix(value) {
value = trim(value);
if (match(value, /^(happ:\/\/)?crypt4\//i))
return replace(value, /^(happ:\/\/)?crypt4\//i, "");
return value;
}
function hex_to_bytes(h) {
let res = "";
for (let i = 0; i < length(h); i += 2)
res += chr(hex(substr(h, i, 2)));
return res;
}
function sha256_hex(str) {
let p = fs.popen("printf %s " + shell_quote(str) + " | sha256sum 2>/dev/null", "r");
if (!p)
return "";
let out = trim(p.read("all") || "");
p.close();
return split(out, /[ \t]+/)[0];
}
// Inverted S-Box for AES
let InvSbox = [
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
];
// Forward S-box for key expansion
let Sbox = [
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
];
let Rcon = [0x00, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36];
let Mul9 = [], Mul11 = [], Mul13 = [], Mul14 = [];
function gmul(a, b) {
let p = 0;
for (let c = 0; c < 8; c++) {
if ((b & 1) != 0) p ^= a;
let hi = (a & 0x80);
a = (a << 1) & 0xff;
if (hi != 0) a ^= 0x1b;
b >>= 1;
}
return p;
}
for (let i = 0; i < 256; i++) {
push(Mul9, gmul(i, 9));
push(Mul11, gmul(i, 11));
push(Mul13, gmul(i, 13));
push(Mul14, gmul(i, 14));
}
function key_expansion(key_bytes) {
let Nk = length(key_bytes) / 4;
let Nr = Nk + 6;
let w = [];
for (let i = 0; i < length(key_bytes); i += 4)
push(w, [ord(key_bytes, i), ord(key_bytes, i + 1), ord(key_bytes, i + 2), ord(key_bytes, i + 3)]);
for (let i = Nk; i < 4 * (Nr + 1); i++) {
let temp = [w[i - 1][0], w[i - 1][1], w[i - 1][2], w[i - 1][3]];
if (i % Nk == 0) {
let t0 = temp[0];
temp[0] = Sbox[temp[1]] ^ Rcon[i / Nk];
temp[1] = Sbox[temp[2]];
temp[2] = Sbox[temp[3]];
temp[3] = Sbox[t0];
} else if (Nk > 6 && (i % Nk == 4)) {
temp[0] = Sbox[temp[0]];
temp[1] = Sbox[temp[1]];
temp[2] = Sbox[temp[2]];
temp[3] = Sbox[temp[3]];
}
push(w, [w[i - Nk][0] ^ temp[0], w[i - Nk][1] ^ temp[1], w[i - Nk][2] ^ temp[2], w[i - Nk][3] ^ temp[3]]);
}
return { w, Nr };
}
function inv_cipher_block(block, exp) {
let w = exp.w;
let Nr = exp.Nr;
let s = [];
for (let r = 0; r < 4; r++) {
s[r] = [];
for (let c = 0; c < 4; c++)
s[r][c] = ord(block, r + 4 * c);
}
for (let c = 0; c < 4; c++)
for (let r = 0; r < 4; r++)
s[r][c] ^= w[Nr * 4 + c][r];
for (let round = Nr - 1; round >= 1; round--) {
let t1 = s[1][3]; s[1][3] = s[1][2]; s[1][2] = s[1][1]; s[1][1] = s[1][0]; s[1][0] = t1;
let t20 = s[2][0], t21 = s[2][1]; s[2][0] = s[2][2]; s[2][1] = s[2][3]; s[2][2] = t20; s[2][3] = t21;
let t3 = s[3][0]; s[3][0] = s[3][1]; s[3][1] = s[3][2]; s[3][2] = s[3][3]; s[3][3] = t3;
for (let r = 0; r < 4; r++)
for (let c = 0; c < 4; c++)
s[r][c] = InvSbox[s[r][c]];
for (let c = 0; c < 4; c++)
for (let r = 0; r < 4; r++)
s[r][c] ^= w[round * 4 + c][r];
for (let c = 0; c < 4; c++) {
let s0 = s[0][c], s1 = s[1][c], s2 = s[2][c], s3 = s[3][c];
s[0][c] = Mul14[s0] ^ Mul11[s1] ^ Mul13[s2] ^ Mul9[s3];
s[1][c] = Mul9[s0] ^ Mul14[s1] ^ Mul11[s2] ^ Mul13[s3];
s[2][c] = Mul13[s0] ^ Mul9[s1] ^ Mul14[s2] ^ Mul11[s3];
s[3][c] = Mul11[s0] ^ Mul13[s1] ^ Mul9[s2] ^ Mul14[s3];
}
}
let t1 = s[1][3]; s[1][3] = s[1][2]; s[1][2] = s[1][1]; s[1][1] = s[1][0]; s[1][0] = t1;
let t20 = s[2][0], t21 = s[2][1]; s[2][0] = s[2][2]; s[2][1] = s[2][3]; s[2][2] = t20; s[2][3] = t21;
let t3 = s[3][0]; s[3][0] = s[3][1]; s[3][1] = s[3][2]; s[3][2] = s[3][3]; s[3][3] = t3;
for (let r = 0; r < 4; r++)
for (let c = 0; c < 4; c++)
s[r][c] = InvSbox[s[r][c]];
for (let c = 0; c < 4; c++)
for (let r = 0; r < 4; r++)
s[r][c] ^= w[c][r];
let out = "";
for (let c = 0; c < 4; c++)
for (let r = 0; r < 4; r++)
out += chr(s[r][c]);
return out;
}
function aes_cbc_decrypt(ciphertext, key_bytes, iv) {
if (length(ciphertext) % 16 != 0 || length(key_bytes) < 16 || length(iv) != 16)
return null;
let exp = key_expansion(key_bytes);
let plaintext = "";
let prev = iv;
for (let i = 0; i < length(ciphertext); i += 16) {
let block = substr(ciphertext, i, 16);
let dec = inv_cipher_block(block, exp);
let xored = "";
for (let j = 0; j < 16; j++)
xored += chr(ord(dec, j) ^ ord(prev, j));
plaintext += xored;
prev = block;
}
// PKCS#7 unpadding validation
let pad = ord(plaintext, length(plaintext) - 1);
if (pad < 1 || pad > 16 || length(plaintext) < pad)
return null;
for (let i = length(plaintext) - pad; i < length(plaintext); i++) {
if (ord(plaintext, i) != pad)
return null;
}
return substr(plaintext, 0, length(plaintext) - pad);
}
function is_valid_plaintext(str) {
if (str == null || length(str) == 0)
return false;
let len = length(str);
for (let i = 0; i < len; i++) {
let ch = ord(substr(str, i, 1));
if (ch < 32 && ch != 9 && ch != 10 && ch != 13)
return false;
}
return true;
}
function try_openssl_decrypt(cipher, key_hex, iv_hex, cipher_type) {
let tmp_in = "/tmp/tachyon-c4-in." + clock()[0] + "." + clock()[1];
if (fs.writefile(tmp_in, cipher) == null)
return null;
let cmd = "openssl enc -d -" + cipher_type + " -in " + shell_quote(tmp_in) + " -K " + shell_quote(key_hex) + " -iv " + shell_quote(iv_hex) + " 2>/dev/null";
let p = fs.popen(cmd, "r");
let out = p ? p.read("all") : null;
let code = p ? p.close() : -1;
fs.unlink(tmp_in);
if (code != 0 || out == null || length(out) == 0 || !is_valid_plaintext(out))
return null;
return out;
}
function get_system_device_hwid() {
let mac = trim(as_string(fs.readfile("/sys/class/net/eth0/address")));
if (mac == "")
mac = trim(as_string(fs.readfile("/sys/class/net/br-lan/address")));
if (mac == "") {
let mid = trim(as_string(fs.readfile("/etc/machine-id")));
if (mid != "") return mid;
}
if (mac == "") return "";
let p = fs.popen("printf %s " + shell_quote(mac) + " | md5sum 2>/dev/null", "r");
if (!p) return "";
let out = trim(p.read("all") || "");
p.close();
let hash = split(out, /[ \t]+/)[0];
if (length(hash) >= 16)
return substr(hash, 0, 4) + "-" + substr(hash, 4, 4) + "-" + substr(hash, 8, 4) + "-" + substr(hash, 12, 4);
return "";
}
function decrypt_crypt4(payload, ...secret_keys) {
payload = trim(as_string(payload));
if (match(payload, /^(happ:\/\/)?crypt4\//i))
payload = replace(payload, /^(happ:\/\/)?crypt4\//i, "");
payload = replace(payload, /[\r\n\t ]/g, "");
payload = replace(payload, /-/g, "+");
payload = replace(payload, /_/g, "/");
let rem = length(payload) % 4;
if (rem == 1)
return null;
if (rem > 1) {
for (let i = 0; i < 4 - rem; i++)
payload += "=";
}
let binary = b64dec(payload);
if (binary == null || length(binary) < 32)
return null;
let iv = substr(binary, 0, 16);
let cipher = substr(binary, 16);
if (length(cipher) % 16 != 0)
return null;
let keys = [];
let seen_keys = {};
function add_key(k) {
if (type(k) == "array") {
for (let item in k)
add_key(item);
} else {
k = trim(as_string(k));
if (k != "" && !seen_keys[k]) {
seen_keys[k] = true;
push(keys, k);
}
}
}
for (let k in secret_keys)
add_key(k);
let dev_hwid = get_system_device_hwid();
if (dev_hwid != "")
add_key(dev_hwid);
add_key("HappDefaultSalt");
let iv_hex = common.bytes_to_hex(iv);
for (let k in keys) {
let h = sha256_hex(k);
if (length(h) != 64)
continue;
// 1. Try OpenSSL fast path if available
let open_res = try_openssl_decrypt(cipher, h, iv_hex, "aes-256-cbc");
if (open_res != null)
return open_res;
open_res = try_openssl_decrypt(cipher, substr(h, 0, 32), iv_hex, "aes-128-cbc");
if (open_res != null)
return open_res;
// 2. Pure ucode fallback
let full_key = hex_to_bytes(h);
let res = aes_cbc_decrypt(cipher, full_key, iv);
if (res != null && is_valid_plaintext(res))
return res;
res = aes_cbc_decrypt(cipher, substr(full_key, 0, 16), iv);
if (res != null && is_valid_plaintext(res))
return res;
}
return null;
}
function module_exports() {
return {
is_crypt4,
strip_prefix,
decrypt: decrypt_crypt4
};
}
if ((sourcepath(1) != null && sourcepath(1) != "") || ARGV[0] == null)
return module_exports();
let mode = ARGV[0] || "";
if (mode == "is-crypt4")
exit(is_crypt4(ARGV[1]) ? 0 : 1);
else if (mode == "decrypt") {
let dec = decrypt_crypt4(ARGV[1], ARGV[2], ARGV[3]);
if (dec == null)
exit(1);
print(dec);
}