├── README.md ├── gethashes.py ├── lenovo_key_pub.der ├── my_key.pem ├── my_key_pub ├── my_key_pub.der └── my_key_pub.pem /README.md: -------------------------------------------------------------------------------- 1 | # thinkpad-shahash 2 | This is a small utility which checks and recomputes sha1 hashes used to validate Lenovo ThinkPad X220/T420 (and probably other Sandy Bridge ThinkPads) firmware integrity. You can hear 5 beeps twice if the firmware fails validation and you have TPM (security chip) turned on, which is pretty common for modified firmwares. 3 | 4 | This utility is now fully automatic, it can re-hash TCPA blocks, recompute RSA signatures of said blocks and replace Lenovo public key with yours. 5 | 6 | ## How to get rid of 5 beeps automatically 7 | run 8 | `gethashes.py --output recomputed_fw.FL1 modified_fw.FL1` 9 | where `modified_fw.FL1` is a file you modified and need to recompute hashes for and `recomputed_fw.FL1` is an output file. 10 | 11 | ## How to get rid of 5 beeps manually 12 | You need to recompute SHA1 hashes of modified firmware files in UEFI Volume 7A9354D9-0468-444A-81CE-0BF617D890DF, change RSA key and change SHA1 hashes, RSA public key and RSA signature to yours. 13 | 14 | 1. Change all the files you want in UEFI Volume 7A9354D9-0468-444A-81CE-0BF617D890DF. 15 | 16 | 2. Generate your RSA 1024 bit key with exponent=3 17 | 18 | ``` 19 | $ openssl genrsa -3 -out my_key.pem 1024 20 | Generating RSA private key, 1024 bit long modulus 21 | ............................++++++ 22 | .......................................................++++++ 23 | e is 3 (0x3) 24 | 25 | $ openssl rsa -in my_key.pem -outform der -pubout -out my_key_pub.der 26 | writing RSA key 27 | 28 | $ openssl rsa -pubin -inform der -in my_key_pub.der -text -noout 29 | Public-Key: (1024 bit) 30 | Modulus: 31 | 00:e4:f7:98:41:2f:2d:a3:67:29:75:04:9d:f4:d6: 32 | d6:4c:fc:b6:42:37:3b:aa:d1:65:31:8b:d1:99:af: 33 | bb:04:dc:e0:03:08:bb:2c:28:6a:51:a7:d9:ec:fb: 34 | 2a:af:9b:c2:5b:1a:e5:2c:12:5b:e2:37:f4:1f:fc: 35 | c1:64:79:48:f8:93:6a:b2:ad:ae:f5:a8:b9:40:cf: 36 | a2:39:be:31:6d:dd:3f:48:5e:ca:9f:12:19:5b:32: 37 | b9:11:1c:67:81:7b:c0:9a:08:16:0f:88:43:8c:64: 38 | 0a:80:90:a4:1f:a7:25:f6:bb:30:e0:ef:30:36:32: 39 | ec:49:a2:81:af:8d:11:72:21 40 | Exponent: 3 (0x3) 41 | 42 | ``` 43 | 44 | 3. Run this script, find and replace modified checksums in the firmware using hex editor 45 | 46 | 4. Search for "TCPABIOS" string using hex editor. Right before next block (where another TCPA block starts) copy 128 bytes from the end of the block. This is RSA signature. 47 | 48 | 5. Search for FF 12 04 00, this is RSA modulus (should be right after TCPABBLK block). Replace the modulus with the modulus from your key (it always starts with 00 and is 129 bytes long). 49 | 50 | 6. Create 128 byte file with SHA1 sum of the whole TCPABIOS block excluding RSA signature (so, right from the TCPABIOS header and until lots of zeros and FF FF, excluding FF FF), padded with zeros from the beginning, and sign it with your key: 51 | 52 | ``` 53 | openssl rsautl -inkey my_key.pem -sign -in mod_sign_sha1 -raw > mod_signature 54 | ``` 55 | 56 | 7. Replace the signature (last 128 bytes of TCPABIOS block). 57 | -------------------------------------------------------------------------------- /gethashes.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import sys 3 | import os 4 | import binascii 5 | import hashlib 6 | import struct 7 | import argparse 8 | from subprocess import Popen, PIPE 9 | 10 | ZERO_BLOCK = b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"\ 11 | b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xFF\xFF" 12 | PUBKEY = b"\xFF\x12\x04\x00" 13 | TCPA_BLOCK_LEN = 39 14 | TCPA_HEADER_LEN = 29 15 | PUBKEY_FILE = "my_key_pub" 16 | PRIVKEY_FILE = "my_key.pem" 17 | OPENSSL = "openssl" 18 | 19 | sig_replacements = [] 20 | 21 | def find_tcpa_block(data): 22 | ibmsecur = data.find(b"IBMSECUR") 23 | if not ibmsecur: 24 | return None 25 | tcpa_temp = data[ibmsecur-100:ibmsecur+(16*1024)] 26 | tcpa_start = tcpa_temp.find(b"TCPA") 27 | tcpa_end = tcpa_temp.find(ZERO_BLOCK) 28 | if not (tcpa_start and tcpa_end): 29 | return None 30 | return tcpa_temp[tcpa_start:tcpa_end + 1] 31 | 32 | def replace_pubkey(data, newpubkey): 33 | pubkey = data.find(PUBKEY) 34 | if not pubkey: 35 | return None 36 | pubkey = pubkey-1+len(PUBKEY) 37 | assert(len(newpubkey) == 129) 38 | return data[:pubkey] + newpubkey + data[pubkey+129:] 39 | 40 | def replace_checksum(data, oldsum, newsum): 41 | oldsum_offset = data.find(oldsum) 42 | if not oldsum: 43 | return None 44 | assert(len(newsum) == 20) 45 | assert(len(oldsum) == 20) 46 | return data[:oldsum_offset] + newsum + data[oldsum_offset+20:] 47 | 48 | def replace_data(data, offset, replacewith): 49 | return data[:offset] + replacewith + data[offset+len(replacewith):] 50 | 51 | def save_data(name, data): 52 | with open(name, 'wb') as f: 53 | f.write(data) 54 | 55 | def sign_data(input_data): 56 | out = Popen([OPENSSL,"rsautl","-inkey",os.path.join(os.path.dirname(os.path.realpath(__file__)), PRIVKEY_FILE),"-sign","-raw"], 57 | stdout=PIPE, stdin=PIPE) 58 | out = out.communicate(input=input_data)[0] 59 | return out 60 | 61 | class TCPABlock: 62 | def __init__(self, data): 63 | # Get struct length and block ID 64 | cut_data = data[:3] 65 | self.block_num, self.block_id, self.block_len = struct.unpack("8}".format(tcpa_data.block_num), 141 | binascii.hexlify(tcpa_data.sha1sum).decode(), error_str) 142 | error_str = '' 143 | if args.output: 144 | # Search for tcpa_block in fullfile. 145 | # tcpa_offset points to the latest non-zero block right now 146 | fullfile_tcpablock_offset = fullfile.find(tcpa_block) 147 | assert(fullfile_tcpablock_offset != -1) 148 | fullfile_tcpablock_offset -= TCPA_HEADER_LEN 149 | # check if we're really reached TCPA header 150 | assert(fullfile[fullfile_tcpablock_offset:fullfile_tcpablock_offset+4] ==\ 151 | b"TCPA") 152 | fullfile_tcpablock_signature_offset = fullfile_tcpablock_offset + TCPA_HEADER_LEN +\ 153 | tcpa_offset + TCPA_BLOCK_LEN # skip zero block 154 | 155 | # replace checksums from the queue 156 | for replacement in sig_replacements: 157 | fullfile = replace_checksum(fullfile, replacement[0], replacement[1]) 158 | sig_replacements = [] 159 | 160 | # are we really reached signature offset? 161 | assert(fullfile[fullfile_tcpablock_signature_offset:fullfile_tcpablock_signature_offset+3] ==\ 162 | b"\xFF\xFF\x83") 163 | # if so, let's recompute TCPA block hash and re-sign it 164 | newhash = hashlib.sha1() 165 | newhash.update(fullfile[fullfile_tcpablock_offset:fullfile_tcpablock_signature_offset]) 166 | newhash = newhash.digest() 167 | fullfile_tcpablock_signature_offset += 3 # skip FF FF 83 168 | data_to_sign = bytearray(108) + newhash # 128 bytes 169 | signed_data = sign_data(data_to_sign) 170 | assert(len(signed_data) == 128) 171 | fullfile = replace_data(fullfile, fullfile_tcpablock_signature_offset, signed_data) 172 | print("Re-signed hash", binascii.hexlify(newhash).decode()) 173 | 174 | next_ibmsecur = data.find(b"IBMSECUR") 175 | data = data[next_ibmsecur + TCPA_HEADER_LEN:] 176 | 177 | if args.output: 178 | save_data(args.output, fullfile) 179 | -------------------------------------------------------------------------------- /lenovo_key_pub.der: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ValdikSS/thinkpad-shahash/6d8e2232c4f127dc89dfc2488f2152567406beb8/lenovo_key_pub.der -------------------------------------------------------------------------------- /my_key.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIICXAIBAAKBgQDk95hBLy2jZyl1BJ301tZM/LZCNzuq0WUxi9GZr7sE3OADCLss 3 | KGpRp9ns+yqvm8JbGuUsElviN/Qf/MFkeUj4k2qyra71qLlAz6I5vjFt3T9IXsqf 4 | EhlbMrkRHGeBe8CaCBYPiEOMZAqAkKQfpyX2uzDg7zA2MuxJooGvjRFyIQIBAwKB 5 | gQCYpRArdMkXmhujWGlN5I7d/c7Wz30ci5jLsou7ynyt6JVXWydyxZw2b+ad/Mcf 6 | vSw8vJjIDD1BeqK//dZC+4X5yfJthQgB2mMsqADOHdrHO8W4sk6kW0UHXVPojTd0 7 | gOpwIlUiPVWOy8srx4VkmS7XPScXNLRwZR50GxBxI0dMywJBAPqHdcL1yLfCw+P1 8 | JlRv5jL4FgrJ0PnWLOtSbBUshcUDYf28HU4EbwsC65Zn8BfomHZ72Ve+K1afhgYh 9 | 2+LsGZ0CQQDp95ijLSopYbnvq948+VzQnp1IitePW6M7aMgzZ8z1XY/W2j7ePscn 10 | VtNO9Bipdxto+mTIY2JEnxVzxyn1OmVVAkEApwT5LKPbJSyCl/jEOEqZd1AOsdvg 11 | po7InOGdY3MD2Kzr/n1o3q2fXKydDu/1ZUW6+afmOn7HjxUEBBaSl0gREwJBAJv6 12 | ZcIeHBuWe/UdPtNQ6IsUaNsHOl+SbNJF2szv3fjpCo88Kel/L2+PN4n4EHD6EkX8 13 | QzBCQYMUuPfaG/jRmOMCQAlJVbZs3bbcTUdRCmXi5tlkL+1k3xU4UJaaSQ9XhXCo 14 | HPJ88kSsPXKjROEvxE5+55I4XRnyITMNwK2ZHfkKqN4= 15 | -----END RSA PRIVATE KEY----- 16 | -------------------------------------------------------------------------------- /my_key_pub: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ValdikSS/thinkpad-shahash/6d8e2232c4f127dc89dfc2488f2152567406beb8/my_key_pub -------------------------------------------------------------------------------- /my_key_pub.der: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ValdikSS/thinkpad-shahash/6d8e2232c4f127dc89dfc2488f2152567406beb8/my_key_pub.der -------------------------------------------------------------------------------- /my_key_pub.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PUBLIC KEY----- 2 | MIGdMA0GCSqGSIb3DQEBAQUAA4GLADCBhwKBgQDk95hBLy2jZyl1BJ301tZM/LZC 3 | Nzuq0WUxi9GZr7sE3OADCLssKGpRp9ns+yqvm8JbGuUsElviN/Qf/MFkeUj4k2qy 4 | ra71qLlAz6I5vjFt3T9IXsqfEhlbMrkRHGeBe8CaCBYPiEOMZAqAkKQfpyX2uzDg 5 | 7zA2MuxJooGvjRFyIQIBAw== 6 | -----END PUBLIC KEY----- 7 | --------------------------------------------------------------------------------