In some cases, for debugging or auditing, we need to verify kernel module or kexec kernel binary manually. But kernel doesn't allow keyctl tool to export keys from .builtin_trusted_keys keyring. And we can NOT google out any URL for downloading the module certificate of the kernel. In those cases, we can direct extract module certificate from kernel binary.
This approach is base on openSUSE. But I believe it can be applied on other distros.
Where is the module certificate in kernel binary?
Looking at the source code of kernel, the module certificate be included in certs/system_certificates.S
/* SPDX-License-Identifier: GPL-2.0 */ #includeAs the code, it's in __INITRODATA. section, and the __module_cert_start is in the same address with system_certificate_list. In System.map, we can find the virtual address of system_certificate_list:#include __INITRODATA .align 8 .globl system_certificate_list system_certificate_list: __cert_list_start: __module_cert_start: .incbin "certs/signing_key.x509" __module_cert_end: .incbin "certs/x509_certificate_list" __cert_list_end:
vi /boot/System.map-5.14.21-150400.12-default
ffffffff81000000 T _stext
...
ffffffff831fe000 D early_top_pgt
...
ffffffff8338afc8 d __cert_list_start
ffffffff8338afc8 d __module_cert_start
ffffffff8338afc8 D system_certificate_list
ffffffff8338b4d0 d __cert_list_end
ffffffff8338b4d0 d __module_cert_end
ffffffff8338b4d0 D system_certificate_list_size
ffffffff8338b4d8 D module_cert_size
...
As the above example, the virtual address of system_certificate_list is 0xffffffff8338afc8. The __module_cert_start and __cert_list_start are in the same address. Using objdump tool can also print the address of system_certificate_list:
objdump --all-headers vmlinux-5.14.21-150400.12-default | less
...
ffffffff8338afc8 g .init.data 0000000000000000 system_certificate_list
...
Transfer virtual address to the offset in kernel binary file
OK! Now we got the address of __module_cert_start (which equals to system_certificate_list). But it's a virtual address, not the offset in kernel binary file. If we want to use "dd" tool to extract the certificate in kernel binary, we need the offset. Because __module_cert_start is in __INITRODATA section, we can use readelf tool to get the virtual address and offset of .init.data section:readelf --sections vmlinux-5.14.21-150400.12-default | less There are 73 section headers, starting at offset 0x50b83a0: Section Headers: [Nr] Name Type Address Offset ... [45] .init.data PROGBITS ffffffff831fe000 025fe000 00000000001b7130 0000000000000000 WA 0 0 8192 ...As the above example, the address of .init.data is 0xffffffff831fe000, and the offset in file is 0x25fe000. Then we can calculate the offset of __module_cert_start:
Address offset: __module_cert_start - .init.data = 0xffffffff8338afc8 - 0xffffffff831fe000 = 0x18cfc8 File offset for __module_cert_start: 0x25fe000 + 0x18cfc8 = 0x278afc8 = 41,463,752
The length of module certificate
OK! Now we got the offset of __module_cert_start in kernel binary file. But we still need the length. Just like __module_cert_start, we can find the __module_cert_end in System.map
vi /boot/System.map-5.14.21-150400.12-default ffffffff81000000 T _stext ... ffffffff831fe000 D early_top_pgt ... ffffffff8338afc8 d __cert_list_start ffffffff8338afc8 d __module_cert_start ffffffff8338afc8 D system_certificate_list ffffffff8338b4d0 d __cert_list_end ffffffff8338b4d0 d __module_cert_end ffffffff8338b4d0 D system_certificate_list_size ffffffff8338b4d8 D module_cert_size ...Now we can calculate the length of module certificate:
length = __module_cert_end - __module_cert_start = 0xffffffff8338b4d0 - 0xffffffff8338afc8 = 0x508 = 1,288
Extract module certificate by dd
After we got the start offset in file and length. Then we can use dd to extract blob to a file. The file will be the certificate.Before using dd, we need use vmlinux binary file as the source. If you only have vmlinuz file, you can use the scripts/extract-vmlinux tool in linux kernel source to extract vmlinux from vmlinuz:
./scripts/extract-vmlinux ~/vmlinuz-5.14.21-150400.12-default > ~/vmlinux-5.14.21-150400.12-defaultThen running dd to extract module certificate. Remember to use decimal:
dd bs=1 skip=41463752 count=1288 if=./vmlinux-5.14.21-150400.12-default of=~/signkey.crt 1288+0 records in 1288+0 records out 1288 bytes (1.3 kB, 1.3 KiB) copied, 0.00334262 s, 385 kB/sAs the above dd command, we can extract certificate to a signkey.crt file. Then we can use openssl to print out the certificate file:
openssl x509 -in ./signkey.crt -inform DER -noout -text | less Certificate: Data: Version: 3 (0x2) Serial Number: ed:87:85:b7:8f:fc:12:7e Signature Algorithm: sha256WithRSAEncryption Issuer: CN = SUSE Linux Enterprise Secure Boot CA, C = DE, L = Nuremberg, O = SUSE Linux Products GmbH, OU = Build Team, emailAddress = build@suse.de Validity Not Before: Mar 8 10:15:08 2021 GMT Not After : Dec 31 10:15:08 2030 GMT Subject: CN = SUSE Linux Enterprise Secure Boot Signkey, C = DE, L = Nuremberg, O = SUSE Linux Products GmbH, OU = Build Team, emailAddress = build@suse.de Subject Public Key Info: Public Key Algorithm: rsaEncryption RSA Public-Key: (2048 bit) Modulus: 00:b6:f0:29:43:8a:a0:c4:38:9b:3a:96:2e:7c:5c: 90:1b:71:c3:b1:0b:f0:8c:f7:eb:40:77:58:aa:47: ...If you can print the certificate by openssl. Then it confirmed that we extracted right blob of module certificate from kernel binary.