Android 应用多开对抗实践
山中何事,松花酿酒,春水煎茶。本无始,也是无终。不过是一眼风雨,一纸山河。
<span style="font-size: 14px; color: rgb(136, 136, 136); letter-spacing: 0.5px;" <="" span="">
本文为看雪论坛优秀文章
看雪论坛作者ID:Amun
白天午睡梦到些以前给某行业安全峰会写了材料,醒来后把记得的部分重新整理一下,分享出来给大家,尽量写得简洁明了。
未必准确,仅供参考,欢迎纠正补充。
目录
应用多开技术总结
系统级技术
用户级技术
拆招
反系统级应用多开
简单粗暴的代码
验证
可改进
反用户级应用多开
仍然是简单粗暴的代码
验证
威力加强版
对用户级应用多开的理解
业务前端白名单
后记
>>>> 系统级技术
系统级技术
>>>> 用户级技术
用户级技术
“容器”:VirtualApp、MultiDroid
热更新/插件化:DroidPlugin、Excelliance
虚拟系统:虚拟大师
>>>> 反系统级应用多开
反系统级应用多开
简单粗暴的代码
// --- C++ ---
bool isDualApp(){return 0 != getuid()/100000;}
// --- Java ---
import android.os.Process;
static boolean isDualApp(){return 0 != Process.myUid() / 100000;}
# --- adb shell ---
$ ls -al /data/user/
total 52
drwx--x--x 4 system system 4096 2019-09-05 11:49 .
drwxrwx--x 42 system system 4096 2019-04-22 20:32 ..
lrwxrwxrwx 1 root root 10 1970-08-23 18:57 0 -> /data/data
drwxrwx--x 221 system system 16384 2019-09-05 11:50 11
drwxrwx--x 13 system system 16384 2019-09-12 17:53 999
// android.os.UserManger.java
public boolean isAdminUser() {
return isUserAdmin(UserHandle.myUserId());
}
// ...
public boolean isPrimaryUser() {
UserInfo user = getUserInfo(UserHandle.myUserId());
return user != null && user.isPrimary();
}
// android.os.UserHandle.java
/**
* @hide A user id constant to indicate the "owner" user of the device
* @deprecated Consider using either {@link UserHandle#USER_SYSTEM} constant or
* check the target user's flag {@link android.content.pm.UserInfo#isAdmin}.
*/
public static final int USER_OWNER = 0;
// ...
public static final int USER_SYSTEM = 0;
// ...
public static final int PER_USER_RANGE = 100000;
// ...
public static int getUserId(int uid) {
if (MU_ENABLED) {
return uid / PER_USER_RANGE;
} else {
return UserHandle.USER_SYSTEM;
}
}
验证
# --- adb shell ---
$ ps -ef | grep u999
u999_a118 14392 905 0 14:07:52 ? 00:00:01 com.miui.analytics
u999_system 19793 905 0 19:55:24 ? 00:00:00 com.android.keychain
shell 20408 13712 3 19:58:11 pts/0 00:00:00 grep u999
$ cat /proc/14392/status
Name: .miui.analytics
Umask: 0077
State: S (sleeping)
Tgid: 14392
Ngid: 0
Pid: 14392
PPid: 905
TracerPid: 0
Uid: 99910118 99910118 99910118 99910118
Gid: 99910118 99910118 99910118 99910118
可改进
>>>> 反用户级应用多开
反用户级应用多开
仍然是简单粗暴的代码
// --- C++ ---
bool isDualApp(std::string dataDir) {
return 0 == access((dataDir + "/../").c_str(), R_OK);
}
// --- Java ---
import java.io.File;
boolean isDualApp(String dataDir){
return new File(dataDir + File.separator + "..").canRead();
}
# ls -al /data/app/com.example.checksandbox-ElZnrZIb5m2rbBv_3K8nZQ\=\=/
total 2420
drwxr-xr-x 3 system system 4096 2019-09-24 16:15 .
drwxrwx--x 80 system system 12288 2019-09-24 16:15 ..
-rw-r--r-- 1 system system 2440234 2019-09-24 16:15 base.apk
drwxr-xr-x 3 system system 4096 2019-09-24 16:15 lib
# ls -al /data/user/0/com.example.checksandbox/
total 40
drwx------ 5 u0_a366 u0_a366 4096 2019-09-24 16:15 .
drwxrwx--x 271 system system 20480 2019-09-24 16:15 ..
drwxrws--x 2 u0_a366 u0_a366_cache 4096 2019-09-24 16:15 cache
drwxrws--x 2 u0_a366 u0_a366_cache 4096 2019-09-24 16:15 code_cache
drwxrwxr-x 5 u0_a366 u0_a366 4096 2019-09-24 16:15 lldb
验证
威力加强版
graph LR;
A[dataDir的fd] --> B[fd反查真实路径]
B --> C[尝试访问真实路径的父目录]
// --- Java ---
import android.support.annotation.RequiresApi;
import java.io.File;
import java.io.FileDescriptor;
import java.io.FileOutputStream;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
(api = Build.VERSION_CODES.O)
boolean isDualAppEx(String dataDir) {
try {
String simpleName = "wtf_jack";
String testPath = dataDir + File.separator + simpleName;
FileOutputStream fos = new FileOutputStream(testPath);
FileDescriptor fileDescriptor = fos.getFD();
Field fid_descriptor = fileDescriptor.getClass().getDeclaredField("descriptor");
fid_descriptor.setAccessible(true);
// 获取到 fd
int fd = (Integer) fid_descriptor.get(fileDescriptor);
// fd 反查真实路径
String fdPath = String.format("/proc/self/fd/%d", fd);
String realPath = Files.readSymbolicLink(Paths.get(fdPath)).toString();
if (!realPath.substring(realPath.lastIndexOf(File.separator))
.equals(File.separator + simpleName)){
// 文件名被修改
return true;
}
// 尝试访问真实路径的父目录
String fatherDirPath = realPath.replace(simpleName, "..");
Log.d(TAG, "isDualAppEx: " + fatherDirPath);
File fatherDir = new File(fatherDirPath);
if (fatherDir.canRead()) {
// 父目录可访问
return true;
}
} catch (Exception e) {
e.printStackTrace();
return true;
}
return false;
}
对用户级应用多开的理解
业务前端白名单
APK 包名 APK 签名公钥 APK 内文件(文件名/CRC/HASH)
看雪ID:Amun
https://bbs.pediy.com/user-850401.htm
推荐文章++++