frnk Postado Maio 17, 2020 em 17:20 Compartilhar Postado Maio 17, 2020 em 17:20 (editado) Eae guys, Estou postando um crackme que estava pegando poeira aqui no meu hd. A versão original era um PE então só adaptei (O original não tem os créditos do autor) hehe A ideia era fazer uma Keygen. Editado Maio 17, 2020 em 17:22 por frnk 4 Citar Link para o comentário Compartilhar em outros sites More sharing options...
Caio Campos Postado Junho 2, 2020 em 01:13 Compartilhar Postado Junho 2, 2020 em 01:13 Cara, muito obrigado! Estou iniciando meus estudos em Engenharia Reversa e pode ter certeza que colaborou bastante com meu aprendizado ? 2 Citar Link para o comentário Compartilhar em outros sites More sharing options...
Apoiador Byte ipax Postado Junho 14, 2020 em 15:03 Apoiador Byte Compartilhar Postado Junho 14, 2020 em 15:03 (editado) Opa, muito interessante a iniciativa, obrigado por ter compartilhado ? Como já tem um tempinho que postou e ninguém respondeu, tirei uns minutinhos agora pela manha para analisar e resolver. O crackme é bem simples e consiste de 2 funções principais (anti-debug e o algoritmo em si). 1- anti-debug, ispresent() com a função ptrace(). 000005df 6a00 push 0x0 {var_10} 000005e1 6a01 push 0x1 {var_14} 000005e3 6a00 push 0x0 {var_18} 000005e5 6a00 push 0x0 {var_1c} 000005e7 e874feffff call ptrace 000005ec 83c410 add esp, 0x10 000005ef 83f8ff cmp eax, 0xffffffff Traduzindo para C essa função, teriamos algo assim: int ispresent() { int pval; if (ptrace(PTRACE_TRACEME, 0, 1, 0) == 0xffffffff) printf("Não vai nem me pagar uma cerveja\n"); pval = 1; else pval = 0; return pval; } PTRACE_TRACEME - Indicate that this process is to be traced by its parent RETURN VALUE - On error, all requests return -1, and errno is set appropriately Quando utilizamos a função ptrace() com o request PTRACE_TRACEME, os valores de pid_t pid, void *addr e void *data são ignorados, portanto os valores utilizados na função poderiam ser qualquer um ? 2- algoritmo, nadaaqui() - Onde a mágica acontece ?️♂️ Tentando ser o mais sucinto possível, o programa armazena em um float o valor 720300.0 e divide esse valor (incial) e o resultado subsequente por cada "numero" enviado pelo usuário. Em C seria mais ou menos assim: long double nadaaqui(char *userInput) { float key = 720300.0; while ( *userInput ) key = key / (long double)(*userInput++ - 48); return key; } A atribuição de key pode ter sido feita de diversas formas no código original, no exemplo acima utilizei -48 por conta dos caracteres que são enviados no userInput. Um observação importante é que o programa só aceita números 0-9, portanto como o que é enviado é um char teriamos de acordo com a tabela ASCII: Oct Dec Hex Char 060 48 30 0 061 49 31 1 062 50 32 2 063 51 33 3 064 52 34 4 ... Acredito que na implementação original o autor utilizou isdigit() (ctype.h) para fazer essa verificação. 0x000006bc <+93>: call 0x470 <__ctype_b_loc@plt> De qualquer forma, se enviarmos 0 como char ele seria 48 em decimal, então 48-48 = 0. No código, val = val / (long double)0; Caso enviassemos 1 como char, seria 49, então 49-48 = 1. No código, val = val / (long double)1; Deu para entender a idéia. 3- main() Como tinha comentado, o código é bem simples e main() executa algumas funções. 1- Verifica se o programa está sendo debugado (Pode deixar que eu pago a breja :D) 2- Verifica se a sequência digitada contém caracteres inválidos (Somente números são permitidos). 3- Verifica se o resultado de nadaaqui() == 1.0, sendo assim, a chave é validada ou não. 0000073d d9e8 fld1 0000073f dfe9 fucomip st0, st1 00000741 ddd8 fstp st0, st0 Com isso essa lógica em mente, escrevi o keygen ?: #include <stdio.h> #include <stdlib.h> #include <string.h> #include <math.h> #include <time.h> #include <stdbool.h> long double validateNumber(int, float); int nothingToCheck(); int main() { struct timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts); srand((time_t)ts.tv_nsec); nothingToCheck(); return 0; } int randomNumber() { return (rand() % 6) + 2; } long double validateNumber(int num, float result) { float key; key = result / (long double)num; if (roundf(key) != key) return false; return key; } int nothingToCheck() { char num[32]; float result = 720300.0; int counter, valid; counter = valid = 0; while (!valid) { num[counter] = randomNumber(); if (validateNumber(num[counter], result)) { result = validateNumber(num[counter], result); counter++; } else continue; if (result == 1.000000) valid = 1; } counter++; num[counter] = '\0'; printf("Key: "); for (int i = 0; i < strlen(num) - 1; i++) printf("%d", num[i]); printf("\n"); return true; } Compilado com a flag: -lm (obrigatória por conta do roundf()) Algumas considerações sobre o keygen: Para ganhos de performance, utilizei um seed de nanoseconds para o rand() gerar números aleatórios entre 2 e 7 e utilizar como userInput, visto que divisões por 0 daria inf e por ´1 daria o mesmo número. As divisões por 8 e 9 nunca resultariam em um resultado sem dízima portando inválidos. Com certeza existe uma outra forma de fazer esse cálculo mas não atrapalhou no resultado final. ipax@core:~/crackme$ ./mbin-hehe-keygen Key: 523257777 ipax@core:~/crackme$ ./hehe 523257777 Isso aí!! A chave é válida! ?♂️ Como uma brincadeira extra, tentei gerar uma lista das combinações ou keys possíveis e cheguei ao número de 5338. Muito divertido e como falei, obrigado por compartilhar. Envie mais! ? Editado Junho 14, 2020 em 15:13 por ipax 1 1 Citar Link para o comentário Compartilhar em outros sites More sharing options...
frnk Postado Junho 20, 2020 em 15:46 Autor Compartilhar Postado Junho 20, 2020 em 15:46 On 6/14/2020 at 12:03 PM, ipax said: Opa, muito interessante a iniciativa, obrigado por ter compartilhado ? Como já tem um tempinho que postou e ninguém respondeu, tirei uns minutinhos agora pela manha para analisar e resolver. O crackme é bem simples e consiste de 2 funções principais (anti-debug e o algoritmo em si). 1- anti-debug, ispresent() com a função ptrace(). 000005df 6a00 push 0x0 {var_10} 000005e1 6a01 push 0x1 {var_14} 000005e3 6a00 push 0x0 {var_18} 000005e5 6a00 push 0x0 {var_1c} 000005e7 e874feffff call ptrace 000005ec 83c410 add esp, 0x10 000005ef 83f8ff cmp eax, 0xffffffff Traduzindo para C essa função, teriamos algo assim: int ispresent() { int pval; if (ptrace(PTRACE_TRACEME, 0, 1, 0) == 0xffffffff) printf("Não vai nem me pagar uma cerveja\n"); pval = 1; else pval = 0; return pval; } PTRACE_TRACEME - Indicate that this process is to be traced by its parent RETURN VALUE - On error, all requests return -1, and errno is set appropriately Quando utilizamos a função ptrace() com o request PTRACE_TRACEME, os valores de pid_t pid, void *addr e void *data são ignorados, portanto os valores utilizados na função poderiam ser qualquer um ? 2- algoritmo, nadaaqui() - Onde a mágica acontece ?️♂️ Tentando ser o mais sucinto possível, o programa armazena em um float o valor 720300.0 e divide esse valor (incial) e o resultado subsequente por cada "numero" enviado pelo usuário. Em C seria mais ou menos assim: long double nadaaqui(char *userInput) { float key = 720300.0; while ( *userInput ) key = key / (long double)(*userInput++ - 48); return key; } A atribuição de key pode ter sido feita de diversas formas no código original, no exemplo acima utilizei -48 por conta dos caracteres que são enviados no userInput. Um observação importante é que o programa só aceita números 0-9, portanto como o que é enviado é um char teriamos de acordo com a tabela ASCII: Oct Dec Hex Char 060 48 30 0 061 49 31 1 062 50 32 2 063 51 33 3 064 52 34 4 ... Acredito que na implementação original o autor utilizou isdigit() (ctype.h) para fazer essa verificação. 0x000006bc <+93>: call 0x470 <__ctype_b_loc@plt> De qualquer forma, se enviarmos 0 como char ele seria 48 em decimal, então 48-48 = 0. No código, val = val / (long double)0; Caso enviassemos 1 como char, seria 49, então 49-48 = 1. No código, val = val / (long double)1; Deu para entender a idéia. 3- main() Como tinha comentado, o código é bem simples e main() executa algumas funções. 1- Verifica se o programa está sendo debugado (Pode deixar que eu pago a breja :D) 2- Verifica se a sequência digitada contém caracteres inválidos (Somente números são permitidos). 3- Verifica se o resultado de nadaaqui() == 1.0, sendo assim, a chave é validada ou não. 0000073d d9e8 fld1 0000073f dfe9 fucomip st0, st1 00000741 ddd8 fstp st0, st0 Com isso essa lógica em mente, escrevi o keygen ?: #include <stdio.h> #include <stdlib.h> #include <string.h> #include <math.h> #include <time.h> #include <stdbool.h> long double validateNumber(int, float); int nothingToCheck(); int main() { struct timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts); srand((time_t)ts.tv_nsec); nothingToCheck(); return 0; } int randomNumber() { return (rand() % 6) + 2; } long double validateNumber(int num, float result) { float key; key = result / (long double)num; if (roundf(key) != key) return false; return key; } int nothingToCheck() { char num[32]; float result = 720300.0; int counter, valid; counter = valid = 0; while (!valid) { num[counter] = randomNumber(); if (validateNumber(num[counter], result)) { result = validateNumber(num[counter], result); counter++; } else continue; if (result == 1.000000) valid = 1; } counter++; num[counter] = '\0'; printf("Key: "); for (int i = 0; i < strlen(num) - 1; i++) printf("%d", num[i]); printf("\n"); return true; } Compilado com a flag: -lm (obrigatória por conta do roundf()) Algumas considerações sobre o keygen: Para ganhos de performance, utilizei um seed de nanoseconds para o rand() gerar números aleatórios entre 2 e 7 e utilizar como userInput, visto que divisões por 0 daria inf e por ´1 daria o mesmo número. As divisões por 8 e 9 nunca resultariam em um resultado sem dízima portando inválidos. Com certeza existe uma outra forma de fazer esse cálculo mas não atrapalhou no resultado final. ipax@core:~/crackme$ ./mbin-hehe-keygen Key: 523257777 ipax@core:~/crackme$ ./hehe 523257777 Isso aí!! A chave é válida! ?♂️ Como uma brincadeira extra, tentei gerar uma lista das combinações ou keys possíveis e cheguei ao número de 5338. Muito divertido e como falei, obrigado por compartilhar. Envie mais! ? Que analise SHOWWWW!!! Que bom que gostou !!! ? Citar Link para o comentário Compartilhar em outros sites More sharing options...
frnk Postado Junho 20, 2020 em 15:48 Autor Compartilhar Postado Junho 20, 2020 em 15:48 On 6/1/2020 at 10:13 PM, Caio Campos said: Cara, muito obrigado! Estou iniciando meus estudos em Engenharia Reversa e pode ter certeza que colaborou bastante com meu aprendizado ? hehe esse ai tbm me abriu a mente Citar Link para o comentário Compartilhar em outros sites More sharing options...
Apoiador Byte paulosgf Postado Outubro 24, 2021 em 21:04 Apoiador Byte Compartilhar Postado Outubro 24, 2021 em 21:04 Salve galera! Primeiramente quero agradecer a mais este exemplar para estudo de ER. Me ensinou conceitos em que eu só tinha vagamente ouvido falar. Realmente abriu a mente! Como foi comentado pelos colegas, existe de fato pelo menos mais uma abordagem de solução. Trata-se do problema de fatoração em números primos de determinado número. A criptografia de chave pública RSA usa este conceito, e sua segurança se baseia no fato de que é computacionalmente trabalhoso fatorar números primos gigantescos, exigindo geralmente milhares de anos de processamento por força bruta para se quebrar as chaves com tecnologia atual. A não ser que se empregue computação quântica! ? Como os valores em questão são de tamanho aceitável, é possível empregar um algoritmo próprio para a fatoração. Segue então um exemplo adaptado por mim deste código, cujo crédito é de rathbhupendra e a referência vem de https://www.geeksforgeeks.org/print-all-prime-factors-of-a-given-number/ #include <stdio.h> #include <math.h> // Imprimir todos fatores primos de dado número void primeFactors(int n) { // Passo 1: trata de números compostos (primo x primo) // Enquanto for par, divide número por 2 e imprime 2 while (n%2 == 0) { printf("%d", 2); n = n/2; } // Passo 2: ainda tratando de números compostos // Quando o número se tornar ímpar, inicia um laço iterando de // 3 à raiz quadrada do dado número, incrementando de 2 em 2, // imprime este índice e divide o número pelo mesmo for (int i = 3; i <= sqrt(n); i = i+2) { while (n%i == 0) { printf("%d", i); n = n/i; } } // Passo 3: trata dos números primos // Se o dado número não terminou em 1, então é um primo // maior que 2. Imprimir este valor, concluindo a fatoração if (n > 2) printf ("%d", n); printf("\n"); } int main() { int n; printf("%s", "Numero a ser fatorado: "); scanf("%d", &n); primeFactors(n); return 0; } // This is code is contributed by rathbhupendra Segue resultado de sua execução: paulo@nirvana:~/crack/crackmes$ ./fatoresPrimos Numero a ser fatorado: 720300 223557777 paulo@nirvana:~/crack/crackmes$ ./crackme-linux02 223557777 Isso aí!! A chave é válida! paulo@nirvana:~/crack/crackmes$ Abraços! 1 Citar Link para o comentário Compartilhar em outros sites More sharing options...
Posts Recomendados
Participe da conversa
Você pode postar agora e se cadastrar mais tarde. Se você tem uma conta, faça o login para postar com sua conta.