SecureBoot

 druide

SecureBoot


SecureBoot est une technologie mise en place sur Ubuntu (entre autre) visant à empêcher l'installation de programmes malveillants. Pour ce faire, lors du démarrage de la machine, le système effectue plusieurs vérification visant à garantir l'intégrité du programme qu'il va lancer. Les programmes concernés sont :

  1. /boot/efi/EFI/ubuntu/shimx64.efi, binaire signé par Microsoft, nécessaire au lancement de grubx64.efi si le SecureBoot est activé
  2. /boot/efi/EFI/ubuntu/grubx64.efi, ... ben c'est grub..., mais signé par Canonical
  3. /boot/vmlinuz-3.13.0-XX-generic, le noyau Linux signé par Canonical




grubx64.efi

Cet exécutable est de type PE32+, tel qu'on les trouvent sur Windows.


druide@bidouille:~$ file /boot/efi/EFI/ubuntu/grubx64.efi 
/boot/efi/EFI/ubuntu/grubx64.efi: PE32+ executable (EFI application) x86-64 (stripped to external PDB), for MS Windows


Ça signifie qu'il possède des entêtes de fichier de type MSDOS, PE/COFF. Il sera nécessaire de les comprendre pour pouvoir comprendre et générer la signature des binaires.

Principe

Comme dit plus haut, on souhaite garantir qu'on utilise exactement (au bit près) le même exécutable que celui crée par le(s) codeur(s) d'origine. Comment réaliser cette tâche? Et bien en appliquant le même principe que l'empreinte digitale pour les humains. En effet, si on possède l'empreinte digitale d'une personnes, on peut affirmer avec certitude qu'il s'agit de telle ou telle personne. L'empreinte digitale appartient à une seule personne dans le monde.

L'empreinte « digitale » numérique d'un fichier se réalise avec une fonction de hachage de type sha256. Pour réaliser l'empreinte numérique d'un fichier PE32+ (Authenticode), il faut suivre les étapes décrites par Microsoft dans ce document Authenticode PE.docx



En résumé: on crée un sha256 du fichier binaire en omettant les champs Checksum, Certificate Table et Attribute Certificate Table.


Cette empreinte identifie de manière unique le binaire tel qu'il a été crée à l'origine. Elle est incluse dans le-dit binaire.

Si on reçoit un binaire grubx64.efi signé, on peut générer un sha256 en respectant les indications de Microsoft et le comparer à celui contenu dans le binaire. Si les deux sont identiques, le binaire est tel qu'à « sa sortie d'usine ». Ça c'est la base mais bien évidement, ça n'est pas suffisant. En effet, si je crée un virus du nom de grubx64.efi et que je place dans le binaire le sha256 que j'aurai également crée, un utilisateur qui télécharge mon binaire et qui réalise cette vérification ne pourra pas se rendre compte qu'il n'a pas affaire au grubx64.efi « d'usine ». Il pourra seulement valider que les deux sha256 sont identiques et donc que le binaire qu'il possède correspond bien au virus crée à l'origine !!! Pour garantir qu'on a bien affaire au binaire grubx64.efi d'origine, il faut encore protéger le sha256 ou, en d'autre terme, garantir que le sha256 est le même que celui crée « à l'usine ». C'est là qu'interviennent les certificats.

Certificats et signature

Il y a 2 certificats impliqués dans la signature de grubx64.efi chez Ubuntu ainsi qu'une structure opaque pkcs#7 résultant de la signature du binaire (protection du sha256). Le certificat racine de Canonical



Le certificat de Canonical pour le signature du binaire



La structure opaque pkcs#7



Pour la vérification des certificats, on peut lire mon post précédent. En résumé, avec la clé publique contenue dans le certificat racine de Canonical on peut déchiffrer la signature contenue dans le certificat de signature de Canonical. On trouve alors le sha256 du certificat de signature. On peut générer à notre tour le sha256 du certificat de signature et donc valider qu'il s'agit bien du certificat que Canonical à signer car eux seul on pu, avec leur clé privé, créer cette signature. Pour la structure opaque pkcs#7 c'est le même principe. On trouve à la fin de cette structure, une signature. C'est l'objet rsaEncryption. On pourra extraire cette signature et la déchiffrer avec la clé publique du certificat de signature de Canonical. Mais avant toute chose, il faut déjà pouvoir extraire la structure pkcs#7. Pour réaliser cette tâche on peut faire son propre binaire (dans ce cas il faut au préalable comprendre les entêtes du fichier PE32+) ou utiliser les outils sbsigntool. Dans le premier cas, il faut commencer par se rendre à l'offset 0x3c de grubx64.efi. Là on trouve l'adresse où se trouve l'entête PE qui débute par le « nombre magique » PE\0\0 soit en hexadécimale 50 45 00 00.

