In this post, I’ll be reversing the crackme provided here, source:

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

#include <time.h>

#define PASSWORD "password1"
#define PASSLEN 9

void succeed() {
    printf("Access granted!\n");
    exit(0);
}

void fail() {
    printf("Access denied.\n");
    exit(1);
}

int cur_hour() {
    time_t rawtime;
    time(&rawtime);
    if (errno != 0) {
        printf("ERROR: Could not get time: %s", strerror(errno));
        return(-1);
    }
    struct tm *current_time = localtime(&rawtime);
    if (errno != 0) {
        printf("ERROR: Could not get time: %s", strerror(errno));
        return(-1);
    }
    return current_time->tm_hour;
}

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

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

    int hour = cur_hour();
    char* input = argv[1];

    if (strncmp(input, PASSWORD, PASSLEN) != 0) {
        fail();
    }

    if (hour < 5 || hour > 6) {
        fail();
    }

    succeed();
}
   0x0000000008000780 <+0>:     push   %rbx
   0x0000000008000781 <+1>:     sub    $0x10,%rsp
   0x0000000008000785 <+5>:     cmp    $0x2,%edi
   0x0000000008000788 <+8>:     je     0x80007a1 <main+33>
   0x000000000800078a <+10>:    lea    0x2f0(%rip),%rdi        # 0x8000a81
   0x0000000008000791 <+17>:    callq  0x8000720 <puts@plt>
   0x0000000008000796 <+22>:    add    $0x10,%rsp
   0x000000000800079a <+26>:    mov    $0xffffffff,%eax
   0x000000000800079f <+31>:    pop    %rbx
   0x00000000080007a0 <+32>:    retq
   0x00000000080007a1 <+33>:    xor    %eax,%eax
   0x00000000080007a3 <+35>:    mov    %rsi,0x8(%rsp)
   0x00000000080007a8 <+40>:    callq  0x8000960 <cur_hour>
   0x00000000080007ad <+45>:    mov    0x8(%rsp),%rsi
   0x00000000080007b2 <+50>:    mov    $0x9,%edx
   0x00000000080007b7 <+55>:    mov    %eax,%ebx
   0x00000000080007b9 <+57>:    mov    0x8(%rsi),%rdi
   0x00000000080007bd <+61>:    lea    0x2d8(%rip),%rsi        # 0x8000a9c
   0x00000000080007c4 <+68>:    callq  0x8000710 <strncmp@plt>
   0x00000000080007c9 <+73>:    test   %eax,%eax
   0x00000000080007cb <+75>:    mov    $0x0,%eax
   0x00000000080007d0 <+80>:    jne    0x80007da <main+90>
   0x00000000080007d2 <+82>:    sub    $0x5,%ebx
   0x00000000080007d5 <+85>:    cmp    $0x1,%ebx
   0x00000000080007d8 <+88>:    jbe    0x80007df <main+95>
   0x00000000080007da <+90>:    callq  0x8000940 <fail>
   0x00000000080007df <+95>:    callq  0x8000920 <succeed>
|  address  |           string             |
|-----------|------------------------------|
| 0x8000a81 | "Need exactly one argument." |
| 0x8000a9c | "password1"                  |

Beginning with:

   0x0000000008000780 <+0>:     push   %rbx
   0x0000000008000781 <+1>:     sub    $0x10,%rsp
   0x0000000008000785 <+5>:     cmp    $0x2,%edi
   0x0000000008000788 <+8>:     je     0x80007a1 <main+33>
   0x000000000800078a <+10>:    lea    0x2f0(%rip),%rdi        # "Need exactly one argument."
   0x0000000008000791 <+17>:    callq  0x8000720 <puts@plt>
   0x0000000008000796 <+22>:    add    $0x10,%rsp
   0x000000000800079a <+26>:    mov    $0xffffffff,%eax
   0x000000000800079f <+31>:    pop    %rbx
   0x00000000080007a0 <+32>:    retq

Equivalent to the recurring pattern of:

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

At +33:

   0x00000000080007a1 <+33>:    xor    %eax,%eax
   0x00000000080007a3 <+35>:    mov    %rsi,0x8(%rsp)
   0x00000000080007a8 <+40>:    callq  0x8000960 <cur_hour>
   0x00000000080007ad <+45>:    mov    0x8(%rsp),%rsi
   0x00000000080007b2 <+50>:    mov    $0x9,%edx
   0x00000000080007b7 <+55>:    mov    %eax,%ebx
   0x00000000080007b9 <+57>:    mov    0x8(%rsi),%rdi
   0x00000000080007bd <+61>:    lea    0x2d8(%rip),%rsi        # "password1"
   0x00000000080007c4 <+68>:    callq  0x8000710 <strncmp@plt>
   0x00000000080007c9 <+73>:    test   %eax,%eax
   0x00000000080007cb <+75>:    mov    $0x0,%eax
   0x00000000080007d0 <+80>:    jne    0x80007da <main+90>
   0x00000000080007d2 <+82>:    sub    $0x5,%ebx
   0x00000000080007d5 <+85>:    cmp    $0x1,%ebx
   0x00000000080007d8 <+88>:    jbe    0x80007df <main+95>
   0x00000000080007da <+90>:    callq  0x8000940 <fail>
   0x00000000080007df <+95>:    callq  0x8000920 <succeed>

