Decrypting .eslock files

I was feeling nostalgic the other day so I decided to take a trip down memory lane by browsing some old entries on my phone. To my surprise, I found a few encrypted files that I don't remember putting there. After trying a few passwords that made sense, it quickly became apparent that a mental dictionary-based brute force approach was not the way to go.

I turned to Google in search of answers. The first pages mention an app that can decrypt .eslock files, and while it looked promising, I wasn't sure it would be smart to trust a black box of an app with data that I once deemed private enough to encrypt. I had to find another way.

This blog post in particular caught my eye. It states that the password of an encrypted file is hashed with the MD5 algorithm then stored within the file itself ! Since MD5 is considered to be obsolete, surely I would be able to un-hash the password if I were to get my hands on it. With no pointers on how an .eslock file is structured, I figured I'd open it in a hexadecimal editor and see where it goes from there.

I first created an empty file on my phone that I then encrypted using "foo" as a password. Since I know that the MD5 hash of "foo" is acbd18db4cc2f85cedef654fccc4a4d8, all I had to do was look for it in the empty file.

I copied the file over to my laptop then opened it in Vim. I then switched to the hexadecimal mode by entering the :%!xxd command. The hash was apparent at first glance, as you can see in the following screenshot :

It appears that the hash is preceded with two bytes : 0xff and 0x10. But was this always going to be the case ? I was able to confirm this hypothesis after inspecting a couple of other files in the hexadecimal editor. With this in mind, I concluded that in order to extract the password from the .eslock file, all I had to do was find the last occurrence of 0xff10 then take the following 16 bytes. I wrote the following D utility to automate this procedure :

import std.stdio;
import std.file : read;

void main(string[] args)
{
    ubyte[] data = cast(ubyte[]) args[1].read;
    int idx = data.findHash;
    if(idx == -1 || idx >= data.length)
    {
        writeln("Couldn't find the hash.");
        return;
    }
    foreach(i; idx .. idx + 16)
        writef("%x", data[i]);
}

int findHash(const ref ubyte[] data)
{
    foreach_reverse(idx, chunk; data)
        if(idx - 1 >= 0 && data[idx] == 0x10 && data[idx - 1] == 0xff)
            return idx + 1;
    return -1;
}

And that's it. It's far from being optimal but it does the trick. Keep in mind that I'm using an old (3.2.4.1) version of the ES file explorer app, so I wouldn't be surprised if they changed this behavior in newer versions. Update of June 1st 2017 : I tried it with the 4.1.6.4 version of the software and it still works.

I was able to un-hash the password thanks to this website. I assume it contains a database of clear <> hashed key-value pairs, which would mean that someone other than myself used a password that I thought was unique. What a small world !

As to the file itself, I was surprised to find out that its content was actually...

dramatic pause

This blog post.

A glitch in the matrix perhaps ?

Edit : Further testing has made me realize that the hash is always 29 bytes away from the EOF. With that in mind, the following function will extract the hash without loading the entire file in memory :

import std.digest.md;
import std.stdio;
//...
string getHash(const ref string path)
{
    auto fh = File(path, "rb");
    fh.seek(-29, SEEK_END);
    ubyte[16] hash;
    fh.rawRead(hash);
    return hash.toHexString.idup;
}

Commentaires

  1. I know this is an old post, but I came across it when trying to crack some eslocks file.. I found that FF10 and end - 31 bytes is not always present, it seems that the 10 at end - 30 bytes is the only thing that is consistent between them, in around 300 eslock files, 10 was always present 30 bytes from the end of the file.

    For anyone that runs across this post and can't get the D program to run (I'm not familiar with D, so its probably my own failing), here is a perl script that works as well, it outputs hex md5 hashes suitable to use with john the ripper in raw-md5 mode.

    #!/usr/bin/perl
    use strict;

    my $file = $ARGV[0];
    open my $fh, '<', $file or die("Could not read $file: $!");
    seek $fh, -31, 2;
    read $fh, my $buffer, 18;
    my $header = substr($buffer,0,2);
    my $hexHeader = unpack("H4", $header);
    my $hash = substr($buffer,2,16);
    my $hexHash = unpack("H32", $hash);
    if($hexHeader =~ /ff10/) {
    print $hexHash."\n";
    } elsif ($hexHeader =~ /[0-9a-f][0-9a-f]10/) {
    warn("Header ff10 not found at position end - 31 bytes, found $hexHeader\n");
    print $hexHash."\n";
    } else {
    warn("Header ff10 not found at position end - 31 bytes, possibly invalid, found $hexHeader\n");
    print $hexHash."\n";
    }

    RépondreSupprimer
    Réponses
    1. Thanks for the high quality input ! I've been meaning to update this article with an Android app but I keep procrastinating. I wonder if the presence of the ff10 prefix depends on the version of ES file app it was encrypted with.

      Supprimer

Enregistrer un commentaire

Posts les plus consultés de ce blog

Writing a fast(er) youtube downloader

My experience with Win by Inwi

Porting a Golang and Rust CLI tool to D