druide@bidouille:~$ hd grubx64.efi  | more
00000000  4d 5a 90 00 03 00 00 00  04 00 00 00 ff ff 00 00  |MZ..............|
00000010  b8 00 00 00 00 00 00 00  40 00 00 00 00 00 00 00  |........@.......|
00000020  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
00000030  00 00 00 00 00 00 00 00  00 00 00 00 80 00 00 00  |................|  ## 
De là, on trouve toutes les entêtes « standards » d'un exécutable PE32+. Celle qui nous intéresse, c'est l'entrée DataDirectory et plus précisément l'entrée 4 de cette table. En effet, l'entrée 4 correspond à l'entrée Certificate Table. Chaque entrée est de la forme

typedef struct _IMAGE_DATA_DIRECTORY {
    DWORD   VirtualAddress;
    DWORD   Size;
} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;
Si on se rend à l'adresse VirtualAddress on trouve la structure opaque pkcs#7. Reste plus qu'à la rendre lisible et la placer dans un fichier. Ci-dessous, une partie du code permettant cette tâche:

...
WINCERTIFICATE *certs = NULL;
int certs_size = 0;
const uint8_t *tmp_buf;
PKCS7 *p7;
FILE *fout = NULL;
...
certs = ptr + headers->pe32.DataDirectory[4].VirtualAddress;
certs_size = headers->pe32.DataDirectory[4].Size;

tmp_buf = &certs->bCertificate[0];
p7 = d2i_PKCS7(NULL, &tmp_buf, certs->dwLength);
fout = fopen("./p7", "w");
i2d_PKCS7_fp(fout, p7);
fclose(fout);
...


Dans le second cas, il suffit de demander à l'outil d'extraire (--detach) la structure. C'est clairement plus simple mais on apprend moins :)


druide@bidouille:~$ sudo apt-get install sbsigntool
sbattach --detach ~/p7 /boot/efi/EFI/ubuntu/grubx64.efi
Maintenant qu'on à la structure pcks#7 dans le fichier p7, on peut extraire la signature qui se trouve à l'offset 1643

druide@bidouille:~$ openssl asn1parse -inform der -in p7 -i
    0:d=0  hl=4 l=1899 cons: SEQUENCE          
    4:d=1  hl=2 l=   9 prim:  OBJECT            :pkcs7-signedData
   15:d=1  hl=4 l=1884 cons:  cont [ 0 ]        
   19:d=2  hl=4 l=1880 cons:   SEQUENCE          
   23:d=3  hl=2 l=   1 prim:    INTEGER           :01
   26:d=3  hl=2 l=  15 cons:    SET               
   28:d=4  hl=2 l=  13 cons:     SEQUENCE          
   30:d=5  hl=2 l=   9 prim:      OBJECT            :sha256

  ....

 1630:d=6  hl=2 l=   9 prim:       OBJECT            :rsaEncryption
 1641:d=6  hl=2 l=   0 prim:       NULL              
 1643:d=5  hl=4 l= 256 prim:      OCTET STRING      [HEX DUMP]:C2F5E07E7039311AD1C5D07BF4D4AA42E2FBB3FF75B1109A8EE0DE8E71DEDF74855701D67
B49BDAAB4DD402D18EA883B2B994C807A172AAE736063E12C2DA88213939A04809AC0401E1B8EF0B1478B023027A841E5B66C751A7787828205E0789CCC6B7422AF80E19
740F1B9791A28D40A3E6AEE8B71F958332442D451E4219EBF15551657E362F04939FC0DCBDEB3F1FBCB2002619E6B64AD6B1C79C9CF458E5F6548FD03C33AECCFEDFFD21
9386059C020BD7F1FD018A42027A88940E9BBFF576301C0BBB2DD049AFCB22FC80D8149055AFE53B5F58FE93760D532AF593FF4C08003BC82CF875F60B1D75B057553E91
1AF4A64BBF8B66C07B563B13DC578D9
druide@bidouille:~$ openssl asn1parse -inform der -in p7 -out signature -noout -strparse 1643
La signature est maintenant dans le fichier du même nom. Déchiffrons là avec la clé publique du certificat Caonical Ltd. Secure Boot Signing

