一加的剪切板应用(com.oneplus.clipboard)

样本:
http://cloud.appscan.io/app-report.html?id=491412fcc1f84e2491379a0984d0dd8a6a466cf5
https://koodous.com/apks/2f8a01035e0409d1a44c5d658bac0ba4e900df6f017556ce07b33a6c5c9ffa99

下载样本,用apktool解压apk,得到AndroidManifest.xml。没有入口Activity。找到一个Receiver:com.oneplus.clipboard.receiver.BootReceiver
screenshot_2018-01-29_ 3 58 47
监听开机事件,开机之后,会启动:com.oneplus.clipboard.service.ClipBoardService这个Service。
screenshot_2018-01-29_ 4 05 15
然后在这个Service启动之后调用的onStartCommand()方法中设置粘贴板监听器:
screenshot_2018-01-29_ 3 54 54
它实现的onPrimaryClipChanged()方法的内容为:

private OnPrimaryClipChangedListener mPrimaryClipChangedListener = new OnPrimaryClipChangedListener() {
        public void onPrimaryClipChanged() {
            boolean quick_clipboard = true;
            LogUtil.i("mPrimaryClipChangedListener");
            if (System.getInt(ClipBoardService.this.mContext.getContentResolver(), "oem_quick_clipboard", 0) != 1) {
                quick_clipboard = false;
            }
            if (quick_clipboard && ClipBoardService.this.mClipboardManager.hasPrimaryClip()) {
                long nowTime = System.currentTimeMillis();
                LogUtil.i("nowTime - mPreviousTime ==  " + (nowTime - ClipBoardService.this.mPreviousTime));
                if (nowTime - ClipBoardService.this.mPreviousTime < 200) {
                    ClipBoardService.this.mPreviousTime = nowTime;
                } else if (ClipBoardService.this.mClipDataStr == null || !ClipBoardService.this.mClipDataStr.contains(ClipBoardService.VERIFICATION_CODE)) {
                    LogUtil.v("currentActivity === " + MyActivityManager.getActivityManager().currentActivity());
                    if (!(MyActivityManager.getActivityManager().currentActivity() instanceof EmailActivity) && !(MyActivityManager.getActivityManager().currentActivity() instanceof NormalTextActivity) && !(MyActivityManager.getActivityManager().currentActivity() instanceof PhoneActivity) && !(MyActivityManager.getActivityManager().currentActivity() instanceof WebTextActivity) && !(MyActivityManager.getActivityManager().currentActivity() instanceof ExpressActivity) && !(MyActivityManager.getActivityManager().currentActivity() instanceof MapActivity)) {
                        EventBus.getDefault().post(new FinishEvent());
                        ClipBoardService.this.mPreviousTime = nowTime;
                        ClipData clipData = ClipBoardService.this.mClipboardManager.getPrimaryClip();
                        String miniType = ClipBoardService.this.mClipboardManager.getPrimaryClipDescription().getMimeType(0);
                        if (clipData != null && clipData.getItemAt(0) != null && clipData.getItemAt(0).getText() != null) {
                            if ("text/plain".equals(miniType)) {
                                String clipDataStr = clipData.getItemAt(0).getText().toString();
                                if (!TextUtils.isEmpty(clipDataStr) && !TextUtils.isEmpty(clipDataStr.trim())) {
                                    if (!TextUtils.isEmpty(clipDataStr) && clipDataStr.contains(ClipBoardService.VERIFICATION_CODE)) {
                                        ClipBoardService.this.mClipDataStr = clipDataStr;
                                        return;
                                    }
                                }
                                return;
                            } else if ("text/html".equals(miniType)) {
                                if (TextUtils.isEmpty(clipData.getItemAt(0).getHtmlText())) {
                                    return;
                                }
                            } else if ("text/uri-list".equals(miniType)) {
                                if (TextUtils.isEmpty(clipData.getItemAt(0).getUri().toString())) {
                                    return;
                                }
                            } else if ("text/vnd.android.intent".equals(miniType) && clipData.getItemAt(0).getIntent() == null) {
                                return;
                            }
                            LogUtil.v("startOverlayPermissionActivity");
                            Navigation.startOverlayPermissionActivity(ClipBoardService.this.mContext, clipData);
                        }
                    }
                } else {
                    ClipBoardService.this.mClipDataStr = null;
                }
            }
        }
    };

