rp:使用socket赋予app root权限

使用socket赋予app root权限

奥利奥真好吃

2018.04.26 17:41:23字数 764阅读 981

感谢原作者分享:

https://www.jianshu.com/p/dce099c7cf92

 

我们知道,在system/bin下面有很多可执行文件,包括但不限于iptables等需要root权限才可以操作,我们不可能为了部分需求将整机root处理,这时就需要单独为我们特定的APP建立一个获取root权限的通道。
那么在一台普通user版本的手机上谁有root权限呢?答案是init.rc里的service!所以我们优先考虑从这里打开突破口。同时想一想为什么install这种就不需要root权限呢?从源码中我们发现了两个很重要的文件Installd.cpp与InstallerConnection.java,第一感觉是系统使用了LocalSocket实现了跨进程?没错,APP所在的进程肯定是没有root权限的,它必然需要将自己的需求告知一个已经拥有root权限的进程,所以跨进程是必须的。再看init.rc,这里也有install的想关配置,我们看到它在这里配置了socket,那么流程也就明白了,init.rc启动了一个service同时配置了一个socket,然后在installd.cpp中进行socket的监听,当客户端向该socket发送信息后,这个拥有root权限的服务端将得到客户端的请求。
根据如上分析,我们参照源码实现让app可以执行iptables -L这条简单的但却需要root权限的命令

frameworks\native\cmds\mysocketservice添加我们的模块

mysocket_service.cpp

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <string>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <sys/un.h>
#include <cutils/sockets.h>
#include <utils/Log.h>
#include <android/log.h>
#include <fcntl.h>
#define LOG_TAG "SOCKET_SERVER"
#define SOCKET_PATH "mysocket"
#define BUFFER_MAX    1024
using namespace std;
static int readx(int s, void *_buf, int count)
{
    char *buf = (char *)_buf;
    int n = 0, r;
    if (count < 0) return -1;
    while (n < count) {
        r = read(s, buf + n, count - n);
        if (r < 0) {
            if (errno == EINTR) continue;
            ALOGE("read error: %s\n", strerror(errno));
            return -1;
        }
        if (r == 0) {
            ALOGE("eof\n");
            return -1; /* EOF */
        }
        n += r;
    }
    return 0;
}

static int writex(int s, const void *_buf, int count)
{
    const char *buf = (const char *)  _buf;
    int n = 0, r;
    if (count < 0) return -1;
    while (n < count) {
        r = write(s, buf + n, count - n);
        if (r < 0) {
            if (errno == EINTR) continue;
            ALOGE("write error: %s\n", strerror(errno));
            return -1;
        }
        n += r;
    }
    return 0;
}
static string execCMD(char* command)
{
    string result="";
    FILE *fpRead;
    strcat(command," 2>&1");
    fpRead = popen(command, "r");
    ALOGI("command = %s\n", command);
    char buf[1024];
    while(fgets(buf,sizeof(buf),fpRead)!=NULL)
    {       
        result+=buf;
        ALOGI("buf = %s\n", buf);
    }
    if(fpRead!=NULL)
        pclose(fpRead);
    return result;
}

int main(const int argc, const char *argv[]) {
    char buf[BUFFER_MAX];
    struct sockaddr addr;
    socklen_t alen;
    int lsocket, s;
    ALOGI("socketserver firing up\n");
    lsocket = android_get_control_socket(SOCKET_PATH);
    if (lsocket < 0) {
        ALOGE("Failed to get socket from environment: %s\n", strerror(errno));
        exit(1);
    }
    if (listen(lsocket, 5)) {
        ALOGE("Listen on socket failed: %s\n", strerror(errno));
        exit(1);
    }
    fcntl(lsocket, F_SETFD, FD_CLOEXEC);

    for (;;) {
        alen = sizeof(addr);
        s = accept(lsocket, &addr, &alen);
        if (s < 0) {
            ALOGE("Accept failed: %s\n", strerror(errno));
            continue;
        }
        fcntl(s, F_SETFD, FD_CLOEXEC);

        ALOGI("new connection\n");
        for (;;) {
            unsigned short count;
            if (readx(s, &count, sizeof(count))) {
                ALOGE("failed to read size\n");
                break;
            }
            if ((count < 1) || (count >= BUFFER_MAX)) {
                ALOGE("invalid size %d\n", count);
                break;
            }
            if (readx(s, buf, count)) {
                ALOGE("failed to read command\n");
                break;
            }
            buf[count] = 0;
            ALOGI("buf = %s  count = %d\n", buf, count);
            
            string result=execCMD(buf);
            count=result.length();      
            if (writex(s, &count, sizeof(count))) return -1;
            if (writex(s, result.c_str(), count)) return -1;
        }
        
        ALOGI("closing connection\n");
        close(s);
    }

    return 0;
}

