我开发了一个 Android 应用程序,其中还包含一个用 C 编写的 native 部分(不依赖于应用程序)。
如果共享库不工作,应用程序本身就毫无用处。
我想让 native 部分(一个共享库)只做它的工作,如果存在一个未修改的应用程序版本(.apk),它已经交付。
对我来说最好的方法是这样的:
通过这种方式,我想保护我的应用程序免受修改和盗版。
这样做有什么技巧吗?我刚刚找到了在 Java 中检查自己的签名的帖子,但如果可以反编译应用程序,那就不是开玩笑了。
最佳答案
由于我被要求发布一些代码,我现在如何从 C 中检查我的 Java 应用程序的 CRC 代码,这里有一些 fragment 。
由于性能原因,我无法发布完整的工作解决方案,因为它分布在多行中,但我希望这是最完整和最有效的:
在您的 MyApplication.java 中:
public class MyApplication extends Application {
private static Context context;
public static Context getAppContext() {
return MyApplication.context;
}
@Override
public void onCreate() {
super.onCreate();
MyApplication.context = getApplicationContext();
}
}
Android.mk:
LOCAL_CFLAGS += -O3 -DDEBUG_MODE=0 -DCLASSES_CRC=2331492378
在您的 C 代码中:
#define LOG_TAG "Your Log Tag"
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
#if DEBUG_MODE
#define LOGDH(...) LOGD(__VA_ARGS__)
#define LOGIH(...) LOGI(__VA_ARGS__)
#define LOGEH(...) LOGE(__VA_ARGS__)
#else
#define LOGDH(...) //
#define LOGIH(...) //
#define LOGEH(...) //
#endif
int isSecure = -1;
jclass MyApplication;
jclass Context;
jclass ApplicationInfo;
jclass ZipFile;
jclass ZipEntry;
jclass CheckedInputStream;
jclass Adler32;
jclass Checksum;
jmethodID MyApplication_getAppContextMethodId;
jmethodID Context_getApplicationInfoMethodId;
jmethodID ZipFile_ConstructorMethodId;
jmethodID CheckedInputStream_ConstructorMethodId;
jmethodID Adler32_ConstructorMethodId;
jmethodID ZipFile_getEntryMethodId;
jmethodID ZipFile_getInputStreamMethodId;
jmethodID CheckedInputStream_readMethodId;
jmethodID CheckedInputStream_getChecksumMethodId;
jmethodID Checksum_getValueMethodId;
jfieldID ApplicationInfo_flagsFieldId;
jfieldID ApplicationInfo_FLAG_DEBUGGABLEFieldId;
jfieldID ApplicationInfo_sourceDirFieldId;
static long getClassesCRC(JNIEnv *env) {
jobject appContextInstance = (*env)->CallStaticObjectMethod(env,
MyApplication, MyApplication_getAppContextMethodId);
if (!appContextInstance) {
LOGEH("Unable to get instance of AppContext");
return false;
}
jobject applicationInfoInstance = (*env)->CallObjectMethod(env,
appContextInstance, Context_getApplicationInfoMethodId);
if (!appContextInstance) {
LOGEH("Unable to get instance of ApplicationInfo");
return false;
}
jobject zipFileInstance = (*env)->NewObject(env, ZipFile,
ZipFile_ConstructorMethodId,
(*env)->GetObjectField(env, applicationInfoInstance,
ApplicationInfo_sourceDirFieldId));
if (!zipFileInstance) {
LOGEH("Unable to get instance of ZipFile");
return -1;
}
jstring classesDexString = (*env)->NewStringUTF(env, "classes.dex");
jobject zipEntryInstance = (*env)->CallObjectMethod(env, zipFileInstance,
ZipFile_getEntryMethodId, classesDexString);
if (!zipFileInstance) {
LOGEH("Unable to get instance of ZipEntry");
return -1;
}
(*env)->DeleteLocalRef(env, classesDexString);
jobject adler32Instance = (*env)->NewObject(env, Adler32,
Adler32_ConstructorMethodId);
if (!adler32Instance) {
LOGEH("Unable to get instance of Adler32");
return -1;
}
jobject inputStreamInstance = (*env)->CallObjectMethod(env, zipFileInstance,
ZipFile_getInputStreamMethodId, zipEntryInstance);
if (!inputStreamInstance) {
LOGEH("Unable to get instance of InputStream");
return -1;
}
jobject checkedInputStreamInstance = (*env)->NewObject(env,
CheckedInputStream, CheckedInputStream_ConstructorMethodId,
inputStreamInstance, adler32Instance);
if (!checkedInputStreamInstance) {
LOGEH("Unable to get instance of CheckedInputStream");
return -1;
}
int bufferSize = 128;
jbyteArray bufferBytes = (*env)->NewByteArray(env, bufferSize);
while ((*env)->CallIntMethod(env, checkedInputStreamInstance,
CheckedInputStream_readMethodId, bufferBytes) > 0) {
}
(*env)->DeleteLocalRef(env, bufferBytes);
jobject checksumInstance = (*env)->CallObjectMethod(env,
checkedInputStreamInstance, CheckedInputStream_getChecksumMethodId);
if (!checksumInstance) {
LOGEH("Unable to get instance of CheckSum");
return -1;
}
return (*env)->CallLongMethod(env, checksumInstance,
Checksum_getValueMethodId);
}
static bool isDebuggable(JNIEnv *env) {
jobject appContextInstance = (*env)->CallStaticObjectMethod(env,
MyApplication, Application_getAppContextMethodId);
if (!appContextInstance) {
LOGEH("Unable to get instance of AppContext");
return false;
}
jobject applicationInfoInstance = (*env)->CallObjectMethod(env,
appContextInstance, Context_getApplicationInfoMethodId);
if (!appContextInstance) {
LOGEH("Unable to get instance of ApplicationInfo");
return false;
}
int FLAG_DEBUGGABLE = (*env)->GetStaticIntField(env, ApplicationInfo,
ApplicationInfo_FLAG_DEBUGGABLEFieldId);
int flags = (*env)->GetIntField(env, applicationInfoInstance,
ApplicationInfo_flagsFieldId);
return (0 != (flags &= FLAG_DEBUGGABLE));
}
static bool isSecureEnvironment(JNIEnv *env) {
//isSecure = true; // TODO remove this
if (isSecure == -1) {
isSecure = true;
if (isDebuggable(env)) {
// someone used the app in debug-mode
#if DEBUG_MODE != 1
// TODO report
#endif
LOGEH("App IS DEBUGGABLE!");
isSecure = false;
} else {
// check CRC
long classesCRC = getClassesCRC(env);
if (classesCRC != (long) CLASSES_CRC) {
#if DEBUG_MODE != 1
// TODO report
#endif
LOGEH("CRC-CHECK FAILED: %lu", classesCRC);
isSecure = false;
}
}
}
return isSecure;
}
static bool initJavaClasses(JNIEnv * env) {
jclass local = (*env)->FindClass(env, "eu/my/MyApplication");
MyApplication = (*env)->NewGlobalRef(env, local);
if (!MyApplication) {
LOGEH("Unable to find the MyApplication class");
return false;
}
local = (*env)->FindClass(env, "android/content/Context");
Context = (*env)->NewGlobalRef(env, local);
(*env)->DeleteLocalRef(env, local);
if (!Context) {
LOGEH("Unable to find the Context class");
return false;
}
local = (*env)->FindClass(env, "android/content/pm/ApplicationInfo");
ApplicationInfo = (*env)->NewGlobalRef(env, local);
(*env)->DeleteLocalRef(env, local);
if (!ApplicationInfo) {
LOGEH("Unable to find the ApplicationInfo class");
return false;
}
local = (*env)->FindClass(env, "java/util/zip/ZipFile");
ZipFile = (*env)->NewGlobalRef(env, local);
(*env)->DeleteLocalRef(env, local);
if (!ZipFile) {
LOGEH("Unable to find the ZipFile class");
return false;
}
local = (*env)->FindClass(env, "java/util/zip/ZipEntry");
ZipEntry = (*env)->NewGlobalRef(env, local);
(*env)->DeleteLocalRef(env, local);
if (!ZipEntry) {
LOGEH("Unable to find the ZipEntry class");
return false;
}
local = (*env)->FindClass(env, "java/util/zip/CheckedInputStream");
CheckedInputStream = (*env)->NewGlobalRef(env, local);
(*env)->DeleteLocalRef(env, local);
if (!CheckedInputStream) {
LOGEH("Unable to find the CheckedInputStream class");
return false;
}
local = (*env)->FindClass(env, "java/util/zip/Adler32");
Adler32 = (*env)->NewGlobalRef(env, local);
(*env)->DeleteLocalRef(env, local);
if (!Adler32) {
LOGEH("Unable to find the Adler32 class");
return false;
}
local = (*env)->FindClass(env, "java/util/zip/Checksum");
Checksum = (*env)->NewGlobalRef(env, local);
(*env)->DeleteLocalRef(env, local);
if (!Checksum) {
LOGEH("Unable to find the Checksum class");
return false;
}
return true;
}
static bool initJavaMethods(JNIEnv * env) {
MyApplication_getAppContextMethodId = (*env)->GetStaticMethodID(env,
MyApplication, "getAppContext", "()Landroid/content/Context;");
if (!MyApplication_getAppContextMethodId) {
LOGEH("Unable to find the getAppContext method");
return false;
}
Context_getApplicationInfoMethodId = (*env)->GetMethodID(env, Context,
"getApplicationInfo", "()Landroid/content/pm/ApplicationInfo;");
if (!Context_getApplicationInfoMethodId) {
LOGEH("Unable to find the getApplicationInfo method");
return false;
}
ZipFile_ConstructorMethodId = (*env)->GetMethodID(env, ZipFile, "<init>",
"(Ljava/lang/String;)V");
if (!ZipFile_ConstructorMethodId) {
LOGEH("Unable to find the constructor method");
return false;
}
CheckedInputStream_ConstructorMethodId = (*env)->GetMethodID(env,
CheckedInputStream, "<init>",
"(Ljava/io/InputStream;Ljava/util/zip/Checksum;)V");
if (!CheckedInputStream_ConstructorMethodId) {
LOGEH("Unable to find the constructor method");
return false;
}
Adler32_ConstructorMethodId = (*env)->GetMethodID(env, Adler32, "<init>",
"()V");
if (!Adler32_ConstructorMethodId) {
LOGEH("Unable to find the constructor method");
return false;
}
ZipFile_getEntryMethodId = (*env)->GetMethodID(env, ZipFile, "getEntry",
"(Ljava/lang/String;)Ljava/util/zip/ZipEntry;");
if (!ZipFile_getEntryMethodId) {
LOGEH("Unable to find the getEntry method");
return false;
}
ZipFile_getInputStreamMethodId = (*env)->GetMethodID(env, ZipFile,
"getInputStream", "(Ljava/util/zip/ZipEntry;)Ljava/io/InputStream;");
if (!ZipFile_getInputStreamMethodId) {
LOGEH("Unable to find the getInputStream method");
return false;
}
CheckedInputStream_readMethodId = (*env)->GetMethodID(env, CheckedInputStream,
"read", "([B)I");
if (!CheckedInputStream_readMethodId) {
LOGEH("Unable to find the read method");
return false;
}
CheckedInputStream_getChecksumMethodId = (*env)->GetMethodID(env,
CheckedInputStream, "getChecksum", "()Ljava/util/zip/Checksum;");
if (!CheckedInputStream_getChecksumMethodId) {
LOGEH("Unable to find the getChecksum method");
return false;
}
Checksum_getValueMethodId = (*env)->GetMethodID(env, Checksum, "getValue",
"()J");
if (!Checksum_getValueMethodId) {
LOGEH("Unable to find the getValue method");
return false;
}
return true;
}
static bool initJavaFields(JNIEnv * env) {
ApplicationInfo_flagsFieldId = (*env)->GetFieldID(env, ApplicationInfo, "flags",
"I");
if (!ApplicationInfo_flagsFieldId) {
LOGEH("Unable to find the flags field");
return false;
}
ApplicationInfo_FLAG_DEBUGGABLEFieldId = (*env)->GetStaticFieldID(env,
ApplicationInfo, "FLAG_DEBUGGABLE", "I");
if (!ApplicationInfo_FLAG_DEBUGGABLEFieldId) {
LOGEH("Unable to get static field FLAG_DEBUGGABLE");
return false;
}
ApplicationInfo_sourceDirFieldId = (*env)->GetFieldID(env, ApplicationInfo,
"sourceDir", "Ljava/lang/String;");
if (!ApplicationInfo_sourceDirFieldId) {
LOGEH("Unable to get static field sourceDir");
return false;
}
return true;
}
jint JNI_OnLoad(JavaVM* vm, void* reserved) {
(void) reserved; // Suppress the warning.
JNIEnv * env;
if ((*vm)->GetEnv(vm, (void **) &env, JNI_VERSION_1_6) != JNI_OK) {
return -1;
}
if (!initJavaClasses(env)) {
return -1;
}
if (!initJavaMethods(env)) {
return -1;
}
if (!initJavaFields(env)) {
return -1;
}
return JNI_VERSION_1_6;
}
不要忘记将 MyApplication 的方法添加到 Proguard 设置中以防止它们被删除!
用法:
此方法稍微复杂一些,因为它需要 2 次构建才能获得有效的 APK。 但由于 CRC 是在 C 内部检查的,而不仅仅是 APK 文件的虚拟签名被采用,所以这种检查几乎是防弹的。
在我的情况下,例如,我什至不设置许可机制以防签名无效。
此外,由于此方法仅使用数字而不使用字符,因此已完全编译。
希望这对某人有帮助!
关于android - 检查 C/ native 代码中的 .apk 签名,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/15025304/
总的来说,我对ruby还比较陌生,我正在为我正在创建的对象编写一些rspec测试用例。许多测试用例都非常基础,我只是想确保正确填充和返回值。我想知道是否有办法使用循环结构来执行此操作。不必为我要测试的每个方法都设置一个assertEquals。例如:describeitem,"TestingtheItem"doit"willhaveanullvaluetostart"doitem=Item.new#HereIcoulddotheitem.name.shouldbe_nil#thenIcoulddoitem.category.shouldbe_nilendend但我想要一些方法来使用
我试图在一个项目中使用rake,如果我把所有东西都放到Rakefile中,它会很大并且很难读取/找到东西,所以我试着将每个命名空间放在lib/rake中它自己的文件中,我添加了这个到我的rake文件的顶部:Dir['#{File.dirname(__FILE__)}/lib/rake/*.rake'].map{|f|requiref}它加载文件没问题,但没有任务。我现在只有一个.rake文件作为测试,名为“servers.rake”,它看起来像这样:namespace:serverdotask:testdoputs"test"endend所以当我运行rakeserver:testid时
作为我的Rails应用程序的一部分,我编写了一个小导入程序,它从我们的LDAP系统中吸取数据并将其塞入一个用户表中。不幸的是,与LDAP相关的代码在遍历我们的32K用户时泄漏了大量内存,我一直无法弄清楚如何解决这个问题。这个问题似乎在某种程度上与LDAP库有关,因为当我删除对LDAP内容的调用时,内存使用情况会很好地稳定下来。此外,不断增加的对象是Net::BER::BerIdentifiedString和Net::BER::BerIdentifiedArray,它们都是LDAP库的一部分。当我运行导入时,内存使用量最终达到超过1GB的峰值。如果问题存在,我需要找到一些方法来更正我的代
Rails2.3可以选择随时使用RouteSet#add_configuration_file添加更多路由。是否可以在Rails3项目中做同样的事情? 最佳答案 在config/application.rb中:config.paths.config.routes在Rails3.2(也可能是Rails3.1)中,使用:config.paths["config/routes"] 关于ruby-on-rails-Rails3中的多个路由文件,我们在StackOverflow上找到一个类似的问题
如何在buildr项目中使用Ruby?我在很多不同的项目中使用过Ruby、JRuby、Java和Clojure。我目前正在使用我的标准Ruby开发一个模拟应用程序,我想尝试使用Clojure后端(我确实喜欢功能代码)以及JRubygui和测试套件。我还可以看到在未来的不同项目中使用Scala作为后端。我想我要为我的项目尝试一下buildr(http://buildr.apache.org/),但我注意到buildr似乎没有设置为在项目中使用JRuby代码本身!这看起来有点傻,因为该工具旨在统一通用的JVM语言并且是在ruby中构建的。除了将输出的jar包含在一个独特的、仅限ruby
在rails源中:https://github.com/rails/rails/blob/master/activesupport/lib/active_support/lazy_load_hooks.rb可以看到以下内容@load_hooks=Hash.new{|h,k|h[k]=[]}在IRB中,它只是初始化一个空哈希。和做有什么区别@load_hooks=Hash.new 最佳答案 查看rubydocumentationforHashnew→new_hashclicktotogglesourcenew(obj)→new_has
我需要从一个View访问多个模型。以前,我的links_controller仅用于提供以不同方式排序的链接资源。现在我想包括一个部分(我假设)显示按分数排序的顶级用户(@users=User.all.sort_by(&:score))我知道我可以将此代码插入每个链接操作并从View访问它,但这似乎不是“ruby方式”,我将需要在不久的将来访问更多模型。这可能会变得很脏,是否有针对这种情况的任何技术?注意事项:我认为我的应用程序正朝着单一格式和动态页面内容的方向发展,本质上是一个典型的网络应用程序。我知道before_filter但考虑到我希望应用程序进入的方向,这似乎很麻烦。最终从任何
为了将Cucumber用于命令行脚本,我按照提供的说明安装了arubagem。它在我的Gemfile中,我可以验证是否安装了正确的版本并且我已经包含了require'aruba/cucumber'在'features/env.rb'中为了确保它能正常工作,我写了以下场景:@announceScenario:Testingcucumber/arubaGivenablankslateThentheoutputfrom"ls-la"shouldcontain"drw"假设事情应该失败。它确实失败了,但失败的原因是错误的:@announceScenario:Testingcucumber/ar
我在我的项目中添加了一个系统来重置用户密码并通过电子邮件将密码发送给他,以防他忘记密码。昨天它运行良好(当我实现它时)。当我今天尝试启动服务器时,出现以下错误。=>BootingWEBrick=>Rails3.2.1applicationstartingindevelopmentonhttp://0.0.0.0:3000=>Callwith-dtodetach=>Ctrl-CtoshutdownserverExiting/Users/vinayshenoy/.rvm/gems/ruby-1.9.3-p0/gems/actionmailer-3.2.1/lib/action_mailer
刚入门rails,开始慢慢理解。有人可以解释或给我一些关于在application_controller中编码的好处或时间和原因的想法吗?有哪些用例。您如何为Rails应用程序使用应用程序Controller?我不想在那里放太多代码,因为据我了解,每个请求都会调用此Controller。这是真的? 最佳答案 ApplicationController实际上是您应用程序中的每个其他Controller都将从中继承的类(尽管这不是强制性的)。我同意不要用太多代码弄乱它并保持干净整洁的态度,尽管在某些情况下ApplicationContr