Jump to content

Mais um Crackme


frnk

Recommended Posts

  • 3 weeks later...
  • 2 weeks later...
  • Supporter - 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! ?

 

Edited by ipax
  • Curtir 1
  • l33t 1
Link to comment
Share on other 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 to comment
Share on other sites

  • 1 year later...
  • Supporter - 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 to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...