在APP中如何使用系统属性执行shell脚本

背景

前不久有一个白名单的需求,之前的实现方式由APP来调用framework中添加的接口,再调用到system/netd/ ,之后在执行iptables的命令来设置防火墙规则,详情可见这篇: Android11 iptables实现网络防火墙

但是用这种方式后发现,当防火墙的需求发生变化,每次在firewallController中修改iptables命令,都需要重新增量编译,浪费调试时间,于是有了该篇

本篇主要内容不是为实现白名单需求,而是介绍如何在APP种通过系统属性调用Shell脚本

方案

本次方案的具体方式为

  1. APP添加白名单时,将该规则按指定格式添加到一个文件中,假设文件保存在/data/netWhiteList.txt

    java 复制代码
    # 格式
    IP:192.168.1.1 Netmask:24 Port:8000
  2. 编写Shell脚本,内容是大概为: 根据白名单文件,遍历文件内规则,执行iptables命令,设置防火墙规则

  3. Shell脚本添加到rc文件中,设置为service

  4. 设置Shell脚本的se权限

  5. 通过系统属性来调用ctl.start拉起service,执行特定的脚本,还可以指定参数

属性 ctrl.startctrl.stop 是用来启动和停止服务。这里的服务是指定义在 rc 后缀文件中的服务。当我们向 ctrl.start 属性写入一个值时,属性服务将使用该属性值作为服务名找到该服务,启动该服务。这项服务的启动结果将会放入 init.svc.<服务名> 属性中,可以通过查询这个属性值,以确定服务是否已经启动。

平台

展锐 T618 Android11

