样本:
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
。
监听开机事件,开机之后,会启动:com.oneplus.clipboard.service.ClipBoardService
这个Service。
然后在这个Service启动之后调用的onStartCommand()
方法中设置粘贴板监听器:
它实现的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,
也就是无法彻底干掉它吗?
其最后会通过
Navigation.startOverlayPermissionActivity(ClipBoardService.this.mContext, clipData);
->
这一句会打开com.oneplus.clipboard.ui.OverlayPermissionActivity
。
在其onCreate()
方法中:
会调用startDealWithDataService(this.mClipData);
将粘贴板中的数据传进去。
然后会启动com.oneplus.clipboard.service.DealWithDataService
这个Service。
onCreate()
->
onStartCommand()
->
->
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,却是这样的结果。换了代理也不行。
根据作者提供的截图,查看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的,但是并没有找到除此处外,其他调用该构造方法的地方。
其单例模式静态获取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
被调用过,
然而打开这里查看发现,这句代码所在的方法makeTedUrl()
却没有在已知任何地方调用过。
据说上传数据的条件是:
即
- 粘贴板的数据不是『数字』,不是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