// ccs_obfuscator/include/cipher.h // // SCR-level cipher/decipher pass for ccs_obfuscator algorithm 1. // In-place XTEA ciphering at the level of each edp's edpfix_ and // edpdyn_ vectors. Identity fields (cnmnum_, kwnnum_, edpnum_) and // container shapes (cnms_/kwns_/edps_) are left intact — only the // rule-body payloads are scrambled. This preserves the runtime's // structural walkability for the inverse pass (decipher needs to // reach the same edps), while making decompile output meaningless // because the (kwn, edpnum) references inside edpfix_/edpdyn_ now // point at garbage. // // xtea.h works on std::vector; cppcc's edpfix_ / // edpdyn_ are std::vector (signed long long // in the 64-bit build, signed long in the 32-bit build). On the // 64-bit build the underlying width is identical and we cipher // per-block via memcpy adapter (strict-aliasing-safe). On the // 32-bit build this header static_asserts to fail — XTEA blocks // are 64-bit and tag::Long must be 64-bit too. // // Reversibility: decipher_runtime(cipher_runtime(R, K), K) == R // in the sense of edpfix_/edpdyn_ contents, and therefore the // SCB produced by generateBinaryFromRuntime is byte-identical to // the SCB that would be produced from the un-ciphered runtime. #ifndef CCS_OBFUSCATOR_CIPHER_H_ #define CCS_OBFUSCATOR_CIPHER_H_ #include #include #include #include "SyntaxControlledRuntime.h" #include "xtea.h" namespace ccs_obf { namespace cipher { // Compile-time gate: this header assumes 64-bit tag::Long so each // element of edpfix_/edpdyn_ is exactly one XTEA block. The 32-bit // build (#define _CPPCC_TAG_32BITS_ in cppcc/include/SyntaxControlled // Runtime.h) shrinks tag::Long to 32 bits; algorithm 1 would need a // different block primitive there. static_assert(sizeof(ccsscb::scr::tag::Long) == sizeof(std::uint64_t), "ccs_obf::cipher: requires 64-bit tag::Long (XTEA block size)"); // Encipher one std::vector in place. Each element is one // XTEA block. Per-block adapter: copy the bits into a uint64_t, // cipher, copy back. memcpy avoids strict-aliasing UB that a raw // pointer cast between signed long long* and uint64_t* could trigger // under aggressive optimizer assumptions. inline void encipher_taglong_vector(std::vector& v, const xtea::Key& key) { for (auto& w : v) { std::uint64_t u = 0; std::memcpy(&u, &w, sizeof(u)); xtea::encipher_block(u, key); std::memcpy(&w, &u, sizeof(u)); } } inline void decipher_taglong_vector(std::vector& v, const xtea::Key& key) { for (auto& w : v) { std::uint64_t u = 0; std::memcpy(&u, &w, sizeof(u)); xtea::decipher_block(u, key); std::memcpy(&w, &u, sizeof(u)); } } // Walk runtime.cnms_[cnmnumCurrent_].kwns_.index2data, then each // kwn.edps_.index2data, and cipher edpfix_ + edpdyn_ in place. // Identity fields (cnmnum_/kwnnum_/edpnum_) are left untouched — // only the rule-body payloads are scrambled. The container shape // is preserved so the inverse pass can reach the same edps. inline void encipher_runtime(ccsscb::scr::SyntaxControlledRuntime& runtime, const xtea::Key& key) { auto& currentContext = runtime.cnms_.dataByKey(runtime.cnmnumCurrent_); for (auto& kwn : currentContext.kwns_.index2data) { for (auto& edp : kwn.edps_.index2data) { encipher_taglong_vector(edp.edpfix_, key); encipher_taglong_vector(edp.edpdyn_, key); } } } inline void decipher_runtime(ccsscb::scr::SyntaxControlledRuntime& runtime, const xtea::Key& key) { auto& currentContext = runtime.cnms_.dataByKey(runtime.cnmnumCurrent_); for (auto& kwn : currentContext.kwns_.index2data) { for (auto& edp : kwn.edps_.index2data) { decipher_taglong_vector(edp.edpfix_, key); decipher_taglong_vector(edp.edpdyn_, key); } } } // Structural-validity check used by the algorithm-1 gtest to assert // "decompile fails on ciphered SCR". Decodes each tag in edpfix_ / // edpdyn_ via setlongedf into (kwn, edpnum), checks whether kwn is // in the runtime's existing kwn-number range AND edpnum is in the // existing edp range under that kwn. Returns (total_refs, valid_refs). // For original SCR: valid_refs == total_refs. For ciphered SCR with // 32-cycle XTEA: valid_refs is statistically near zero. // // Note: this validation reads the runtime WITHOUT mutating it. // runtime.kwnLookup() would create kwns on miss; we walk // index2data directly to avoid that. struct EdpRefStats { std::size_t total = 0; std::size_t valid = 0; }; inline EdpRefStats validate_edp_refs( const ccsscb::scr::SyntaxControlledRuntime& runtime) { EdpRefStats stats; auto& currentContext = const_cast(runtime) .cnms_.dataByKey(runtime.cnmnumCurrent_); // Build a kwnnum -> kwn-index map by scanning index2data (read-only). std::vector kwnExists; for (const auto& k : currentContext.kwns_.index2data) { if (k.kwnnum_ < 0) continue; if (static_cast(k.kwnnum_) >= kwnExists.size()) kwnExists.resize(static_cast(k.kwnnum_) + 1, false); kwnExists[static_cast(k.kwnnum_)] = true; } // Per-kwn: count of edps available, indexed by kwnnum. std::vector edpCount(kwnExists.size(), 0); for (const auto& k : currentContext.kwns_.index2data) { if (k.kwnnum_ < 0) continue; edpCount[static_cast(k.kwnnum_)] = k.edps_.index2data.size(); } auto check_one = [&](ccsscb::scr::tag::Long rrr) { ++stats.total; ccsscb::scr::tag::Long k = 0, n = 0; ccsscb::scr::tag::setlongedf(&rrr, k, n); if (k < 0 || n < 0) return; auto ku = static_cast(k); auto nu = static_cast(n); if (ku >= kwnExists.size() || !kwnExists[ku]) return; if (nu >= edpCount[ku]) return; ++stats.valid; }; for (const auto& k : currentContext.kwns_.index2data) { for (const auto& e : k.edps_.index2data) { for (const auto& w : e.edpfix_) check_one(w); for (const auto& w : e.edpdyn_) check_one(w); } } return stats; } } // namespace cipher } // namespace ccs_obf #endif // CCS_OBFUSCATOR_CIPHER_H_