1
0
mirror of git://f0xx.org/android_cast synced 2026-07-29 04:18:09 +03:00

docs, obfuscation

This commit is contained in:
Anton Afanasyeu
2026-06-19 22:41:27 +02:00
parent 55cdcb49bf
commit b9f94fe005
57 changed files with 3229 additions and 105 deletions

View File

@@ -100,3 +100,19 @@ find_library(log-lib log)
find_library(android-lib android)
find_library(dl-lib dl)
target_link_libraries(androidcast_codecs ${log-lib} ${android-lib} ${dl-lib})
# Per-account obfuscated entitlement flags (separate .so; safe to omit on load failure).
set(OBFUSCATE_ROOT ${CMAKE_SOURCE_DIR}/obfuscate)
add_library(androidcast_entitlements SHARED
${NDK_BRIDGE}/jni/entitlements_jni.c
${OBFUSCATE_ROOT}/obfuscate.c
${OBFUSCATE_ROOT}/obfuscate_file.c
)
target_include_directories(androidcast_entitlements PRIVATE
${OBFUSCATE_ROOT}
)
target_compile_options(androidcast_entitlements PRIVATE -std=c11 -Wall -Wextra)
target_link_libraries(androidcast_entitlements ${log-lib})

103
ndk/jni/entitlements_jni.c Normal file
View File

@@ -0,0 +1,103 @@
#include <jni.h>
#include <stdint.h>
#include <stdlib.h>
#include "obfuscate.h"
#include "obfuscate_file.h"
static jint throw_illegal_state(JNIEnv *env, const char *msg)
{
jclass cls = (*env)->FindClass(env, "java/lang/IllegalStateException");
if (cls != NULL)
(*env)->ThrowNew(env, cls, msg);
return OBF_ERR_NULL;
}
JNIEXPORT jint JNICALL
Java_com_foxx_androidcast_entitlements_EntitlementNativeBridge_nativeReadFrom(
JNIEnv *env, jclass clazz, jstring path, jlong key, jlongArray out_val)
{
const char *cpath;
uint64_t decoded = 0;
int32_t rv;
jlong slot;
(void)clazz;
if (path == NULL || out_val == NULL)
return throw_illegal_state(env, "path or out_val is null");
cpath = (*env)->GetStringUTFChars(env, path, NULL);
if (cpath == NULL)
return OBF_ERR_IO;
rv = read_from(cpath, (uint64_t)key, &decoded);
(*env)->ReleaseStringUTFChars(env, path, cpath);
if (rv != OBF_OK)
return rv;
slot = (jlong)decoded;
(*env)->SetLongArrayRegion(env, out_val, 0, 1, &slot);
return OBF_OK;
}
JNIEXPORT jint JNICALL
Java_com_foxx_androidcast_entitlements_EntitlementNativeBridge_nativeWriteTo(
JNIEnv *env, jclass clazz, jstring path, jlong val, jlong key)
{
const char *cpath;
int32_t rv;
(void)clazz;
if (path == NULL)
return throw_illegal_state(env, "path is null");
cpath = (*env)->GetStringUTFChars(env, path, NULL);
if (cpath == NULL)
return OBF_ERR_IO;
rv = write_to(cpath, (uint64_t)val, (uint64_t)key);
(*env)->ReleaseStringUTFChars(env, path, cpath);
return rv;
}
JNIEXPORT jint JNICALL
Java_com_foxx_androidcast_entitlements_EntitlementNativeBridge_nativeDecode(
JNIEnv *env, jclass clazz, jlong encoded_val, jlong key, jlongArray out_val)
{
uint64_t decoded = 0;
int32_t rv;
jlong slot;
(void)clazz;
if (out_val == NULL)
return throw_illegal_state(env, "out_val is null");
rv = decode((uint64_t)encoded_val, (uint64_t)key, &decoded);
if (rv != OBF_OK)
return rv;
slot = (jlong)decoded;
(*env)->SetLongArrayRegion(env, out_val, 0, 1, &slot);
return OBF_OK;
}
JNIEXPORT jint JNICALL
Java_com_foxx_androidcast_entitlements_EntitlementNativeBridge_nativeEncode(
JNIEnv *env, jclass clazz, jlong val, jlong key, jlongArray out_encoded)
{
uint64_t encoded = 0;
int32_t rv;
jlong slot;
(void)clazz;
if (out_encoded == NULL)
return throw_illegal_state(env, "out_encoded is null");
rv = encode((uint64_t)val, (uint64_t)key, &encoded);
if (rv != OBF_OK)
return rv;
slot = (jlong)encoded;
(*env)->SetLongArrayRegion(env, out_encoded, 0, 1, &slot);
return OBF_OK;
}

