免Root实现静默安装和点击任意位置(非无障碍)

  • 本篇文章已授权微信公众号 guolin_blog (郭霖)独家发布

0 前言

最近有了个需求:免 root 实现任意位置点击和静默安装。这个做过的小伙伴应该都知道正常情况下是不可能实现的。无障碍只能实现对已知控件的点击,并不能指定坐标。但是确实有人另辟蹊径做出来了,譬如做游戏手柄的飞智,他们是用一个激活器,手机开 usb 调试,然后插在激活器上并授权,飞智游戏厅就被「激活」了,然后可以实现任意位置点击。如果不了解的可以去他们官网了解下,在这里不多赘述了。无独有偶,黑域也使用了类似的手段,也可以用电脑的usb调试激活。我们知道,任意位置坐标xy点击是可以在 pc 上通过 shell 命令「input tap x y」来实现的,也不需要 root 权限。但是在应用内通过「Runtime.getRuntime().exec」执行这个 shell 命令却提示「permission denied」也就是权限不足。但是飞智或者黑域却好像使用了某种魔法,提升了自己的权限,那么问题来了:如何用 usb 调试给 app 提权?

1 原理揭晓

「如何用 usb 调试给 app 提权」这个问题乍一看确实没问题,但是知乎有个回答是「先问是不是,再问为什么」我觉得说的很好。我被这个问题给困扰了很久,最后发现我问错了。先放出结论「并不是给 app 提权,而是运行了一个有 shell 权限的新程序」

刚才的问题先放一边,我来问大家个新问题,怎样让 app 获取 root 权限?这个问题答案已经有不少了,网上一查便可知其实是获取「Runtime.getRuntime().exec」的流,在里面用su提权,然后就可以执行需要 root 权限的 shell 命令,比如挂载 system 读写,访问 data 分区,用 shell 命令静默安装,等等。话说回来,是不是和我们今天的主题有点像,如何使 app 获取 shell 权限?嗯,其实差不多,思路也类似,因为本来 root 啦, shell 啦,根本就不是 Android 应用层的名词呀,他们本来就是 Linux 里的名词,只不过是 Android 框架运行于 Linux 层之上, 我们可以调用 shell 命令,也可以在shell 里调用 su 来使shell 获取 root 权限,来绕过 Android 层做一些被限制的事。然而在 app 里调用 shell 命令,其进程还是 app 的,权限还是受限。所以就不能在 app 里运行 shell 命令,那么问题来了,不在 app 里运行在哪运行?答案是在 pc 上运行。当然不可能是 pc 一直连着手机啦,而是 pc 上在 shell 里运行独立的一个 java 程序,这个程序因为是在 shell 里启动的,所以具有 shell 权限。我们想一下,这个 Java 程序在 shell 里运行,建立本地 socket 服务器,和 app 通信,远程执行 app 下发的代码。因为即使拔掉了数据线,这个 Java 程序也不会停止,只要不重启他就一直活着,执行我们的命令,这不就是看起来 app 有了 shell 权限?现在真相大白,飞智和黑域用 usb 调试激活的那一下,其实是启动那个 Java 程序,飞智是执行模拟按键,黑域是监听系统事件,你想干啥就任你开发了。「注:黑域和飞智由于进程管理的需要,其实是先用 shell 启动一个 so ,然后再用 so 做跳板启动 Java 程序,而且 so 也充当守护进程,当 Java 意外停止可以重新启动,读着有兴趣可以自行研究,在此不多做说明」

2 好耶!是 app_process

那么如何具体用 shell 运行 Java 程序呢?肯定不是「java xxx.jar」啦,Android 能运行的格式是 dex 。没错,就是apk 里那个 dex 。然后我们可以通过「app_process」开启动 Java 。app_process 的参数如下

app_process [vm-options] cmd-dir [options] start-class-name [main-options]

这个诡异又可怕的东西是没有 -help 的。我们要么看源码,要么看别人分析好的。本人水平有限,这里选择看别人分析好的:

vm-options – VM 选项
cmd-dir –父目录 (/system/bin)
options –运行的参数 :
    –zygote
    –start-system-server
    –application (api>=14)
    –nice-name=nice_proc_name (api>=14)
start-class-name –包含main方法的主类  (com.android.commands.am.Am)
main-options –启动时候传递到main方法中的参数

3 实践

