Success Case
For testing, I built a mainline kernel that it built with CONFIG_MODULE_SIG_ALL. Then I got many signed kernel modules and a auto-generated key-pair. As last time, I still use acer-wmi.ko as the sample for verification.
The kernel auto-generated key-pair can be found in the certs folder in kernel source:
linux-gsoz:~/linux.nfs # ls certs/signing_key.*
certs/signing_key.pem certs/signing_key.x509
The signing_key.pem is private key, and signing_key.x509 is the certificate that it includes public key.
STEP 1. Extract the PKCS#7 signature in acer-wmi.ko
The PKCS#7 is attached on acer-wmi.ko. The layout of kernel module structure is as following:PKCS#7 (PKEY_ID_PKCS7 = 2): module start +-----------------------+ info->hdr = mod | | | | | | | | | module data | | | | | | | -+-+-----------------------| | | | ms->sig_len---> | | pkcs#7 signature | | | | -+-+-----------------------| | |struct module_signature| | | | ms 12 bytes --> | | u8 id_type; | | | | | | __be32 sig_len | -+-+-----------------------| markerlen ---> | MODULE_SIG_STRING | module end +-----------------------+ info->len
The module_signature code in kernel is:
kernel/module_signing.c /* * Module signature information block. * * The constituents of the signature section are, in order: * * - Signer's name * - Key identifier * - Signature data * - Information block */ struct module_signature { u8 algo; /* Public-key crypto algorithm [0] */ u8 hash; /* Digest algorithm [0] */ u8 id_type; /* Key identifier type [PKEY_ID_PKCS7] */ u8 signer_len; /* Length of signer's name [0] */ u8 key_id_len; /* Length of key identifier [0] */ u8 __pad[3]; /* 3 bytes */ __be32 sig_len; /* Length of signature data *//* 4 bytes */ }; kernel/module_signing.c enum pkey_id_type { PKEY_ID_PGP, /* OpenPGP generated key ID */ PKEY_ID_X509, /* X.509 arbitrary subjectKeyIdentifier */ PKEY_ID_PKCS7, /* Signature in PKCS#7 message */ };
So, first, the MODULE_SIG_STRING is in the end of kernel module. The string is "~Module signature appended~.", we can use the string to locate the 12 bytes module_signature that it's just before the MODULE_SIG_STRING.
linux-gsoz:~/tmp/modsign-acer-wmi-pkcs7 # hexdump -C acer-wmi.ko | less ... 00010940 00 00 00 00 00 00 00 00 30 82 01 e6 06 09 2a 86 |........0.....*.| <=== Signature start: 0x10948 00010950 48 86 f7 0d 01 07 02 a0 82 01 d7 30 82 01 d3 02 |H..........0....| ... 00010940 00 00 00 00 00 00 00 00 30 82 01 e6 06 09 2a 86 |........0.....*.| 00010950 48 86 f7 0d 01 07 02 a0 82 01 d7 30 82 01 d3 02 |H..........0....| 00010960 01 01 31 0d 30 0b 06 09 60 86 48 01 65 03 04 02 |..1.0...`.H.e...| ... 00010b20 ce 2d df 65 23 81 12 bd b4 80 27 4e e4 58 23 72 |.-.e#.....'N.X#r| 00010b30 df d9 00 00 02 00 00 00 00 00 00 00 01 ea 7e 4d |..............~M| <=== Signature end: 0x10b31
00010b40 6f 64 75 6c 65 20 73 69 67 6e 61 74 75 72 65 20 |odule signature | 00010b50 61 70 70 65 6e 64 65 64 7e 0a |appended~.| 00010b5a
The above blue numbers are module_signature. The id_type is 02 which means PKCS#7 format. And the sig_len is 0x1ea, it means that the length of signature is 490 bytes. The start - end is 0x10b31 - 0x10948+ 1 = 0x1ea. It matches with the sig_len number.
After finding out the location of signature and the length, then we can extract the signature from ko file. Using dd:
dd bs=1 skip=67912 count=490 if=./acer-wmi.ko of=./acer-wmi.ko.pkcs7
The 67912 is the start location of encrypted digest 0x10948, and the 490 is the 0x1ea length. We can also truncate the ko file to remove the signature for getting a unsigned ko file:
dd bs=1 count=67912 if=./acer-wmi.ko of=./acer-wmi-unsigned.ko
The later we can use the unsigned ko file to compare the hash result with the decrypted digest.
STEP 2. Extract the digest from encrypted digest in PKCS#7
OK, we just extract the PKCS#7 signature from ko file. The signature is a digest that it is encrypted by private key. We can extract the encrypted digest from PKCS#7 blog then using public key to decrypt it. Then we can get a hash number of acer-wmi.ko file.The PKCS#7 signature is encoded to ASN1 format. So we can use openssl's ASN1 parser to decode the signature:
linux-gsoz:~/tmp/modsign-acer-wmi-pkcs7 # openssl asn1parse -inform der -in acer-wmi.ko.pkcs7 0:d=0 hl=4 l= 486 cons: SEQUENCE 4:d=1 hl=2 l= 9 prim: OBJECT :pkcs7-signedData 15:d=1 hl=4 l= 471 cons: cont [ 0 ] 19:d=2 hl=4 l= 467 cons: SEQUENCE 23:d=3 hl=2 l= 1 prim: INTEGER :01 26:d=3 hl=2 l= 13 cons: SET 28:d=4 hl=2 l= 11 cons: SEQUENCE 30:d=5 hl=2 l= 9 prim: OBJECT :sha256 41:d=3 hl=2 l= 11 cons: SEQUENCE 43:d=4 hl=2 l= 9 prim: OBJECT :pkcs7-data 54:d=3 hl=4 l= 432 cons: SET 58:d=4 hl=4 l= 428 cons: SEQUENCE 62:d=5 hl=2 l= 1 prim: INTEGER :01 65:d=5 hl=3 l= 134 cons: SEQUENCE 68:d=6 hl=2 l= 121 cons: SEQUENCE 70:d=7 hl=2 l= 22 cons: SET 72:d=8 hl=2 l= 20 cons: SEQUENCE 74:d=9 hl=2 l= 3 prim: OBJECT :organizationName 79:d=9 hl=2 l= 13 prim: UTF8STRING :openSUSE Test 94:d=7 hl=2 l= 65 cons: SET 96:d=8 hl=2 l= 63 cons: SEQUENCE 98:d=9 hl=2 l= 3 prim: OBJECT :commonName 103:d=9 hl=2 l= 56 prim: UTF8STRING :Build time autogenerated kernel key for openSUSE testing 161:d=7 hl=2 l= 28 cons: SET 163:d=8 hl=2 l= 26 cons: SEQUENCE 165:d=9 hl=2 l= 9 prim: OBJECT :emailAddress 176:d=9 hl=2 l= 13 prim: IA5STRING :user@suse.com 191:d=6 hl=2 l= 9 prim: INTEGER :9B47FAF791E7D1E3 202:d=5 hl=2 l= 11 cons: SEQUENCE 204:d=6 hl=2 l= 9 prim: OBJECT :sha256 215:d=5 hl=2 l= 13 cons: SEQUENCE 217:d=6 hl=2 l= 9 prim: OBJECT :rsaEncryption 228:d=6 hl=2 l= 0 prim: NULL 230:d=5 hl=4 l= 256 prim: OCTET STRING [HEX DUMP]:0C3B4D84F348F7DA533FCA7836E459A696BFF5695A049934EC0B6BC4A3EB9FC3E3D392A65D61EA0AD430DF4CF41D87F75F20F11B16D793E07A7B259F25CAB280E030D3F69710D4ADA2A080F8E0186C77C2927556FEB05F070F430829C7BA5103C0E332D9E7F4AB4C3F3C7E0C6B010FF3CFCAB6716B869FD436A8018BD107269521D5369E94C4BEA02186228E781CD9D71C2DBDA7F3A4D148260BB6AF8E704BA9A223401B7B43832CE0068768A28EB0F8E69FDDA1479BC4FAB1CF12D0DEDB05FAA6B0718B91F6B42148230059E6BEC6350455A4692C9EA63B9E23C04B17E5EAC49FFDA08BDE8D108CD998E244FCF9CE2DDF65238112BDB480274EE4582372DFD9
It shows the PKCS#7 structure. It includes hash algorithm for digest, sign key, encryption algorithm, and the encrypted digest in the end of signature. In the above case, the encrypted digest starts from 230 + 4 bytes, and its length is 256 bytes. So we can use dd to extract the encrypted digest to a file:
dd if=acer-wmi.ko.pkcs7 of=acer-wmi.ko.encrypted_digest bs=1 skip=$[ 230 + 4 ] count=256
linux-gsoz:~/tmp/modsign-acer-wmi-pkcs7 # hexdump -C acer-wmi.ko.encrypted_digest 00000000 0c 3b 4d 84 f3 48 f7 da 53 3f ca 78 36 e4 59 a6 |.;M..H..S?.x6.Y.| 00000010 96 bf f5 69 5a 04 99 34 ec 0b 6b c4 a3 eb 9f c3 |...iZ..4..k.....| 00000020 e3 d3 92 a6 5d 61 ea 0a d4 30 df 4c f4 1d 87 f7 |....]a...0.L....| 00000030 5f 20 f1 1b 16 d7 93 e0 7a 7b 25 9f 25 ca b2 80 |_ ......z{%.%...| 00000040 e0 30 d3 f6 97 10 d4 ad a2 a0 80 f8 e0 18 6c 77 |.0............lw| 00000050 c2 92 75 56 fe b0 5f 07 0f 43 08 29 c7 ba 51 03 |..uV.._..C.)..Q.| 00000060 c0 e3 32 d9 e7 f4 ab 4c 3f 3c 7e 0c 6b 01 0f f3 |..2....L?<~.k...| 00000070 cf ca b6 71 6b 86 9f d4 36 a8 01 8b d1 07 26 95 |...qk...6.....&.| 00000080 21 d5 36 9e 94 c4 be a0 21 86 22 8e 78 1c d9 d7 |!.6.....!.".x...| 00000090 1c 2d bd a7 f3 a4 d1 48 26 0b b6 af 8e 70 4b a9 |.-.....H&....pK.| 000000a0 a2 23 40 1b 7b 43 83 2c e0 06 87 68 a2 8e b0 f8 |.#@.{C.,...h....| 000000b0 e6 9f dd a1 47 9b c4 fa b1 cf 12 d0 de db 05 fa |....G...........| 000000c0 a6 b0 71 8b 91 f6 b4 21 48 23 00 59 e6 be c6 35 |..q....!H#.Y...5| 000000d0 04 55 a4 69 2c 9e a6 3b 9e 23 c0 4b 17 e5 ea c4 |.U.i,..;.#.K....| 000000e0 9f fd a0 8b de 8d 10 8c d9 98 e2 44 fc f9 ce 2d |...........D...-| 000000f0 df 65 23 81 12 bd b4 80 27 4e e4 58 23 72 df d9 |.e#.....'N.X#r..| 00000100
STEP 3. Using public key to decrypt digest
Now we have a acer-wmi.ko.encrypted_digest, then we can use public key to decrypt the blob. Finally we will get the hash of acer-wmi.ko.First, we need to extract the public key from X.509 certificate:
openssl x509 -inform der -in signing_key.x509 -noout -pubkey > signing_key_public.pem
linux-gsoz:~/tmp/modsign-acer-wmi-pkcs7 # hexdump -C signing_key_public.pem 00000000 2d 2d 2d 2d 2d 42 45 47 49 4e 20 50 55 42 4c 49 |-----BEGIN PUBLI| 00000010 43 20 4b 45 59 2d 2d 2d 2d 2d 0a 4d 49 49 42 49 |C KEY-----.MIIBI| 00000020 6a 41 4e 42 67 6b 71 68 6b 69 47 39 77 30 42 41 |jANBgkqhkiG9w0BA| 00000030 51 45 46 41 41 4f 43 41 51 38 41 4d 49 49 42 43 |QEFAAOCAQ8AMIIBC| 00000040 67 4b 43 41 51 45 41 79 6b 79 78 65 4c 51 63 44 |gKCAQEAykyxeLQcD| 00000050 42 77 6f 65 7a 75 50 44 76 32 4b 0a 56 49 59 6c |BwoezuPDv2K.VIYl| 00000060 4d 49 62 62 6c 38 42 45 44 49 35 54 5a 38 7a 66 |MIbbl8BEDI5TZ8zf| 00000070 39 32 41 6b 52 4b 71 59 57 6a 41 52 68 4f 44 39 |92AkRKqYWjARhOD9| 00000080 51 4c 45 59 2f 79 47 79 6c 2b 36 6a 59 4a 68 65 |QLEY/yGyl+6jYJhe| 00000090 47 4a 74 2b 43 68 6a 66 4b 32 67 36 0a 64 52 51 |GJt+ChjfK2g6.dRQ| 000000a0 52 35 33 6f 6c 2b 6e 39 4c 52 49 38 44 61 64 61 |R53ol+n9LRI8Dada| 000000b0 70 78 4a 31 34 4e 53 59 45 4d 67 72 46 30 39 69 |pxJ14NSYEMgrF09i| 000000c0 72 59 33 42 55 78 72 54 39 51 39 75 67 55 50 62 |rY3BUxrT9Q9ugUPb| 000000d0 6b 32 33 67 50 72 51 43 54 35 74 54 7a 0a 36 43 |k23gPrQCT5tTz.6C| 000000e0 4e 6f 50 2f 2f 33 6b 4b 67 30 6c 45 31 51 71 66 |NoP//3kKg0lE1Qqf| 000000f0 52 44 58 35 34 70 53 49 49 51 6c 5a 45 47 6b 34 |RDX54pSIIQlZEGk4| 00000100 2f 4e 51 4d 4f 67 36 6e 43 5a 4e 44 58 57 48 53 |/NQMOg6nCZNDXWHS| 00000110 4d 37 61 69 4e 79 4f 44 57 63 64 53 6a 61 0a 6e |M7aiNyODWcdSja.n| 00000120 39 59 69 64 76 33 59 74 36 2f 35 47 39 33 51 30 |9Yidv3Yt6/5G93Q0| 00000130 74 59 52 2f 68 44 5a 35 6b 49 32 4b 4e 39 53 4b |tYR/hDZ5kI2KN9SK| 00000140 69 34 53 6f 75 39 55 36 67 35 6a 33 2b 49 61 66 |i4Sou9U6g5j3+Iaf| 00000150 38 62 6b 71 4d 57 61 72 2f 4d 62 73 34 52 46 0a |8bkqMWar/Mbs4RF.| 00000160 44 33 70 71 34 62 59 46 66 74 63 32 6e 31 71 31 |D3pq4bYFftc2n1q1| 00000170 68 6a 55 50 33 47 49 49 4b 63 53 64 54 78 59 49 |hjUP3GIIKcSdTxYI| 00000180 2b 44 55 34 75 78 76 39 2b 6c 77 4c 49 42 30 43 |+DU4uxv9+lwLIB0C| 00000190 69 59 54 50 6b 65 79 72 2f 35 73 54 2f 70 61 63 |iYTPkeyr/5sT/pac| 000001a0 0a 54 77 49 44 41 51 41 42 0a 2d 2d 2d 2d 2d 45 |.TwIDAQAB.-----E| 000001b0 4e 44 20 50 55 42 4c 49 43 20 4b 45 59 2d 2d 2d |ND PUBLIC KEY---| 000001c0 2d 2d 0a |--.| 000001c3
Then using openssl to decrypt the digest by public key:
openssl rsautl -verify -pubin -inkey signing_key_public.pem < acer-wmi.ko.encrypted_digest > acer-wmi.sha256
If the signature is invalid or broken, then the above decrypt process can not success. Finally we got a blob. This is also a ASN.1 encoded blob:
linux-gsoz:~/tmp/modsign-acer-wmi-pkcs7 # openssl asn1parse -inform der -in acer-wmi.sha256
0:d=0 hl=2 l= 49 cons: SEQUENCE
2:d=1 hl=2 l= 13 cons: SEQUENCE
4:d=2 hl=2 l= 9 prim: OBJECT :sha256
15:d=2 hl=2 l= 0 prim: NULL
17:d=1 hl=2 l= 32 prim: OCTET STRING [HEX DUMP]:EBDA845DD7B1FE77EEF2B1AC06617C7C74075F919D64DCB3B496B0042F70A83F
This hash should equals to the sha256 resume of unsigned acer-wmi.ko file:
linux-gsoz:~/tmp/modsign-acer-wmi-pkcs7 # sha256sum acer-wmi-unsigned.ko
ebda845dd7b1fe77eef2b1ac06617c7c74075f919d64dcb3b496b0042f70a83f acer-wmi-unsigned.ko
OK, then we confirmed that the PKCS#7 signature is valid.