Home Windows Vulnserver Buffer Overflow - Parte 1 Objetivos e Análise do Código
Post
Cancel

Windows Vulnserver Buffer Overflow - Parte 1 Objetivos e Análise do Código

BUFFER OVERFLOW EM WINDOWS

Neste laboratório, vamos explorar várias técnicas de buffer overflow no SO Windows, o intuito é de entender a mecânica por trás de aplicações e programas e encontrar uma forma de manipulá-las.

Como programa alvo desta PoC, vamos utilizar o Vulnserver.exe, um programa intencionalmente vulnerável para exploração.

Vulnserver é um servidor TCP para Windows desenvolvido por Stephen Bradshal, seu GitHub pode ser acessado aqui.

SOBRE O QUE ESTE ESTUDO SE TRATA

Este estudo trata da exploração e entendimento do fluxo de memória de um programa no SO Windows. Como interpretar um Debugger e como nos aproveitar de funções vulneráveis à buffer overflow. Dando uma visão geral sobre criação de exploits e base para análise e desenvolvimento de malwares.

SOBRE O QUE ESTE ESTUDO NÃO SE TRATA

Este estudo não vai ensinar Assembly, vamos somente nos aprofundar na linguagem ao ponto que possamos entender seu funcionamento. Também não vamos utilizar técnicas avançadas como bypass de ASLR, portanto esta proteção estará desabilitada em nossa máquina alvo.

Na maior parte dos cenários reais, não teremos o código fonte do programa para analisarmos, mas como se trata de um cenário de estudos, vamos analisar o código fonte para entender alguns pontos.

LABORATÓRIO

Para este laboratório, utilizaremos:

  • Uma máquina virtual Windows 10 21H1 x64 como alvo;
  • O programa vulnerável “Vulnserver.exe”;
  • O debbuger Immunuty na máquina alvo;
  • O plugin mona.py para o Immunity;
  • Uma máquina Kali Linux 2021.2 como atacante;
  • Bastante Python;
  • A suite Metasploit Framework;

OBSERVAÇÕES IMPORTANTES

Existem várias outras técnicas para explorar as mesmas vulnerabilidades apresentadas neste estudo.

Os endereços de memória e os saltos matemáticos que faremos, podem mudar dependendo da versão ou atualização do SO alvo, porém a mecânica será sempre a mesma.

Conforme os evoluimos na exploração, vamos evoluindo a linha de raciocínio, portanto, seria importante seguir o fluxo das execuções.

MATERIAL

Para fins de organização, este estudo será dividido em partes, cada um abordando um comando diferente do Vulnserver, aumentando a complexidade gradativamente, e também será disponibilizado um PDF com a PoC completa. abaixo os links para cada parte.

  1. Análise do código
  2. Comando TRUN
  3. Comando GTER
  4. Comando GMON
  5. Comando KSTET
  6. Comando LTER
  7. Conclusão
  8. Download do PDF

Go Go Go!!!

ANÁLISE DO CÓDIGO

Ao analisarmos o código vulnerver.c podemos encontrar algumas funções inseguras em C. Estas são responsáveis por permitir o buffer overflow. No código, podemos encontrar as seguintes funções.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
void Function1(char *Input) {
	char Buffer2S[140];
	strcpy(Buffer2S, Input);
}

void Function2(char *Input) {
	char Buffer2S[60];
	strcpy(Buffer2S, Input);
}

void Function3(char *Input) {
	char Buffer2S[2000];	
	strcpy(Buffer2S, Input);
}

void Function4(char *Input) {
	char Buffer2S[1000];
	strcpy(Buffer2S, Input);
}

As quatro funções utilizam a “strcpy” que é uma função vnlnerável em C. Esta função copia o valor de uma entrada para um buffer, mas esta função não verifica se o tamanho da entrada é o mesmo ou inferior ao buffer de destino. Portanto, se uma entrada for repassada e seu tamanho for maior que o buffer de destino, teremos um buffer overflow que irá sobrescrever outros endereços de memória.

Continuando a análise do código, podemos identificar onde estas funções são chamadas pelo programa.

1
2
3
4
5
6
7
else if (strncmp(RecvBuf, "KSTET ", 6) == 0) {
    char *KstetBuf = malloc(100);
		strncpy(KstetBuf, RecvBuf, 100);
		memset(RecvBuf, 0, DEFAULT_BUFLEN);
		Function2(KstetBuf);
		SendResult = send( Client, "KSTET SUCCESSFUL\n", 17, 0 );
    }

Essa função nos diz que se recebemos nosso buffer seguido de “KSTET ” o programa vai alocar 100 bytes na memória, copia 100 bytes para um novo buffer e reseta o buffer recebido para 0. Logo em seguida chama a “Function2”, uma de nossas funções vulneráveis.

Porém, na primeira imagem vimos que a Fnction2 aceita somente 60 bytes, se o parâmetro envia 100 bytes, temos um overflow de 40 bytes no buffer.

Portanto identificamos que as vulnerabiidades do programa vêm do buffer de entrada até o buffer overflow, vamos tentar identificar outras partes do programa com partes vulneráveis.