8
ndk/obfuscate/README.md Normal file
View File

@@ -0,0 +1,8 @@
# Entitlements obfuscation (C)
Shared by `libandroidcast_entitlements.so` and the archived spike under `reference-design/obfuscate_entitlements/`.
- `encode` / `decode` — 40-bit payload + 24-bit verification tag, keyed by per-account `uint64_t` **key** (not wall clock).
- `write_to` / `read_from` — file layout: 32-bit dummy header, N×uint64_t payloads, 32-bit dummy tail.
Reference demo: `tmp/obfuscate_test/` (standalone; not wired into the main app).

122
ndk/obfuscate/obfuscate.c Normal file
View File

@@ -0,0 +1,122 @@
#include "obfuscate.h"
#include <stddef.h>
static uint16_t rotl16(uint16_t x, unsigned int n)
{
n &= 15u;
return (uint16_t)((x << n) | (x >> (16u - n)));
}
static uint64_t rotl40(uint64_t x, unsigned int n)
{
n %= OBF_PAYLOAD_BITS;
x &= OBF_PAYLOAD_MASK;
if (n == 0u)
return x;
return ((x << n) | (x >> (OBF_PAYLOAD_BITS - n))) & OBF_PAYLOAD_MASK;
}
static uint64_t rotr40(uint64_t x, unsigned int n)
{
n %= OBF_PAYLOAD_BITS;
x &= OBF_PAYLOAD_MASK;
if (n == 0u)
return x;
return ((x >> n) | (x << (OBF_PAYLOAD_BITS - n))) & OBF_PAYLOAD_MASK;
}
/* Lightweight seed mixer — same output for same seed on any platform. */
static uint64_t derive_key(uint64_t seed)
{
uint64_t x = seed ^ 0xA5A5A5A5A5A5A5A5ULL;
x ^= x >> 33;
x *= 0xFF51AFD7ED558CCDULL;
x ^= x >> 33;
x *= 0xC4CEB9FE1A85EC53ULL;
x ^= x >> 33;
return x & OBF_PAYLOAD_MASK;
}
static unsigned int derive_rotation(uint64_t seed)
{
uint64_t mix = seed * 0x9E3779B97F4A7C15ULL;
return (unsigned int)((mix >> 59) + 1u); /* 1..8 */
}
/*
* 24-bit FEC-style tag: four 16-bit shards XOR-mixed with key and seed.
* Detects single-bit flips and wrong seed with high probability.
*/
static uint32_t compute_tag(uint64_t val, uint64_t seed)
{
uint64_t k = derive_key(seed);
uint64_t a = (val & 0xFFFFULL) ^ (k & 0xFFFFULL);
uint64_t b = ((val >> 16) & 0xFFFFULL) ^ ((k >> 16) & 0xFFFFULL);
uint64_t c = ((val >> 32) & 0xFFFFULL) ^ ((k >> 32) & 0xFFFFULL);
uint64_t d = ((val >> 48) & 0xFFFFULL) ^ ((k >> 48) & 0xFFFFULL);
uint64_t p1 = a ^ rotl16((uint16_t)b, 3) ^ c;
uint64_t p2 = b ^ rotl16((uint16_t)c, 7) ^ d;
uint64_t p3 = c ^ rotl16((uint16_t)d, 11) ^ a;
uint64_t mix = (p1 * 0x01000193ULL) ^ (p2 << 16) ^ (p3 << 8);
mix ^= seed;
mix *= 0x85EBCA77C2B2AE63ULL;
mix ^= mix >> 33;
return (uint32_t)(mix & 0xFFFFFFULL);
}
static uint64_t obfuscate(uint64_t val, uint64_t seed)
{
uint64_t key = derive_key(seed);
unsigned int rot = derive_rotation(seed);
return rotl40(val & OBF_PAYLOAD_MASK, rot) ^ key;
}
static uint64_t deobfuscate(uint64_t obscured, uint64_t seed)
{
uint64_t key = derive_key(seed);
unsigned int rot = derive_rotation(seed);
return rotr40(obscured ^ key, rot);
}
int32_t encode(uint64_t val, uint64_t seed, uint64_t *out)
{
uint64_t packed;
uint32_t tag;
if (out == NULL)
return OBF_ERR_NULL;
if (val > OBF_PAYLOAD_MASK)
return OBF_ERR_RANGE;
tag = compute_tag(val, seed);
packed = ((uint64_t)tag << OBF_PAYLOAD_BITS) | obfuscate(val, seed);
*out = packed;
return OBF_OK;
}
int32_t decode(uint64_t encoded_val, uint64_t seed, uint64_t *out)
{
uint32_t stored_tag;
uint32_t expected_tag;
uint64_t obscured;
uint64_t val;
if (out == NULL)
return OBF_ERR_NULL;
stored_tag = (uint32_t)(encoded_val >> OBF_PAYLOAD_BITS);
obscured = encoded_val & OBF_PAYLOAD_MASK;
val = deobfuscate(obscured, seed);
expected_tag = compute_tag(val, seed);
if (stored_tag != expected_tag)
return OBF_ERR_VERIFY;
*out = val;
return OBF_OK;
}

