My PoC walk through for CVE-2018–6789

By: @straight_blast ;


On March 6, 2018, a security researcher named “meh” (will be referred to as author from now on) published a blog post[1] on the vulnerability CVE-2018–6789 that she identified in EXIM 4.89 and below. She gave detailed explanation on how to exploit the vulnerability, however no proof of concept code was release. I decided to develop a PoC based on her strategy, and this blog is a walk through of my proof of concept code. Before proceeding with reading this post, it is mandatory for the readers to read and understand the author’s blog post as she did an excellent job describing the EXIM heap management and exploitation strategy. Readers are expected to have an understanding of heap exploitation and are encouraged to read through the author’s listed references.

The Vulnerability

As highlighted by the author, there is an off-by-one calculation mistake in the base64 decoded buffer length in the “b64decode” function in base64.c.

An invalid base64 encoded string of length 4N+3 will allocate a buffer of size 3N+1, but will consume 3N+2 bytes from the decoding. This allows heap memory to be overwritten when parsing base64 string.


I setup a test environment using docker.

Installed the necessary libraries and tools I’ll be using.

Add a user for the EXIM process.

Download EXIM 4.89 through the GitHub repository.

I have to do the following to build the EXIM.

Modify line 134 of “exim/src/Local/Makefile” to include an EXIM_USER.

Enable plain text authenticator by uncommenting.

Edit “exim/src/OS/Makefile-Linux” and change the following.


This enables symbols for debugging purposes.

Run “make install” under “exim/src” folder. The installed binary should be located at “/usr/exim/bin/” folder.

Enable “AUTH” for EXIM by editing “/usr/exim/configure” and find the “begin authenticator” section. Make the following changes.

Run EXIM as a daemon.

Have GDB attach to EXIM.

Observing the vulnerability

Attach the debugger to EXIM, and set a breakpoint at the “b64decode” function.

Send a base64 encoded string composing of 54 characters through the “AUTH PLAIN” command.

When “b64decode” function gets a breakpoint hit, set a new breakpoint for “store_get_3” function.

Step to line 143 and print the “size” variable.

The above debugger output indicates “size” is 40. 40 bytes of space will be required to store the decoded base64 string.

The space that holds the decoded base64 string is in agreement of “size” being 40.

Repeat above procedure, but include one more character into the base64 string.

The “size” variable is calculated to 40.

The space that holds the decoded string consists of 41 characters. One more than the expecting size.

Create 0x6060 free chunk block

Send an “EHLO” command with a hostname of 8000 ASCII characters will put a 0x6060 freed size chunk into the unsorted bin.

The address and content of “sender_helo_name”.

The “EHLO” command performs a series of memory allocation and deallocation. I ended up with the following heap layout.

Chunk 0x55d7e5887c30 is the 1st item in the unsorted bin.

Chunk 0x55d7e5887c30 has a size of 0x6060.

Cut the blocks

The author suggested to cut the 0x6060 free chunk up as follow.

It took 5 steps to get the desire layout.

1. Send 1st “EHLO” command with hostname of 8000 ASCII characters, to get 0x6060 free chunk into unsorted bin.

2. Send 2nd “EHLO” command with hostname of 16 ASCII characters, to free the 0x1f50 space occupied by 8000 chars hostname from 1st “EHLO”, and to use the 0x20 free chunk space from small bin to store the new 16 chars hostname.

The 0x20 free block was discovered in the small bin.

3. Send an unrecognized command of 2000 “0xff” characters, to carve a 0x2020 size chunk from the merged free chunk (size: 0x7fb0).

4. Send 3rd “EHLO” command with hostname of 8200 ASCII characters, to carve a 0x2020 size chunk from the merged free chunk (size: 0x5f90).

5. The 3rd “EHLO” command will free the hostname from the 2nd “EHLO” command along with previous allocated store blocks (this includes the storage for the unrecognized command).

The PoC so far:

The heap is in the desired state according to the debugger.

