Ir para conteúdo

Mais um Crackme


frnk

Posts Recomendados

  • 3 semanas depois...
  • 2 semanas depois...
  • Apoiador Byte

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 por ipax
  • Curtir 1
  • l33t 1
Link para o comentário
Compartilhar em outros sites

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 !!! ?

Link para o comentário
Compartilhar em outros sites

  • 1 ano depois...
  • Apoiador Byte

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!
 

  • l33t 1
Link para o comentário
Compartilhar em outros sites

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.

Visitante
Responder

×   Você colou conteúdo com formatação.   Remover formatação

  Apenas 75 emojis são permitidos.

×   Seu link foi automaticamente incorporado.   Mostrar como link

×   Seu conteúdo anterior foi restaurado.   Limpar o editor

×   Não é possível colar imagens diretamente. Carregar ou inserir imagens do URL.

  • Quem Está Navegando   0 membros estão online

    • Nenhum usuário registrado visualizando esta página.
×
×
  • Criar Novo...