For this challenge, I’ve decided that I should leave this certain repository LeoTindall has made here, and so I will be trying to reverse the final challenge located here, source:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <cpuid.h>

void succeed(char* string) {
    printf("Yes, %s is correct!\n", string);
    exit(0);
}

void fail(char* string) {
    printf("No, %s is not correct.\n", string);
    exit(1);
}

void shift_int_to_char(int i, char* buff) {
    buff[0] = (i) & 0xFF;
    buff[1] = (i >> 8) & 0xFF;
    buff[2] = (i >> 16) & 0xFF;
    buff[3] = (i >> 24) & 0xFF;
}

int main(int argc, char** argv) {

    if (argc != 2) {
        printf("Need exactly one argument.\n");
        return -1;
    }

    unsigned int eax, ebx, ecx, edx;
    char* buff = malloc(sizeof(char) * 15);
    __get_cpuid(0, &eax, &ebx, &ecx, &edx);
    buff[0] = 'N';
    shift_int_to_char(ebx, buff + 1);
    shift_int_to_char(edx, buff + 5);
    shift_int_to_char(ecx, buff + 9);
    buff[13] = 'Q';
    buff[14] = '\0';
    
    int correct = (strcmp(buff, argv[1]) == 0);
    free(buff);

    if (correct) {
        succeed(argv[1]);
    } else {
        fail(argv[1]);
    }
}

Compiled, as usual, with gcc -O3 -o crackme crackme.c:

   0x00000000080006e0 <+0>:     push   %r14
   0x00000000080006e2 <+2>:     push   %r13
   0x00000000080006e4 <+4>:     push   %r12
   0x00000000080006e6 <+6>:     push   %rbp
   0x00000000080006e7 <+7>:     push   %rbx
   0x00000000080006e8 <+8>:     sub    $0x10,%rsp
   0x00000000080006ec <+12>:    cmp    $0x2,%edi
   0x00000000080006ef <+15>:    je     0x800070f <main+47>
   0x00000000080006f1 <+17>:    lea    0x329(%rip),%rdi        # 0x8000a21
   0x00000000080006f8 <+24>:    callq  0x8000680 <puts@plt>
   0x00000000080006fd <+29>:    add    $0x10,%rsp
   0x0000000008000701 <+33>:    mov    $0xffffffff,%eax
   0x0000000008000706 <+38>:    pop    %rbx
   0x0000000008000707 <+39>:    pop    %rbp
   0x0000000008000708 <+40>:    pop    %r12
   0x000000000800070a <+42>:    pop    %r13
   0x000000000800070c <+44>:    pop    %r14
   0x000000000800070e <+46>:    retq
   0x000000000800070f <+47>:    mov    $0xf,%edi
   0x0000000008000714 <+52>:    mov    %rsi,0x8(%rsp)
   0x0000000008000719 <+57>:    callq  0x80006b0 <malloc@plt>
   0x000000000800071e <+62>:    xor    %edi,%edi
   0x0000000008000720 <+64>:    mov    %rax,%rbp
   0x0000000008000723 <+67>:    mov    0x8(%rsp),%rsi
   0x0000000008000728 <+72>:    mov    %edi,%eax
   0x000000000800072a <+74>:    cpuid
   0x000000000800072c <+76>:    test   %eax,%eax
   0x000000000800072e <+78>:    jne    0x80007c6 <main+230>
   0x0000000008000734 <+84>:    mov    %r14d,%eax
   0x0000000008000737 <+87>:    mov    %r12b,0x9(%rbp)
   0x000000000800073b <+91>:    mov    %r14b,0x1(%rbp)
   0x000000000800073f <+95>:    sar    $0x8,%eax
   0x0000000008000742 <+98>:    mov    %r13b,0x5(%rbp)
   0x0000000008000746 <+102>:   mov    %rbp,%rdi
   0x0000000008000749 <+105>:   mov    %al,0x2(%rbp)
   0x000000000800074c <+108>:   mov    %r14d,%eax
   0x000000000800074f <+111>:   shr    $0x18,%r14d
   0x0000000008000753 <+115>:   sar    $0x10,%eax
   0x0000000008000756 <+118>:   movb   $0x4e,0x0(%rbp)
   0x000000000800075a <+122>:   mov    %r14b,0x4(%rbp)
   0x000000000800075e <+126>:   mov    %al,0x3(%rbp)
   0x0000000008000761 <+129>:   mov    %r13d,%eax
   0x0000000008000764 <+132>:   movb   $0x51,0xd(%rbp)
   0x0000000008000768 <+136>:   sar    $0x8,%eax
   0x000000000800076b <+139>:   movb   $0x0,0xe(%rbp)
   0x000000000800076f <+143>:   mov    %al,0x6(%rbp)
   0x0000000008000772 <+146>:   mov    %r13d,%eax
   0x0000000008000775 <+149>:   shr    $0x18,%r13d
   0x0000000008000779 <+153>:   sar    $0x10,%eax
   0x000000000800077c <+156>:   mov    %r13b,0x8(%rbp)
   0x0000000008000780 <+160>:   mov    %al,0x7(%rbp)
   0x0000000008000783 <+163>:   mov    %r12d,%eax
   0x0000000008000786 <+166>:   sar    $0x8,%eax
   0x0000000008000789 <+169>:   mov    %al,0xa(%rbp)
   0x000000000800078c <+172>:   mov    %r12d,%eax
   0x000000000800078f <+175>:   shr    $0x18,%r12d
   0x0000000008000793 <+179>:   mov    %r12b,0xc(%rbp)
   0x0000000008000797 <+183>:   mov    0x8(%rsi),%r12
   0x000000000800079b <+187>:   sar    $0x10,%eax
   0x000000000800079e <+190>:   mov    %al,0xb(%rbp)
   0x00000000080007a1 <+193>:   mov    %r12,%rsi
   0x00000000080007a4 <+196>:   callq  0x80006a0 <strcmp@plt>
   0x00000000080007a9 <+201>:   mov    %rbp,%rdi
   0x00000000080007ac <+204>:   mov    %eax,%r13d
   0x00000000080007af <+207>:   callq  0x8000670 <free@plt>
   0x00000000080007b4 <+212>:   test   %r13d,%r13d
   0x00000000080007b7 <+215>:   mov    %r12,%rdi
   0x00000000080007ba <+218>:   jne    0x80007c1 <main+225>
   0x00000000080007bc <+220>:   callq  0x8000910 <succeed>
   0x00000000080007c1 <+225>:   callq  0x8000930 <fail>
   0x00000000080007c6 <+230>:   mov    %edi,%eax
   0x00000000080007c8 <+232>:   cpuid
   0x00000000080007ca <+234>:   mov    %ebx,%r14d
   0x00000000080007cd <+237>:   mov    %ecx,%r12d
   0x00000000080007d0 <+240>:   mov    %edx,%r13d
   0x00000000080007d3 <+243>:   jmpq   0x8000734 <main+84>