druide@bidouille:~$ openssl x509 -inform der -in canonical-signing-public.der -pubkey -noout > pubkey
druide@bidouille:~$ openssl rsautl -verify -in signature -inkey pubkey -pubin -asn1parse
    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      
      0000 - 50 89 7a 92 04 ec ea 3a-45 a4 d3 2a 31 77 13 0a   P.z....:E..*1w..
      0010 - 5d b8 84 6c 3d 6d 88 7c-ec 46 8a eb da dc 06 fe   ]..l=m.|.F......


Bien, ça marche.

Mais on regarde quoi là ?

  • On regarde le contenu de la signature de la structure pkcs#7 déchiffré avec la clé publique de Caonical Ltd. Secure Boot Signing

Et qu'est-ce qu'on y voit ?

  • Un sha256

Et c'est le sha256 de quoi ?

  • D'une partie de la structure opaque pkcs#7 et plus précisément, de la partie contenant le messageDigest

authenticatedAttributes

Dans la structure opaque pkcs#7, on trouve une partie nommée The Authenticode timestamp SignerInfo structure qui contient les éléments (authenticatedAttributes) suivants:
  • ContentType (1.2.840.113549.1.9.3) is set to PKCS #7 Data (1.2.840.113549.1.7.1)
  • Signing Time (1.2.840.113549.1.9.5) is set to the UTC time of timestamp generation time
  • Message Digest (1.2.840.113549.1.9.4) is set to the hash value of the SignerInfo structure's encryptedDigest value. The hash algorithm that is used to calculate the hash value is the same as that specified in the SignerInfo structure’s digestAlgorithm value of the timestamp.
source: Authenticode PE On reviendra plus tard sur ce contenu. Pour l'instant, prenons les authenticatedAttributes et essayons d'en faire un sha256 pour voir s'il correspond à celui contenu dans la signature:

## Recherche des authenticatedAttributes dans la structure
druide@bidouille:~$ openssl asn1parse -inform der -in p7 -i -dump
 ...
 1396:d=5  hl=3 l= 229 cons:      cont [ 0 ]        
 1399:d=6  hl=2 l=  25 cons:       SEQUENCE          
 1401:d=7  hl=2 l=   9 prim:        OBJECT            :contentType
 1412:d=7  hl=2 l=  12 cons:        SET               
 1414:d=8  hl=2 l=  10 prim:         OBJECT            :1.3.6.1.4.1.311.2.1.4
 1426:d=6  hl=2 l=  28 cons:       SEQUENCE          
 1428:d=7  hl=2 l=   9 prim:        OBJECT            :signingTime
 1439:d=7  hl=2 l=  15 cons:        SET               
 1441:d=8  hl=2 l=  13 prim:         UTCTIME           :140515201813Z
 1456:d=6  hl=2 l=  47 cons:       SEQUENCE          
 1458:d=7  hl=2 l=   9 prim:        OBJECT            :messageDigest
 1469:d=7  hl=2 l=  34 cons:        SET               
 1471:d=8  hl=2 l=  32 prim:         OCTET STRING      
      0000 - 91 fb 3e 8a 7e c6 ee 85-2a db c3 d8 90 79 10 ca   ..>.~...*....y..
      0010 - 3c 30 d1 bc 3d 32 d2 b7-aa 93 07 7f a2 b4 86 67   |
00000050  8a 7e c6 ee 85 2a db c3  d8 90 79 10 ca 3c 30 d1  |.~...*....y..
Bingo! C'est le même hash. On peut donc dire que le sha256 qu'on vient de crée avec les authenticatedAttributes et le même que celui que Canonical à crée. Donc, la partie authenticatedAttributes est exactement la même, au bit près que celle que Canonical avait « entre les mains ». Le fait que le sha256 de Canonical se trouve dans un message chiffré et déchiffrable avec leur clé publique, prouve que c'est bien Canonical qui possède la clé privé qui a généré ce sha256.

messageDigest

Tout ça c'est bien beau mais qu'est qui nous prouve que le binaire grubx64.efi est bien le même que celui d'origine. On a pas encore fait le lien entre le sha256 du binaire et Canonical. C'est le travail du messageDigest. Si on reprend le contenu des authenticatedAttributes:

