mirror of
git://f0xx.org/android_cast
synced 2026-07-29 04:18:09 +03:00
docs, obfuscation
This commit is contained in:
@@ -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
103
ndk/jni/entitlements_jni.c
Normal 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
8
ndk/obfuscate/README.md
Normal 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
122
ndk/obfuscate/obfuscate.c
Normal 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
30
ndk/obfuscate/obfuscate.h
Normal 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 */
|
||||
215
ndk/obfuscate/obfuscate_file.c
Normal file
215
ndk/obfuscate/obfuscate_file.c
Normal 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);
|
||||
}
|
||||
28
ndk/obfuscate/obfuscate_file.h
Normal file
28
ndk/obfuscate/obfuscate_file.h
Normal 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 */
|
||||
Reference in New Issue
Block a user