随着对用户我的信息保护的愈发重视,相关政策也呼之欲出。例如 “禁止在用户赞成隐私政策前,访问用户我的信息”。java
目前应用商店经过在系统层,监控app运行过程当中对api的访问。咱们的APP,对于应用商店来讲是黑盒,因此在系统层监控是恰当的。android
而咱们的APP对咱们来讲是白盒,咱们能够有更多方式实现监控,甚至“篡改”。git
只要是.class,就均可以aop。 咱们编写gradle插件,利用javassist修改class文件。github
例如这段代码apache
TelephonyManager telephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
String subscriberId = telephonyManager.getSubscriberId();
复制代码
咱们能够把它修改为api
TelephonyManager telephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
//插入log代码
Log.d("alvin",Log.getStackTraceString(new Throwable("android.telephony.TelephonyManager.getSubscriberId")));
String subscriberId = telephonyManager.getSubscriberId();
复制代码
一旦调用了这段代码,就会打印相似堆栈logmarkdown
java.lang.Throwable: android.telephony.TelephonyManager.getSubscriberId
at com.ta.utdid2.a.a.d.getImsi(SourceFile:87)
at com.ta.utdid2.device.b.a(SourceFile:50)
at com.ta.utdid2.device.b.b(SourceFile:72)
at com.ta.utdid2.device.UTDevice.a(SourceFile:50)
at com.ta.utdid2.device.UTDevice.getUtdid(SourceFile:14)
at com.ut.device.UTDevice.getUtdid(SourceFile:19)
at com.alibaba.sdk.android.push.impl.j.a(Unknown Source:10)
at com.alibaba.sdk.android.push.impl.j.register(Unknown Source:58)
at com.a.push.service.PushServiceImpl.initPushService(PushServiceImpl.java:59)
at com.a.BaseApplication.initPushService(BaseApplication.java:465)
复制代码
TelephonyManager telephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
//String subscriberId = telephonyManager.getSubscriberId();
//替换成:
String subscriberId = (String) MainApp.privacyVisitProxy("android.telephony.TelephonyManager", "getSubscriberId", telephonyManager,new Class[0], new Object[0]);
复制代码
咱们代理了系统api访问,就能够本身操控了。app
咱们大概讲讲步骤和核心代码maven
若是建立gradle插件 可参考 Gradle系列一 -- Groovy、Gradle和自定义Gradle插件
插件编写参考了美团的热修复框架 Robust
使用javassist修改class文件 Javassist 使用指南
这里咱们用 buildSrc方式。
plugins {
id 'groovy'
}
repositories {
jcenter()
google()
mavenCentral()
}
dependencies {
implementation gradleApi() //gradle sdk
implementation localGroovy() //groovy sdk
compile group: 'org.smali', name: 'dexlib2', version: '2.2.4'
implementation 'com.android.tools.build:gradle:3.6.1'
implementation 'org.javassist:javassist:3.20.0-GA'
}
sourceSets {
main {
groovy {
srcDir 'src/main/groovy'
}
java {
srcDir "src/main/java"
}
resources {
srcDir 'src/main/resources'
}
}
}
复制代码
PrivacyCheckPlugin.groovy文件
class PrivacyCheckPlugin implements Plugin<Project> {
@Override
void apply(Project project) {
println "this is my custom plugin PrivacyCheckPlugin"
project.android.registerTransform(new PrivacyCheckTransformRob(project))
}
}
复制代码
PrivacyCheckTransformRob.groovy文件
class PrivacyCheckTransformRob extends Transform {
ClassPool classPool = ClassPool.default
Project project
@Override
void transform(TransformInvocation transformInvocation) throws Exception {
super.transform(transformInvocation)
println "----------Privacy check transform start----------"
project.android.bootClasspath.each {
classPool.appendClassPath(it.absolutePath)
}
//1.全部的class通过修改后聚集到这个jar文件中
File jarFile = generateAllClassOutJarFile(transformInvocation)
//2.聚集全部class,包括咱们编写的java代码和第三方jar中的class
def ctClasses = ConvertUtils.toCtClasses(transformInvocation.inputs, classPool)
//3.注入并打包进jarFile (*核心)
PrivacyCheckRob.insertCode(ctClasses, jarFile)
println "----------Privacy check transform end----------"
}
private File generateAllClassOutJarFile(TransformInvocation transformInvocation) {
File jarFile = transformInvocation.outputProvider.getContentLocation(
"main", getOutputTypes(), getScopes(), Format.JAR);
println("jarFile:" + jarFile.absolutePath)
if (!jarFile.getParentFile().exists()) jarFile.getParentFile().mkdirs();
if (jarFile.exists()) jarFile.delete();
return jarFile
}
}
复制代码
ConvertUtils.groovy
class ConvertUtils {
//遍历全部input:directoryInputs 和 jarInput
static List<CtClass> toCtClasses(Collection<TransformInput> inputs, ClassPool classPool) {
List<String> classNames = new ArrayList<>()
List<CtClass> allClass = new ArrayList<>();
def startTime = System.currentTimeMillis()
inputs.each {
it.directoryInputs.each {
println("directory input:"+it.file.absolutePath)
def dirPath = it.file.absolutePath
classPool.insertClassPath(it.file.absolutePath)
org.apache.commons.io.FileUtils.listFiles(it.file, null, true).each {
if (it.absolutePath.endsWith(SdkConstants.DOT_CLASS)) {
def className = it.absolutePath.substring(dirPath.length() + 1, it.absolutePath.length() - SdkConstants.DOT_CLASS.length()).replaceAll(Matcher.quoteReplacement(File.separator), '.')
//META-INF.versions.9.module-info问题解决,参考 https://github.com/Meituan-Dianping/Robust/issues/447
if (!"META-INF.versions.9.module-info".equals(className)) {
if (classNames.contains(className)) {
throw new RuntimeException("You have duplicate classes with the same name : " + className + " please remove duplicate classes ")
}
classNames.add(className)
}
}
}
}
it.jarInputs.each {
println("jar input:"+it.file.absolutePath)
classPool.insertClassPath(it.file.absolutePath)
def jarFile = new JarFile(it.file)
Enumeration<JarEntry> classes = jarFile.entries();
while (classes.hasMoreElements()) {
JarEntry libClass = classes.nextElement();
String className = libClass.getName();
if (className.endsWith(SdkConstants.DOT_CLASS)) {
className = className.substring(0, className.length() - SdkConstants.DOT_CLASS.length()).replaceAll('/', '.')
if (!"META-INF.versions.9.module-info".equals(className)) {
if (classNames.contains(className)) {
throw new RuntimeException("You have duplicate classes with the same name : " + className + " please remove duplicate classes ")
}
classNames.add(className)
}
}
}
}
}
def cost = (System.currentTimeMillis() - startTime) / 1000
println "read all class file cost $cost second"
classNames.each { allClass.add(classPool.get(it)) }
...
return allClass;
}
}
复制代码
PrivacyCheckRob.java
public class PrivacyCheckRob {
public static void insertCode(List<CtClass> ctClasses, File jarFile) throws Exception {
long startTime = System.currentTimeMillis();
ZipOutputStream outStream = new JarOutputStream(new FileOutputStream(jarFile));
for (CtClass ctClass : ctClasses) {
if (ctClass.isFrozen()) ctClass.defrost();
if (!ctClass.isFrozen()&&!ctClass.getName().equals("com.a.privacychecker.MainApp")) {
for (CtMethod ctMethod : ctClass.getDeclaredMethods()) {
ctMethod.instrument(new ExprEditor() {
@Override
public void edit(MethodCall m) throws CannotCompileException {
String mLongName = m.getClassName() + "." + m.getMethodName();
if (PrivacyConstants.privacySet.contains(mLongName)) {
systemOutPrintln(mLongName,m,ctMethod);
// InjectAddLog.execute(m);
// InjectHookReturnValue.execute(m);
InjectMethodProxy.execute(m);
}
}
private void systemOutPrintln(String mLongName, MethodCall m,CtMethod ctMethod) {
StringBuilder sb = new StringBuilder();
sb.append("\n========");
sb.append("\ncall: " + mLongName);
sb.append("\n at: " + ctMethod.getLongName() + "(" + ctMethod.getDeclaringClass().getSimpleName() + ".java:" + m.getLineNumber() + ")");
System.out.println(sb.toString());
}
});
}
}
zipFile(ctClass.toBytecode(), outStream, ctClass.getName().replaceAll("\\.", "/") + ".class");
}
outStream.close();
float cost = (System.currentTimeMillis() - startTime) / 1000.0f;
System.out.println("insertCode cost " + cost + " second");
}
public static void zipFile(byte[] classBytesArray, ZipOutputStream zos, String entryName) {
try {
ZipEntry entry = new ZipEntry(entryName);
zos.putNextEntry(entry);
zos.write(classBytesArray, 0, classBytesArray.length);
zos.closeEntry();
zos.flush();
} catch (Exception e) {
e.printStackTrace();
}
}
}
复制代码
public class InjectMethodProxy {
public static void execute(MethodCall m) throws CannotCompileException {
System.out.println(m.getSignature());
// System.out.println(Arrays.toString(Desc.getParams(m.getSignature())));
String replace = "{ $_ =($r)( com.a.privacychecker.MainApp.privacyVisitProxy(\""+ m.getClassName()+"\",\""+m.getMethodName()+"\", $0,$sig, $args)); }";
m.replace(replace);
}
}
复制代码
记得要添加依赖
dependencies {
api 'org.javassist:javassist:3.22.0-GA'
}
复制代码
public class MainApp extends Application {
public static boolean allowVisit = false;
@Override
public void onCreate() {
super.onCreate();
}
//实际hook代码调用处,实际有删减,能够到github查看
public static Object privacyVisitProxy(String clzName, String methodName, Object obj, Class[] paramsClasses, Object[] paramsValues) {
if (allowVisit) {
//若是容许访问,能够反射,也可根据参数主动调用api访问
return obj == null ? RefInvoke.invokeStaticMethod(clzName, methodName, paramsClasses, paramsValues)
: RefInvoke.invokeInstanceMethod(obj, methodName, paramsClasses, paramsValues);
} else {
String mLongName = clzName + "." + methodName;
if (mLongName.equals(PrivacyConstants.Privacy_getSubscriberId)) {
return "invalid_SubscriberId";
} else if (mLongName.equals(PrivacyConstants.Privacy_getDeviceId)) {
return "invalid_deviceId";
} else if (mLongName.equals(PrivacyConstants.Privacy_getSSID)) {
return "<unknown ssid>";
} else if (mLongName.equals(PrivacyConstants.Privacy_getMacAddress)) {
return "02:00:00:00:00:00";
} else {
return null;
}
}
}
}
复制代码
到此为止就结束了。其实就是利用javassist hook代码。