30
ndk/obfuscate/obfuscate.h Normal file
View File

@@ -0,0 +1,30 @@
#ifndef OBFUSCATE_H
#define OBFUSCATE_H
#include <stdint.h>
/* Payload uses lower 40 bits; upper 24 bits hold verification tag. */
#define OBF_PAYLOAD_BITS 40u
#define OBF_PAYLOAD_MASK ((1ULL << OBF_PAYLOAD_BITS) - 1ULL)
/* Return codes */
#define OBF_OK 0
#define OBF_ERR_NULL -1 /* out pointer is NULL */
#define OBF_ERR_RANGE -2 /* val exceeds encodable payload width */
#define OBF_ERR_VERIFY -3 /* tag mismatch (wrong seed or corrupted data) */
/*
* Encode uint64_t payload (e.g. flag bits) using seed-derived keystream.
* seed is typically unix time in whole seconds shared by encoder and decoder.
*
* On success writes single uint64_t to *out and returns OBF_OK.
*/
int32_t encode(uint64_t val, uint64_t seed, uint64_t *out);
/*
* Decode and verify. seed must match the value used at encode time.
* On success writes recovered payload to *out and returns OBF_OK.
*/
int32_t decode(uint64_t encoded_val, uint64_t seed, uint64_t *out);
#endif /* OBFUSCATE_H */

View File

