frnk Posted May 17, 2020 at 05:20 PM Share Posted May 17, 2020 at 05:20 PM (edited) 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. Edited May 17, 2020 at 05:22 PM by frnk 4 Quote Link to comment Share on other sites More sharing options...
Caio Campos Posted June 2, 2020 at 01:13 AM Share Posted June 2, 2020 at 01:13 AM Cara, muito obrigado! Estou iniciando meus estudos em Engenharia Reversa e pode ter certeza que colaborou bastante com meu aprendizado ? 2 Quote Link to comment Share on other sites More sharing options...
Supporter - Byte ipax Posted June 14, 2020 at 03:03 PM Supporter - Byte Share Posted June 14, 2020 at 03:03 PM (edited) 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! ? Edited June 14, 2020 at 03:13 PM by ipax 1 1 Quote Link to comment Share on other sites More sharing options...
frnk Posted June 20, 2020 at 03:46 PM Author Share Posted June 20, 2020 at 03:46 PM 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 !!! ? Quote Link to comment Share on other sites More sharing options...
frnk Posted June 20, 2020 at 03:48 PM Author Share Posted June 20, 2020 at 03:48 PM 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 Quote Link to comment Share on other sites More sharing options...
Supporter - Byte paulosgf Posted October 24, 2021 at 09:04 PM Supporter - Byte Share Posted October 24, 2021 at 09:04 PM 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 Quote Link to comment Share on other sites More sharing options...
Recommended Posts
Join the conversation
You can post now and register later. If you have an account, sign in now to post with your account.