Strings:

|  address  |           string             |
|-----------|------------------------------|
| 0x8000a21 | "Need exactly one argument." |

As this is a bigger binary, I will read over the binary and pinpoint certain branch locations with immediate context, and make certain notes:

|   offset   |                      deduction                       |
|------------|------------------------------------------------------|
| <main+47>  | continuation after successful entry                  |
| <main+230> | initialization with `cpuid` and leads to `<main+84>` |
| <main+225> | fail procedure                                       |
| <main+84>  | primary logic, bunch of bitwise operations           |

NOTES:

The program only has two points of failure, one for the unexpected `argc` and one for after the primary logic check, at `+225 <fail>`
   0x00000000080006e0 <+0>:     push   %r14
   0x00000000080006e2 <+2>:     push   %r13
   0x00000000080006e4 <+4>:     push   %r12
   0x00000000080006e6 <+6>:     push   %rbp
   0x00000000080006e7 <+7>:     push   %rbx
   0x00000000080006e8 <+8>:     sub    $0x10,%rsp
   0x00000000080006ec <+12>:    cmp    $0x2,%edi
   0x00000000080006ef <+15>:    je     0x800070f <main+47>
   0x00000000080006f1 <+17>:    lea    0x329(%rip),%rdi        # "Need exactly one argument."
   0x00000000080006f8 <+24>:    callq  0x8000680 <puts@plt>
   0x00000000080006fd <+29>:    add    $0x10,%rsp
   0x0000000008000701 <+33>:    mov    $0xffffffff,%eax
   0x0000000008000706 <+38>:    pop    %rbx
   0x0000000008000707 <+39>:    pop    %rbp
   0x0000000008000708 <+40>:    pop    %r12
   0x000000000800070a <+42>:    pop    %r13
   0x000000000800070c <+44>:    pop    %r14
   0x000000000800070e <+46>:    retq

Generic entry as is for previous crackmes:

int
main (int argc, char **argv)
{
  if (argc != 2)
  {
    puts ("Need exactly one argument.");
    return -1;
  }
  /* <...> */
}

Then:

   0x000000000800070f <+47>:    mov    $0xf,%edi
   0x0000000008000714 <+52>:    mov    %rsi,0x8(%rsp)
   0x0000000008000719 <+57>:    callq  0x80006b0 <malloc@plt>
   0x000000000800071e <+62>:    xor    %edi,%edi
   0x0000000008000720 <+64>:    mov    %rax,%rbp
   0x0000000008000723 <+67>:    mov    0x8(%rsp),%rsi
   0x0000000008000728 <+72>:    mov    %edi,%eax
   0x000000000800072a <+74>:    cpuid
   0x000000000800072c <+76>:    test   %eax,%eax
   0x000000000800072e <+78>:    jne    0x80007c6 <main+230>

We save argv at 0x8(%rsp), allocate a 15 byte buffer at %rbp and call cpuid with a parameter of 0; therefore by this table we can deduce that, %eax will contain something, %ebx will contain the four bytes "Genu", %ecx will contain "ntel" and %edx will contain "ineI".

We can cut the entire decompilation short by setting a breakpoint on +196 and doing an i r and getting the %rdi register, tracing it to the string and voila:

(gdb) b *0x00000000080007a4
Breakpoint 3 at 0x80007a4
(gdb) r arg
Starting program: /home/unazed/crackme arg

Breakpoint 3, 0x00000000080007a4 in main ()
(gdb) i r
[...]
rdi            0x8402010        138420240
[...]
(gdb) x/s 0x8402010
0x8402010:      "NGenuineIntelQ"

Afterwards, plugging in "NGenuineIntelQ" to see whether it works:

>./crackme "NGenuineIntelQ"
Yes, NGenuineIntelQ is correct!

Although this form of reverse engineering where you trace and pinpoint certain strings in the programs, breakpointing important calls like strncmp, etc., is useful in this context since I know that the program isn’t malicious and hence not requiring of any more control-flow analysis, but in contexts where you have arbitrary code it is much better to analyse where the rest of the code leads and further decompile the program, as it is safer and more reliable.

Thanks for reading.