How to enter/answer a terminal prompt from a Perl script?

559 Views Asked by At

I'm trying to crack a forgotten password for a luks partition. I generated a list of combinations, and now I'm trying to decrypt the volume from a Perl script.

The problem is to enter the prompt from the script itself, since: system('cryptsetup', ('open', '/dev/sdd1', 'crypt-vol', '--type=luks')) just spits Enter passphrase for /dev/sdd1 and waits for me to enter it manually.

How can I approach this?

Many thankyous for any help.

* it's my volume and I haven't forgotten the password completely, so I created the list of combinations provided that I remember some details. It's like >6k of possibilities, so it should be feasible to break it.

2

There are 2 best solutions below

0
On BEST ANSWER

Don't, use a 'keyfile' with cryptsetup. A key file can be STDIN.

So:

echo "passphrase_here" | cryptsetup -d - <other  options>

In perl you could use IPC::Run2 for this, which allows you read/write to the FH, but if you just need a return code to test the passphrase, that's not needed.

E.g. https://blog.sleeplessbeastie.eu/2019/03/27/how-to-test-luks-passphrase/

So:

open ( my $crypter, '|-', "cryptsetup luksOpen -d - --test-passphrase " )

print {$crypter} "Your passphrase";

close ( $crypter );
print "Got return code of $?"
0
On

I actually liked the idea to use STDIN, so if anyone ever lights upon this page, consider if you can use STDIN in your situation.

But I'd like to post my own answer, which uses the Expect Perl module, because:

  • I used it and it's proven to work at least on Linux (though the question in the comments mentions that the module had problems to run on Windows);
  • since the question was titled without any mention of cryptsetup (which can accept the password on STDIN), so the Expect module is like a general solution here, which should work for other tools;

My solution is below. I didn't read the documentation much, so there may be better ways to do this. For me it was enough to grasp the idea of the object instantiation, and the expect/send methods.

You create an instance as if you just started a program in the terminal:

my $exp = Expect->new('progName', ($arg1, $arg2, $etc));

Then it's possible to interact with it through the expect (which waits/confirms the program's output to the user) and send (which allows the user to 'type') methods.

expect may be called in either scalar or list context (I used the list one) and it accepts a string or regexp of what output to expect and for how long. If no expected output occurred within the specified time it throws an error:

                       #sec to wait for  #use regexp (don't match exactly) #regexp to match against
my @out = $exp->expect(10,               '-re',                            'smth');

send just accepts the input

$exp->send('some chars');

That's it, one just need to create a script, which would work as if it was a human user.


And just in case it might be handy to someone, I'll post my complete particular solution for cryptsetup (I've tested it on a dummy volume, which it was able to mount, and I've run it on a real volume with 6k combinations tried without any noticeable problems):

*I forgot to close the file descriptor here, so one should add close($fd) where appropriate

use strict;
use warnings;
use feature 'say';


# I installed Expect with its deps locally in my situation, so I had to change @INC
BEGIN {
  unshift(@INC, './perl-mods/lib/perl5/');
  unshift(@INC, './perl-mods/lib/perl5/x86_64-linux-thread-multi/');
}

use Expect;

my $devName = '/dev/sdb3';
my $combinationsFileName = 'combs.txt';
open(my $fd, '<', $combinationsFileName);
my $lineCount = 0;

while (<$fd>) {
  my $pass = $_;
  chomp($_);
  say $_ . ' ' . ++$lineCount;

  my $exp = Expect->new('cryptsetup', ('open', $devName, 'crypt-vol')) or die 'err 1';
  my @out = $exp->expect(10, '-re', 'Enter passphrase') or die 'err 2';
  $exp->send($pass);
  @out = $exp->expect(10, '-re', 'No key available') or die 'err 3';

  #if cryptsetup returned an error code
  if ($out[1]) {
    die $out[1] . ' ' . $pass;
  }
}