druide@bidouille:~$ openssl asn1parse -inform der -in p7 -i -dump
 1396:d=5  hl=3 l= 229 cons:      cont [ 0 ]        
 1399:d=6  hl=2 l=  25 cons:       SEQUENCE          
 1401:d=7  hl=2 l=   9 prim:        OBJECT            :contentType
 1412:d=7  hl=2 l=  12 cons:        SET               
 1414:d=8  hl=2 l=  10 prim:         OBJECT            :1.3.6.1.4.1.311.2.1.4
 1426:d=6  hl=2 l=  28 cons:       SEQUENCE          
 1428:d=7  hl=2 l=   9 prim:        OBJECT            :signingTime
 1439:d=7  hl=2 l=  15 cons:        SET               
 1441:d=8  hl=2 l=  13 prim:         UTCTIME           :140515201813Z
 1456:d=6  hl=2 l=  47 cons:       SEQUENCE          
 1458:d=7  hl=2 l=   9 prim:        OBJECT            :messageDigest
 1469:d=7  hl=2 l=  34 cons:        SET               
 1471:d=8  hl=2 l=  32 prim:         OCTET STRING      
      0000 - 91 fb 3e 8a 7e c6 ee 85-2a db c3 d8 90 79 10 ca   ..>.~...*....y..
      0010 - 3c 30 d1 bc 3d 32 d2 b7-aa 93 07 7f a2 b4 86 67   
On peut voir qu'ils contiennent entre autre l'objet OID 1.3.6.1.4.1.311.2.1.4. Selon Microsoft, il s'agit de l'OID: SPC_INDIRECT_DATA_OBJID. En d'autre terme, c'est spcIndirectDataContext. On peut trouvé cet objet au début de la structure opaque pkcs#7 dans la partie signed content:

druide@bidouille:~$ openssl asn1parse -inform der -in p7 -i -dump
    0:d=0  hl=4 l=1899 cons: SEQUENCE          
    4:d=1  hl=2 l=   9 prim:  OBJECT            :pkcs7-signedData
   15:d=1  hl=4 l=1884 cons:  cont [ 0 ]        
   19:d=2  hl=4 l=1880 cons:   SEQUENCE          
   23:d=3  hl=2 l=   1 prim:    INTEGER           :01
   26:d=3  hl=2 l=  15 cons:    SET               
   28:d=4  hl=2 l=  13 cons:     SEQUENCE          
   30:d=5  hl=2 l=   9 prim:      OBJECT            :sha256
   41:d=5  hl=2 l=   0 prim:      NULL              
   43:d=3  hl=2 l= 120 cons:    SEQUENCE          
   45:d=4  hl=2 l=  10 prim:     OBJECT            :1.3.6.1.4.1.311.2.1.4
   57:d=4  hl=2 l= 106 cons:     cont [ 0 ]        
   59:d=5  hl=2 l= 104 cons:      SEQUENCE          
   61:d=6  hl=2 l=  51 cons:       SEQUENCE  
...
Dans cet objet on trouve le sha256 correspondant au binaire grubx64.efi. On peut donc l'extraire et en générer un nouveau sha256. Ce nouveau sha256 devra correspondre à celui présent dans les authenticatedAttributes. Il y a une petite particularité. Lorsqu'on extrait le contenu signed content pour en générer un sha256, il faut au préalable retirer les 4 octets du début correspondant à la SEQUENCE et à la longueur LENGTH:

