How to crack loop-aes / loopaes
#1
This is quite an old file/disk/partition encryption mechansim (but still in active development, see sourceforge CHANGES file), but was quite heavily used back then. see https://sourceforge.net/projects/loop-aes/

I / we (team hashcat and others !) came across this (file) encryption algorithm during CMIYC 2022 (see https://contest-2022.korelogic.com). We had to crack the "LoopAESLoopAESLoopAES" container (unknown file, with unknown password at the start of the cracking contest).

During contest, we figured this out by installing the original software on a linux (arch) virtual machine (VM), because we found out that it's easy to install loop-aes tools with Arch or other linux distros that support(ed) it (https://aur.archlinux.org/packages?K=loop-aes, some VM/dockers etc you can find around the web have it already installed, so no need to mess around the installation problems etc).

It's interesting that there are also some old ubuntu packages for this and this package "patch" the "mount" / "umount" / "losetup" etc tools (e.g https://launchpad.net/ubuntu/+source/loop-aes-utils), but they are all deprecated, only "cryptsetup" has still some basic supports with keyfiles. The ubuntu packages are called something like "loop-aes-utils" and they basically come all from some "debian" tools (https://snapshot.debian.org/package/loop-aes-utils/ , e.g. https://snapshot.debian.org/archive/debi...rig.tar.gz)

BTW: I've also managed to install the ubuntu package "loop-aes-util" on a modern/new Ubuntu 22.10 virtual machine, but be aware that it messes around with your "losetup", "mount", "umount" tools, so I wouldn't recommend it (only within a throw-away VM maybe)

I've also noticed that on sourceforge there are only new versions of loop-aes (couldn't even find them on the web wayback machine), but I was able to find some description and older versions on some random web pages, e.g. http://mhensler.de/cryptohome/encryption_en.php , http://mhensler.de/cryptohome/download/

One problem with the difficult to install loop-aes support is that it needs to have kernel support, because it relies on the "patched" mount/umount/losetup tools etc.... I'm not sure why the developers didn't include a standalone tool that can do all this independently, but I guess the idea is that you normaly only use(d) loop-aes when you want to encrypt a whole disk/partition with full operating system support (i.e. it loads the keyfiles and asks for password and everything is integrated in the OS).

Our "LoopAESLoopAESLoopAES" didn't use any keyfile, so this posts also only deals with this simple type of containers used during CMIYC 2022: AES128 (use -e AES128 for losetup) and no keyfiles used, direct password input with AES128 encryption:

Code:
echo PasswordPasswordPassword | losetup -p 0 -e AES128 /dev/loop0 ./LoopAESLoopAESLoopAES

It's also important to know that the passwords need to be at least 20 characters long.

The password for the "LoopAESLoopAESLoopAES" was just "PasswordPasswordPassword" (easy, right? but you still had to get the details about this encryption and manage to install the loop-aes tools or have an old VM already supporting it etc).


The algorithm uses sha256 of the (long) password and only uses the first 16 bytes of this output as a key for AES-CBC (actually it uses a counter, like AES-CTR, but not exactly the same way), the IV (initialization vector) is a 16-byte increasing number, like a block counter, starting with 0 and increasing with every 512 bytes (i.e. you can seek into the file/disk/partition if you know the block number/offset).

Here is a very simple POC (proof of concept):
Code:
#!/usr/bin/env perl

# Author:  philsmd
# Date:    August 2022
# License: public domain, credits go to philsmd and hashcat

# uses SHA256 ($PASS) + AES128-CBC ($BLOCK) with increasing IV for block number (512 bytes)

use strict;
use warnings;

use Crypt::CBC;
use Digest::SHA qw (sha256);


#
# Constants
#

my $PASS      = "PasswordPasswordPassword";
my $FILE_NAME = "LoopAESLoopAESLoopAES";


#
# Start
#

my $fh;

if (! open ($fh, "<", $FILE_NAME))
{
  exit (1);
}

binmode ($fh);

my $file_content = "";

{
  local $/;

  $file_content = <$fh>;
}

close ($fh);


# key derivation:

my $sha256_hash = sha256 ($PASS);

my $key = substr ($sha256_hash, 0, 16); # only first half of SHA256 digest is used o.O


# main loop over all 512 byte blocks:

