web-dev-qa-db-ja.com

カーネルモジュールの署名を確認する方法

カーネルソースをコンパイルするときに、CONFIG_MODULE_SIG*オプションを使用してカーネルモジュールに署名することを選択できます。 modinfoツールはモジュールの署名を検証するタスクを処理する必要がありますが、何年もの間いくつかのバグがあり、ツールはもはや仕事をすることができません。私が得るすべては以下です:

sig_id:         PKCS#7
signer:
sig_key:
sig_hashalgo:   md4
signature:      30:82:02:F4:06:09:2A:86:48:86:F7:0D:01:07:02:A0:82:02:E5:30:
                ...

したがって、キーはなく、ハッシュアルゴリズムはmd4です。これはカーネルでコンパイルされていません。

では、モジュールの署名を手動で確認して検証する方法は?それは可能ですか?

7

はい、それは可能ですが、かなり複雑です。

まず、モジュールの署名を抽出する必要があります-そのために、カーネルソースから extract-module.sig.pl スクリプトを使用できます。

$ scripts/extract-module-sig.pl -s MODULE.ko >/tmp/modsig.
Read 789006 bytes from module file
Found magic number at 789006
Found PKCS#7/CMS encapsulation
Found 670 bytes of signature [3082029a06092a864886f70d010702a0]

次に、証明書と公開鍵をカーネルから抽出する必要があります。 extract-sys-certs.pl スクリプトを使用できます。

$ scripts/extract-sys-certs.pl /PATH/TO/vmlinux /tmp/cert.x509
Have 32 sections
Have 28167 symbols
Have 1346 bytes of certs at VMA 0xffffffff81be6db8
Certificate list in section .init.data
Certificate list at file offset 0xde6db8
$ openssl x509 -pubkey -noout -inform der -in /tmp/cert.x509 -out /tmp/pubkey

Linuxカーネルのビルドディレクトリのcerts/signing_key.x509またはcerts/signing_key.pemファイルから公開鍵を抽出することもできます。

これで、/tmp/modsig/tmp/cert.x509に必要なすべてのデータが揃い、PKCS#7署名を検証するために必要な数十の手順を続行できます。

レシピ全体については、このブログ post をご覧ください。


プロセス全体(extract-certs.plステップを除く)をPerlスクリプトに入れようとしました。

次のように使用できます。

Perl checkmodsig.pl /path/to/cert.x509 mod1.ko mod2.ko ...

[〜#〜] ymmv [〜#〜]。私は、sha512署名を使用してカスタムビルドされたカーネルでのみこれを試しました。もちろん、遅くて壊れやすいopenssl x509asn1parseおよびrsautlの呼び出しをまとめるのではなく、opensslライブラリを直接使用することで、これをはるかに効率的に行うことができます。

checkmodsig.pl

use strict;

sub through {
    my ($cmd, $data, $cb) = @_;
    use IPC::Open2;
    my $pid = open2 my $from, my $to, ref $cmd ? @$cmd : $cmd;
    print $to $data; close $to; my $out;
    if($cb){ while(<$from>){ last if $out = $cb->($_) } }
    else { local $/; $out = <$from>; }
    waitpid ($pid, 0);
    die "status $?" if $? != 0;
    $out;
}
sub gethash {
    my ($d) = @_; my ($alg, $hash);
    through [qw(openssl asn1parse -inform der)], $d, sub {
        if(/(\d+):d=\d+ +hl= *(\d+) +l= *(\d+) +prim: +OCTET STRING/){
            $hash = substr $d, $1 + $2, $3
        }elsif(/prim: +OBJECT +:(sha\w+)/){
            $alg = $1;
        }
        undef
    };
    $alg, $hash
}

use File::Temp;
my $tf = new File::Temp;
my $pub_key;
my @type = qw(PGP X509 PKCS7);
my $r = 0;
if((my $cert = shift) =~ /(\.x509)$|\.pem$/i){
    $pub_key = $tf->filename;
    system qw(openssl x509 -pubkey -noout),
        '-inform', $1 ? 'der' : 'pem',
        '-in', $cert, '-out', $pub_key;
    die "status $?" if $? != 0;
}
die "no certificate/key file" unless $pub_key;
for my $kof (@ARGV){
    open my $ko, '<', $kof or die "open $kof: $!\n";
    seek $ko, -4096, 2 or die "seek: $!";
    read $ko, my $d, 4096 or die "read: $!";
    my ($algo, $hash, $type, $signer_len, $key_id_len, $sig_len, $magic) =
        unpack 'C5x3Na*', substr $d, -40;
    die "no signature in $kof"
        unless $magic eq "~Module signature appended~\n";
    die "this script only knows about PKCS#7 signatures"
        unless $type[$type] eq 'PKCS7';

    my $hash = gethash substr $d, - 40 - $sig_len, $sig_len;
    die "hash not found" unless $hash;

    my ($alg, $vhash) = gethash
        through [qw(openssl rsautl -verify -pubin -inkey), $pub_key],
            $hash;

    seek $ko, 0, 0 or die "seek: $!";
    read $ko, my $d, (-s $ko) - $sig_len - 40 or die "read: $!";
    use Digest::SHA;
    my $fhash = new Digest::SHA($alg)->add($d)->digest;

    if($fhash eq $vhash){
        print "OK $kof\n";
    }else{
        print "**FAIL** $kof\n";
        $r = 1;
        warn 'orig=', unpack('H*', $vhash), "\n";
        warn 'file=', unpack('H*', $fhash), "\n";
    }
}
exit $r;
4
mosvy