当socket配置好后,开机后将在/dev/socket此目录下生成相应的socket节点,我们看到这里使用了read和write函数对此节点进行读写,因为这个socket实现跨进程的本质就是上层客户端发指令写这个节点,而服务端先读取节点即可知道客户端发来的消息具体是什么。读取出消息指令后,我们这里使用popen函数执行这个shell命令。需要注意的是读取到客户端消息后,我们在其后追回一个字符串:" 2>&1",目的是将标准错误同时重定向到文件,否则popen只能得到一个标准输出。
配置Android.mk

LOCAL_PATH:= $(call my-dir)
svc_c_flags =   \
    -Wall -Wextra \

include $(CLEAR_VARS)
LOCAL_SHARED_LIBRARIES := liblog libselinux
LOCAL_SRC_FILES := mysocket_service.cpp
LOCAL_CFLAGS += $(svc_c_flags)
LOCAL_MODULE := mysocket
LOCAL_MODULE_TAGS := optional
LOCAL_CLANG := true
include $(BUILD_EXECUTABLE)

build\target\product\base.mk加入项目编译

PRODUCT_PACKAGES += mysocket

system\core\rootdir\init.rc配置socket

service mysocket /system/bin/mysocket
   class main
   socket mysocket stream 0666 root system
   oneshot

external\sepolicy配置SELinux权限

file_contexts

/dev/socket/mysocket                             u:object_r:mysocket_st:s0

mysocket.te

allow mysocket_st mysocket_st:sock_file create_file_perms; 
type_transition mysocket_st socket_device:sock_file mysocket_st;

system_server.te

allow system_server mysocket_st:sock_file rw_file_perms;

init.te

neverallow init {fs_type }:file execute_no_trans;
allow init system_file:file execute_no_trans;
allow init shell_exec:file execute_no_trans;
allow init init:rawip_socket {create getopt};

system_app.te

allow system_app mysocket_st:sock_file rw_file_perms;

file.te

type mysocket_st, file_type;

需要注意的是:不同的指令所需要的SELinux权限不同,我这里仅仅针对iptables的相关命令配置了权限,当执行ip等其它指令时依然可能显示permission denied,所以具体问题还需要具体分析。另外在配置权限时要注意系统添加的neverallow语句,避免权限冲突。

客户端的调用

import android.net.LocalSocket;
import android.net.LocalSocketAddress;
import android.os.SystemClock;
import android.util.Slog;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

/**
 * Represents a connection to {@code installd}. Allows multiple connect and
 * disconnect cycles.
 *
 * @hide for internal use only
 */
public class SocketClient {
    private static final String TAG = "MySocketClient";
    private static final boolean LOCAL_DEBUG = true;

    private InputStream mIn;
    private OutputStream mOut;
    private LocalSocket mSocket;

    private byte buf[] = new byte[1024];//初始大小,如果返回的结果超出大小则重新初始化

    public SocketClient() {
    }

    public synchronized String transact(String cmd) {
        String result=null;
        if (LOCAL_DEBUG) {
            Slog.i(TAG, "send: '" + cmd + "'");
        }
        if (connect()&&writeCommand(cmd)) {
             final int replyLength = readReply();
             if (replyLength > 0) {
                 result = new String(buf, 0, replyLength);
                 if (LOCAL_DEBUG) {
                     Slog.i(TAG, "recv: '" + result + "'");
                 }
             } else {
                 if (LOCAL_DEBUG) {
                     Slog.i(TAG, "fail");
                 }
             }
        }else{
            if (LOCAL_DEBUG) {
                Slog.i(TAG, "connect or write command failed!");
            }
        }
       return result;
    }