for (my $i = 0; $i < length ($file_content); $i += 512)
{
  my $block_num = $i / 512;

  # we could use pack () for the IV, but problem is it's 16 bytes (128 bits)

  my $iv = "";

  for (my $j = 0; $j < 16; $j++)
  {
    $iv .= chr ($block_num & 0xff);

    $block_num >>= 8; # or $block_num /= 256;
  }


  # AES128-CBC decryption:

  my $aes = Crypt::CBC->new ({
    cipher      => "Crypt::Rijndael",
    keysize     => 16,
    literal_key => 1,
    header      => "none",
    iv          => $iv,
    key         => $key,
    padding     => "none",
  });

  print $aes->decrypt (substr ($file_content, $i, 512));
}

exit (0);


The crackers could then do some checks with Shannon entropy, for instance like this (to verify whether the password is correct):
Code:
#!/usr/bin/env perl

# Author:  philsmd
# Date:    August 2022
# License: public domain, credits go to philsmd and hashcat

# uses SHA256 ($PASS) + AES128-CBC ($BLOCK) with increasing IV for block number (512 bytes)

use strict;
use warnings;

use Crypt::CBC;
use Digest::SHA qw (sha256);


#
# Constants
#

my $FILE_NAME   = "LoopAESLoopAESLoopAES";
my $MAX_ENTROPY = 7.0;
my $IV          = "\x00" x 16;


#
# Start
#

my $fh;

if (! open ($fh, "<", $FILE_NAME))
{
  print STDERR "ERROR: Could not open file '$FILE_NAME'\n";

  exit (1);
}


binmode ($fh);


my $first_block = "";

read ($fh, $first_block, 512);


close ($fh);


if (length ($first_block) < 512)
{
  print STDERR "ERROR: encrypted input file too short\n";

  exit (1);
}


# main loop over all 512 byte blocks:

while (my $pass = <>)
{
  chomp ($pass);

  # key derivation:

  my $sha256_hash = sha256 ($pass);

  my $key = substr ($sha256_hash, 0, 16); # only first half of SHA256 digest is used o.O


  # AES128-CBC decryption:

  my $aes = Crypt::CBC->new ({
    cipher      => "Crypt::Rijndael",
    iv          => $IV,
    key         => $key,
    keysize     => 16,
    literal_key => 1,
    header      => "none",
    padding     => "none",
  });

  my $decrypted = $aes->decrypt ($first_block);


  # check entropy (Shannon's entropy):

  my $entropy = 0;

  for (my $i = 0; $i < 256; $i++) # 0x00 - 0xff
  {
    my $t = chr ($i);

    my $r = 0;

    for (my $j = 0; $j < 512; $j++)
    {
      my $c = substr ($decrypted, $j, 1);

      if ($c eq $t)
      {
        $r++;
      }
    }

    if ($r == 0)
    {
      next;
    }

    my $w = $r / 512;

    $entropy += -$w * (log ($w) / log (2));
  }

  if ($entropy < $MAX_ENTROPY)
  {
    print "Password found: '$pass'\n";

    exit (0);
  }
}

exit (1);