1
2
3
4
5
6
7
8
9
10
11
12
13
else if (strncmp(RecvBuf, "TRUN ", 5) == 0) {
    char *TrunBuf = malloc(3000);
		memset(TrunBuf, 0, 3000);
		for (i = 5; i < RecvBufLen; i++) {
		    if ((char)RecvBuf[i] == '.') {
						strncpy(TrunBuf, RecvBuf, 3000);				
						Function3(TrunBuf);
						break;
					}
		}
		memset(TrunBuf, 0, 3000);				
		SendResult = send( Client, "TRUN COMPLETE\n", 14, 0 );
}

O comanto “TRUN” tem um funcionamento parecido com KSTET, porém faz uma segunda validação se o caractere “.” está presente no buffer, só após a confirmação ele chama a “Function3” vulnerável.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
else if (strncmp(RecvBuf, "LTER ", 5) == 0) {
    char *LterBuf = malloc(DEFAULT_BUFLEN);
		memset(LterBuf, 0, DEFAULT_BUFLEN);
		i = 0;
		while(RecvBuf[i]) {
		    if ((byte)RecvBuf[i] > 0x7f) {
				    LterBuf[i] = (byte)RecvBuf[i] - 0x7f;
				} else {
						LterBuf[i] = RecvBuf[i];
				}
				i++;
		}
		for (i = 5; i < DEFAULT_BUFLEN; i++) {
		    if ((char)LterBuf[i] == '.') {					
				    Function3(LterBuf);
						break;
				}
		}
		memset(LterBuf, 0, DEFAULT_BUFLEN);
		SendResult = send( Client, "LTER COMPLETE\n", 14, 0 );
}

O comando “LTER” copia o buffer recebido para a variável “LterBuf” e depois subrai 0x7f (127) bytes caso o buffer seja maior que 0x7f. Depois disso o código verifica se o caractere “.” está presente, caso seja verdadeiro, ele chama a “Function3” vulnerável.

1
2
3
4
5
6
7
8
9
10
11
12
else if (strncmp(RecvBuf, "GMON ", 5) == 0) {
    char GmonStatus[13] = "GMON STARTED\n";
		for (i = 5; i < RecvBufLen; i++) {
		    if ((char)RecvBuf[i] == '/') {
				    if (strlen(RecvBuf) > 3950) {
						    Function3(RecvBuf);
						}
						break;
				}
		}				
		SendResult = send( Client, GmonStatus, sizeof(GmonStatus), 0 );
}

Outro comando que podemos analisar é o “GMON”. Ele tem o comportamento parecido com as anteriores, mas verifica se o caractere “/” está presente no buffer recebido e se o bufferé maior que 3950 bytes. Caso seja verdadeiro, o programa chama a “Function3”

1
2
3
4
5
6
7
8
else if (strncmp(RecvBuf, "GTER ", 5) == 0) {
				char *GterBuf = malloc(180);
				memset(GdogBuf, 0, 1024);
				strncpy(GterBuf, RecvBuf, 180);				
				memset(RecvBuf, 0, DEFAULT_BUFLEN);
				Function1(GterBuf);
				SendResult = send( Client, "GTER ON TRACK\n", 14, 0 );
}

O comando “GTER” tem um funcionamento mais simples, ele copia 180 bytes do buffer de entrada para o buffer temporário “GterBuf” e depois envia seuconteúdo para a “Function1”. Como vimos que a Function1 tem um espaço de 140 bytes, temos o buffer overflow.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
else if (strncmp(RecvBuf, "HTER ", 5) == 0) {
				char THBuf[3];
				memset(THBuf, 0, 3);
				char *HterBuf = malloc((DEFAULT_BUFLEN+1)/2);
				memset(HterBuf, 0, (DEFAULT_BUFLEN+1)/2);
				i = 6;
				k = 0;
				while ( (RecvBuf[i]) && (RecvBuf[i+1])) {
					memcpy(THBuf, (char *)RecvBuf+i, 2);
					unsigned long j = strtoul((char *)THBuf, NULL, 16);
					memset((char *)HterBuf + k, (byte)j, 1);
					i = i + 2;
					k++;
				} 
				Function4(HterBuf);
				memset(HterBuf, 0, (DEFAULT_BUFLEN+1)/2);
				SendResult = send( Client, "HTER RUNNING FINE\n", 18, 0 );
} 

O comando “HTER” é o que tem a maior complexidade, pois ele faz a chamada para a “Function4” após um laço while e nossa faze de exploração precisa entender exatamente como este laço se comporta.

Nesse ponto, identificamos no código do programa, todas as funções e comandos vulneráveis, agora podemos passar para a exploração.

ENUMERAÇÃO

O vulnserver.exe escuta conexões na porta 9999 da nossa máquina Windows alvo.

Enumaração do comando.

Através na nossa máquina Kali, podemos nos conectar utilizando o netcat.

Enumaração do comando.

Ao enviarmos o comando “HELP”, o programa nos responde com todos os camandos aceitos, incluindo os vulneráveis que já identificamos. Pela resposta podemos identificar que ele trabalha com o modelo “comando argumento”, que no caso será comando buffer.

Na próxima etapa, iniciaremos a exploração das vulnerabilidades.

Go GO GO!!!

This post is licensed under CC BY 4.0 by the author.