注意到在其onDestroy()方法中会再次打开这个Service,
screenshot_2018-01-29_ 3 54 54
也就是无法彻底干掉它吗?

其最后会通过

Navigation.startOverlayPermissionActivity(ClipBoardService.this.mContext, clipData);

->
screenshot_2018-01-29_ 4 33 44

这一句会打开com.oneplus.clipboard.ui.OverlayPermissionActivity
在其onCreate()方法中:
screenshot_2018-01-29_ 6 00 27
会调用startDealWithDataService(this.mClipData);将粘贴板中的数据传进去。
然后会启动com.oneplus.clipboard.service.DealWithDataService这个Service。
onCreate()->
screenshot_2018-01-29_ 8 48 37
onStartCommand()->
screenshot_2018-01-29_ 8 48 50
->
startClipBoardManager()

    private void startClipBoardManager(Context context, ClipData clipData) {
        if (clipData != null) {
            ClipDescription clipDescription = clipData.getDescription();
            if (clipDescription != null && clipDescription.getMimeTypeCount() > 0) {
                String miniType = clipDescription.getMimeType(0);
                if ("text/plain".equals(miniType) || "text/html".equals(miniType)) {
                    String clipDataStr = clipData.getItemAt(0).getText().toString();
                    this.mClipDataStr = clipDataStr;
                    if ("text/html".equals(miniType)) {
                        this.mClipDataStr = Html.fromHtml(clipData.getItemAt(0).getHtmlText(), 63).toString();
                    }
                    if (!TextUtils.isEmpty(clipDataStr)) {
                        if (UIUtils.isNumeric(clipDataStr)) {
                            NumbericVerify.getInstance(context).verifyNumberic(context, clipDataStr);
                        } else if (!EmailVerify.getInstance(context).verifyMaybeEmail(clipDataStr) || !EmailVerify.getInstance(context).verifyEmail(context, clipDataStr)) {
                            if (!UIUtils.isSupportChina()) {
                                verifyOverSeasText(context, clipDataStr);
                            } else if (ExpressVerify.getInstance(context).maybeExpress(context, clipDataStr)) {
                                verifyMaybeExpress(context, clipDataStr);
                            } else {
                                verifyText(context, clipDataStr);
                            }
                        }
                    }
                } else if ("text/uri-list".equals(miniType)) {
                    clipData.getItemAt(0).getUri();
                } else if ("text/vnd.android.intent".equals(miniType)) {
                    clipData.getItemAt(0).getIntent();
                }
            }
        }
    }

将粘贴板Parcelable化之后,
判断内容是text/plain类型还是text/html类型。(一般粘贴板上的内容都会是文本吧)。
若是text/html类型,则用Html.fromHtml将其转化为txt(碰到img标签会展示为对应的图片)。
参考:
https://developer.android.com/reference/android/text/Html.html#fromHtml(java.lang.String,%20int)
其中
verifyExpress();
verifyMaybeExpress();
verifyMapExpress();
都会调用EntryLoader.getInstance(context).parserOnline():

import org.json.JSONArray;
private static final String PARSER_ONLINE_URL = "http://hitouchtest.meizu.teddymobile.net/?r=api/parse";

    public void parserOnline(String message, final OnlineCallback callback) {
        Map requestMap = new HashMap();
        JSONArray jsonArray = new JSONArray();
        jsonArray.put(message);
        String params = jsonArray.toString();
        if (params != null) {
            requestMap.put("sentences", params);
            // 其中ContactClient.post()方法最终是调用com/loopj/android/http/AsyncHttpRequest.java这个HTTP客户端完成的HTTP请求和响应。
            ContactClient.post(PARSER_ONLINE_URL, new RequestParams(requestMap), new TextHttpResponseHandler() {
                public void onFailure(int i, Header[] headers, String msg, Throwable throwable) {
                    Log.e("online", "onFailure  " + msg);
                    callback.onError(msg);
                }

                public void onSuccess(int i, Header[] headers, String msg) {
                    Log.e("online", "onSuccess  " + msg);
                    callback.onSuccess(EntryLoader.this.jsonToEntites(msg));
                }
            });
        }
    }

