In this post, I will quickly show how you can use a debugger to hack poorly written or packaged code. More specifically, how to enter a wrong password but still get access. Before proceeding, I should be clear, that this is just a demonstration, and the programs out there in production (these days) will definitely not vulnerable to this method (if it is, then it is a shitty program). This is to demonstrate how you can change the execution path as you wish.

First I will show a simple code which prompts for a password to be set, then encrypts it using MD5 sum hash and salt and stores the hash in a file. Then it asks for the same password, reads the hash from the stored file and compares if the two entered passwords are same or not. Then I will show how to reverse engineer the executable file and enter the wrong password, but still make it think that we the correct password was entered.

A simple password verification mechanism

The function int pass_set (char *prompt, char *hash_file, char *hash_type) prompts for a password, stores the hash in the file specified in hash_file, and returns true if the the operation was successful. The hash_type determines the type of the hash which can be MD5, Blowfish, SHA-256 or SHA-512, and the argument should be set using the PASS_HASH_* macros.

The function int pass_check (char *prompt, char *hash_file) will read from a specified hash specified by hash_file, prompt for a password, and compare with the password stored in the hash file. If there is a match, it returns true, else it returns false.

The source code is listed below, click on the link to expand it on the page.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <crypt.h>
#include <time.h>

#define TRUE  1
#define FALSE 0
#define PASS_MAX_LEN  128
#define PASS_HASH_LEN 128

#define PASS_HASH_MD5 "1"
#define PASS_HASH_BLOWFISH "2a"
#define PASS_HASH_SHA256 "5"
#define PASS_HASH_SHA512 "6"

/* Name     : pass_check
 * Arguments:
 *    char *prompt    : A string which will be displayed in the prompt.
 *    char *hash_file : The file name where the hash will be stored.
 *
 * Return: int
 *    TRUE      : If the entered password matches with the one stored in the file.
 *    FALSE     : If the entered password does not match with the one stored in the file.
 *                hash_file does not exist.
 *                hash_file has a different format.
 */
int pass_check (char *prompt, char *hash_file)
{
  FILE *fp;
  char salt[PASS_HASH_LEN], hash[PASS_HASH_LEN], tok_buffer[PASS_HASH_LEN], *tok, *pass, *new_hash = NULL;
  int retval = FALSE;

  fp = fopen (hash_file, "r");

  if (fp == NULL)
  {
    retval = FALSE;
    goto PASS_CHECK_CLEANUP_RETURN;
  }

  fscanf (fp, "%s", hash);
  strcpy (tok_buffer, hash);

  tok = strtok (tok_buffer, "$");
  if (!((strcmp (tok, PASS_HASH_MD5) == 0)    ||
      (strcmp (tok, PASS_HASH_BLOWFISH) == 0) ||
      (strcmp (tok, PASS_HASH_SHA256) == 0)   ||
      (strcmp (tok, PASS_HASH_SHA512) == 0)))
  {
    retval = FALSE;
    goto PASS_CHECK_CLEANUP_RETURN;
  }

  sprintf (salt, "$%s$", tok);
  tok = strtok (NULL, "$");
  strcat (salt, tok);
  strcat (salt, "$");

  pass = getpass (prompt);

  new_hash = crypt (pass, salt);

  #ifdef DEBUG
  printf ("STORED HASH [%s]\n", hash);
  printf ("NEW HASH    [%s]\n", new_hash);
  #endif
  if (strcmp (new_hash, hash) == 0)
  {
    retval = TRUE;
    goto PASS_CHECK_CLEANUP_RETURN;
  }
  else
  {
    retval = FALSE;
    goto PASS_CHECK_CLEANUP_RETURN;
  }

  PASS_CHECK_CLEANUP_RETURN:

  return (retval);
}

/* Name     : pass_set
 * Arguments:
 *    char *prompt    : A string which will be displayed in the prompt.
 *    char *hash_file : The file name where the hash will be stored.
 *    char *hash_type : The type of hash. use any of the PASS_HASH_* macros.
 *
 * Return: int
 *    TRUE      : If the salted hash was successfully stored.
 *    FALSE     : If the salted hash was not stored.
 */