实现

  1. APP中增加规则,向/data/netWhiteList.txt文件中按指定格式写入规则,记得换行

    java 复制代码
    // all white list file
    public static final String WHITE_LIST_FILE_PATH = "/data/networkWhiteList.txt";
    // temp add rule 
    public static final String WHITE_LIST_TEMP_ADD_FILE_PATH = "/data/temp_add.txt";
    
    public void writeRuleToFile(String rule) {
        if (TextUtils.isEmpty(rule)) {
            return;
        }
        FileWriter writer;
        FileWriter tempWriter;
        File file = new File(Constants.WHITE_LIST_FILE_PATH);
        File tempFile = new File(Constants.WHITE_LIST_TEMP_ADD_FILE_PATH);
        try {
            if (!file.exists()) {
                file.createNewFile();
            }
            if (!tempFile.exists()) {
                tempFile.createNewFile();
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        if (!file.exists()) {
            LogUtil.e(TAG, "can't create new File: " + Constants.WHITE_LIST_FILE_PATH);
        }
        try {
            writer = new FileWriter(Constants.WHITE_LIST_FILE_PATH);
            tempWriter = new FileWriter(Constants.WHITE_LIST_TEMP_ADD_FILE_PATH);
            writer.append(rule).append("\\n");
            writer.flush();
            tempWriter.write(rule);
            tempWriter.write("\\n");
            LogUtil.d(TAG, "write rule: " + rule + " over");
            doFirewallCtl(ACTION_ADD);
            writer.close();
            tempWriter.close();
            tempFile.delete();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
  2. 编写Shell脚本,我对Shell脚本实际写的不多,找ChatGPT写完改改还是很是能用,位置的话根据项目情况放置,我这边放在源码/device/sprd/mpool/xxx/module目录下,只要能拷贝到设备中就行,shell脚本内容如下:

    bash 复制代码
    #!/system/bin/sh
    
    PARSE_FILE=/data/networkWhiteList.txt
    TEMP_ADD_FILE=/data/temp_add.txt
    TEMP_REMOVE_FILE=/data/temp_delete.txt
    LOG_FILE=/sdcard/firewall_log.txt
    
    function load_rules() {
        while read -r line
        do
            ip=$(echo "$line" | awk -F ' ' '{print $1}' | awk -F ':' '{print $2}')
            netmask=$(echo "$line" | awk -F ' ' '{print $2}' | awk -F ':' '{print $2}')
            port=$(echo "$line" | awk -F ' ' '{print $3}' | awk -F ':' '{print $2}')
            add_drop_rule $ip $netmask
            echo "load rule IP: $ip Netmask: $netmask Port: $port  $(date "+%Y-%m-%d %H:%M:%S")" >> $LOG_FILE
            # fi
        done < $PARSE_FILE
    }
    
    function add_rule() {
        do
            ip=$(echo "$line" | awk -F ' ' '{print $1}' | awk -F ':' '{print $2}')
            netmask=$(echo "$line" | awk -F ' ' '{print $2}' | awk -F ':' '{print $2}')
            port=$(echo "$line" | awk -F ' ' '{print $3}' | awk -F ':' '{print $2}')
            add_drop_rule $ip $netmask
            echo "add rule IP: $ip Netmask: $netmask Port: $port  $(date "+%Y-%m-%d %H:%M:%S")" >> $LOG_FILE
            # fi
        done < $TEMP_ADD_FILE
    }
    
    function remove_rule() {
        do
            ip=$(echo "$line" | awk -F ' ' '{print $1}' | awk -F ':' '{print $2}')
            netmask=$(echo "$line" | awk -F ' ' '{print $2}' | awk -F ':' '{print $2}')
            port=$(echo "$line" | awk -F ' ' '{print $3}' | awk -F ':' '{print $2}')
            remove_drop_rule $ip $netmask
            echo "remove rule IP: $ip Netmask: $netmask Port: $port  $(date "+%Y-%m-%d %H:%M:%S")" >> $LOG_FILE
            # fi
        done < $TEMP_REMOVE_FILE
    }
    
    function add_drop_rule() {
        iptables -I OUTPUT \\! -d $1\\/$2 -j DROP
        iptables -I FORWARD \\! -d $1\\/$2 -j DROP
    }
    
    function remove_drop_rule() {
        iptables -D OUTPUT \\! -d $1\\/$2 -j DROP
        iptables -D FORWARD \\! -d $1\\/$2 -j DROP
    }
    
    function clear_rules() {
        iptables -F
    }
    
    echo "firewall_ctl count: $# param: $1" >> $LOG_FILE
    if [ $# -eq 0 ]; then
        load_rules $PARSE_FILE
    elif [ $1 == "add" ]; then
        add_rule
    elif [ $1 == "remove" ]; then
        remove_rule
    elif [ $1 == "clear" ]; then
        clear_rules
    fi

    脚本后可跟添加,删除,清空等参数,执行不同的操作,需要注意脚本头部的#!/system/bin/sh ,之前使用时遇到过写成#!/system/bin/bash#!/bin/bash ,在设备中手动执行可以,但是使用属性调用时会失败的问题

    写完脚本后记得找个mk文件做文件拷贝

    bash 复制代码
    PRODUCT_COPY_FILES += \\
                      $(LOCAL_PATH)/firewall_ctl.sh:system/bin/firewall_ctl.sh \\
  3. Shell脚本添加到rc文件中,设置为service ,相关的rc文件内容

    bash 复制代码
    #net white list control
    # load rules from file
    service firewall /system/bin/firewall_ctl.sh
        class main
        group root system shell
        user root
        disabled
        oneshot
    
    # add rule
    service firewall_add /system/bin/firewall_ctl.sh add
        class main
        user root 
        group root system shell
        disabled
        oneshot
    
    # remove rule
    service firewall_remove /system/bin/firewall_ctl.sh remove
        class main
        user root 
        group root system shell
        disabled
        oneshot
    
    # clear rules
    service firewall_clear /system/bin/firewall_ctl.sh clear
        class main
        user root 
        group root system shell
        disabled
        oneshot

    写了4个service,后面通过系统属性执行不同的服务就会执行带不同参数的sh脚本了

  4. 添加脚本的SE权限,两个文件分别是:

    bash 复制代码
    source_code/system/sepolicy/vendor/file_contexts
    source_code/system/sepolicy/vendor/firewall_ctl.te

    添加内容,具体根据实际情况修改:

    bash 复制代码
    # /system/sepolicy/vendor/file_contexts
    /system/bin/firewall_ctl.sh                                 u:object_r:firewall_ctl_exec:s0
    bash 复制代码
    # system/sepolicy/vendor/firewall_ctl.te
    type firewall_ctl, domain;
    type firewall_ctl_exec, system_file_type, exec_type, file_type;
    
    typeattribute firewall_ctl coredomain;
    init_daemon_domain(firewall_ctl)
  5. APP中使用系统属性来启动服务,执行特定的脚本操作

    bash 复制代码
    private void doFirewallCtl(int action) {
        switch (action) {
            case ACTION_LOAD:
                SystemProperties.set("ctl.start", "firewall");
                break;
            case ACTION_ADD:
                SystemProperties.set("ctl.start", "firewall_add");
                break;
            case ACTION_REMOVE:
                SystemProperties.set("ctl.start", "firewall_remove");
                break;
            case ACTION_CLEAR:
                SystemProperties.set("ctl.start", "firewall_clear");
                break;
        }
    }

之后如果只是修改iptables的设置命令,就可以直接修改shell脚本了,测试时pull出来修改完后再push进去会方便很多

总结一下:

  1. 编写shell脚本,push到设备/system/bin目录下,手动执行看是否可生效,脚本文件是否正确
  2. 将服务添加到rc文件中
  3. 将脚本放到特定目录下,添加SE权限,执行make selinux_policy 看权限是否有错误,避免整编失败浪费时间
  4. 整编验证

问题

  • 服务调用了但是Shell脚本没执行

    看脚本有没有执行可以在里面重定向到文件来debug,除了看android log外可以看下kernel log ,比如出现:

    diff 复制代码
    02E70 <14> [  114.843612][11-24 17:02:18.843] init: starting service 'firewall_clear'...
    02E7C <11> [  114.848335][11-24 17:02:18.848] init: cannot execv('/system/bin/firewall_ctl.sh'). See the 'Debugging init' section of init's README.md for tips: No such file or directory
    02E7D <14> [  114.849522][11-24 17:02:18.849] init: Control message: Processed ctl.start for 'firewall_clear' from pid: 2315 (com.jathine.netwhitelist)
    02E7E <38> [  114.850086][11-24 17:02:18.850] type=1400 audit(1700816539.075:839): avc: denied { write } for comm="ne.netwhitelist" name="networkWhiteList.txt" dev="dm-5" ino=4709 scontext=u:r:system_app:s0 tcontext=u:object_r:system_data_root_file:s0 tclass=file permissive=1
    02E7F <38> [  114.850344][11-24 17:02:18.850] type=1400 audit(1700816539.075:840): avc: denied { open } for comm="ne.netwhitelist" path="/data/networkWhiteList.txt" dev="dm-5" ino=4709 scontext=u:r:system_app:s0 tcontext=u:object_r:system_data_root_file:s0 tclass=file permissive=1
    02E80 <38> [  114.850540][11-24 17:02:18.850] type=1400 audit(1700816539.075:841): avc: denied { getattr } for comm="ne.netwhitelist" path="/data/networkWhiteList.txt" dev="dm-5" ino=4709 scontext=u:r:system_app:s0 tcontext=u:object_r:system_data_root_file:s0 tclass=file permissive=1
    02E81 <14> [  114.852383][11-24 17:02:18.852] init: Service 'firewall_clear' (pid 3366) exited with status 127 oneshot service took 0.005000 seconds in background
    02E82 <14> [  114.852426][11-24 17:02:18.852] init: Sending signal 9 to service 'firewall_clear' (pid 3366) process group...

    如果在adb中使用sh执行脚本可以运行,但是属性调用不生效,要看下Shell脚本的顶部格式了

相关推荐
CYRUS_STUDIO1 小时前
利用 Linux 信号机制(SIGTRAP)实现 Android 下的反调试
android·安全·逆向
CYRUS_STUDIO2 小时前
Android 反调试攻防实战:多重检测手段解析与内核级绕过方案
android·操作系统·逆向
黄林晴5 小时前
如何判断手机是否是纯血鸿蒙系统
android
火柴就是我5 小时前
flutter 之真手势冲突处理
android·flutter
法的空间6 小时前
Flutter JsonToDart 支持 JsonSchema
android·flutter·ios
循环不息优化不止6 小时前
深入解析安卓 Handle 机制
android
恋猫de小郭6 小时前
Android 将强制应用使用主题图标,你怎么看?
android·前端·flutter
jctech6 小时前
这才是2025年的插件化!ComboLite 2.0:为Compose开发者带来极致“爽”感
android·开源
用户2018792831676 小时前
为何Handler的postDelayed不适合精准定时任务?
android
叽哥7 小时前
Kotlin学习第 8 课:Kotlin 进阶特性:简化代码与提升效率
android·java·kotlin