其中
ContactClient.post()->
com/loopj/android/http/AsyncHttpClient.java
然而我最终POST测试数据到指定URL,却是这样的结果。换了代理也不行。
screenshot_2018-01-29_ 11 18 27

根据作者提供的截图,查看defpackage/nq.java

/* compiled from: TedSdk */
public class nq {
    private static String a;
    private static nq b;

    public nq() {
        if (ComManager.a != null) {
            a = new AppConfig(ComManager.a).get("filter", "url");
        }
        if (TextUtils.isEmpty(a)) {
            a = "http://url-filter.teddymobile.cn/json/filter.do";
        }
    }

    public static synchronized nq a() {
        nq nqVar;
        synchronized (nq.class) {
            if (b == null) {
                b = new nq();
            }
            nqVar = b;
        }
        return nqVar;
    }

    public String a(String str) {
        if (TextUtils.isEmpty(str)) {
            return null;
        }
        String valueOf = String.valueOf(AppUtil.getChannelId(ComManager.a));
        return a + "?url=" + URLEncoder.encode(str) + "&manufacturer=" + valueOf + "&id=" + if.a(SysInfoUtil.getIMEI(ComManager.a), ik.a(DataBus.FILE_MASK));
    }
}

其构造方法是public的,但是并没有找到除此处外,其他调用该构造方法的地方。
screenshot_2018-01-29_ 11 40 49
其单例模式静态获取nq对象的方法

    public static synchronized nq a() {
        nq nqVar;
        synchronized (nq.class) {
            if (b == null) {
                b = new nq();
            }
            nqVar = b;
        }
        return nqVar;
    }

只在这里com/ted/android/contacts/common/url/JumpUrl.java:272被调用过,
screenshot_2018-01-29_ 11 41 42
然而打开这里查看发现,这句代码所在的方法makeTedUrl()却没有在已知任何地方调用过。
screenshot_2018-01-29_ 11 47 26

据说上传数据的条件是:
screenshot_2018-01-29_ 11 29 12

- 粘贴板的数据不是『数字』,不是email地址;
- 是中国的一加手机;
- 粘贴板的数据匹配了快递的正则。

然而作者也并没有找到有上传数据的流量证据。
//TODO
貌似是相关的视频演示:
https://www.youtube.com/watch?v=Es8_r-rXZVQ
参考:
https://twitter.com/fs0c131y/status/956945666898628608
https://www.v2ex.com/t/426372?p=1#r_5263220
https://bbs.pediy.com/thread-224323.htm

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
如果你依然无法访问剪贴板,即使调用了 Clipboard.Clear() 方法,那么可能是因为剪贴板仍然被占用。 这种情况下,你可以使用 Windows API 来获取当前剪贴板的所有者,并尝试等待剪贴板的所有者释放剪贴板。以下是一个示例代码,可以帮助你实现这一功能: ```csharp [DllImport("user32.dll", SetLastError = true)] public static extern IntPtr GetClipboardOwner(); [DllImport("user32.dll", SetLastError = true)] public static extern int GetWindowThreadProcessId(IntPtr hWnd, out int lpdwProcessId); public static void WaitForClipboard() { int retryCount = 0; int processId; while (true) { IntPtr clipboardOwner = GetClipboardOwner(); if (clipboardOwner == IntPtr.Zero) { // 剪贴板已被释放 return; } // 获取剪贴板所有者所在的进程 ID GetWindowThreadProcessId(clipboardOwner, out processId); // 等待一段时间后继续尝试 Thread.Sleep(1000); retryCount++; if (retryCount > 10) { // 已经尝试了多次,仍然无法访问剪贴板 throw new Exception("无法访问剪贴板"); } } } ``` 在调用了 Clipboard.SetText() 方法之后,你可以先调用 Clipboard.Clear() 方法来释放当前应用程序对剪贴板的锁定,然后调用 WaitForClipboard() 方法来等待剪贴板的所有者释放剪贴板。当 WaitForClipboard() 方法返回时,你可以再次尝试访问剪贴板。 请注意,这种方法可能会导致你的程序出现一些问题,因为它会阻止当前线程的执行。因此,你应该仔细考虑是否需要使用它。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值