int pass_set (char *prompt, char *hash_file, char *hash_type)
{
  FILE *fp;
  char *pass, *hash, salt[PASS_HASH_LEN];
  int retval = FALSE, call_ret;

  pass = getpass (prompt);
  srand (time (NULL));
  sprintf (salt, "$%s$%d$", hash_type, rand ());

  hash = crypt (pass, salt); 

  #ifdef DEBUG
  printf ("Hash [%s]", hash);
  #endif
  fp = fopen (hash_file, "w");
  if (fp == NULL)
  {
    retval = FALSE;
    goto PASS_SET_CLEANUP_RETURN;
  }

  call_ret = fprintf (fp, "%s", hash);
  if (call_ret != (int) strlen (hash))
  {
    retval = FALSE;
    goto PASS_SET_CLEANUP_RETURN;
  }
  else
  {
    retval = TRUE;
    goto PASS_SET_CLEANUP_RETURN;
  }

  PASS_SET_CLEANUP_RETURN:

  memset (pass, 0x00, strlen (pass) * sizeof (char));
  free (pass);

  memset (hash, 0x00, strlen (pass) * sizeof (char));
  free (hash);

  memset (salt, 0x00, PASS_HASH_LEN * sizeof (char));

  fclose (fp);

  return (retval);
}

/* Main code to test functionality */
int main (void)
{
  int call_ret;
  char hash_file[] = "test_hash";

  call_ret = pass_set ("Set password: ", hash_file, PASS_HASH_MD5);
  if (call_ret == TRUE)
  {
    printf ("Password set\n");

    call_ret = pass_check ("Enter password to verify: ", hash_file);
    if (call_ret == TRUE)
    {
      printf ("Password VALID\n");
    }
    else
    {
      printf ("Password invalid or incorrect hash file\n");
    }
  }
  else
  {
    printf ("Password set failed\n");
  }

  return 0;
}

If you define DEBUG macro while compilation, the code will print the hashes. For the demo, I have compiled the code with DEBUG

gcc -g mypass.c -lcrypt -Wall -Wextra -DDEBUG -o mypass

Here is a sample output:

$./mypass
Set password:
Hash [$1$10315074$hHPh6vlV7pYZ38TnTlobR/]
Password set
Enter password to verify:
STORED HASH [$1$10315074$hHPh6vlV7pYZ38TnTlobR/]
NEW HASH    [$1$10315074$UKBMF96T0CJ76uhwyXm95/]
Password invalid or incorrect hash file

$./mypass
Set password:
Hash [$1$20171390$o6dHUYUJGd0STzrqBzaHl0]
Password set
Enter password to verify:
STORED HASH [$1$20171390$o6dHUYUJGd0STzrqBzaHl0]
NEW HASH    [$1$20171390$o6dHUYUJGd0STzrqBzaHl0]
Password VALID

The code uses getpass, so the password will not show as you type. The implementation uses crypt to generate the salted hashes. The salt is generated using srand and rand. The main function, calls pass_set to set the password, and then if the password setting was successful, it calls pass_check to verify. Note that in the first execution the STORED HASH and NEW HAS are different, but for the second execution they are identical.

The hack: Bypassing the password verification

Now we have the basic program in place, therefore we can proceed with the hack. Although we should remember serious password programs will not be implemented like this, and will have all the symbols stripped and also have obfuscated function names, so that it is difficult to guess from the function names.

If this was a code you know, then you can know the function already to look into, which is pass_check. Else you can do the following.

$nm mypass | grep -i " T "
0000000000400a70 t deregister_tm_clones
0000000000400af0 t __do_global_dtors_aux
0000000000601e08 t __do_global_dtors_aux_fini_array_entry
0000000000401004 T _fini
0000000000400b10 t frame_dummy
0000000000601e00 t __frame_dummy_init_array_entry
00000000004008b8 T _init
0000000000601e08 t __init_array_end
0000000000601e00 t __init_array_start
0000000000401000 T __libc_csu_fini
0000000000400f90 T __libc_csu_init
0000000000400efe T main
0000000000400b36 T pass_check
0000000000400d64 T pass_set
0000000000400ab0 t register_tm_clones
0000000000400a40 T _start

This shows all the symbols in the text section of the executable. Manually scanning through the list, one can make out that the functions of interest are pass_check and pass_set. Once we have the function, pass_check in this case, we proceed dissassembling it. Use objdump to get the dissassembled code for pass_check.

$objdump -d mypass

This will dissassemble the entire executable. Isolate the function pass_check, which looks like this.

