{"id":169,"date":"2020-06-30T09:00:00","date_gmt":"2020-06-30T09:00:00","guid":{"rendered":"https:\/\/reversea.me\/?p=169"},"modified":"2020-11-29T20:18:19","modified_gmt":"2020-11-29T20:18:19","slug":"authenticode-ii-verifying-authenticode-with-openssl","status":"publish","type":"post","link":"https:\/\/reversea.me\/index.php\/authenticode-ii-verifying-authenticode-with-openssl\/","title":{"rendered":"Authenticode (II): Verifying Authenticode with OpenSSL"},"content":{"rendered":"<span class=\"span-reading-time rt-reading-time\" style=\"display: block;\"><span class=\"rt-label rt-prefix\">Reading Time: <\/span> <span class=\"rt-time\"> 5<\/span> <span class=\"rt-label rt-postfix\">minutes<\/span><\/span>\n<p>Following our <a href=\"https:\/\/reversea.me\/index.php\/authenticode-i-understanding-windows-authenticode\/\">previous post<\/a>, in which Microsoft Authenticode was introduced, in this post we&#8217;ll use OpenSSL to verify embedded signatures into PE files.<\/p>\n\n\n\n<p>Note that this post was tested using Python 3 and OpenSSL 1.1.0l, so it may vary in your own setup. <strong>You should always use official <a href=\"https:\/\/docs.microsoft.com\/en-us\/sysinternals\/downloads\/sigcheck\">Microsoft&#8217;s sigcheck tool<\/a> whenever it is possible<\/strong>: what we explain here is just a way to overcome limitations when working with other operating systems than Windows.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Prerequisites<\/h3>\n\n\n\n<ul class=\"wp-block-list\"><li>Python 3 (&gt;= 3.5.3)<ul><li>pefile (&gt;= 2019.4.18)<\/li><\/ul><\/li><li>OpenSSL 1.1.0l<\/li><\/ul>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Extracting embedded Authenticode signature<\/strong><\/h2>\n\n\n\n<p>As we already know, the Security directory entry within the Data directories array of the PE optional header stores the <strong>file offset and size<\/strong> of the Authenticode signature. So, we can try the following Python code snippet to extract the signature (<em>we are using the <strong><a href=\"https:\/\/docs.microsoft.com\/en-us\/sysinternals\/downloads\/sigcheck\">Microsoft&#8217;s sigcheck tool<\/a><\/strong> as a signed file example<\/em>):<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"python\" class=\"language-python\">In [1]: import pefile\n\nIn [2]: pe = pefile.PE('sigcheck64.exe', fast_load=True)    # fast_load avoids to load all the directories information for best parse performance\n\nIn [3]: security_directory = pe.OPTIONAL_HEADER.DATA_DIRECTORY[pefile.DIRECTORY_ENTRY['IMAGE_DIRECTORY_ENTRY_SECURITY']]    # Extract Security directory\n\nIn [4]: win_certificate = pe.__data__[security_directory.VirtualAddress:security_directory.VirtualAddress+security_directory.Size]  # Extract WIN_CERTIFICATE<\/code><\/pre>\n\n\n\n<p>Now we have the <code>WIN_CERTIFICATE<\/code>. If you remember from the <a href=\"https:\/\/reversea.me\/index.php\/authenticode-i-understanding-windows-authenticode\/\">previous post<\/a>, it has the following structure:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"cpp\" class=\"language-cpp\">typedef struct _WIN_CERTIFICATE {\n  DWORD dwLength;\n  WORD  wRevision;\n  WORD  wCertificateType;\n  BYTE  bCertificate[ANYSIZE_ARRAY];\n} WIN_CERTIFICATE, *LPWIN_CERTIFICATE;<\/code><\/pre>\n\n\n\n<p>That means that we need to skip first bytes to only work on the PKCS#7 <code>SignedData<\/code> (corresponding to <code>bCertificate<\/code> in the <code>struct<\/code>). We can save it in a file simply with:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"python\" class=\"language-python\">In [5]: signature = win_certificate[0x4+0x2+0x2:]   # Skip WIN_CERTIFICATE information\n\nIn [6]: with open('sigcheck64.exe.cert', 'wb') as f: \n   ...:     f.write(signature)<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Reading PKCS#7 SignedData<\/strong><\/h2>\n\n\n\n<p>Now we have extracted the signature in a separated file. Since it follows the binary DER-encoded ASN.1 format, we can read the signature file providing the appropriate options to <code>openssl<\/code> tool (part of the tool suite of OpenSSL):<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"bash\" class=\"language-bash\">$ openssl asn1parse -inform DER -in sigcheck64.exe.cert | head -n 25\n    0:d=0  hl=4 l=9067 cons: SEQUENCE          \n    4:d=1  hl=2 l=   9 prim: OBJECT            :pkcs7-signedData\n   15:d=1  hl=4 l=9052 cons: cont [ 0 ]        \n   19:d=2  hl=4 l=9048 cons: SEQUENCE          \n   23:d=3  hl=2 l=   1 prim: INTEGER           :01\n   26:d=3  hl=2 l=  15 cons: SET               \n   28:d=4  hl=2 l=  13 cons: SEQUENCE          \n   30:d=5  hl=2 l=   9 prim: OBJECT            :sha256\n   41:d=5  hl=2 l=   0 prim: NULL              \n   43:d=3  hl=2 l=  92 cons: SEQUENCE          \n   45:d=4  hl=2 l=  10 prim: OBJECT            :1.3.6.1.4.1.311.2.1.4\n   57:d=4  hl=2 l=  78 cons: cont [ 0 ]        \n   59:d=5  hl=2 l=  76 cons: SEQUENCE          \n   61:d=6  hl=2 l=  23 cons: SEQUENCE          \n   63:d=7  hl=2 l=  10 prim: OBJECT            :1.3.6.1.4.1.311.2.1.15\n   75:d=7  hl=2 l=   9 cons: SEQUENCE          \n   77:d=8  hl=2 l=   1 prim: BIT STRING        \n   80:d=8  hl=2 l=   4 cons: cont [ 0 ]        \n   82:d=9  hl=2 l=   2 cons: cont [ 2 ]        \n   84:d=10 hl=2 l=   0 prim: cont [ 0 ]        \n   86:d=6  hl=2 l=  49 cons: SEQUENCE          \n   88:d=7  hl=2 l=  13 cons: SEQUENCE          \n   90:d=8  hl=2 l=   9 prim: OBJECT            :sha256\n  101:d=8  hl=2 l=   0 prim: NULL              \n  103:d=7  hl=2 l=  32 prim: OCTET STRING      [HEX DUMP]:1A63DA481D6954EBE6FAAD6771B28AC621CAA92855FBF8B6B5FF1FDE29CF1AAC<\/code><\/pre>\n\n\n\n<p>As specified <a href=\"https:\/\/www.openssl.org\/docs\/man1.0.2\/man1\/asn1parse.html\">in the documentation<\/a>, in each line of this example we have:<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li>A file offset, expressed in decimal.<\/li><li><code>d=XX<\/code> specifies the current depth. The depth is increased within the scope of any <code>SET<\/code> or <code>SEQUENCE<\/code>.<\/li><li><code>hl=XX<\/code> gives the header length (in bytes) of the current type.<\/li><li><code>l=XX<\/code> gives the length of the content, also in bytes.<\/li><\/ul>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Calculating hash<\/strong><\/h2>\n\n\n\n<p>You probably noticed at least two interest strings in the previous output:  <code>sha256<\/code> and <code>HEX DUMP<\/code> data. As you imagine, the hex dump data corresponds to the SHA-256 hash (as indicated by the other string, <em>easy, huh?<\/em>) of the embedded signature corresponding to the signed-file. Right, but&#8230; <em>how do we calculate the hash of the file to check that both hashes match?<\/em><\/p>\n\n\n\n<p>Well, we already know which parts are skipped during the Authenticode hash calculation process (we explained that also in our <a href=\"https:\/\/reversea.me\/index.php\/authenticode-i-understanding-windows-authenticode\/\">previous post<\/a>). Therefore, we can calculate it by ourselves as follows:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"python\" class=\"language-python\">In [7]: checksum_offset = pe.OPTIONAL_HEADER.dump_dict()['CheckSum']['FileOffset']  # CheckSum file offset\n\nIn [8]: certificate_table_offset = security_directory.dump_dict()['VirtualAddress']['FileOffset'] # IMAGE_DIRECTORY_ENTRY_SECURITY file offset\n\nIn [9]: certificate_virtual_addr = security_directory.VirtualAddress\n\nIn [10]: certificate_size = security_directory.Size\n\nIn [11]: raw_data = pe.__data__\n\nIn [12]: hash_data = raw_data[:checksum_offset] + raw_data[checksum_offset+0x04:certificate_table_offset]   # Skip OptionalHeader.CheckSum field and continue until IMAGE_DIRECTORY_ENTRY_SECURITY\n\nIn [13]: hash_data += raw_data[certificate_table_offset+0x08:certificate_virtual_addr] + raw_data[certificate_virtual_addr+certificate_size:]   # Skip IMAGE_DIRECTORY_ENTRY_SECURITY and certificate\n\nIn [14]: import hashlib\n\nIn [15]: hashlib.sha256(hash_data).hexdigest()\nOut[15]: '1a63da481d6954ebe6faad6771b28ac621caa92855fbf8b6b5ff1fde29cf1aac'<\/code><\/pre>\n\n\n\n<p>Hurrah! We have calculated the SHA-256 hash and it matches with the content provided in the embedded signature. So now we can completely trust that the binary  file was unmodified after it was signed. However, our work has not finished yet. <strong>We also need to validate the signature<\/strong>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Verifying signature<\/strong><\/h2>\n\n\n\n<p>This is the trickiest part. Here, we can rely on OpenSSL&#8217;s <code>smime<\/code> command to verify the signature. The problem is that the command expects, besides signature, the signed content data. In the case of Authenticode, this content corresponds to the Object Identifier (OID) <code>1.3.6.1.4.1.311.2.1.15<\/code>, called <code>SPC_PE_IMAGE_DATA_OBJID<\/code> <a href=\"https:\/\/support.microsoft.com\/en-us\/help\/287547\/object-ids-associated-with-microsoft-cryptography\">by Microsoft<\/a>.<\/p>\n\n\n\n<p>In the previous output (the parsing of the signature using <code>openssl asn1parse<\/code> command), we can see such specific OID in the signature at offset 63. However, <strong>we need to provide the whole <code>SEQUENCE<\/code><\/strong>, which it usually starts 2 depth above <code>SPC_PE_IMAGE_DATA_OBJID<\/code> object. According to our output, the object is in <code>d=7<\/code>, but the sequence starts in <code>d=5<\/code>, with a header length of 2, and an object length of 76. So, we can use this information to extract the whole sequence:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"python\" class=\"language-python\">In [16]: signed_data = signature[59+2:59+2+76]\n\nIn [17]: with open('sigcheck64.exe.signed.data', 'wb') as f: \n    ...:     f.write(signed_data) <\/code><\/pre>\n\n\n\n<p>Finally, we can use both the signature and the signed data to verify the signature as:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"bash\" class=\"language-bash\">$ openssl smime -verify -inform DER -in sigcheck64.exe.cert -binary -content sigcheck64.exe.signed.data -purpose any -CApath \/etc\/ssl\/certs\/ -out \/tmp\/dummy.txt\nVerification failure\n140429820977216:error:21075075:PKCS7 routines:PKCS7_verify:certificate verify error:..\/crypto\/pkcs7\/pk7_smime.c:285:Verify error:certificate has expired<\/code><\/pre>\n\n\n\n<p>Strictly, the certificate is no longer valid since it has expired (in fact, almost all system PE files in Windows have already their certificate expired). We can provide <code>-no_check_time<\/code> argument to avoid temporary restrictions to the validation process:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"bash\" class=\"language-bash\">$ openssl smime -verify -inform DER -in sigcheck64.exe.cert -binary -content sigcheck64.exe.signed.data -purpose any -CApath \/etc\/ssl\/certs\/ -no_check_time -out \/tmp\/dummy.txt \nVerification successful<\/code><\/pre>\n\n\n\n<p><em>\u00a1Tach\u00e1n!<\/em><\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Error: &#8220;unable to get local issuer certificate&#8221;<\/strong><\/h3>\n\n\n\n<p>A typical problem is that <code>openssl<\/code> is unable to find the certificate associated with a particular signature, giving an  error like:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code class=\"\">140649320939584:error:21075075:PKCS7 routines:PKCS7_verify:certificate verify error:..\/crypto\/pkcs7\/pk7_smime.c:285:Verify error:unable to get local issuer certificate<\/code><\/pre>\n\n\n\n<p>If we run the command under <code>strace<\/code> we can quickly identify which certificate is missed. In our case, it is <code>523e3c59.0<\/code>, which corresponds to &#8220;Microsoft Root Certificate Authority 2011&#8221;:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code class=\"\">[... redacted ...]\nstat(\"\/etc\/ssl\/certs\/523e3c59.0\", 0x7ffc5cae25e0) = -1 ENOENT (No such file or directory)\nstat(\"\/etc\/ssl\/certs\/523e3c59.0\", 0x7ffc5cae25e0) = -1 ENOENT (No such file or directory)\nwrite(2, \"Verification failure\\n\", 21Verification failure\n)  = 21\nwrite(2, \"140490832396352:error:21075075:P\"..., 168140490832396352:error:21075075:PKCS7 routines:PKCS7_verify:certificate verify error:..\/crypto\/pkcs7\/pk7_smime.c:285:Verify error:unable to get local issuer certificate\n) = 168\nclose(3)                                = 0\nclose(4)                                = 0\nclose(5)                                = 0\nfutex(0x7fc691e20aac, FUTEX_WAKE_PRIVATE, 2147483647) = 0\nexit_group(4)                           = ?\n+++ exited with 4 +++<\/code><\/pre>\n\n\n\n<p>A quick search on the Internet can provide you with the missing certificate (remember to <strong>always double-check<\/strong> the website from where you get the root certificate). Once downloaded, the error should be gone.<\/p>\n\n\n\n<hr class=\"wp-block-separator\"\/>\n\n\n\n<p>So far so good? At this moment, we have verified the integrity of the signed file plus the validity of the certificate. However, <em>we have not completed all the necessary steps to validate a digital certificate<\/em>. Currently, the <code>openssl<\/code> binary tool binary does not perform <strong>any certificate revocation check<\/strong>. In our upcoming blog, we will fix this issue to fully complete the validation!<\/p>\n","protected":false},"excerpt":{"rendered":"<p><span class=\"span-reading-time rt-reading-time\" style=\"display: block;\"><span class=\"rt-label rt-prefix\">Reading Time: <\/span> <span class=\"rt-time\"> 5<\/span> <span class=\"rt-label rt-postfix\">minutes<\/span><\/span>Following our previous post, in which Microsoft Authenticode was introduced, in this post we&#8217;ll use OpenSSL to verify embedded signatures into PE files. Note that this post was tested using Python 3 and OpenSSL 1.1.0l, so it may vary in your own setup. You should always use official Microsoft&#8217;s sigcheck tool whenever it is possible: [&hellip;]<\/p>\n","protected":false},"author":2,"featured_media":0,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[18,15],"tags":[6,7,19,14],"class_list":["post-169","post","type-post","status-publish","format-standard","hentry","category-ms-windows-internals","category-tools","tag-authenticode","tag-code-signing","tag-openssl","tag-windows","no-featured-image"],"_links":{"self":[{"href":"https:\/\/reversea.me\/index.php\/wp-json\/wp\/v2\/posts\/169","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/reversea.me\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/reversea.me\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/reversea.me\/index.php\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/reversea.me\/index.php\/wp-json\/wp\/v2\/comments?post=169"}],"version-history":[{"count":11,"href":"https:\/\/reversea.me\/index.php\/wp-json\/wp\/v2\/posts\/169\/revisions"}],"predecessor-version":[{"id":305,"href":"https:\/\/reversea.me\/index.php\/wp-json\/wp\/v2\/posts\/169\/revisions\/305"}],"wp:attachment":[{"href":"https:\/\/reversea.me\/index.php\/wp-json\/wp\/v2\/media?parent=169"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/reversea.me\/index.php\/wp-json\/wp\/v2\/categories?post=169"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/reversea.me\/index.php\/wp-json\/wp\/v2\/tags?post=169"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}