因为是 dex 我们就直接在 as 里写吧,提取 dex 也方便。新建个空白项目,初始结构是这样:

image

我们新建个包,存放我们要在 shell 下运行的 Java 代码:

image

这里我们补全 Main 方法,因为我们这个不是个 Android 程序,只是编译成 dex 的纯 Java 程序,所以我们这个的入口是 Main :

package shellService;

public class Main {
    public static void main(String[] args){
        System.out.println("我是在 shell 里运行的!!!");
    }
}

我们在代码里只是打印一行「我是在 shell 里运行的!!!」,因为这里是纯 Java 所以也用的 println。现在编译 apk:

image

因为 apk 就是 zip 所以我们直接解压出 apk 文件里的classes.dex,然后执行 :

adb push classes.dex /data/local/tmp
cd /data/local/tmp
app_process -Djava.class.path=/data/local/tmp/classes.dex /system/bin shellService.Main

这时就能看到已经成功运行啦:

image

这里因为 utf8 在 Windows shell 里有问题,所以乱码了,但是还是说明我们成功了。

4 具有实用性

只能输出肯定是不行的,不具有实用性。我们之前说过,我们应该建立个本地 socket 服务器来接受命令并执行,这里的「Service」类实现了这个功能,因为如何建立 socket 不是文章的重点,所以大家只要知道这个类内部实现了一个「ServiceGetText」接口,在收到命令之后会把命令内容作为参数回掉 getText 方法,然后我们执行 shell 命令之后,吧结果作为字符串返回即可,具体实现可以看查看源码Service

我们新建一个「ServiceThread」来运行「Service」服务和执行设立了命令:

public class ServiceThread extends Thread {
    private static int ShellPORT = 4521;

    @Override
    public void run() {
        System.out.println(">>>>>>Shell服务端程序被调用<<<<<<");
        new Service(new Service.ServiceGetText() {
            @Override
            public String getText(String text) {
                if (text.startsWith("###AreYouOK")){
                    return "###IamOK#";
                }
                try{
                    ServiceShellUtils.ServiceShellCommandResult sr =  ServiceShellUtils.execCommand(text, false);
                    if (sr.result == 0){
                        return "###ShellOK#" + sr.successMsg;
                    } else {
                        return "###ShellError#" + sr.errorMsg;
                    }
                }catch (Exception e){
                    return "###CodeError#" + e.toString();
                }
            }
        }, ShellPORT);
    }
}

其中 ServiceShellUtils 用到了开源项目 ShellUtils 在此感谢。这个类用来执行 shell 命令。

然后在 Main 中调用这个线程:

public class Main {

    public static void main(String[] args){
        new ServiceThread().start();
        while (true);
    }

}

这样,我们服务端就准备好了,我们来写控制服务端的 app 。我们新建类「SocketClient」用来和服务端进行通信,并在活动里调用他(完整代码请参看SocketClientMainActivity):

private void runShell(final String cmd){
        if (TextUtils.isEmpty(cmd)) return;
        new Thread(new Runnable() {
            @Override
            public void run() {
              new SocketClient(cmd, new SocketClient.onServiceSend() {
                  @Override
                  public void getSend(String result) {
                      showTextOnTextView(result);
                  }
              });
            }
        }).start();
    }

然后重复 3 小节的操作,运行这个服务端:

image

然后安装 apk ,运行:

input text HelloWord
image

可以看到,在不 root 的情况下,成功的执行了需要 shell 权限的命令

5 最可爱的人

最后,我真的是要由衷的感谢各种技术分析文章和开源项目,真的太感谢了,没有无条件的奉献就没有互联网这么快的进步。

我对 app_process 利用方法的研究离不开以下项目和前辈的汗水:

Brevent 最早利用app_process进程实现无 root 权限使用的开源应用(虽然已经闭源,仍然尊重并感谢 liudongmiao

Android system log viewer on Android phone without root. 利用app_process进程实现无 root 权限使用的优秀开源应用

Android上app_process启动java进程 通俗易懂的教程

使用 app_process 来调用高权限 API 分析的很深刻的教程

本文的项目可以在GitHub上获取https://github.com/gtf35/app_process-shell-use

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,736评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,167评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,442评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,902评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,302评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,573评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,847评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,562评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,260评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,531评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,021评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,367评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,016评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,068评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,827评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,610评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,514评论 2 269

推荐阅读更多精彩内容