@@ -0,0 +1,215 @@
#include "obfuscate_file.h"
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
static uint32_t dummy_u32(uint64_t seed, uint32_t salt)
{
uint64_t x = seed ^ ((uint64_t)salt * 0x9E3779B97F4A7C15ULL);
x ^= x >> 33;
x *= 0xFF51AFD7ED558CCDULL;
x ^= x >> 33;
return (uint32_t)(x ^ (x >> 32));
}
static void put_u32_le(uint8_t *dst, uint32_t v)
{
dst[0] = (uint8_t)(v);
dst[1] = (uint8_t)(v >> 8);
dst[2] = (uint8_t)(v >> 16);
dst[3] = (uint8_t)(v >> 24);
}
static void put_u64_le(uint8_t *dst, uint64_t v)
{
unsigned int i;
for (i = 0; i < 8u; i++)
dst[i] = (uint8_t)(v >> (8u * i));
}
static uint32_t get_u32_le(const uint8_t *src)
{
return (uint32_t)src[0] | ((uint32_t)src[1] << 8) | ((uint32_t)src[2] << 16) |
((uint32_t)src[3] << 24);
}
static uint64_t get_u64_le(const uint8_t *src)
{
uint64_t v = 0;
unsigned int i;
for (i = 0; i < 8u; i++)
v |= (uint64_t)src[i] << (8u * i);
return v;
}
int32_t obf_file_payload_count(long file_size, size_t *count_out)
{
long payload_bytes;
if (file_size < 0)
return OBF_ERR_IO;
if ((unsigned long)file_size < OBF_FILE_OVERHEAD + sizeof(uint64_t))
return OBF_ERR_FORMAT;
payload_bytes = file_size - (long)OBF_FILE_OVERHEAD;
if (payload_bytes % (long)sizeof(uint64_t) != 0)
return OBF_ERR_FORMAT;
*count_out = (size_t)payload_bytes / sizeof(uint64_t);
return OBF_OK;
}
static int32_t write_encoded_file(const char *path, const uint64_t *encoded, size_t count,
uint64_t seed)
{
FILE *fp;
uint8_t hdr[OBF_FILE_HDR_SIZE];
uint8_t tail[OBF_FILE_TAIL_SIZE];
uint8_t block[sizeof(uint64_t)];
size_t i;
if (path == NULL || encoded == NULL || count == 0)
return OBF_ERR_NULL;
fp = fopen(path, "wb");
if (fp == NULL)
return OBF_ERR_IO;
put_u32_le(hdr, dummy_u32(seed, 0xA11CEu));
if (fwrite(hdr, 1, sizeof(hdr), fp) != sizeof(hdr)) {
fclose(fp);
return OBF_ERR_IO;
}
for (i = 0; i < count; i++) {
put_u64_le(block, encoded[i]);
if (fwrite(block, 1, sizeof(block), fp) != sizeof(block)) {
fclose(fp);
return OBF_ERR_IO;
}
}
put_u32_le(tail, dummy_u32(seed, 0x7A1Lu));
if (fwrite(tail, 1, sizeof(tail), fp) != sizeof(tail)) {
fclose(fp);
return OBF_ERR_IO;
}
if (fclose(fp) != 0)
return OBF_ERR_IO;
return OBF_OK;
}
int32_t write_many_to(const char *path, const uint64_t *vals, size_t count, uint64_t seed)
{
uint64_t *encoded;
size_t i;
int32_t rv;
if (path == NULL || vals == NULL || count == 0)
return OBF_ERR_NULL;
encoded = (uint64_t *)malloc(count * sizeof(uint64_t));
if (encoded == NULL)
return OBF_ERR_IO;
for (i = 0; i < count; i++) {
rv = encode(vals[i], seed, &encoded[i]);
if (rv != OBF_OK) {
free(encoded);
return rv;
}
}
rv = write_encoded_file(path, encoded, count, seed);
free(encoded);
return rv;
}
int32_t write_to(const char *path, uint64_t val, uint64_t seed)
{
return write_many_to(path, &val, 1, seed);
}
int32_t read_many_from(const char *path, uint64_t seed, uint64_t *out_vals, size_t out_cap,
size_t *out_count)
{
FILE *fp;
long file_size;
size_t count;
size_t i;
uint8_t hdr[OBF_FILE_HDR_SIZE];
uint8_t tail[OBF_FILE_TAIL_SIZE];
uint8_t block[sizeof(uint64_t)];
uint64_t encoded;
uint64_t decoded;
int32_t rv;
if (path == NULL || out_vals == NULL || out_count == NULL)
return OBF_ERR_NULL;
fp = fopen(path, "rb");
if (fp == NULL)
return OBF_ERR_IO;
if (fseek(fp, 0, SEEK_END) != 0) {
fclose(fp);
return OBF_ERR_IO;
}
file_size = ftell(fp);
if (file_size < 0 || fseek(fp, 0, SEEK_SET) != 0) {
fclose(fp);
return OBF_ERR_IO;
}
rv = obf_file_payload_count(file_size, &count);
if (rv != OBF_OK) {
fclose(fp);
return rv;
}
if (count > out_cap) {
fclose(fp);
return OBF_ERR_OVERFLOW;
}
if (fread(hdr, 1, sizeof(hdr), fp) != sizeof(hdr)) {
fclose(fp);
return OBF_ERR_IO;
}
(void)get_u32_le(hdr);
for (i = 0; i < count; i++) {
if (fread(block, 1, sizeof(block), fp) != sizeof(block)) {
fclose(fp);
return OBF_ERR_IO;
}
encoded = get_u64_le(block);
rv = decode(encoded, seed, &decoded);
if (rv != OBF_OK) {
fclose(fp);
return rv;
}
out_vals[i] = decoded;
}
if (fread(tail, 1, sizeof(tail), fp) != sizeof(tail)) {
fclose(fp);
return OBF_ERR_IO;
}
(void)get_u32_le(tail);
fclose(fp);
*out_count = count;
return OBF_OK;
}
int32_t read_from(const char *path, uint64_t seed, uint64_t *out)
{
size_t count = 0;
return read_many_from(path, seed, out, 1, &count);
}

View File

@@ -0,0 +1,28 @@
#ifndef OBFUSCATE_FILE_H
#define OBFUSCATE_FILE_H
#include "obfuscate.h"
#include <stddef.h>
#include <stdint.h>
/* 32-bit dummy header/tail — skipped by decode; only uint64_t payloads are processed. */
#define OBF_FILE_HDR_SIZE 4u
#define OBF_FILE_TAIL_SIZE 4u
#define OBF_FILE_OVERHEAD (OBF_FILE_HDR_SIZE + OBF_FILE_TAIL_SIZE)
#define OBF_ERR_IO -4
#define OBF_ERR_FORMAT -5
#define OBF_ERR_OVERFLOW -6
int32_t write_to(const char *path, uint64_t val, uint64_t seed);
int32_t read_from(const char *path, uint64_t seed, uint64_t *out);
int32_t write_many_to(const char *path, const uint64_t *vals, size_t count, uint64_t seed);
int32_t read_many_from(const char *path, uint64_t seed, uint64_t *out_vals, size_t out_cap,
size_t *out_count);
/* How many uint64_t payloads are stored (excludes 32-bit head/tail). */
int32_t obf_file_payload_count(long file_size, size_t *count_out);
#endif /* OBFUSCATE_FILE_H */