顯示具有 kernel 標籤的文章。 顯示所有文章
顯示具有 kernel 標籤的文章。 顯示所有文章

2023年4月10日 星期一

Extract module certificate from Linux kernel binary

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 */
#include 
#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:

As 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:


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-default
Then 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/s
As 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.

2018年9月13日 星期四

Check the signature of kernel module (PKCS#7 format)

I have written a blog for how to check the signature of kernel module (old format). In the mainline kernel, 3f1e1bea3 patch in v4.3-rc1 switches the format of kernel module signature to PKCS#7. This blog introduces how to verify the PKCS#7 signature manually.

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.

2015年10月12日 星期一

How to taint a hibernation snapshot image in a qemu swap disk file

For testing hibernation signature verification, I need try to taint the hibernation snapshot image in swap partition. Using qemu to test to taint snapshot image is more convenient than the real swap partition.

First, I created a qemu raw disk file to be the /dev/sdb swap partition to guest OS:

  qemu-img create ./swap.image 1G

Please be sure the format of file is raw but not qcow2, otherwise the swap header will not in the first page of swap file. I didn't investigate the detail of qcow2 format, but looks the magic string, SWAPSPACE2, is in the position after 5M offset but not the first page:

00050ff0  53 50 41 43 45 32 53 57  41 50 53 50 41 43 45 32  |SPACE2SWAPSPACE2|

The position of magic string in raw format file is in the first page:

00000fe0  01 00 00 00 00 00 00 00  03 00 00 00 53 57 41 50  |............SWAP|
00000ff0  53 50 41 43 45 32 53 57  41 50 53 50 41 43 45 32  |SPACE2SWAPSPACE2|

My whole investigation is base on the raw format disk file.

After create a disk file, then add it to guest OS to be the swap partition. My guest OS is openSUSE:

a. Add the new disk file to be the hdb of guest OS in qemu command:
  -hdb /home/joeyli/qemu-vm/swap.image

b. Launch guest OS, format the sdb disk to be swap:
  mkswap /dev/sdb

c. Add to /etc/fstab
  /dev/sdb swap swap defaults 0 0

d. Add kernel parameter to indicate sdb to be the resume partition:
  resume=/dev/sdb

e. Add the following kernel parameter to grub2 conf for testing hibernation:
earlyprintk=ttyS0,115200 console=tty0 console=ttyS0,115200 debug no_console_suspend=1 loglevel=9 nomodeset earlyprintk=efi hibernate=nocompress

Using hibernate=nocompress to avoid crc32 check, anyway, it's just for testing hibernation verification mechanism. We will taint a snapshot image in swap, then CRC checking maybe failed before hibernation verification.

After /dev/sdb show in guest OS, then trying hibernation. Before hibernation, you should find the magic string of swap parition:

00000fe0  01 00 00 00 00 00 00 00  03 00 00 00 53 57 41 50  |............SWAP|
00000ff0  53 50 41 43 45 32 53 57  41 50 53 50 41 43 45 32  |SPACE2SWAPSPACE2|

The declaration of swap header in kernel source is the swap_header union in swap.h header:

include/linux/swap.h
/*
 * Magic header for a swap area. The first part of the union is
 * what the swap magic looks like for the old (limited to 128MB)
 * swap area format, the second part of the union adds - in the
 * old reserved area - some extra information. Note that the first
 * kilobyte is reserved for boot loader or disk label stuff...
 *
 * Having the magic at the end of the PAGE_SIZE makes detecting swap
 * areas somewhat tricky on machines that support multiple page sizes.
 * For 2.5 we'll probably want to move the magic to just beyond the
 * bootbits...
 */
union swap_header {
        struct {
                char reserved[PAGE_SIZE - 10];
                char magic[10];                 /* SWAP-SPACE or SWAPSPACE2 */
        } magic;
        struct {
                char            bootbits[1024]; /* Space for disklabel etc. */
                __u32           version;
                __u32           last_page;
                __u32           nr_badpages;
                unsigned char   sws_uuid[16];
                unsigned char   sws_volume[16];
                __u32           padding[117];
                __u32           badpages[1];
        } info;
};

So, the magic string is in the end of the first page of partition. On the other hand, other meta-datas are written to the first page from top-down.

After launched hibernation, the magic string will be changed to S1SUSPEND:

00000fe0  01 00 00 00 00 00 00 00  05 00 00 00 53 57 41 50  |............SWAP|
00000ff0  53 50 41 43 45 32 53 31  53 55 53 50 45 4e 44 00  |SPACE2S1SUSPEND.|

After hibernation resuming, either success or fail, the magic string will be changed back to SWAPSPACE2.

The hibernation verification mechanism is calculating the signature of data pages in snapshot image. To test the mechanism is to taint the content in data pages:


Find out the position of data pages is the most important thing to taint data pages in snapshot image. The snapshot image header is swsusp_info, it's in the first page of snapshot. The hibernation mechanism uses the reserved space from bottom-up of the first page of swap partition:

kernel/power/swap.c
struct swsusp_header {      /* 1 page, 4096 bytes */
char reserved[PAGE_SIZE - 20 - sizeof(sector_t) - sizeof(int) -
sizeof(u32)]; /* 4096 - 20 - 8 - 4 - 4 = 4060 */
u32     crc32;             /* 4 bytes */
sector_t image;          /* sector_t of snapshot image, unsigned long 8 bytes */
unsigned int flags;      /* unsigned int, 4 bytes */
char    orig_sig[10];   /* 10 bytes */
char    sig[10];           /* 10 bytes *//* SWAPSPACE2 or S1SUSPEND */
} __attribute__((packed));

The swsusp_header actually is also mapping to the first page of swap, in the same space of swap_header:
The main field of swsusp_header to find the position snapshot image is "image", it's a number of sector_t, the size the same with page on swap. For example, if image = 1, then means the offset of snapshot image in swap is:
    offest = (swap header + image sector) * PAGE_SIZE = (1 + 1) * 4096

Then follow this offset, we can find out the snapshoti image header, swsusp_info:

kernel/power/power.h
struct swsusp_info {
        struct new_utsname      uts;     /* on x86, struct restore_data_record, 24 bytes */
                                                         /* 0x0123456789ABCDEFUL */
        u32                     version_code;
        unsigned long     num_physpages;
        int                       cpus;
        unsigned long      image_pages;    /* data pages number */
        unsigned long      pages;               /* total pages number that are in snapshot */
        unsigned long      size;
} __aligned(PAGE_SIZE);

I want to taint the content of data pages to emulate someone modified the image data. There have some important field in swsusp_info structure, pages and image_pages. So the offset of the beginning of data pages area is: 
    data pages offset = (swap header + image sector) * PAGE_SIZE + (pages - image_pages) * PAGE_SIZE

Then, using printf and dd command to modify the content of data pages. For example, this is the command to modify one byte in 5242848 offset:
    printf '\x32' | dd conv=notrunc of=./swap.image bs=1 seek=5242848

Here is a simple C program to check the swap header and swsusp header in qemu swap raw file, and taint 3 bytes in 3 different position for testing.