Trigger the bug

The heap layout is as follow.

The author’s next strategy is to allocate a 0x2020 chunk though “PLAIN AUTH” command, and have a one byte overrun into the 3rd “EHLO” command hostname.

I allocated the free chunk above the 3rd “EHLO” hostname by sending an “AUTH PLAIN” command with a 10935 characters of base64 string. This string will also overwrite the least significant byte of the heap size value of the 3rd “EHLO” hostname chunk.

Revised code for the “exploit” method.

By sending “X” as the last character of the encoded string, it overwrites its following chunk’s meta-data size value from 0x2020 to 0x2005.

Finding 0xf1

The author’s strategy is to overwrite the one byte with the “0xf1”, to extend the chunk size from 0x2020 to 0x20f0.

I reviewed how EXIM implemented the base64 decode algorithm, and I learned that the last two bytes of the base64 encoded string determines the last byte of the decoded string.

I traced the code and the expression that evaluates the last decoded character is at line 193 of base64.c.

The above expression uses variable “y” and variable “x”. So I have to identify their source.

The variable “y” holds the 2nd to last character from the encoded string, and variable “x” holds the last character from the encoded string.

The “y” and “x” variable goes through a table lookup before it is evaluated by line 193.

Line 170 implements the table lookup for variable “y”.

Line 192 implements the table lookup for variable “x”.

The “dec64table” is in the base64.c.

I did the following to identify the last two based64 characters I needed to decode it to “0xf1”.

  1. Create a dec64table, just like the one in base64.c, but remove all entries with the value 255. This is because the base64 decode algorithm returns -1 if it identifies a mapped value that returns 255.

2. Compute all the possible values for “y<<4” and “x>>2”.

3. Brute force all entries in “new_table_x” and “new_table_y” such that “x|y” evaluates with a value that ends in “f1”.

I got this output, and select to use “x = 1, y = 752”.

4. Find “x” where “x>>2=1”. I can find “x” since I computed it in the “new_table_x”.

Though manual inspection, index 17, 18, 19, 20 contains the value 1 in “new_table_x”. The corresponding indexes in “table” returns the value 4, 5, 6, 7. Inspecting which indexes in the “dec64table” returns the value 4, 5, 6, 7 are ASCII characters E, F, G, H.

5. The same procedure is applied to find the original value of “y”.

I identified index 60 returns the value 752 in “new_table_y”, and returns value 47 in “table”. The ASCII value P is the original value.

Revised code for the “exploit” method.

Inspecting the memory agrees with me.

First round of patching

In order to free the extended chunk (size: 0x20f0), I have to allocate a 0x2020 block with fake heap header data, to fake a chunk that is below the extended chunk.

0x55d7e5887d00 + 0x20f0 = 0x55d7e5889df0. So the address 0x55d7e5889df0 should be the start of the fake chunk.

Revised code for the “exploit” method.

It takes 8200 decoded base64 characters to fit in a 0x2020 chunk block. The difference between 0x55d7e5889d20 and 0x55d7e5889df0 is 176, so I have to prefix my decoded value with 176 characters.

I create the fake heap meta data:

· Prev_size: 0

· Size: 0x1f50 (the difference between 0x55d7e5889df0 and 0x55d7e588bd40 is 0x1f50)

The suffix of the string are just fillers.

Examining the memory is in agreement with what I want to achieve.

Free extended chunk

I free the extended chunk by using a 4th “EHLO” command with a small hostname.

Revised code for the “exploit” method.

Debugger output when freeing the 3rd “EHLO” hostname (aka: extended chunk).

The unsorted bin indicates the extended chunk is the first item in the list.

Overlap blocks and 2nd round of patching

The 4th “EHLO” goes through a series of allocation and deallocation which puts me in this memory layout.

The unsorted bin list.

I observed the following from the heap layout.