druide@bidouille:~$ openssl asn1parse -inform der -in p7 -out tbs -noout -strparse 57
## Il faut enlever les 4 octets du début
druide@bidouille:~$ hd tbs
00000000  a0 6a 30 68 30 33 06 0a  2b 06 01 04 01 82 37 02  |.j0h03..+.....7.|
00000010  01 0f 30 25 03 01 00 a0  20 a2 1e 80 1c 00 3c 00  |..0%.... .....010...`|
00000040  86 48 01 65 03 04 02 01  05 00 04 20 95 43 13 25  |.H.e....... .C.%|
00000050  1a fd 08 74 21 da f4 33  30 0a 52 4d 4f 77 df a2  |...t!..30.RMOw..|
00000060  18 1a b9 62 35 3f 4b 17  c2 8e 5c 17              |...b5?K...\.|
0000006c
## On cherche la longueur
druide@bidouille:~$ ls -l tbs
-rw-rw-r-- 1 piferrari piferrari 108 jan 24 00:14 tbs
## 108 - 4 = 104 octets 
druide@bidouille:~$ tail -c 104 tbs > hashDUhash
## Génération du hash
druide@bidouille:~$ sha256sum hashDUhash
91fb3e8a7ec6ee852adbc3d8907910ca3c30d1bc3d32d2b7aa93077fa2b48667  hashDUhash
Et voilà ! Ce sha256 est le même que celui présent dans messageDigest.

Le sha256 du binaire

La dernière étape c'est la génération du sha256 du binaire selon Microsoft. Comme expliqué plus haut dans ce document, Microsoft génère le sha256 en omettant quelques champs des entêtes du fichier PE32+. On ne peut donc pas simplement faire un sha256 sur le binaire.

Les outils à disposition ne font généralement pas que le sha256. Il signe directement le binaire. Le sha256 fait alors partie du processus de signature mais il n'est pas visible. Pour réussir à obtenir le sha256 on peut créer un programme qui fait ce travail. L'explication de le génération du sha256 se trouve dans le document de Microsoft. Pour ma part et pour faire simple, j'ai téléchargé les sources des outils sbsigntools et j'ai crée un programme utilisant les librairies et les interfaces contenues dans les sources :)


#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <string.h> 

#include "image.h"
#include "fileio.h"

int main(int argc, char **argv)
{
	struct image *image;
	uint8_t sig_buf[32];
	size_t sig_size;
	int i=0;
	
	image = image_load("/boot/efi/EFI/ubuntu/grubx64.efi");
	if (!image) {
		fprintf(stderr, "Can't open image\n");
		return EXIT_FAILURE;
	}
	
	image_hash_sha256(image, sig_buf);
	for(i=0;i<32;i++)
	{
		fprintf(stdout, "%02x", sig_buf[i]);
	}
	fprintf(stdout, "\n");
	
	return 0;
}

druide@bidouille:~/sbsigntool-0.6/src$ gcc -g -I.. -I. -I../lib/ccan -L. fileio.c image.c main.c ../lib/ccan/libccan.a `pkg-config --cflags --libs openssl` -o sha256_ms
druide@bidouille:~/sbsigntool-0.6/src$  ./sha256_ms 
954313251afd087421daf433300a524d4f77dfa2181ab962353f4b17c28e5c17


C'est bien le même que celui présent dans la structure opaque pkcs#7.



druide@bidouille:~$ openssl asn1parse -inform der -in p7 -i -dump
    0:d=0  hl=4 l=1899 cons: SEQUENCE          
    4:d=1  hl=2 l=   9 prim:  OBJECT            :pkcs7-signedData
   15:d=1  hl=4 l=1884 cons:  cont [ 0 ]        
   19:d=2  hl=4 l=1880 cons:   SEQUENCE          
   23:d=3  hl=2 l=   1 prim:    INTEGER           :01
   26:d=3  hl=2 l=  15 cons:    SET               
   28:d=4  hl=2 l=  13 cons:     SEQUENCE          
   30:d=5  hl=2 l=   9 prim:      OBJECT            :sha256
   41:d=5  hl=2 l=   0 prim:      NULL              
   43:d=3  hl=2 l= 120 cons:    SEQUENCE          
   45:d=4  hl=2 l=  10 prim:     OBJECT            :1.3.6.1.4.1.311.2.1.4
   57:d=4  hl=2 l= 106 cons:     cont [ 0 ]        
   59:d=5  hl=2 l= 104 cons:      SEQUENCE          
   61:d=6  hl=2 l=  51 cons:       SEQUENCE          
   63:d=7  hl=2 l=  10 prim:        OBJECT            :1.3.6.1.4.1.311.2.1.15
   75:d=7  hl=2 l=  37 cons:        SEQUENCE          
   77:d=8  hl=2 l=   1 prim:         BIT STRING        
      0001 - 
   80:d=8  hl=2 l=  32 cons:         cont [ 0 ]        
   82:d=9  hl=2 l=  30 cons:          cont [ 2 ]        
   84:d=10 hl=2 l=  28 prim:           cont [ 0 ]        
  114:d=6  hl=2 l=  49 cons:       SEQUENCE          
  116:d=7  hl=2 l=  13 cons:        SEQUENCE          
  118:d=8  hl=2 l=   9 prim:         OBJECT            :sha256
  129:d=8  hl=2 l=   0 prim:         NULL              
  131:d=7  hl=2 l=  32 prim:        OCTET STRING      
      0000 - 95 43 13 25 1a fd 08 74-21 da f4 33 30 0a 52 4d   .C.%...t!..30.RM
      0010 - 4f 77 df a2 18 1a b9 62-35 3f 4b 17 c2 8e 5c 17   Ow.....b5?K...\.
La boucle est bouclée

Résumé

On peut résumer les étapes de la signature ainsi:


That's all folks

Lien ô tech

Authenticode PE PE Format PE/COFF Object IDs associated with Microsoft cryptography Authenticode Pourquoi mes sha256 ne fonctionnaient pas Décortiquage d'un pkcs#7 Inspiration et code source partiel

  • 4 years 8 months before
  • |