mirror of
https://github.com/chiteroman/TrickyStore.git
synced 2025-07-17 15:29:32 +00:00
Rewrite Zygisk lib code!
- Read custom field values from json (like PIF) - Simplify code
This commit is contained in:
@@ -1,73 +1,24 @@
|
||||
#include <android/log.h>
|
||||
#include <array>
|
||||
#include <cstdlib>
|
||||
#include <fcntl.h>
|
||||
#include <jni.h>
|
||||
#include <memory>
|
||||
#include <string_view>
|
||||
#include <sys/stat.h>
|
||||
#include <tuple>
|
||||
#include <unistd.h>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <filesystem>
|
||||
|
||||
#include "logging.hpp"
|
||||
#include "zygisk.hpp"
|
||||
#include "cJSON.h"
|
||||
|
||||
#define JSON_PATH "/data/adb/pif.json"
|
||||
#define JSON_TS_PATH "/data/adb/tricky_store/pif.json"
|
||||
#define JSON_PIF_PATH "/data/adb/playintegrityfix/pif.json"
|
||||
#define JSON_PIF_FORK_PATH "/data/adb/modules/playintegrityfix/custom.pif.json"
|
||||
|
||||
using zygisk::Api;
|
||||
using zygisk::AppSpecializeArgs;
|
||||
using zygisk::ServerSpecializeArgs;
|
||||
using namespace std::string_view_literals;
|
||||
|
||||
template <size_t N> struct FixedString {
|
||||
// NOLINTNEXTLINE(*-explicit-constructor)
|
||||
[[maybe_unused]] consteval inline FixedString(const char (&str)[N]) {
|
||||
std::copy_n(str, N, data);
|
||||
}
|
||||
consteval inline FixedString() = default;
|
||||
char data[N] = {};
|
||||
};
|
||||
|
||||
using PropValue = std::array<char, 127>;
|
||||
|
||||
template<typename T, FixedString Field, bool Version=false>
|
||||
struct Prop {
|
||||
using Type [[maybe_unused]] = T;
|
||||
bool has_value{false};
|
||||
PropValue value {};
|
||||
|
||||
[[maybe_unused]] inline consteval static const char *getField() {
|
||||
return Field.data;
|
||||
}
|
||||
[[maybe_unused]] inline consteval static bool isVersion() {
|
||||
return Version;
|
||||
}
|
||||
};
|
||||
|
||||
static_assert(sizeof(Prop<void, "", false>) % sizeof(void*) == 0);
|
||||
|
||||
using SpoofConfig = std::tuple<
|
||||
Prop<jstring, "MANUFACTURER">,
|
||||
Prop<jstring, "MODEL">,
|
||||
Prop<jstring, "FINGERPRINT">,
|
||||
Prop<jstring, "BRAND">,
|
||||
Prop<jstring, "PRODUCT">,
|
||||
Prop<jstring, "DEVICE">,
|
||||
Prop<jstring, "RELEASE", true>,
|
||||
Prop<jstring, "ID">,
|
||||
Prop<jstring, "INCREMENTAL", true>,
|
||||
Prop<jstring, "TYPE">,
|
||||
Prop<jstring, "TAGS">,
|
||||
Prop<jstring, "SECURITY_PATCH", true>,
|
||||
Prop<jstring, "BOARD">,
|
||||
Prop<jstring, "HARDWARE">,
|
||||
Prop<jint, "DEVICE_INITIAL_SDK_INT", true>
|
||||
>;
|
||||
|
||||
|
||||
ssize_t xread(int fd, void *buffer, size_t count) {
|
||||
static ssize_t xread(int fd, void *buffer, size_t count) {
|
||||
ssize_t total = 0;
|
||||
char *buf = (char *)buffer;
|
||||
char *buf = (char *) buffer;
|
||||
while (count > 0) {
|
||||
ssize_t ret = read(fd, buf, count);
|
||||
if (ret < 0) return -1;
|
||||
@@ -78,9 +29,9 @@ ssize_t xread(int fd, void *buffer, size_t count) {
|
||||
return total;
|
||||
}
|
||||
|
||||
ssize_t xwrite(int fd, const void *buffer, size_t count) {
|
||||
static ssize_t xwrite(int fd, const void *buffer, size_t count) {
|
||||
ssize_t total = 0;
|
||||
char *buf = (char *)buffer;
|
||||
char *buf = (char *) buffer;
|
||||
while (count > 0) {
|
||||
ssize_t ret = write(fd, buf, count);
|
||||
if (ret < 0) return -1;
|
||||
@@ -91,11 +42,6 @@ ssize_t xwrite(int fd, const void *buffer, size_t count) {
|
||||
return total;
|
||||
}
|
||||
|
||||
void trim(std::string_view &str) {
|
||||
str.remove_prefix(std::min(str.find_first_not_of(" \t"), str.size()));
|
||||
str.remove_suffix(std::min(str.size() - str.find_last_not_of(" \t") - 1, str.size()));
|
||||
}
|
||||
|
||||
class TrickyStore : public zygisk::ModuleBase {
|
||||
public:
|
||||
void onLoad(Api *api, JNIEnv *env) override {
|
||||
@@ -105,65 +51,57 @@ public:
|
||||
|
||||
void preAppSpecialize(AppSpecializeArgs *args) override {
|
||||
api_->setOption(zygisk::DLCLOSE_MODULE_LIBRARY);
|
||||
if (args->app_data_dir == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto app_data_dir = env_->GetStringUTFChars(args->app_data_dir, nullptr);
|
||||
auto nice_name = env_->GetStringUTFChars(args->nice_name, nullptr);
|
||||
if (!args) return;
|
||||
|
||||
std::string_view dir(app_data_dir);
|
||||
std::string_view process(nice_name);
|
||||
const char *dir = env_->GetStringUTFChars(args->app_data_dir, nullptr);
|
||||
|
||||
bool isGms = false, isGmsUnstable = false;
|
||||
isGms = dir.ends_with("/com.google.android.gms");
|
||||
isGmsUnstable = process == "com.google.android.gms.unstable";
|
||||
if (!dir) return;
|
||||
|
||||
env_->ReleaseStringUTFChars(args->app_data_dir, app_data_dir);
|
||||
env_->ReleaseStringUTFChars(args->nice_name, nice_name);
|
||||
bool isGms = std::string_view(dir).ends_with("/com.google.android.gms");
|
||||
|
||||
env_->ReleaseStringUTFChars(args->app_data_dir, dir);
|
||||
|
||||
if (!isGms) return;
|
||||
|
||||
if (!isGms) {
|
||||
return;
|
||||
}
|
||||
api_->setOption(zygisk::FORCE_DENYLIST_UNMOUNT);
|
||||
if (!isGmsUnstable) {
|
||||
|
||||
const char *name = env_->GetStringUTFChars(args->nice_name, nullptr);
|
||||
|
||||
if (!name) return;
|
||||
|
||||
bool isGmsUnstable = std::string_view(name) == "com.google.android.gms.unstable";
|
||||
|
||||
env_->ReleaseStringUTFChars(args->nice_name, name);
|
||||
|
||||
if (!isGmsUnstable) return;
|
||||
|
||||
size_t jsonVectorSize;
|
||||
std::vector<char> temp;
|
||||
|
||||
int fd = api_->connectCompanion();
|
||||
|
||||
xread(fd, &jsonVectorSize, sizeof(jsonVectorSize));
|
||||
temp.resize(jsonVectorSize);
|
||||
xread(fd, temp.data(), jsonVectorSize);
|
||||
|
||||
close(fd);
|
||||
|
||||
if (temp.empty()) {
|
||||
LOGI("Couldn't receive JSON file from fd!");
|
||||
return;
|
||||
}
|
||||
|
||||
int enabled = 0;
|
||||
SpoofConfig spoofConfig{};
|
||||
auto fd = api_->connectCompanion();
|
||||
if (fd >= 0) [[likely]] {
|
||||
// read enabled
|
||||
xread(fd, &enabled, sizeof(enabled));
|
||||
if (enabled) {
|
||||
xread(fd, &spoofConfig, sizeof(spoofConfig));
|
||||
}
|
||||
close(fd);
|
||||
}
|
||||
if (enabled) {
|
||||
LOGI("spoofing build vars in GMS!");
|
||||
auto buildClass = env_->FindClass("android/os/Build");
|
||||
auto buildVersionClass = env_->FindClass("android/os/Build$VERSION");
|
||||
|
||||
std::apply([this, &buildClass, &buildVersionClass](auto &&... args) {
|
||||
((!args.has_value ||
|
||||
(setField<typename std::remove_cvref_t<decltype(args)>::Type>(
|
||||
std::remove_cvref_t<decltype(args)>::isVersion() ? buildVersionClass
|
||||
: buildClass,
|
||||
std::remove_cvref_t<decltype(args)>::getField(),
|
||||
args.value) &&
|
||||
(LOGI("%s set %s to %s",
|
||||
std::remove_cvref_t<decltype(args)>::isVersion() ? "VERSION" : "Build",
|
||||
std::remove_cvref_t<decltype(args)>::getField(),
|
||||
args.value.data()), true))
|
||||
? void(0)
|
||||
: LOGE("%s failed to set %s to %s",
|
||||
std::remove_cvref_t<decltype(args)>::isVersion() ? "VERSION" : "Build",
|
||||
std::remove_cvref_t<decltype(args)>::getField(),
|
||||
args.value.data())), ...);
|
||||
}, spoofConfig);
|
||||
}
|
||||
std::string jsonString(temp.cbegin(), temp.cend());
|
||||
json = cJSON_ParseWithLength(jsonString.c_str(), jsonVectorSize);
|
||||
}
|
||||
|
||||
void postAppSpecialize(const AppSpecializeArgs *args) override {
|
||||
if (!json) return;
|
||||
|
||||
UpdateBuildFields();
|
||||
|
||||
cJSON_Delete(json);
|
||||
}
|
||||
|
||||
void preServerSpecialize(ServerSpecializeArgs *args) override {
|
||||
@@ -173,132 +111,105 @@ public:
|
||||
private:
|
||||
Api *api_{nullptr};
|
||||
JNIEnv *env_{nullptr};
|
||||
cJSON *json{nullptr};
|
||||
|
||||
template<typename T>
|
||||
inline bool setField(jclass clazz, const char* field, const PropValue& value);
|
||||
void UpdateBuildFields() {
|
||||
jclass buildClass = env_->FindClass("android/os/Build");
|
||||
jclass versionClass = env_->FindClass("android/os/Build$VERSION");
|
||||
|
||||
template<>
|
||||
inline bool setField<jstring>(jclass clazz, const char* field, const PropValue& value) {
|
||||
auto id = env_->GetStaticFieldID(clazz, field, "Ljava/lang/String;");
|
||||
if (!id) return false;
|
||||
env_->SetStaticObjectField(clazz, id, env_->NewStringUTF(value.data()));
|
||||
return true;
|
||||
}
|
||||
cJSON *currentElement = nullptr;
|
||||
cJSON_ArrayForEach(currentElement, json) {
|
||||
const char *key = currentElement->string;
|
||||
|
||||
template<>
|
||||
inline bool setField<jint>(jclass clazz, const char* field, const PropValue& value) {
|
||||
auto id = env_->GetStaticFieldID(clazz, field, "I");
|
||||
if (!id) return false;
|
||||
char *p = nullptr;
|
||||
jint x = static_cast<jint>(strtol(value.data(), &p, 10));
|
||||
if (p == value.data()) {
|
||||
return false;
|
||||
if (cJSON_IsString(currentElement)) {
|
||||
const char *value = currentElement->valuestring;
|
||||
jfieldID fieldID = env_->GetStaticFieldID(buildClass, key, "Ljava/lang/String;");
|
||||
|
||||
if (env_->ExceptionCheck()) {
|
||||
env_->ExceptionClear();
|
||||
|
||||
fieldID = env_->GetStaticFieldID(versionClass, key, "Ljava/lang/String;");
|
||||
|
||||
if (env_->ExceptionCheck()) {
|
||||
env_->ExceptionClear();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (fieldID != nullptr) {
|
||||
jstring jValue = env_->NewStringUTF(value);
|
||||
|
||||
env_->SetStaticObjectField(buildClass, fieldID, jValue);
|
||||
if (env_->ExceptionCheck()) {
|
||||
env_->ExceptionClear();
|
||||
continue;
|
||||
}
|
||||
|
||||
LOGI("Set '%s' to '%s'", key, value);
|
||||
}
|
||||
} else if (cJSON_IsNumber(currentElement)) {
|
||||
int value = currentElement->valueint;
|
||||
jfieldID fieldID = env_->GetStaticFieldID(buildClass, key, "I");
|
||||
|
||||
if (env_->ExceptionCheck()) {
|
||||
env_->ExceptionClear();
|
||||
|
||||
fieldID = env_->GetStaticFieldID(versionClass, key, "I");
|
||||
|
||||
if (env_->ExceptionCheck()) {
|
||||
env_->ExceptionClear();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (fieldID != nullptr) {
|
||||
env_->SetStaticIntField(buildClass, fieldID, value);
|
||||
|
||||
if (env_->ExceptionCheck()) {
|
||||
env_->ExceptionClear();
|
||||
continue;
|
||||
}
|
||||
|
||||
LOGI("Set '%s' to '%d'", key, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
env_->SetStaticIntField(clazz, id, x);
|
||||
return true;
|
||||
}
|
||||
|
||||
template<>
|
||||
inline bool setField<jboolean>(jclass clazz, const char* field, const PropValue& value) {
|
||||
auto id = env_->GetStaticFieldID(clazz, field, "Z");
|
||||
if (!id) return false;
|
||||
auto x = std::string_view(value.data());
|
||||
if (x == "1" || x == "true") {
|
||||
env_->SetStaticBooleanField(clazz, id, JNI_TRUE);
|
||||
} else if (x == "0" || x == "false") {
|
||||
env_->SetStaticBooleanField(clazz, id, JNI_FALSE);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
void read_config(FILE *config, SpoofConfig &spoof_config) {
|
||||
char *l = nullptr;
|
||||
struct finally {
|
||||
char *(&l);
|
||||
static std::vector<char> readFile(const char *str) {
|
||||
FILE *file = fopen(str, "r");
|
||||
|
||||
~finally() { free(l); }
|
||||
} finally{l};
|
||||
size_t len = 0;
|
||||
ssize_t n;
|
||||
while ((n = getline(&l, &len, config)) != -1) {
|
||||
if (n <= 1) continue;
|
||||
std::string_view line{l, static_cast<size_t>(n)};
|
||||
if (line.back() == '\n') {
|
||||
line.remove_suffix(1);
|
||||
}
|
||||
auto d = line.find_first_of('=');
|
||||
if (d == std::string_view::npos) {
|
||||
LOGW("Ignore invalid line %.*s", static_cast<int>(line.size()), line.data());
|
||||
continue;
|
||||
}
|
||||
auto key = line.substr(0, d);
|
||||
trim(key);
|
||||
auto value = line.substr(d + 1);
|
||||
trim(value);
|
||||
std::apply([&key, &value](auto &&... args) {
|
||||
((key == std::remove_cvref_t<decltype(args)>::getField() &&
|
||||
(LOGD("Read config: %.*s = %.*s", static_cast<int>(key.size()), key.data(),
|
||||
static_cast<int>(value.size()), value.data()),
|
||||
args.value.size() >= value.size() + 1 ?
|
||||
(args.has_value = true,
|
||||
strlcpy(args.value.data(), value.data(),
|
||||
std::min(args.value.size(), value.size() + 1))) :
|
||||
(LOGW("Config value %.*s for %.*s is too long, ignored",
|
||||
static_cast<int>(value.size()), value.data(),
|
||||
static_cast<int>(key.size()), key.data()), true))) || ...);
|
||||
}, spoof_config);
|
||||
}
|
||||
if (!file) return {};
|
||||
|
||||
auto fileSize = std::filesystem::file_size(str);
|
||||
|
||||
std::vector<char> vector(fileSize);
|
||||
|
||||
fread(vector.data(), fileSize, 1, file);
|
||||
|
||||
fclose(file);
|
||||
|
||||
LOGI("[companion_handler] file '%s', size: %ld", str, fileSize);
|
||||
|
||||
return vector;
|
||||
}
|
||||
|
||||
static void companion_handler(int fd) {
|
||||
constexpr auto kSpoofConfigFile = "/data/adb/tricky_store/spoof_build_vars"sv;
|
||||
constexpr auto kDefaultSpoofConfig =
|
||||
R"EOF(MANUFACTURER=Google
|
||||
MODEL=Pixel 8 Pro
|
||||
FINGERPRINT=google/husky_beta/husky:15/AP31.240617.010/12136053:user/release-keys
|
||||
BRAND=google
|
||||
PRODUCT=husky_beta
|
||||
DEVICE=husky
|
||||
RELEASE=15
|
||||
ID=AP31.240617.010
|
||||
INCREMENTAL=12136053
|
||||
TYPE=user
|
||||
TAGS=release-keys
|
||||
SECURITY_PATCH=2024-07-05
|
||||
)EOF"sv;
|
||||
struct stat st{};
|
||||
int enabled = stat(kSpoofConfigFile.data(), &st) == 0;
|
||||
xwrite(fd, &enabled, sizeof(enabled));
|
||||
std::vector<char> jsonVector;
|
||||
|
||||
if (!enabled) {
|
||||
return;
|
||||
}
|
||||
jsonVector = readFile(JSON_PATH);
|
||||
if (jsonVector.empty()) jsonVector = readFile(JSON_TS_PATH);
|
||||
if (jsonVector.empty()) jsonVector = readFile(JSON_PIF_PATH);
|
||||
if (jsonVector.empty()) jsonVector = readFile(JSON_PIF_FORK_PATH);
|
||||
|
||||
int cfd = -1;
|
||||
if (st.st_size == 0) {
|
||||
cfd = open(kSpoofConfigFile.data(), O_RDWR);
|
||||
if (cfd > 0) {
|
||||
xwrite(cfd, kDefaultSpoofConfig.data(), kDefaultSpoofConfig.size());
|
||||
lseek(cfd, 0, SEEK_SET);
|
||||
}
|
||||
} else {
|
||||
cfd = open(kSpoofConfigFile.data(), O_RDONLY);
|
||||
}
|
||||
if (cfd < 0) {
|
||||
LOGE("[companion_handler] Failed to open spoof_build_vars");
|
||||
return;
|
||||
}
|
||||
size_t jsonVectorSize = jsonVector.size();
|
||||
|
||||
SpoofConfig spoof_config{};
|
||||
std::unique_ptr<FILE, decltype([](auto *f) { fclose(f); })> config{fdopen(cfd, "r")};
|
||||
read_config(config.get(), spoof_config);
|
||||
|
||||
xwrite(fd, &spoof_config, sizeof(spoof_config));
|
||||
xwrite(fd, &jsonVectorSize, sizeof(jsonVectorSize));
|
||||
xwrite(fd, jsonVector.data(), jsonVectorSize);
|
||||
}
|
||||
|
||||
// Register our module class and the companion handler function
|
||||
REGISTER_ZYGISK_MODULE(TrickyStore)
|
||||
|
||||
REGISTER_ZYGISK_COMPANION(companion_handler)
|
||||
|
||||
Reference in New Issue
Block a user