1. The extended chunk at address 0x55d7e5587d10 is no longer identified as an individual chunk. It has merged with the chunk 0x55d7e5885ce0 creating a free block of size 0x4110.

2. The fake chunk I created at address 0x55d7e5889df0 which I carved from 0x55d7e5889d20 is now a legit chunk.

3. The chunk at address 0x55d7e5889d20 is no longer identified as an individual chunk. However, it is noted as a free chunk in the unsorted bin. The size of it is 0x3f70.

If I allocate the chunk at address 0x55d7e5889d20, it will overlap BOTH 0x55d7e5887ce0 and 0x55d7e5889df0.

After freeing extended chunk

The unsorted bin after freeing the extended chunk.

In order to allocate the extended super chunk at address 0x55d7e5885ce0, I have to allocate the first three items in the unsorted bin list. One of the item being chunk address 0x55d7e5889d20, which partially overlaps with chunk at address 0x55d7e5889df0. I have to ensure the heap meta-data for chunk 0x55d7e5889df0 is valid, or else, I am unable to allocate the super chunk. Therefore, I have to include heap meta-data in my input for the 0x55d7e5889d20 allocation to patch the header of chunk 0x55d7e5889df0.

For the first and third item in the unsorted bin, I use two unrecognized commands to filler them up.

For the second item, I use a “AUTH PLAIN” command.

The heap meta-data for chunk at address 0x55d7e5889df0 is patched.

The “AUTH PLAIN” command makes additional allocation for expanding strings, which happens to allocate a 0x2020 block from the super extended chunk at address 0x55d7e5885ce0. This leaves me with a free block at 0x55d7e5887d00 of size 0x20f0.

The extended free chunk is the first item on the unsorted bin list.

Allocate the extended chunk and free the ACL store block

The extended chunk at address 0x55d7e5887d00 overlaps with 0x55d7e5889d20.

Using a debugger I confirmed the overlapping is true.

The author suggested to allocate the extended chunk and have it overwrite the “next” pointer in the 0x55d7e5889d20 chunk, so the “next” pointer reference a store block that contains ACL.

The start of user control data in a store block is +0x20 offset from the start of the allocated chunk. So the actual starting point I can control for the extended chunk is at address 0x55d7e5887d20.

The offset between 0x55d7e5889d30 (position of next pointer) and 0x55d7e5887d20 is 8208.

Next, I identify the available ACL strings.

I identify which store block the ACL string belongs to through manual inspection.

The starting address from the ACL store block (0x55d7e5864470) that I can control with my user input should start at 0x55d7e5864490.

The offset between 0x55d7e5864490 and 0x55d7e58645b0 is 288.

The ACL store block header starts at 0x55d7e5864480. This is the value I want to overwrite the “next” pointer with.

Lastly, I send another “EHLO” command with a small hostname to force all previous store block to be reset / free. This will free the ACL store block, so I can allocate it later.

Revised code for the “exploit” method.

“next” pointer successfully overwritten.

The ACL store block about to get free.

It should be noted that the author suggested to perform a partial overwrite against the “next” pointer. I did not implement the code to perform partial overwrite. This will be left as an exercise for the reader.

Overwrite “acl_check_rcpt”

I overwrite the “acl_check_rcpt” with the standard bash reverse shell command.

Revised code for the “exploit” method.

Successfully overwritten the content of “acl_smtp_rcpt”.

Shell Time

The content of “acl_smtp_rcpt” gets evaluated when I send a “RCPT TO” command. The perquisite for the “RCPT TO” command is to send the “MAIL FROM” command first.

Revised code for the “exploit” method.

Exploitation Demo

PoC Exploit Code

Useful Breakpoints

Breakpoint 1: to observe the “store_malloc” call under “store_get_3” function.

Breakpoint 2: to observe the “store_free_3” call under “store_reset_3” function.

Breakpoint 3: to observe the “store_free” call under “check_ehlo” function.

Breakpoint 4: to observe the “string_copy_malloc” call under the “check_ehlo” function.