    public String execute(String cmd) {
        return transact(cmd); 
    }

    private boolean connect() {
        if (mSocket != null) {
            return true;
        }
        Slog.i(TAG, "connecting...");
        try {
            mSocket = new LocalSocket();

            LocalSocketAddress address = new LocalSocketAddress("mysocket",
                    LocalSocketAddress.Namespace.RESERVED);

            mSocket.connect(address);

            mIn = mSocket.getInputStream();
            mOut = mSocket.getOutputStream();
        } catch (IOException ex) {
            disconnect();
            return false;
        }
        return true;
    }

    public void disconnect() {
        Slog.i(TAG, "disconnecting...");
        try {
            if (mSocket != null)
                mSocket.close();
        } catch (IOException ex) {
        }
        try {
            if (mIn != null)
                mIn.close();
        } catch (IOException ex) {
        }
        try {
            if (mOut != null)
                mOut.close();
        } catch (IOException ex) {
        }
        mSocket = null;
        mIn = null;
        mOut = null;
    }


    private boolean readFully(byte[] buffer, int len) {
        int off = 0, count;
        if (len < 0)
            return false;
        while (off != len) {
            try {
                count = mIn.read(buffer, off, len - off);
                if (count <= 0) {
                    Slog.i(TAG, "read error " + count);
                    break;
                }
                off += count;
            } catch (IOException ex) {
                Slog.i(TAG, "read exception");
                break;
            }
        }
        Slog.i(TAG, "read " + len + " bytes");
        if (off == len)
            return true;
        disconnect();
        return false;
    }

    private int readReply() {
        if (!readFully(buf, 2)) {
            return -1;
        }

        final int len = (((int) buf[0]) & 0xff) | ((((int) buf[1]) & 0xff) << 8);
        
        if ((len < 1)) {
            Slog.i(TAG, "invalid reply length (" + len + ")");
            disconnect();
            return -1;
        }
        if(len > buf.length){
            buf = new byte[len];  //重新扩容
        }

        if (!readFully(buf, len)) {
            return -1;
        }

        return len;
    }

    private boolean writeCommand(String cmdString) {
        final byte[] cmd = cmdString.getBytes();
        final int len = cmd.length;
        if ((len < 1) || (len > buf.length)) {
            return false;
        }

        buf[0] = (byte) (len & 0xff);
        buf[1] = (byte) ((len >> 8) & 0xff);
        try {
            mOut.write(buf, 0, 2);
            mOut.write(cmd, 0, len);
        } catch (IOException ex) {
            Slog.i(TAG, "write error");
            disconnect();
            return false;
        }
        return true;
    }
}

import android.app.Activity;
import android.os.Bundle;
import android.text.method.ScrollingMovementMethod;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;

public class MainActivity extends Activity {
    private TextView tv_result;
    private EditText et_cmd;
    private Button bt_exec;
    private String cmd=null;
    private SocketClient client;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        tv_result=(TextView) findViewById(R.id.textview);
        et_cmd= (EditText)findViewById(R.id.editText);
        bt_exec=(Button)findViewById(R.id.button);
        tv_result.setMovementMethod(ScrollingMovementMethod.getInstance());
        client=new SocketClient();
        bt_exec.setOnClickListener(new OnClickListener() {
            
            @Override
            public void onClick(View v) {
                // TODO Auto-generated method stub
                cmd=et_cmd.getText().toString();
                final String result=client.execute(cmd);
                if(result!=null)
                    tv_result.setText(result);
                else
                    tv_result.setText("null");
            }
        });
    }
    @Override
    protected void onDestroy() {
        // TODO Auto-generated method stub
        super.onDestroy();
        if(client!=null)
            client.disconnect();
    }
}

运行结果如下图所求:

 

screen.png

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值