Internet Explorer CVE-2019–1367 Exploitation — part 1
Originally written by Taha Karim
Extracting the Exploit from the PCAP
In this section we will go through the process of extracting the exploit from the pcaps generously provided by malware-traffic-analysis.
Luckily for us the pcaps contain the traffic of a successful exploitation of a target!
This will help us to get what further network requests were made after a successful exploitation and shellcode execution, ultimately to get to the dropped payload (that is in the pcap as well!)
Looking at the first HTTP request, we can see Accept-Language: ko-KR, a good indication that it is targeting Korean users. This is in line with Magnitude Exploit Kit targeting :
base64 only data is returned, typical to Magnitude Exploit KIT landing page:
Decoding it will give us a stage 2, obfuscated:
Magnitude Exploit KIT stage2
stage 2 use some simple de-obfuscation steps to ultimately see a final request to b6883l0bak[.]pophot.website, returning Jscript encoded data and setting IE8 compatibility mode :
Jscrip.Encode with IE8 compatibility mode
Note: By default, IE11, IE10, and IE9 uses Jscript9.dll which is not impacted by CVE-2019–1367 vulnerability. Setting IE8 compatibility mode, will force IE browser to use the legacy jscript.dll ( and trigger the vulnerable code of CVE-2019–1367 bug)
We decoded this Jscript.Encode encoded data using Windows Script Decoder 1.8 tool that can be found here:
| /**********************************************************************/ | |
| /* scrdec.c - Decoder for Microsoft Script Encoder */ | |
| /* Version 1.8 */ | |
| /* */ | |
| /* COPYRIGHT: */ | |
| /* (c)2000-2005 MrBrownstone, mrbrownstone@ virtualconspiracy.com */ | |
| /* v1.8 Now correctly decodes characters 0x00-0x1F, thanks to 'Zed' */ | |
| /* v1.7 Bypassed new HTMLGuardian protection and added -dumb switch */ | |
| /* to disable this */ | |
| /* v1.6 Added HTML Decode option (-htmldec) */ | |
| /* v1.5 Bypassed a cleaver trick defeating this tool */ | |
| /* v1.4 Some changes by Joe Steele to correct minor stuff */ | |
| /* */ | |
| /* DISCLAIMER: */ | |
| /* This program is for demonstrative and educational purposes only. */ | |
| /* Use of this program is at your own risk. The author cannot be held */ | |
| /* responsible if any laws are broken by use of this program. */ | |
| /* */ | |
| /* If you use or distribute this code, this message should be held */ | |
| /* intact. Also, any program based upon this code should display the */ | |
| /* copyright message and the disclaimer. */ | |
| /**********************************************************************/ | |
| #include <stdio.h> | |
| #include <string.h> | |
| #include <stdlib.h> | |
| #define LEN_OUTBUF 64 | |
| #define LEN_INBUF 1024 | |
| #define STATE_INIT_COPY 100 | |
| #define STATE_COPY_INPUT 101 | |
| #define STATE_SKIP_ML 102 | |
| #define STATE_CHECKSUM 103 | |
| #define STATE_READLEN 104 | |
| #define STATE_DECODE 105 | |
| #define STATE_UNESCAPE 106 | |
| #define STATE_FLUSHING 107 | |
| #define STATE_DBCS 108 | |
| #define STATE_INIT_READLEN 109 | |
| #define STATE_URLENCODE_1 110 | |
| #define STATE_URLENCODE_2 111 | |
| #define STATE_WAIT_FOR_CLOSE 112 | |
| #define STATE_WAIT_FOR_OPEN 113 | |
| #define STATE_HTMLENCODE 114 | |
| unsigned char rawData[292] = { | |
| 0x64,0x37,0x69, 0x50,0x7E,0x2C, 0x22,0x5A,0x65, 0x4A,0x45,0x72, | |
| 0x61,0x3A,0x5B, 0x5E,0x79,0x66, 0x5D,0x59,0x75, 0x5B,0x27,0x4C, | |
| 0x42,0x76,0x45, 0x60,0x63,0x76, 0x23,0x62,0x2A, 0x65,0x4D,0x43, | |
| 0x5F,0x51,0x33, 0x7E,0x53,0x42, 0x4F,0x52,0x20, 0x52,0x20,0x63, | |
| 0x7A,0x26,0x4A, 0x21,0x54,0x5A, 0x46,0x71,0x38, 0x20,0x2B,0x79, | |
| 0x26,0x66,0x32, 0x63,0x2A,0x57, 0x2A,0x58,0x6C, 0x76,0x7F,0x2B, | |
| 0x47,0x7B,0x46, 0x25,0x30,0x52, 0x2C,0x31,0x4F, 0x29,0x6C,0x3D, | |
| 0x69,0x49,0x70, 0x3F,0x3F,0x3F, 0x27,0x78,0x7B, 0x3F,0x3F,0x3F, | |
| 0x67,0x5F,0x51, 0x3F,0x3F,0x3F, 0x62,0x29,0x7A, 0x41,0x24,0x7E, | |
| 0x5A,0x2F,0x3B, 0x66,0x39,0x47, 0x32,0x33,0x41, 0x73,0x6F,0x77, | |
| 0x4D,0x21,0x56, 0x43,0x75,0x5F, 0x71,0x28,0x26, 0x39,0x42,0x78, | |
| 0x7C,0x46,0x6E, 0x53,0x4A,0x64, 0x48,0x5C,0x74, 0x31,0x48,0x67, | |
| 0x72,0x36,0x7D, 0x6E,0x4B,0x68, 0x70,0x7D,0x35, 0x49,0x5D,0x22, | |
| 0x3F,0x6A,0x55, 0x4B,0x50,0x3A, 0x6A,0x69,0x60, 0x2E,0x23,0x6A, | |
| 0x7F,0x09,0x71, 0x28,0x70,0x6F, 0x35,0x65,0x49, 0x7D,0x74,0x5C, | |
| 0x24,0x2C,0x5D, 0x2D,0x77,0x27, 0x54,0x44,0x59, 0x37,0x3F,0x25, | |
| 0x7B,0x6D,0x7C, 0x3D,0x7C,0x23, 0x6C,0x43,0x6D, 0x34,0x38,0x28, | |
| 0x6D,0x5E,0x31, 0x4E,0x5B,0x39, 0x2B,0x6E,0x7F, 0x30,0x57,0x36, | |
| 0x6F,0x4C,0x54, 0x74,0x34,0x34, 0x6B,0x72,0x62, 0x4C,0x25,0x4E, | |
| 0x33,0x56,0x30, 0x56,0x73,0x5E, 0x3A,0x68,0x73, 0x78,0x55,0x09, | |
| 0x57,0x47,0x4B, 0x77,0x32,0x61, 0x3B,0x35,0x24, 0x44,0x2E,0x4D, | |
| 0x2F,0x64,0x6B, 0x59,0x4F,0x44, 0x45,0x3B,0x21, 0x5C,0x2D,0x37, | |
| 0x68,0x41,0x53, 0x36,0x61,0x58, 0x58,0x7A,0x48, 0x79,0x22,0x2E, | |
| 0x09,0x60,0x50, 0x75,0x6B,0x2D, 0x38,0x4E,0x29, 0x55,0x3D,0x3F, | |
| 0x51,0x67,0x2f | |
| } ; | |
| const unsigned char pick_encoding[64] = { | |
| 1, 2, 0, 1, 2, 0, 2, 0, 0, 2, 0, 2, 1, 0, 2, 0, | |
| 1, 0, 2, 0, 1, 1, 2, 0, 0, 2, 1, 0, 2, 0, 0, 2, | |
| 1, 1, 0, 2, 0, 2, 0, 1, 0, 1, 1, 2, 0, 1, 0, 2, | |
| 1, 0, 2, 0, 1, 1, 2, 0, 0, 1, 1, 2, 0, 1, 0, 2 | |
| }; | |
| unsigned char transformed[3][127]; | |
| int digits[0x7a]; | |
| int urlencoded = 0; | |
| int htmlencoded = 0; | |
| int verbose = 0; | |
| int smart = 1; | |
| unsigned char unescape (unsigned char c) | |
| { | |
| static unsigned char escapes[] = "#&!*$"; | |
| static unsigned char escaped[] = "\r\n<>@"; | |
| int i=0; | |
| if (c > 127) | |
| return c; | |
| while (escapes[i]) | |
| { | |
| if (escapes[i] == c) | |
| return escaped[i]; | |
| i++; | |
| } | |
| return '?'; | |
| } | |
| void maketrans (void) | |
| { | |
| int i, j; | |
| for (i=0; i<32; i++) | |
| for (j=0; j<3; j++) | |
| transformed[j][i] = i; | |
| for (i=31; i<=127; i++) | |
| for (j=0; j<3; j++) | |
| transformed[j][rawData[(i-31)*3 + j]] = (i==31) ? 9 : i; | |
| } | |
| void makedigits (void) | |
| { | |
| int i; | |
| for (i=0; i<26; i++) | |
| { | |
| digits['A'+i] = i; | |
| digits['a'+i] = i+26; | |
| } | |
| for (i=0; i<10; i++) | |
| digits['0'+i] = i+52; | |
| digits[0x2b] = 62; | |
| digits[0x2f] = 63; | |
| } | |
| unsigned long int decodeBase64 (unsigned char *p) | |
| { | |
| unsigned long int val = 0; | |
| val += (digits[p[0]] << 2); | |
| val += (digits[p[1]] >> 4); | |
| val += (digits[p[1]] & 0xf) << 12; | |
| val += ((digits[p[2]] >> 2) << 8); | |
| val += ((digits[p[2]] & 0x3) << 22); | |
| val += (digits[p[3]] << 16); | |
| val += ((digits[p[4]] << 2) << 24); | |
| val += ((digits[p[5]] >> 4) << 24); | |
| /* 543210 543210 543210 543210 543210 543210 | |
| 765432 | |
| 10 | |
| ba98 | |
| fedc | |
| 76 | |
| 543210 | |
| fedcba 98---- | |
| |- LSB -||- -||- -| |- MSB -| | |
| */ | |
| return val; | |
| } | |
| /* | |
| Char. number range | UTF-8 octet sequence | |
| (hexadecimal) | (binary) | |
| --------------------+--------------------------------------------- | |
| 0000 0000-0000 007F | 0xxxxxxx | |
| 0000 0080-0000 07FF | 110xxxxx 10xxxxxx | |
| 0000 0800-0000 FFFF | 1110xxxx 10xxxxxx 10xxxxxx | |
| 0001 0000-0010 FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx | |
| */ | |
| int isLeadByte (unsigned int cp, unsigned char ucByte) | |
| { | |
| /* Code page 932 - Japanese Shift-JIS - 0x81-0x9f | |
| 0xe0-0xfc | |
| 936 - Simplified Chinese GBK - 0xa1-0xfe | |
| 949 - Korean Wansung - 0x81-0xfe | |
| 950 - Traditional Chinese Big5 - 0x81-0xfe | |
| 1361 - Korean Johab - 0x84-0xd3 | |
| 0xd9-0xde | |
| 0xe0-0xf9 */ | |
| switch (cp) | |
| { | |
| case 932: | |
| if ((ucByte > 0x80) && (ucByte < 0xa0)) return 1; | |
| if ((ucByte > 0xdf) && (ucByte < 0xfd)) return 1; | |
| else return 0; | |
| case 936: | |
| if ((ucByte > 0xa0) && (ucByte < 0xff)) return 1; | |
| else return 0; | |
| case 949: | |
| case 950: | |
| if ((ucByte > 0x80) && (ucByte < 0xff)) return 1; | |
| else return 0; | |
| case 1361: | |
| if ((ucByte > 0x83) && (ucByte < 0xd4)) return 1; | |
| if ((ucByte > 0xd8) && (ucByte < 0xdf)) return 1; | |
| if ((ucByte > 0xdf) && (ucByte < 0xfa)) return 1; | |
| else return 0; | |
| default: | |
| return 0; | |
| } | |
| } | |
| struct entitymap { | |
| char *entity; | |
| char mappedchar; | |
| }; | |
| struct entitymap entities[] = { | |
| {"excl",33},{"quot",34},{"num",35},{"dollar",36},{"percent",37}, | |
| {"amp",38},{"apos",39},{"lpar",40},{"rpar",41},{"ast",42}, | |
| {"plus",43},{"comma",44},{"period",46},{"colon",58},{"semi",59}, | |
| {"lt",60},{"equals",61},{"gt",62},{"quest",63},{"commat",64}, | |
| {"lsqb",91},{"rsqb",93},{"lowbar",95},{"lcub",123},{"verbar",124}, | |
| {"rcub",125},{"tilde",126}, {NULL, 0} | |
| }; | |
| char decodeMnemonic ( unsigned char *mnemonic) | |
| { | |
| int i=0; | |
| while (entities[i].entity != NULL) | |
| { | |
| if (strcmp(entities[i].entity, mnemonic)==0) | |
| return entities[i].mappedchar; | |
| i++; | |
| } | |
| printf ("Warning: did not recognize HTML entity '%s'\n", mnemonic); | |
| return '?'; | |
| } | |
| int ScriptDecoder (unsigned char *inname, unsigned char *outname, unsigned int cp) | |
| { | |
| unsigned char inbuf[LEN_INBUF+1]; | |
| unsigned char outbuf[LEN_OUTBUF+1]; | |
| unsigned char c, c1, c2, lenbuf[7], csbuf[7], htmldec[8]; | |
| unsigned char marker[] = "#@~^"; | |
| int ustate, nextstate, state = 0; | |
| int i, j, k, m, ml, hd = 0; | |
| int utf8 = 0; | |
| unsigned long int csum = 0, len = 0; | |
| FILE *infile, *outfile; | |
| infile = fopen ((const char*)inname, "rb"); | |
| outfile = fopen ((const char*)outname, "wb"); | |
| if (!infile || !outfile) | |
| { | |
| printf ("Error opening file!\n"); | |
| return 10; | |
| } | |
| maketrans(); | |
| makedigits(); | |
| memset (inbuf, 0, sizeof (inbuf)); | |
| memset (outbuf, 0, sizeof (outbuf)); | |
| memset (lenbuf, 0, sizeof (lenbuf)); | |
| state = STATE_INIT_COPY; | |
| i = 0; | |
| j = 0; | |
| while (state) | |
| { | |
| if (inbuf[i] == 0) | |
| { | |
| if (feof (infile)) | |
| { | |
| if (len) | |
| { | |
| printf ("Error: Premature end of file.\n"); | |
| if (utf8>0) | |
| printf ("Tip: The file seems to contain special characters, try the -cp option.\n"); | |
| } | |
| break; | |
| } | |
| memset (inbuf, 0, sizeof (inbuf)); | |
| fgets ((char*)inbuf, LEN_INBUF, infile); | |
| i = 0; | |
| continue; | |
| } | |
| if (j == LEN_OUTBUF) | |
| { | |
| fwrite (outbuf, sizeof(char), j, outfile); | |
| j = 0; | |
| } | |
| if ((urlencoded==1) && (inbuf[i]=='%')) | |
| { | |
| ustate = state; /* save state */ | |
| state = STATE_URLENCODE_1; /* enter decoding state */ | |
| i++; /* flush char */ | |
| continue; | |
| } | |
| /* 2 means we do urldecoding but wanted to avoid decoding an | |
| already decoded % for the second time */ | |
| if (urlencoded==2) | |
| urlencoded=1; | |
| if ((htmlencoded==1) && (inbuf[i]=='&')) | |
| { | |
| ustate = state; | |
| state = STATE_HTMLENCODE; | |
| hd = 0; | |
| i++; | |
| continue; | |
| } | |
| /* 2 means we do htmldecoding but wanted to avoid decoding an | |
| already decoded & for the second time */ | |
| if (htmlencoded==2) | |
| htmlencoded=1; | |
| switch (state) | |
| { | |
| case STATE_INIT_COPY: | |
| ml = strlen ((const char*)marker); | |
| m = 0; | |
| state = STATE_COPY_INPUT; | |
| break; | |
| /* after decoding a block, we have to wait for the current | |
| script block to be closed (>) */ | |
| case STATE_WAIT_FOR_CLOSE: | |
| if (inbuf[i] == '>') | |
| state = STATE_WAIT_FOR_OPEN; | |
| outbuf[j++] = inbuf[i++]; | |
| break; | |
| /* and a new block to be opened again (<) */ | |
| case STATE_WAIT_FOR_OPEN: | |
| if (inbuf[i] == '<') | |
| state = STATE_INIT_COPY; | |
| outbuf[j++] = inbuf[i++]; | |
| break; | |
| case STATE_COPY_INPUT: | |
| if (inbuf[i] == marker[m]) | |
| { | |
| i++; | |
| m++; | |
| } | |
| else | |
| { | |
| if (m) | |
| { | |
| k = 0; | |
| state = STATE_FLUSHING; | |
| } | |
| else | |
| outbuf[j++] = inbuf[i++]; | |
| } | |
| if (m == ml) | |
| state = STATE_INIT_READLEN; | |
| break; | |
| case STATE_FLUSHING: | |
| outbuf[j++] = marker[k++]; | |
| m--; | |
| if (m==0) | |
| state = STATE_COPY_INPUT; | |
| break; | |
| case STATE_SKIP_ML: | |
| i++; | |
| if (!(--ml)) | |
| state = nextstate; | |
| break; | |
| case STATE_INIT_READLEN: | |
| ml = 6; | |
| state = STATE_READLEN; | |
| break; | |
| case STATE_READLEN: | |
| lenbuf[6-ml] = inbuf[i++]; | |
| if (!(--ml)) | |
| { | |
| len = decodeBase64 (lenbuf); | |
| if (verbose) | |
| printf ("Msg: Found encoded block containing %d characters.\n", len); | |
| m = 0; | |
| ml = 2; | |
| state = STATE_SKIP_ML; | |
| nextstate = STATE_DECODE; | |
| } | |
| break; | |
| case STATE_DECODE: | |
| if (!len) | |
| { | |
| ml = 6; | |
| state = STATE_CHECKSUM; | |
| break; | |
| } | |
| if (inbuf[i] == '@') | |
| state = STATE_UNESCAPE; | |
| else | |
| { | |
| if ((inbuf[i] & 0x80) == 0) | |
| { | |
| outbuf[j++] = c = transformed[pick_encoding[m%64]][inbuf[i]]; | |
| csum += c; | |
| m++; | |
| } | |
| else | |
| { | |
| if (!cp && (inbuf[i] & 0xc0)== 0x80) | |
| { | |
| // utf-8 but not a start byte | |
| len++; | |
| utf8=1; | |
| } | |
| outbuf[j++] = inbuf[i]; | |
| if ((cp) && (isLeadByte (cp,inbuf[i]))) | |
| state = STATE_DBCS; | |
| } | |
| } | |
| i++; | |
| len--; | |
| break; | |
| case STATE_DBCS: | |
| outbuf[j++] = inbuf[i++]; | |
| state = STATE_DECODE; | |
| break; | |
| case STATE_UNESCAPE: | |
| outbuf[j++] = c = unescape (inbuf[i++]); | |
| csum += c; | |
| len--; | |
| m++; | |
| state = STATE_DECODE; | |
| break; | |
| case STATE_CHECKSUM: | |
| csbuf[6-ml] = inbuf[i++]; | |
| if (!(--ml)) | |
| { | |
| csum -= decodeBase64 (csbuf); | |
| if (csum) | |
| { | |
| printf ("Error: Incorrect checksum! (%lu)\n", csum); | |
| if (cp) | |
| printf ("Tip: Maybe try another codepage.\n"); | |
| else | |
| { | |
| if (utf8>0) | |
| printf ("Tip: The file seems to contain special characters, try the -cp option.\n"); | |
| else | |
| printf ("Tip: the file may be corrupted.\n"); | |
| } | |
| csum=0; | |
| } | |
| else | |
| { | |
| if (verbose) | |
| printf ("Msg: Checksum OK\n"); | |
| } | |
| m = 0; | |
| ml = 6; | |
| state = STATE_SKIP_ML; | |
| if (smart) | |
| nextstate = STATE_WAIT_FOR_CLOSE; | |
| else | |
| nextstate = STATE_INIT_COPY; | |
| } | |
| break; | |
| /* urlencoded, the first character */ | |
| case STATE_URLENCODE_1: | |
| c1 = inbuf[i++] - 0x30; | |
| if (c1 > 0x9) c1-= 0x07; | |
| if (c1 > 0x10) c1-= 0x20; | |
| state = STATE_URLENCODE_2; | |
| break; | |
| /* urlencoded, second character */ | |
| case STATE_URLENCODE_2: | |
| c2 = inbuf[i] - 0x30; | |
| if (c2 > 0x9) c2-= 0x07; | |
| if (c2 > 0x10) c2-= 0x20; | |
| inbuf[i] = c2 + (c1<<4); /* copy decoded char back on input */ | |
| urlencoded=2; /* avoid looping in case this was an % */ | |
| state = ustate; /* restore old state */ | |
| break; | |
| /* htmlencoded */ | |
| case STATE_HTMLENCODE: | |
| c1 = inbuf[i]; | |
| if (c1 != ';') | |
| { | |
| i++; | |
| htmldec[hd++] = c1; | |
| if (hd>7) | |
| { | |
| htmldec[7]=0; | |
| printf ("Error: HTML decode encountered a too long mnemonic (%s...)\n", htmldec); | |
| exit(10); | |
| } | |
| } | |
| else /* ';' = end of mnemonic */ | |
| { | |
| htmldec[hd] = 0; | |
| inbuf[i] = decodeMnemonic (htmldec); /* skip the & */ | |
| htmlencoded = 2; /* avoid looping in case of & */ | |
| state = ustate; | |
| } | |
| break; | |
| default: | |
| printf ("Internal Error: Invalid state: %d\n", state); | |
| break; | |
| } | |
| } | |
| fwrite (outbuf, sizeof (char), j, outfile); | |
| fclose (infile); | |
| fclose (outfile); | |
| return 0; | |
| } | |
| int main (int argc, char **argv) | |
| { | |
| int i, cp = 0; | |
| if (argc < 3) | |
| { | |
| puts ("ScrDec v1.8 - Decoder for Microsoft Script Encoder\n" | |
| "(c)2000-2005 MrBrownstone, mrbrownstone@ virtualconspiracy.com\n" | |
| "Home page: http://www.virtualconspiracy.com/scrdec.html\n\n" | |
| "Usage: scrdec18 <infile> <outfile> [-cp codepage] [-urldec|-htmldec]\n" | |
| " [-verbose] [-dumb]\n\n" | |
| "Code pages can be 932 - Japanese\n" | |
| " 936 - Chinese (Simplified)\n" | |
| " 950 - Chinese (Traditional)\n" | |
| " 949 - Korean (Wansung)\n" | |
| " 1361 - Korean (Johab)\n" | |
| "Any other code pages don't need to be specified.\n\n" | |
| "Use -urldec to unescape %xx style encoding on the fly, or\n" | |
| " -htmldec to unescape & style encoding.\n" | |
| "For extra information, add the -verbose switch\n" | |
| "You might not want to use the smart HTMLGuardian defeation mechanism.\n" | |
| " In that case, add the -dumb switch.\n"); | |
| return 10; | |
| } | |
| i=3; | |
| while (i<argc) | |
| { | |
| if (strcmp (argv[i], "-cp")==0) | |
| { | |
| i++; | |
| if (i<argc) cp = atoi (argv[i]); | |
| else | |
| { | |
| puts ("-cp should be followed by a code page identifier"); | |
| return 10; | |
| } | |
| } | |
| else | |
| if (strcmp (argv[i], "-urldec")==0) | |
| urlencoded = 1; | |
| else | |
| if (strcmp (argv[i], "-htmldec")==0) | |
| htmlencoded = 1; | |
| else | |
| if (strcmp (argv[i], "-verbose")==0) | |
| verbose = 1; | |
| else | |
| if (strcmp (argv[i], "-dumb")==0) | |
| smart = 0; | |
| i++; | |
| } | |
| return ScriptDecoder ((unsigned char*)argv[1], (unsigned char*)argv[2], cp); | |
| } |
After decoding, we are greeted with the full CVE-2019–1367 exploit, but obfuscated:
Some snippets from Magnitude Exploit KIT CVE-2019–1367
The exploit comes with some of the native javascript functions encoded, obfuscating the real nature of the script. but still we can spot the vulnerable Array.Sort() function , and the use of the Enumerator object, and subsequent calls to CollectGarbage() for some heap feng shui!
Understanding the vulnerability
When dealing with unknown vulnerabilities, Patch diffing or debug trace diffing aka differential debugging are used to detect the root cause and this is usually the way to go to find vulnerable code path(s) or understand a recent patch intended to kill a bug. F-secure did a write up analyzing CVE-2020–9674 using differential debugging.
To put it in simple words, CVE-2019–1367 is caused by Jscript’s Garbage collector (GC) that doesn’t track properly the arguments of Array.sort() callback function.
Not updating the reference count of a currently in-use object. This results in the object currently in-use to be prematurely freed.
In other words, the arguments of Array.sort() callback function will be pointing to a prematurely freed memory allocation after calling Garbage collection this is also knowns as dangling pointer.
Why this is a big deal? Well if we can somehow replace the memory pointed by this dangling pointer (and we will see this in part2, the exploitation part) by an object we control we can start executing our own code : which is typically a shellcode that will execute and perform tasks like: Popping a calculator, or /*spoiler alert*/ downloading a ransomware as we will see in part 3.
We can write a minimalistic proof of concept to quickly demonstrate this vulnerability with the elements we have until now. We will create an empty array to be able to call Array.sort() function, save a references to arguments, force garbage collection, then try to access to the saved references again and see what happens.
CVE-2019–1367 proof of concept
Below is our proof-of-concept for CVE-2019–1367. We allocated enough objects to allocate more than 50 GC blocks. Why 50? If we look at the GcBlockFactory::freeBlk method in Jscript.dll we can see that GC maintains a list of 50 free GC Blocks. When garbage collection is called, GC blocks marked as free will be added to this list, if the counter of the free block is ≥ 50 then next free blocks won’t be added to the block list, but will get directly deallocated:
GcGlockFactory::FreeBlk decompiled
The garbage collection process in jscript.dll is based on the mark-sweep algorithm :
In IE Jscript engine this can be translated to 3 different steps:
Step 1: Mark all the VARs. The marking is done via | 0x881.
Step 2: Scavenge functions are called for each VARs object (each object have its scavenge function implemented) Which consist of checking if any references to these marked objects do exist, if it’s the case then the objects are unmarked.
Step 3: Reclaim Garbage is called to free all the marked VARs. Note: A whole GC block is Freed if all of its VARs are marked and therefore freed.
We can write a proof-of-concept where we will allocate more than 50 GcBlocks, and keep a reference of one of the Objects in Arguments, trigger garbage collection and access to that reference again via Alert() to trigger the bug.
We did a test on both IE8 and IE11 with the below code:
In IE8, we get a crash (tested on Windows 7 SP1 32bits)
IE 8 32bits crash
IE11 the current tab crashes then get reloaded (tested on Windows 10 x64)
IE11 crashing tab
Attaching WinDBG to IE11 with page heap enabled, we can this time catch the exception as we can see in the trace below where EDI points to an unspecified memory address 0x23d28a30:
Analyzing 0x23d28a30 memory address we can see it is pointing to a memory address belonging to a memory block that got freed. In the stack trace, we can notice the calls to GcAlloc::ReclaimGarbage and especially GcBlockFactory::FreeBlk that was responsible for freeing the block:
With this minimal Proof of concept, we confirmed the use-after-free vulnerability, addressed by CVE-2019–1367.
This use-after-free bug can be exploited and that is covered in part2.
Readers can go to part3 for the shellcode analysis, resulting from a successful exploitation of this bug!