0000000000400b36 <pass_check>:
  400b36:       55                      push   %rbp
  400b37:       48 89 e5                mov    %rsp,%rbp
  400b3a:       48 81 ec c0 01 00 00    sub    $0x1c0,%rsp
  400b41:       48 89 bd 48 fe ff ff    mov    %rdi,-0x1b8(%rbp)
  400b48:       48 89 b5 40 fe ff ff    mov    %rsi,-0x1c0(%rbp)
  400b4f:       48 c7 45 f8 00 00 00    movq   $0x0,-0x8(%rbp)
  400b56:       00
  400b57:       c7 45 f4 00 00 00 00    movl   $0x0,-0xc(%rbp)
  400b5e:       48 8b 85 40 fe ff ff    mov    -0x1c0(%rbp),%rax
  400b65:       be 10 10 40 00          mov    $0x401010,%esi
  400b6a:       48 89 c7                mov    %rax,%rdi
  400b6d:       e8 7e fe ff ff          callq  4009f0 <fopen@plt>
  400b72:       48 89 45 e8             mov    %rax,-0x18(%rbp)
  400b76:       48 83 7d e8 00          cmpq   $0x0,-0x18(%rbp)
  .
  .
  (output truncated)
  .
  .
  400d4b:       48 89 c2                mov    %rax,%rdx
  400d4e:       48 8b 45 f8             mov    -0x8(%rbp),%rax
  400d52:       be 00 00 00 00          mov    $0x0,%esi
  400d57:       48 89 c7                mov    %rax,%rdi
  400d5a:       e8 01 fc ff ff          callq  400960 <memset@plt>
  400d5f:       8b 45 f4                mov    -0xc(%rbp),%eax
  400d62:       c9                      leaveq
  400d63:       c3                      retq

Note the address of leaveq instruction, 0x400d62. (Note: leaveq releases the stack frame before the return retq, in brief). At this point, everything is done, and the function has copied the return value into %eax or the accumulator. If you note the address 400b57 at the beginning, you will notice that at offset -0xc from the base pointer a value of 0 is set, this is actually when the code initializes the retval to FALSE. Although we do not need to know how exactly the code is implemented. Let’s just focus on the address for leaveq, 0x400d62.

Start the program using gdb, and set a break point at 0x400d62, which is just before returning, and then run the program. Then input a password, say “hello”. When the program prompts to verify the password, enter something else, say “qwerty”. Now, the execution will stop at our set break point.

$gdb --quiet ./mypass
Reading symbols from ./mypass...done.
(gdb) break *0x400d62
Breakpoint 1 at 0x400d62: file anti_debug.c, line 90.
(gdb) run
Starting program: /home/phoxis/Documents/works/codes/ravenholm/quick_stuffs/mypass
Missing separate debuginfos, use: dnf debuginfo-install glibc-2.22-18.fc23.x86_64
Set password:
Hash [$1$15942509$20xzbGxHOoAjkFUaInWh6/]Password set
Enter password to verify:
STORED HASH [$1$15942509$20xzbGxHOoAjkFUaInWh6/]
NEW HASH    [$1$15942509$9H6ALJhFMF/Xgn3B0x4XC0]

Breakpoint 1, pass_check (prompt=0x401079 "Enter password to verify: ", hash_file=0x7fffffffdb90 "test_hash") at anti_debug.c:90
warning: Source file is more recent than executable.
90      }
Missing separate debuginfos, use: dnf debuginfo-install nss-softokn-freebl-3.27.0-1.0.fc23.x86_64

Note how the value of STORED HASH and NEW HASH are different. At this point you can type in print retval to confirm the value of retval, which will be 0. This value of retval is returned via the register eax, and it is already copied, as we have seen from the dissassembled code. Therefore what we need to do is just to replace the value of eax with 1, and let the execution continue. Which can be done by the following:

(gdb) set $eax = 1
(gdb) continue
Continuing.
Password VALID
[Inferior 1 (process 30786) exited normally]

Check the result, it says “Password VALID”, although the the STORED HASH and NEW HASH is different, and also the value of retval was also set to 0. Therefore we have provided an incorrect password pair, but changed the return value while the code is in execution and bypassed the password mechanism.

Strip

Strip the executable But, if you strip the executable.

strip mypass

Read the wikipedia entry: https://en.wikipedia.org/wiki/Strip_(Unix).

“Furthermore, the use of strip can improve the security of the binary against reverse engineering. It will be more difficult to analyze a binary without its information and object’s names.”

There are a few other ways so that one cannot reverse engineer the executable like this, but the first hurdle will be to strip the executable. Although this might result in trouble while debugging, therefore you can only strip the require symbols using the -N option. Once you have stripped the executable using the above command, try nm mypass it will show, “nm: mypass: no symbols”. When you dissassemble it, you will observe that the task of isolating the points to intercept are not that easy to find, actually it has now become extremely difficult to understand what exactly is going on.

At the end

This is a simple and interesting example to show how you can use the debugger to change the execution at run time as you wish. Not necessarily for the password matching, but maybe some other cause. There are other ways to stop one from reverse engineering and modifying the execution path. One is Executable Compression, and another is to make the executable detect that it is running within a debugger and stop execution immediately.

This concludes the post. Let me know in the comments your views, and if you know other methods to reverse engineer stripped or non-stripped executables.

Advertisement

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s