feat: verify dex files

This commit is contained in:
hyb1996
2018-02-23 11:21:18 +08:00
parent 29402055ab
commit e4cda0b64f
7 changed files with 123 additions and 26 deletions

View File

@@ -8,8 +8,8 @@ android {
applicationId "com.stardust.scriptdroid" applicationId "com.stardust.scriptdroid"
minSdkVersion 17 minSdkVersion 17
targetSdkVersion 23 targetSdkVersion 23
versionCode 251 versionCode 252
versionName "3.1.0 Alpha" versionName "3.1.0 Alpha2"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
multiDexEnabled true multiDexEnabled true
ndk { ndk {

View File

@@ -31,6 +31,7 @@ public class LogActivity extends BaseActivity {
setToolbarAsBack(getString(R.string.text_log)); setToolbarAsBack(getString(R.string.text_log));
mStardustConsole = (StardustConsole) AutoJs.getInstance().getGlobalConsole(); mStardustConsole = (StardustConsole) AutoJs.getInstance().getGlobalConsole();
mConsoleView.setConsole(mStardustConsole); mConsoleView.setConsole(mStardustConsole);
mConsoleView.findViewById(R.id.input_container).setVisibility(View.GONE);
} }
@Click(R.id.fab) @Click(R.id.fab)

View File

@@ -3,10 +3,8 @@ package com.stardust.scriptdroid.ui.main;
import android.Manifest; import android.Manifest;
import android.content.Intent; import android.content.Intent;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.support.annotation.RequiresApi;
import android.support.design.widget.FloatingActionButton; import android.support.design.widget.FloatingActionButton;
import android.support.design.widget.TabLayout; import android.support.design.widget.TabLayout;
import android.support.v4.app.ActivityCompat; import android.support.v4.app.ActivityCompat;
@@ -41,7 +39,6 @@ import com.stardust.scriptdroid.ui.main.sample.SampleListFragment_;
import com.stardust.scriptdroid.ui.main.scripts.MyScriptListFragment_; import com.stardust.scriptdroid.ui.main.scripts.MyScriptListFragment_;
import com.stardust.scriptdroid.ui.main.task.TaskManagerFragment_; import com.stardust.scriptdroid.ui.main.task.TaskManagerFragment_;
import com.stardust.theme.ThemeColorManager; import com.stardust.theme.ThemeColorManager;
import com.stardust.theme.app.ThemeColorMaterialDialog;
import com.stardust.theme.dialog.ThemeColorMaterialDialogBuilder; import com.stardust.theme.dialog.ThemeColorMaterialDialogBuilder;
import com.stardust.util.DeveloperUtils; import com.stardust.util.DeveloperUtils;
import com.stardust.scriptdroid.tool.AccessibilityServiceTool; import com.stardust.scriptdroid.tool.AccessibilityServiceTool;
@@ -253,8 +250,8 @@ public class MainActivity extends BaseActivity implements OnActivityResultDelega
@Override @Override
protected void onStart() { protected void onStart() {
super.onStart(); super.onStart();
if (!BuildConfig.DEBUG && !DeveloperUtils.checkSignature(this)) { if (!BuildConfig.DEBUG) {
finish(); DeveloperUtils.verifyApk(this, R.string.dex_crcs);
} }
if (!DeveloperUtils.isActivityRegistered(this, ADActivity.class) || if (!DeveloperUtils.isActivityRegistered(this, ADActivity.class) ||
!DeveloperUtils.isServiceRegistered(this, DownloadService.class)) { !DeveloperUtils.isServiceRegistered(this, DownloadService.class)) {

View File

@@ -9,6 +9,7 @@ import android.support.annotation.Nullable;
import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView; import android.support.v7.widget.RecyclerView;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.Log;
import android.view.View; import android.view.View;
import android.widget.TextView; import android.widget.TextView;
import android.widget.Toast; import android.widget.Toast;
@@ -310,6 +311,8 @@ public class DrawerFragment extends android.support.v4.app.Fragment {
} }
private void setUpUserInfo(@Nullable User user) { private void setUpUserInfo(@Nullable User user) {
if (mUserName == null || mAvatar == null)
return;
if (user == null) { if (user == null) {
mUserName.setText(R.string.not_login); mUserName.setText(R.string.not_login);
mAvatar.setIcon(R.drawable.profile_avatar_placeholder); mAvatar.setIcon(R.drawable.profile_avatar_placeholder);
@@ -318,11 +321,11 @@ public class DrawerFragment extends android.support.v4.app.Fragment {
mAvatar.setUser(user); mAvatar.setUser(user);
} }
setCoverImage(user); setCoverImage(user);
} }
private void setCoverImage(User user) { private void setCoverImage(User user) {
if (mDefaultCover == null || mShadow == null || mHeaderView == null)
return;
if (user == null || TextUtils.isEmpty(user.getCoverUrl()) || user.getCoverUrl().equals("/assets/images/cover-default.png")) { if (user == null || TextUtils.isEmpty(user.getCoverUrl()) || user.getCoverUrl().equals("/assets/images/cover-default.png")) {
mDefaultCover.setVisibility(View.VISIBLE); mDefaultCover.setVisibility(View.VISIBLE);
mShadow.setVisibility(View.GONE); mShadow.setVisibility(View.GONE);
@@ -336,7 +339,9 @@ public class DrawerFragment extends android.support.v4.app.Fragment {
.into(new SimpleTarget<Drawable>() { .into(new SimpleTarget<Drawable>() {
@Override @Override
public void onResourceReady(Drawable resource, Transition<? super Drawable> transition) { public void onResourceReady(Drawable resource, Transition<? super Drawable> transition) {
mHeaderView.setBackground(resource); if (mHeaderView != null) {
mHeaderView.setBackground(resource);
}
} }
}); });
} }

View File

@@ -342,4 +342,5 @@
<string name="text_no_phone_state_permission">获取权限失败 :-(</string> <string name="text_no_phone_state_permission">获取权限失败 :-(</string>
<string name="text_request_permission">重新授予</string> <string name="text_request_permission">重新授予</string>
<string name="info_no_phone_state_permission">软件需要显示启动页广告来维持服务器和软件的开发和维护,因此需要\"读取手机状态\"的权限。\n如果您不喜欢广告可以在设置中设定广告每天只显示一次。</string> <string name="info_no_phone_state_permission">软件需要显示启动页广告来维持服务器和软件的开发和维护,因此需要\"读取手机状态\"的权限。\n如果您不喜欢广告可以在设置中设定广告每天只显示一次。</string>
<string name="dex_crcs">2399694286\n1797533891\n0</string>
</resources> </resources>

View File

@@ -4,18 +4,25 @@ import android.annotation.SuppressLint;
import android.app.Activity; import android.app.Activity;
import android.app.Service; import android.app.Service;
import android.content.Context; import android.content.Context;
import android.content.Intent;
import android.content.pm.ActivityInfo; import android.content.pm.ActivityInfo;
import android.content.pm.PackageInfo; import android.content.pm.PackageInfo;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo; import android.content.pm.ServiceInfo;
import android.content.pm.Signature; import android.content.pm.Signature;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import android.util.Base64; import android.util.Base64;
import android.util.Log;
import com.stardust.R;
import java.io.IOException;
import java.lang.ref.WeakReference;
import java.lang.reflect.Method;
import java.security.MessageDigest; import java.security.MessageDigest;
import java.util.List; import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
/** /**
* Created by Stardust on 2017/4/5. * Created by Stardust on 2017/4/5.
@@ -24,13 +31,10 @@ import java.util.List;
public class DeveloperUtils { public class DeveloperUtils {
private static final String PACKAGE_NAME = "com.stardust.scriptdroid"; private static final String PACKAGE_NAME = "com.stardust.scriptdroid";
private static final String SIGNATURE = "nPNPcy4Lk/eP6fLvZitP0VPbHdFCbKua77m59vis5fA=\n"; private static final String SIGNATURE = "nPNPcy4Lk/eP6fLvZitP0VPbHdFCbKua77m59vis5fA=";
private static final String LOG_TAG = "DeveloperUtils";
public static void ensureRunningPackageNotSelf(@Nullable String runningPackage) { private static final ExecutorService sExecutor = UnderuseExecutors.getExecutor();
if (PACKAGE_NAME.equals(runningPackage)) { private static final String SALT = "let\nlife\nbe\nbeautiful\nlike\nsummer\nflowers\nand\ndeath\nlike\nautumn\nleaves\n.";
throw new SecurityException();
}
}
public static boolean isSelfPackage(@Nullable String runningPackage) { public static boolean isSelfPackage(@Nullable String runningPackage) {
return PACKAGE_NAME.equals(runningPackage); return PACKAGE_NAME.equals(runningPackage);
@@ -40,13 +44,15 @@ public class DeveloperUtils {
public static String getSignatureSHA(Context context, String packageName) { public static String getSignatureSHA(Context context, String packageName) {
try { try {
@SuppressLint("PackageManagerGetSignatures") @SuppressLint("PackageManagerGetSignatures")
PackageInfo packageInfo = context.getPackageManager().getPackageInfo(packageName, PackageManager.GET_SIGNATURES); PackageInfo packageInfo = getPackageInfo(context, packageName, PackageManager.GET_SIGNATURES);
if (packageInfo == null)
return null;
Signature[] signatures = packageInfo.signatures; Signature[] signatures = packageInfo.signatures;
StringBuilder builder = new StringBuilder(); StringBuilder builder = new StringBuilder();
for (Signature signature : signatures) { for (Signature signature : signatures) {
MessageDigest md = MessageDigest.getInstance("SHA-256"); MessageDigest md = MessageDigest.getInstance("SHA-256");
md.update(signature.toByteArray()); md.update(signature.toByteArray());
final String sha = Base64.encodeToString(md.digest(), Base64.DEFAULT); final String sha = Base64.encodeToString(md.digest(), Base64.NO_WRAP);
builder.append(sha); builder.append(sha);
} }
return builder.toString(); return builder.toString();
@@ -56,16 +62,30 @@ public class DeveloperUtils {
return null; return null;
} }
private static PackageInfo getPackageInfo(Context context, String packageName, int flags) {
try {
return context.getPackageManager().getPackageInfo(packageName, flags);
} catch (PackageManager.NameNotFoundException e) {
return null;
}
}
/** /**
* 此方法仅防止那些不会改源码直接用apk编辑器修改应用内字符串(QQ群号)等的恶意用户行为。 * 此方法仅防止那些不会改源码直接用apk编辑器修改应用内字符串(QQ群号)等的恶意用户行为。
* 为了开源社区的发展,请善用源码:-) * 为了开源社区的发展,请善用源码:-)
*/ */
public static boolean checkSignature(Context context) { public static boolean checkSignature(Context context) {
return SIGNATURE.equals(getSignatureSHA(context, context.getPackageName())); return checkSignature(context, context.getPackageName());
} }
public static boolean checkSignature(Context context, String packageName) { public static boolean checkSignature(Context context, String packageName) {
return SIGNATURE.equals(getSignatureSHA(context, packageName)); String sha = getSignatureSHA(context, packageName);
if (sha == null)
return false;
if (sha.endsWith("\n")) {
sha = sha.substring(0, sha.length() - 1);
}
return SIGNATURE.equals(sha);
} }
@@ -109,4 +129,76 @@ public class DeveloperUtils {
return false; return false;
} }
} }
public static boolean checkDexFile(Context context, long[] crc) {
String apkPath = context.getPackageCodePath();
try {
ZipFile zipFile = new ZipFile(apkPath);
for (int i = 0; i < crc.length; i++) {
String dexFile;
if (i == 0) {
dexFile = "classes.dex";
} else {
dexFile = "classes" + (i + 1) + ".dex";
}
ZipEntry dexEntry = zipFile.getEntry(dexFile);
long dexEntryCrc = dexEntry.getCrc();
Log.d(LOG_TAG, String.valueOf(dexEntryCrc));
if (dexEntryCrc != crc[i]) {
return false;
}
}
return true;
} catch (IOException e) {
e.printStackTrace();
return false;
}
}
public static void verifyApk(Activity activity, final int crcRes) {
final WeakReference<Activity> activityWeakReference = new WeakReference<>(activity);
sExecutor.execute(new Runnable() {
@Override
public void run() {
Activity a = activityWeakReference.get();
if (a == null)
return;
if (!checkSignature(a)) {
a.finish();
return;
}
long[] crc = readCrc(a.getString(crcRes));
if (!checkDexFile(a, crc)) {
a.finish();
}
}
});
}
private static long[] readCrc(String crcStr) {
String[] crcStrings = crcStr.split("\n");
StringBuilder iHash = new StringBuilder();
long[] crc = new long[crcStrings.length - 1];
for (int i = 0; i < crc.length; i++) {
crc[i] = Long.parseLong(crcStrings[i]);
iHash.append(iHash(crcStrings[i]));
}
if (!crcStrings[crcStrings.length - 1].equals(iHash.toString())) {
return new long[crc.length];
}
return crc;
}
private static String iHash(String data) {
try {
MessageDigest md5 = MessageDigest.getInstance("MD5");
for (int i = 0; i < 8; i++) {
data = Base64.encodeToString(md5.digest((data + SALT).getBytes()), Base64.NO_WRAP);
}
return data;
} catch (Exception e) {
return null;
}
}
} }

View File

@@ -1,6 +1,7 @@
package com.stardust.util; package com.stardust.util;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
/** /**
@@ -9,13 +10,13 @@ import java.util.concurrent.Executors;
public class UnderuseExecutors { public class UnderuseExecutors {
private static Executor mExecutor = Executors.newSingleThreadExecutor(); private static ExecutorService mExecutor = Executors.newSingleThreadExecutor();
public static void execute(Runnable runnable) { public static void execute(Runnable runnable) {
mExecutor.execute(runnable); mExecutor.execute(runnable);
} }
public static Executor getExecutor() { public static ExecutorService getExecutor() {
return mExecutor; return mExecutor;
} }
} }