or use a more intuitive approach (just check how many different bytes out of the 0x00-0xff range are being used; note: encrypted data/ciphertext or incorrectly decrypted plaintext - with wrong password - use most of the bytes, therefore it's random, of course):
Code:
#!/usr/bin/env perl

# Author:  philsmd
# Date:    August 2022
# License: public domain, credits go to philsmd and hashcat

# uses SHA256 ($PASS) + AES128-CBC ($BLOCK) with increasing IV for block number (512 bytes)

use strict;
use warnings;

use Crypt::CBC;
use Digest::SHA qw (sha256);


#
# Constants
#

my $FILE_NAME   = "LoopAESLoopAESLoopAES";
my $MAX_ENTROPY = 256 / 2; # only 7 bits out of 8
my $IV          = "\x00" x 16;


#
# Start
#

my $fh;

if (! open ($fh, "<", $FILE_NAME))
{
  print STDERR "ERROR: Could not open file '$FILE_NAME'\n";

  exit (1);
}


binmode ($fh);


my $first_block = "";

read ($fh, $first_block, 512);


close ($fh);


if (length ($first_block) < 512)
{
  print STDERR "ERROR: encrypted input file too short\n";

  exit (1);
}


# main loop over all 512 byte blocks:

while (my $pass = <>)
{
  chomp ($pass);

  # key derivation:

  my $sha256_hash = sha256 ($pass);

  my $key = substr ($sha256_hash, 0, 16); # only first half of SHA256 digest is used o.O


  # AES128-CBC decryption:

  my $aes = Crypt::CBC->new ({
    cipher      => "Crypt::Rijndael",
    iv          => $IV,
    key         => $key,
    keysize     => 16,
    literal_key => 1,
    header      => "none",
    padding     => "none",
  });

  my $decrypted = $aes->decrypt ($first_block);


  # check entropy (intuitive approach):

  my %chars_seen = ();

  for (my $i = 0; $i < 512; $i++)
  {
    my $c = substr ($decrypted, $i, 1);

    $chars_seen{$c} = 1;
  }

  my $entropy = scalar (keys (%chars_seen));

  if ($entropy < $MAX_ENTROPY)
  {
    print "Password found: '$pass'\n";

    exit (0);
  }
}

exit (1);

same can be done with a counter instead of the perl hash map:
Code:
#!/usr/bin/env perl

# Author:  philsmd
# Date:    August 2022
# License: public domain, credits go to philsmd and hashcat

# uses SHA256 ($PASS) + AES128-CBC ($BLOCK) with increasing IV for block number (512 bytes)

use strict;
use warnings;

use Crypt::CBC;
use Digest::SHA qw (sha256);


#
# Constants
#

my $FILE_NAME   = "LoopAESLoopAESLoopAES";
my $MAX_ENTROPY = 256 / 2; # only 7 bits out of 8
my $IV          = "\x00" x 16;


#
# Start
#

my $fh;

if (! open ($fh, "<", $FILE_NAME))
{
  print STDERR "ERROR: Could not open file '$FILE_NAME'\n";

  exit (1);
}


binmode ($fh);


my $first_block = "";

read ($fh, $first_block, 512);


close ($fh);


if (length ($first_block) < 512)
{
  print STDERR "ERROR: encrypted input file too short\n";

  exit (1);
}


# main loop over all 512 byte blocks:

while (my $pass = <>)
{
  chomp ($pass);

  # key derivation:

  my $sha256_hash = sha256 ($pass);

  my $key = substr ($sha256_hash, 0, 16); # only first half of SHA256 digest is used o.O


  # AES128-CBC decryption:

  my $aes = Crypt::CBC->new ({
    cipher      => "Crypt::Rijndael",
    iv          => $IV,
    key         => $key,
    keysize     => 16,
    literal_key => 1,
    header      => "none",
    padding     => "none",
  });

  my $decrypted = $aes->decrypt ($first_block);


  # check entropy (intuitive approach):

  my @chars_seen = (0) x 256;

  my $entropy = 0;

  for (my $i = 0; $i < 512; $i++)
  {
    my $t = ord (substr ($decrypted, $i, 1));

    if ($chars_seen[$t] == 1)
    {
      next;
    }

    $chars_seen[$t] = 1;

    $entropy++;
  }

  if ($entropy < $MAX_ENTROPY)
  {
    print "Password found: '$pass'\n";

    exit (0);
  }
}

exit (1);


It's an interesting algorithm, still a little bit strange that only 16 bytes of the sha256 output are being used, they could also xor the 2 halves or do something else with it.

I think it makes little sense to add this to hashcat, because it's probably rarely used these days with other/better file/disk/partition encryption algorithms widely supported by many operating systems (like veracrypt/luks etc).

Hope this at least helps somebody who tries to find out the details about this algorithm

Thanks and cheers



Note: that as mentioned above this post mainly deals with AES128 encrypted files that were encrypted by the loopaes tool and have non-random data (e.g. a non-encrypted/non-compressed file or an included known/common file system type with non-random headers)... you might need to test if your files or file system that you are trying to target have also this properties (non-random, such that we can figure out if the password was correct by an entropy check).

The alternatives for loop-aes/loopaes would also include AES192 and AES256, I didn't test these myself and have also no example data, but the general idea would be similar just a different key-derivation and decryption mechanism will be used for these alternatives (btw: there are even further options, besides AES, but discussions about completely different encryption and key derivation types will be too far for this simple/small post/review about loopaes after CMIYC 2022). Always generate some examples and try to crack them, you can adapt my tools depending on your usage (and the key derivation/encryption differences for you specific file/container that you are trying to decrypt/crack).
Reply