adb forward的细节(4):使用adb forward打造一个PC端与手机端交互的工具
本文根据《adb forward的细节(1):概述》一文中的原理,做了一个PC端应用与手机端应用交互的工具。
本文目的是,学习和理解 adb forward 的原理,重点没有放在所做的工具上。所以这个PC端应用与手机端应用交互的工具比较简陋,通过命令行实现的。当然你可以根据自己的想法作出更好的工具。
★ 1. 先做一些准备工作
♦ 1.1 下载代码
- 下载PC端ClientApp的代码,此代码在Eclipse中编译,在PC端运行:
git clone https://github.com/galian123/ClientApp
- 下载Samples工程的代码,此代码在Android Studio中编译,运行在手机中
git clone https://github.com/galian123/Samples
♦ 1.2 安装Eclipse (装过的请忽略)
Eclipse下载地址
Eclipse zip包格式下载地址
选『Eclipse IDE for Java Developers 』
♦ 1.3 安装fatjar (装过的请忽略)
FatJar的下载地址:https://github.com/galian123/ClientApp/raw/master/Fatjar_0.0.32.zip
将此 Fatjar_0.0.32.zip 解压缩,将 .jar 拷贝到 Eclipse 的 plugins 目录中。
然后,重启Eclipse。
注:fatjar目前最新只有0.0.31版本。其实,Fatjar_0.0.32.zip 的内容与 fatjar 0.0.31版本 是一样的,只是目录结构调整了,目的是让fatjar可以加载到eclipse中。
安装后,如图:
Build Fat Jar时, 别忘了写Main Class, 如图中的 com.galian.ClientApp
:
♦ 1.4 安装Android Studio
Android Studio下载地址:https://developer.android.com/studio/
目前最新3.1.2版本。
★ 2. PC端 ClientApp 代码
在 Eclipse 中 Import 这个工程,然后 Build project,并通过『+Build Fat Jar』生成 ClientApp_fat.jar
注意:为了节省篇幅,省略了try-catch中的catch块的代码,在实际编码中catch异常并作出适当处理是必须的。
♦ 2.1 创建 client socket,连接127.0.0.1:8888的server
private static void startClient() {
try {
// 连接到 地址127.0.0.1,端口8888的server
socket = new Socket("127.0.0.1", 8888);
mRunning = true;// 为了exit设置的变量
// InThread 接收手机端传来的数据
new Thread(new InThread(socket)).start();
// OutThread 用于从PC端输入并传给手机端
new Thread(new OutThread(socket)).start();
} catch ...
}
♦ 2.2 InThread 接收手机端传来的数据
static class InThread implements Runnable {
Socket socket = null;
public InThread(Socket s) {
socket = s;
}
@Override
public void run() {
while (mRunning) {
if (socket.isClosed()) {
mRunning = false;
break;
}
InputStream in = null;
try {
in = socket.getInputStream();
} catch ...
InputStreamReader reader = new InputStreamReader(in);
try {
char[] buf = new char[1024000];
int cnt = reader.read(buf);
if (cnt > 0) {
String msg = new String(buf, 0, cnt);
System.out.println(msg);
}
} catch ...
}
}
}
♦ 2.3 OutThread 从PC端输入并传给手机端
static class OutThread implements Runnable {
Socket socket = null;
public OutThread(Socket s) {
socket = s;
}
@Override
public void run() {
System.out.println("Please type in something...");
System.out.println("Type in 'exit' to exit.");
System.out.println("Type in 'getattacksurface package_name' to get attack surface.");
System.out.println();
while (mRunning) {
if (socket.isClosed()) {
mRunning = false;
break;
}
OutputStream out = null;
try {
out = socket.getOutputStream();
} catch ...
InputStreamReader reader = new InputStreamReader(System.in);
String msg = "";
try {
char[] buf = new char[10240];
int cnt = reader.read(buf);
if (cnt > 0) {
msg = new String(buf, 0, cnt);
}
} catch ...
msg = msg.trim();// delete trailing linefeed
if (msg.equalsIgnoreCase("exit")) {
mRunning = false;
try {
socket.close();
} catch ...
break;
}
// input "getattacksurface package_name", get the attack surface of that package
try {
out.write(msg.getBytes());
out.flush();
} catch ...
}
}
}
★ 3. 手机端 ServerActivity 代码
注意:为了节省篇幅,省略了try-catch中的catch块的代码,在实际编码中catch异常并作出适当处理是必须的。
♦ 3.1 声明网络权限
一定要声明android.permission.INTERNET
权限,否则会报异常java.net.SocketException: Permission denied
:
在AndroidManifest.xml中声明权限:
<uses-permission android:name="android.permission.INTERNET" />
♦ 3.2 启动Server
启动Server之后,会显示『Server is started.』的Toast。
void startServer() {
new ServerThread().start();
}
♦ 3.3 ServerThread
private class ServerThread extends Thread {
@Override
public void run() {
if (!mRunning) {
try {
mServerSocket = new ServerSocket(PORT);
mRunning = true;
} catch ...
while (mRunning) {
Socket socket;
try {
socket = mServerSocket.accept();
new ProcessClientRequestThread(socket).start();
} catch ...
}
}
}
}
♦ 3.4 ProcessClientRequestThread
为每一个连接的client,启动一个线程来处理请求。
private class ProcessClientRequestThread extends Thread {
private Socket mSocket = null;
ProcessClientRequestThread(Socket socket) {
mSocket = socket;
}
@Override
public void run() {
while (mRunning) {
if (!mSocket.isConnected()) {
break;
}
InputStream in;
OutputStream out;
try {
in = mSocket.getInputStream();
out = mSocket.getOutputStream();
} catch ...
// 处理client传来的数据
InputStreamReader reader = new InputStreamReader(in);
try {
char[] buf = new char[10240];
int cnt = reader.read(buf);
if (cnt > 0) {
String msg = new String(buf, 0, cnt);
Log.d(TAG, "Receive: " + msg);
String GET_ATTACK_SURFACE = "getattacksurface";
if (msg.startsWith(GET_ATTACK_SURFACE)) {
String pkgName = msg.substring(GET_ATTACK_SURFACE.length()).trim();
Log.d(TAG, "Get Attack Surface for " + pkgName);
ArrayList<String> results = getAttackSurface(pkgName);
for (String res : results) {
out.write(res.getBytes());
out.flush();
}
} else {
String reply = "Server Said: I received '" + msg + "'";
out.write(reply.getBytes());
out.flush();
}
}
} catch ...
}
}
}
♦ 3.5 获取攻击面的代码
private ArrayList<String> getAttackSurface(String pkgName) {
PackageManager pm = getPackageManager();
int[] exportedCnt = new int[4]; // 0 for activity, 1 for receiver, 2 for service, 3 for provider
String sharedUid = null;
// for not exceed binder buffer, get activities solely
int flags = PackageManager.GET_ACTIVITIES;
ArrayList<BaseInfo> activities = new ArrayList<>();
try {
PackageInfo pkgInfo = pm.getPackageInfo(pkgName, flags);
sharedUid = pkgInfo.sharedUserId;
for (ActivityInfo ai : pkgInfo.activities) {
if (ai.exported) {// not check 'enabled', because 'disabled' activity can be enabled later
exportedCnt[0]++;
activities.add(new BaseInfo(ai.name, ai.permission));
}
}
} catch ...
ArrayList<BaseInfo> services = new ArrayList<>();
ArrayList<BaseInfo> receivers = new ArrayList<>();
ArrayList<MyProviderInfo> providers = new ArrayList<>();
flags = PackageManager.GET_PROVIDERS |
PackageManager.GET_SERVICES | PackageManager.GET_RECEIVERS;
try {
PackageInfo pkgInfo = pm.getPackageInfo(pkgName, flags);
if (pkgInfo.receivers != null && pkgInfo.receivers.length > 0) {
for (ActivityInfo ri : pkgInfo.receivers) {
if (ri.exported) {
exportedCnt[1]++;
receivers.add(new BaseInfo(ri.name, ri.permission));
}
}
}
if (pkgInfo.services != null && pkgInfo.services.length > 0) {
for (ServiceInfo si : pkgInfo.services) {
if (si.exported) {
exportedCnt[2]++;
services.add(new BaseInfo(si.name, si.permission));
}
}
}
if (pkgInfo.providers != null && pkgInfo.providers.length > 0) {
for (ProviderInfo pi : pkgInfo.providers) {
if (pi.exported) {
exportedCnt[3]++;
MyProviderInfo mpi = new MyProviderInfo(pi.name, pi.authority, pi.readPermission,
pi.writePermission, pi.grantUriPermissions, pi.multiprocess);
if (pi.uriPermissionPatterns != null && pi.uriPermissionPatterns.length > 0) {
mpi.uriPermissionPatterns = new ArrayList<>();
for (PatternMatcher patternMatcher : pi.uriPermissionPatterns) {
mpi.uriPermissionPatterns.add(new MyPattern(patternMatcher.getType(), patternMatcher.getPath()));
}
}
if (pi.pathPermissions != null && pi.pathPermissions.length > 0) {
mpi.pathPermissions = new ArrayList<>();
for (PathPermission pathPermission : pi.pathPermissions) {
mpi.pathPermissions.add(new MyPathPermission(pathPermission.getType(), pathPermission.getPath(),
pathPermission.getReadPermission(), pathPermission.getWritePermission()));
}
}
providers.add(mpi);
}
}
}
} catch ...
ArrayList<String> results = new ArrayList<>();
StringBuffer buf = new StringBuffer();
buf.append("Package: ").append(pkgName).append("\n");
buf.append("Attack Surface:").append("\n");
buf.append(" ").append(exportedCnt[0]).append(" activities exported").append("\n");
buf.append(" ").append(exportedCnt[1]).append(" broadcast receivers exported").append("\n");
buf.append(" ").append(exportedCnt[2]).append(" services exported").append("\n");
buf.append(" ").append(exportedCnt[3]).append(" content providers exported").append("\n");
buf.append(" Shared UID ( ").append(sharedUid).append(" )").append("\n");
buf.append("\n");
results.add(buf.toString());
buf = new StringBuffer();
buf.append("Exported activities: ").append(exportedCnt[0]).append("\n");
for (int i = 0; i < activities.size(); i++) {
BaseInfo bi = activities.get(i);
buf.append(" ").append(i + 1).append(" ").append(bi.name).append("\n");
buf.append(" Permission: ").append(bi.permission).append("\n");
}
buf.append("\n");
results.add(buf.toString());
buf = new StringBuffer();
buf.append("Exported receivers: ").append(exportedCnt[1]).append("\n");
for (int i = 0; i < receivers.size(); i++) {
BaseInfo bi = receivers.get(i);
buf.append(" ").append(i + 1).append(" ").append(bi.name).append("\n");
buf.append(" Permission: ").append(bi.permission).append("\n");
}
buf.append("\n");
results.add(buf.toString());
buf = new StringBuffer();
buf.append("Exported services: ").append(exportedCnt[2]).append("\n");
for (int i = 0; i < services.size(); i++) {
BaseInfo bi = services.get(i);
buf.append(" ").append(i + 1).append(" ").append(bi.name).append("\n");
buf.append(" Permission: ").append(bi.permission).append("\n");
}
buf.append("\n");
results.add(buf.toString());
buf = new StringBuffer();
buf.append("Exported providers: ").append(exportedCnt[3]).append("\n");
for (int i = 0; i < providers.size(); i++) {
MyProviderInfo mpi = providers.get(i);
buf.append(" ").append(i + 1).append(" ").append(mpi.name).append("\n");
buf.append(" Authority: ").append(mpi.authority).append("\n");
buf.append(" Read Permission: ").append(mpi.readPermission).append("\n");
buf.append(" Write Permission: ").append(mpi.writePermission).append("\n");
buf.append(" Multiprocess Allowed: ").append(mpi.multiprocess ? "True" : "False").append("\n");
buf.append(" Grant Uri Permissions: ").append(mpi.grantUriPermissions ? "True" : "False").append("\n");
if (mpi.uriPermissionPatterns != null && mpi.uriPermissionPatterns.size() > 0) {
buf.append(" UriPermissionPatterns: ").append("\n");
for (int j = 0; j < mpi.uriPermissionPatterns.size(); j++) {
MyPattern myPattern = mpi.uriPermissionPatterns.get(i);
buf.append(" ").append(j + 1).append(" Type: ").append(myPattern.getTypeStr());
buf.append(" ").append(myPattern.pattern).append("\n");
}
}
if (mpi.pathPermissions != null && mpi.pathPermissions.size() > 0) {
buf.append(" PathPermissions: ").append("\n");
for (int j = 0; j < mpi.pathPermissions.size(); j++) {
MyPathPermission myPathPermission = mpi.pathPermissions.get(i);
buf.append(" ").append(j + 1).append(" Type: ").append(myPathPermission.getTypeStr());
buf.append(" ").append(myPathPermission.pattern).append("\n");
buf.append(" Read permission: ").append(myPathPermission.readPermission).append("\n");
buf.append(" Write permission: ").append(myPathPermission.writePermission).append("\n");
}
}
}
buf.append("\n");
results.add(buf.toString());
return results;
}
★ 4. PC端与手机端通信
♦ 4.1 建立通信
执行3步就可以让PC端工具(ClientApp)和手机端工具(Samples app)通信了。
- 第一步:PC端执行
adb forward tcp:8888 tcp:3652
此步执行后,没有输出。
可以通过adb forward --list
看到结果。
$ adb forward --list
4391b53a tcp:8888 tcp:3652
第二步:手机端打开Samples app,打开Server(端口号为3652)
运行Samples app -> 点击『Test Adb Forward(Server)』 button -> 点击『Start Server』,然后会看到『Server is started』的toast。第三步:PC端运行:
java -jar ClientApp_fat.jar
执行后,界面上显示如下,此时可以输入一些文本了。$ java -jar ClientApp_fat.jar Please type in something... Type in 'exit' to exit. Type in 'getattacksurface package_name' to get attack surface.
输入『exit』,退出client端。
输入『getattacksurface xxx』,获取某个package的攻击面。
输入任意其他内容『xxx』,手机端会返回『Server Said: I received ‘xxx’』。
♦ 4.2 基本的操作
输入『hi』、『hello』、『How are you?』等随意的文本,手机端返回『Server Said: I received ‘hi’』这样的文本。
hi
Server Said: I received 'hi'
hello
Server Said: I received 'hello'
How are you?
Server Said: I received 'How are you?'
♦ 4.3 获取手机中的包名
如果要获取手机中某个应用的攻击面,则需要先知道应用的包名。
我的adb版本 1.0.39 :
$ adb version
Android Debug Bridge version 1.0.39
Revision 3db08f2c6889-android
Installed as D:\androidsdk\platform-tools\adb.exe
查看手机中有哪些package,执行adb shell cmd package list packages
。我用的是坚果Pro,找到包名含有『smartisanos』的,只是举个例子,你可以找你的手机中的任意的包名。
$ adb shell cmd package list packages | grep smartisanos
package:com.smartisanos.payment
package:com.smartisanos.setupwizard
package:com.smartisanos.security
...
...
♦ 4.4 获取某个应用的攻击面
例如,获取com.smartisanos.security的攻击面(注:根据drozer工具的run app.package.attacksurface
设计的),仅供参考。
其实检测的并不准确,因为没有考虑权限的类型。
例如,
smartisan.permission.READ_SECURITYCENTER_PACKAGE
和 smartisan.permission.WRITE_SECURITYCENTER_PACKAGE
权限 是signature & system的。
所以使用了这两个权限的Provider基本上是没问题的。
smartisanos.expandservice.data_sync
、 com.smartisanos.permission.CALL_SETTINGS
权限也是signature & system的。使用这两个权限保护的组件大概率也没问题。
获取com.smartisanos.security的攻击面的返回结果:
$ java -jar ClientApp_fat.jar
Please type in something...
Type in 'exit' to exit.
Type in 'getattacksurface package_name' to get attack surface.
getattacksurface com.smartisanos.security (输入的,然后按回车)
Package: com.smartisanos.security (从这行开始,是手机端返回的)
Attack Surface:
9 activities exported
4 broadcast receivers exported
2 services exported
5 content providers exported
Shared UID ( null )
Exported activities: 9
1 com.smartisanos.securitycenter.MainActivity
Permission: null
2 com.smartisanos.security.PermissionsActivity
Permission: null
3 com.smartisanos.security.RadioPermissions
Permission: null
4 com.smartisanos.security.PackagesOverview
Permission: null
5 com.smartisanos.security.PackageDetail
Permission: null
6 com.smartisanos.security.AllPermissionDetail
Permission: null
7 com.smartisanos.security.invokeHistory.InvokeHistoryActivity
Permission: null
8 com.smartisanos.preventdisturb.DisturbActivity
Permission: null
9 com.smartisanos.securitycenter.NetworkOptimizationSetting
Permission: null
Exported receivers: 4
1 com.smartisanos.securitycenter.PackageReceiver
Permission: null
2 com.smartisanos.securitycenter.SyncReceiver
Permission: smartisanos.expandservice.data_sync
3 com.smartisanos.datausage.BootCompletedReceiver
Permission: null
4 com.smartisanos.security.PermissionTestingBroadcastReceiver
Permission: com.smartisanos.permission.CALL_SETTINGS
Exported services: 2
1 com.smartisanos.datausage.TencentService
Permission: null
2 com.smartisanos.cleaner.FakeService
Permission: null
Exported providers: 5
1 com.smartisanos.securitycenter.dynamicResolution.DynamicResolutionProvider
Authority: com.smartisanos.security.dynamicResolution
Read Permission: smartisan.permission.READ_SECURITYCENTER_PACKAGE
Write Permission: smartisan.permission.WRITE_SECURITYCENTER_PACKAGE
Multiprocess Allowed: False
Grant Uri Permissions: False
2 com.smartisanos.securitycenter.appBatteryUseOptimization.AppUseBatteryOptimizationProvider
Authority: com.smartisanos.security.appUseBatteryOptimization
Read Permission: smartisan.permission.READ_SECURITYCENTER_PACKAGE
Write Permission: smartisan.permission.WRITE_SECURITYCENTER_PACKAGE
Multiprocess Allowed: False
Grant Uri Permissions: False
3 com.smartisanos.security.invokeHistory.InvokeHistoryProvider
Authority: com.smartisanos.security.invokeHistory
Read Permission: smartisan.permission.READ_SECURITYCENTER_PACKAGE
Write Permission: smartisan.permission.WRITE_SECURITYCENTER_PACKAGE
Multiprocess Allowed: False
Grant Uri Permissions: False
4 com.smartisanos.security.PermissionAPIProvider
Authority: com.smartisanos.security.apiprovider
Read Permission: smartisan.permission.READ_SECURITYCENTER_PACKAGE
Write Permission: smartisan.permission.READ_SECURITYCENTER_PACKAGE
Multiprocess Allowed: False
Grant Uri Permissions: False
5 com.smartisanos.datausage.DataUsageProvider
Authority: com.smartisanos.tencent
Read Permission: smartisan.permission.READ_SECURITYCENTER_PACKAGE
Write Permission: smartisan.permission.READ_SECURITYCENTER_PACKAGE
Multiprocess Allowed: False
Grant Uri Permissions: False
★ 5. 参考
(1)Fatjar:http://fjep.sourceforge.net/
(2)Drozer: https://labs.mwrinfosecurity.com/tools/drozer/
(3)《adb forward的细节(1):概述》