We see usage of the local stackframe itself with +35 moving argv into 0x8(%rsp), hereafter calling cur_hour and restoring the %rsi register, presumably as a way of preserving %rsi which is not preserved across function calls, i.e. push %rsi; call ...; pop %rsi, then we call strncmp (argv[1], "password1", 0x9);; given that its result is ‘not equal’, it calls fail otherwise we take %ebx which is just %eax from +55, which itself is the return value of cur_hour ();, we subtract 5 from it and compare it to 1, if cur_hour () - 5 <= 1 then succeed ();, in another sense, cur_hour() <= 6, or otherwise cur_hour () - 6 = 0 (as jbe is unsigned, it can’t be less than 0 without overflowing), in another expanded sense we could expand +85 to cur_hour () - 5 - 1 = cur_hour () - 6 implies ZF/CF are either set, that is, the condition is only met if cur_hour () = 6 or cur_hour () = 5 since 5 - 5 <= 1 = 0 <= 1 which is true, and 6 - 5 <= 1 is 1 <= 1 which is true.

Therefore we get the following C:

EDITORIAL NOTE: It’s quite interesting to note that GCC didn’t optimize the strncmp as it had done before in the first post, using a repz cmpsb loop.

UNKNOWN cur_hour (void);
UNKNOWN fail (void);
UNKNOWN succeed (void);

int
main (int argc, char **argv)
{
  if (argc != 2)
  {
    puts ("Need exactly one argument.");
    return -1;
  }
  if (strncmp (argv[1], "password1", 0x9))
  {
    fail ();
  }
  if (5 <= cur_hour() <= 6)
  {
    succeed ();
  }
}

Now, allow us to analyse the fail function, which I presume will be the fastest:

   0x0000000008000940 <+0>:     lea    0x10d(%rip),%rdi        # "Access denied."
   0x0000000008000947 <+7>:     sub    $0x8,%rsp
   0x000000000800094b <+11>:    callq  0x8000720 <puts@plt>
   0x0000000008000950 <+16>:    mov    $0x1,%edi
   0x0000000008000955 <+21>:    callq  0x8000750 <exit@plt>

Equivalent to:

void
fail (void)
{
  puts ("Access denied.");
  exit (1);
}

And now, the succeed function:

   0x0000000008000920 <+0>:     lea    0x11d(%rip),%rdi        # "Access granted!"
   0x0000000008000927 <+7>:     sub    $0x8,%rsp
   0x000000000800092b <+11>:    callq  0x8000720 <puts@plt>
   0x0000000008000930 <+16>:    xor    %edi,%edi
   0x0000000008000932 <+18>:    callq  0x8000750 <exit@plt>

Which is, similarly:

void
succeed (void)
{
  puts ("Access granted!");
  exit (0);
}

And now, most likely the most difficult function, cur_hour:

   0x0000000008000960 <+0>:     push   %rbp
   0x0000000008000961 <+1>:     push   %rbx
   0x0000000008000962 <+2>:     sub    $0x18,%rsp
   0x0000000008000966 <+6>:     lea    0x8(%rsp),%rbp
   0x000000000800096b <+11>:    mov    %rbp,%rdi
   0x000000000800096e <+14>:    callq  0x8000740 <time@plt>
   0x0000000008000973 <+19>:    callq  0x8000700 <__errno_location@plt>
   0x0000000008000978 <+24>:    mov    (%rax),%edi
   0x000000000800097a <+26>:    test   %edi,%edi
   0x000000000800097c <+28>:    jne    0x80009a0 <cur_hour+64>
   0x000000000800097e <+30>:    mov    %rax,%rbx
   0x0000000008000981 <+33>:    mov    %rbp,%rdi
   0x0000000008000984 <+36>:    callq  0x80006f0 <localtime@plt>
   0x0000000008000989 <+41>:    mov    (%rbx),%edi
   0x000000000800098b <+43>:    test   %edi,%edi
   0x000000000800098d <+45>:    jne    0x80009a0 <cur_hour+64>
   0x000000000800098f <+47>:    mov    0x8(%rax),%eax
   0x0000000008000992 <+50>:    add    $0x18,%rsp
   0x0000000008000996 <+54>:    pop    %rbx
   0x0000000008000997 <+55>:    pop    %rbp
   0x0000000008000998 <+56>:    retq
   0x0000000008000999 <+57>:    nopl   0x0(%rax)
   0x00000000080009a0 <+64>:    callq  0x8000760 <strerror@plt>
   0x00000000080009a5 <+69>:    lea    0xb7(%rip),%rdi        # "ERROR: Could not get time: %s"
   0x00000000080009ac <+76>:    mov    %rax,%rsi
   0x00000000080009af <+79>:    xor    %eax,%eax
   0x00000000080009b1 <+81>:    callq  0x8000730 <printf@plt>
   0x00000000080009b6 <+86>:    mov    $0xffffffff,%eax
   0x00000000080009bb <+91>:    jmp    0x8000992 <cur_hour+50>

And indeed it is, it is also equivalently more interesting. Allow me to begin:

   0x0000000008000960 <+0>:     push   %rbp
   0x0000000008000961 <+1>:     push   %rbx
   0x0000000008000962 <+2>:     sub    $0x18,%rsp
   0x0000000008000966 <+6>:     lea    0x8(%rsp),%rbp
   0x000000000800096b <+11>:    mov    %rbp,%rdi
   0x000000000800096e <+14>:    callq  0x8000740 <time@plt>
   0x0000000008000973 <+19>:    callq  0x8000700 <__errno_location@plt>
   0x0000000008000978 <+24>:    mov    (%rax),%edi
   0x000000000800097a <+26>:    test   %edi,%edi
   0x000000000800097c <+28>:    jne    0x80009a0 <cur_hour+64>

Here is the relevant part of the man pages for time:

SYNOPSIS
       #include <time.h>

       time_t time(time_t *tloc);

DESCRIPTION
       time() returns the time as the number of seconds since the Epoch, 1970-01-01 00:00:00 +0000 (UTC).

       If tloc is non-NULL, the return value is also stored in the memory pointed to by tloc.

EDITORIAL NOTE: The tloc argument is obsolescent and should always be NULL in new code. When tloc is NULL, the call cannot fail.

So, 0x8(%rsp) contains time_t, and the subsequent __errno_location call is one to retrieve the errno variable address, afterwards +24 and +26 testing if the errno is non-zero (an error, I presume), if so, jumping to +64:

   0x00000000080009a0 <+64>:    callq  0x8000760 <strerror@plt>
   0x00000000080009a5 <+69>:    lea    0xb7(%rip),%rdi        # "ERROR: Could not get time: %s"
   0x00000000080009ac <+76>:    mov    %rax,%rsi
   0x00000000080009af <+79>:    xor    %eax,%eax
   0x00000000080009b1 <+81>:    callq  0x8000730 <printf@plt>
   0x00000000080009b6 <+86>:    mov    $0xffffffff,%eax
   0x00000000080009bb <+91>:    jmp    0x8000992 <cur_hour+50>

Hence calling printf ("ERROR: Could not get time: %s", strerror (errno)); and return -1; So, we have:

UNKNOWN
cur_hour (void)
{
  time_t time_;
  time (&time_);
  if (errno)
  {
    printf ("ERROR: Could not get time: %s", strerror (errno));
    return -1;
  }
  /* <...> */
}

Otherwise, we continue with:

   0x000000000800097e <+30>:    mov    %rax,%rbx
   0x0000000008000981 <+33>:    mov    %rbp,%rdi
   0x0000000008000984 <+36>:    callq  0x80006f0 <localtime@plt>
   0x0000000008000989 <+41>:    mov    (%rbx),%edi
   0x000000000800098b <+43>:    test   %edi,%edi
   0x000000000800098d <+45>:    jne    0x80009a0 <cur_hour+64>
   0x000000000800098f <+47>:    mov    0x8(%rax),%eax
   0x0000000008000992 <+50>:    add    $0x18,%rsp
   0x0000000008000996 <+54>:    pop    %rbx
   0x0000000008000997 <+55>:    pop    %rbp
   0x0000000008000998 <+56>:    retq

Where we call localtime (&time_);, thereafter checking the errno again with +41, otherwise moving the tm_hour member of the returned structure into %eax and returning it. Finally, we have:

int
cur_hour (void)
{
  time_t time_;
  time (&time_);
  if (errno) goto if_errno;
  struct tm *ptr = localtime (&time_);
  if (errno) goto if_errno;
  return ptr->tm_hour;
  
if_errno:
  printf ("ERROR: Could not get time: %s", strerror (errno));
  return -1;
}

Note that I use the goto statement, as analogous to the assembly code since it removes repetition and ultimately doesn’t make any control flow any more confusing. In total, we have:

int
cur_hour (void)
{
  time_t time_;
  time (&time_);
  if (errno)
    goto if_errno;
  struct tm *ptr = localtime (&time_);
  if (errno)
    goto if_errno;
  return ptr->tm_hour;

if_errno:
  printf ("ERROR: Could not get time: %s", strerror (errno));
  return -1;
}

void
succeed (void)
{
  puts ("Access granted!");
  exit (0);
}

void
fail (void)
{
  puts ("Access denied!");
  exit (1);
}

int
main (int argc, char **argv)
{
  if (argc != 2)
    {
      puts ("Need exactly one argument.");
      return -1;
    }
  if (strncmp (argv[1], "password1", 0x9))
    {
      fail ();
    }
  int hour = cur_hour ();

  if (5 <= hour && hour <= 6)
    {
      succeed ();
    }
  else
    {
      fail ();
    }
}

A few changes made, that is, adding the else clause to the final main’s condition as I’d forgotten to, fixing the conditional itself as C doesn’t support operator linking like a < b < c, rather expanded to a < b && b < c, and finally moved the variable outside into hour. Also the styling change using indent, as I like GNU-style.

All-in-all, fairly interesting to learn about how errno works in the binary-standard with __errno_location, otherwise trivial work.

See you later.