8

字符串类型混淆漏洞的研究

 2 years ago
source link: https://kiprey.github.io/2020/06/ANSI-Unicode-type-confusion-study/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client
  • 现在几乎已经很少会看到关于恶意字符串所造成的漏洞了,更不用说那些可利用的字符串了
  • 这并不奇怪,因为SDL(安全开发生命周期)已经禁止了所有不安全的函数
  • 但是,如果开发人员错误地使用了增强的安全功能,仍然可能造成严重的安全漏洞。
  • 在adobe acrobat Reader DC中,开发人员已经实现了一些增强的安全字符串处理函数。但开发人员有可能会错误使用这些函数。
  • 在一般情况下,这不是什么大问题。然而,在这个特定的软件中处理字符串时,类型混淆的情况也很容易被触发。
  • 在某些场景中,可以利用这两个条件来实现代码执行。

2. 基本概念

Adobe Acrobat Reader DC使用SpiderMonkey(可能是24.2.0版本)作为其JavaScript引擎。

a. 字符串类型

  • 字符串在Windows上可以分为两类: ANSI字符串和Unicode字符串
  • ANSI字符串由一系列ANSI字符组成,其中每个字符编码为8位值;
    Unicode字符串由一系列Unicode字符组成。
  • Windows主要使用UTF-16编码的Unicode字符,其中每个字符编码为16位值。
  • ANSI字符串的结束符是\x00, Unicode字符串的结束符是\x00\x00

b. Byte Order Mark (BOM)

  • 如果一个字符有多个字节的数据,那么它可以用两种形式表示: 大端序(little-endian)和 小端序 (big-endian)
  • 大端序 将最重要字节放在前面,最不重要字节放在最后,而 小端序 顺序则相反
  • 例子 character UTF-16 Encoding Little-Endian Big-Endian 中 U + 4E2D 2D 4E 4E 2D 文 U + 6587 87 65 65 87
  • 对于UTF-16字符串,可以在字符集名称中指定字节顺序。例如,UTF16LE表示字节顺序是little-endian,而UTF-16BE表示字节顺序是big-endian
  • 我们还可以使用字节顺序标记(byte order mark, BOM)字符U+FEFF来指定字符串的字节顺序
  • BOM字符本身的字节顺序表示整个字符串的字节顺序。如果没有显式地指定,字符串的字节顺序可以是特定于平台的
  • 例子 UTF-16 String Little-Endian Big-Endian 中文 FF FE 2D 4E 87 65 FE FF 4E 2D 65 87

注意:BOM字符将始终位于字符串的开头。把它放在别的地方是没有意义的。

  • 这张图片简单的概括了上面内容。注意:BOM字符不唯一。FE FFBOM字符,而图中的EF BB BF 也是BOM字符
    img

c. 一些字符串的处理函数

  • 这里只列举其中的一个函数:strncpy

  • 大部分认为strncpy会比strcpy更安全,原因是strcpy中的第三个参数指定了处理数据的大小

    char *strncpy(char *destination, const char *source, size_t num);
  • 在大多数情况下,这看上去是正确的。但在处理一些特殊的情况时,它仍然很不安全。
    如果源字符串的长度等于或大于第三个参数num的值,则不会将在目标字符串的末尾添加NULL字符。
    而在处理没有终止符的字符串时,它将导致越界访问。以下是strncpy的源代码(来自glibc-2.23):

    char *
    STRNCPY (char *s1, const char *s2, size_t n)
    {
    size_t size = __strnlen (s2, n);
    if (size != n)
    memset (s1 + size, '\0', n - size);
    return memcpy (s1, s2, size);
    }
  • 这是为什么呢?原因是strncpy在设计时本来就不是strcpy的安全版本。

  • strncpy_s是微软实现的一个安全增强版本。在将内容复制到目标缓冲区时,这些函数总是确保可以追加终止null字符。否则,操作失败,将调用无效的参数处理程序。

  • 如果错误地使用了字符串处理函数的安全增强版本,则不能保证它们是安全的。例如,如果开发人员将错误的值作为目标缓冲区的大小,即使是函数strcpy_s也可能导致缓冲区溢出。

    char src[32] = { "0123456789abcdef" };
    char dst[10] = { 0 };
    strcpy_s(dst, 0x7FFF /*dst_size*/, src);

3. 漏洞学习

* 前置说明

  • adobe acrobat reader DC实现了一些增强的安全字符串处理功能,例如以下部分函数:

    Generic API ANSI Version Unicode Version strnlen_safe ASstrnlen_safe miUCSStrlen_safe strncpy_safe ASstrncpy_safe miUCSStrncpy_safe
  • 在处理字符串时,通用api将检查字符串的类型并将请求重定向到相应的函数。下面的代码展示了函数strnlen_safe是如何工作的:

    unsigned int strnlen_safe(char *str, unsigned int max_bytes, void *error_handler) {
    unsigned int result;
    if (str && str[0] == 0xFE && str[1] == 0xFF)
    result = miUCSStrlen_safe(str, max_bytes, error_handler);
    else
    result = ASstrnlen_safe(str, max_bytes, error_handler);
    return result;
    }
  • 在当前情况下,函数根据字符串的前两个字节检查字符串的类型。如果第一个字节是0xFE,第二个字节是0xFF,则该字符串将被识别为Unicode字符串,否则将被识别为ANSI字符串。而实际上,FE FFBOM的大端序标记字符

  • 触发漏洞需要满足以下两个条件:

    • 检查字符串的类型时可能会引发类型混淆。如果前两个字节是FE FF, ANSI字符串可以被识别为Unicode字符串。这可能导致越界访问,因为在ANSI字符串中找不到Unicode null终止符。例如以下表格:

      char . . F a k e

      U n i c o d e . HEX FE FF 46 61 6B 65 20 55 6E 69 63 6F 64 65 00

    • 开发人员不正确地使用了通用api。在大多数情况下,目标缓冲区的大小将设置为0x7FFFFFFF。如之前所述的那样,这可能会导致安全问题

      strnlen_safe(a2,  0x7FFFFFFF, 0)

a. CVE-2019-7032

  • CVE-2019-7032是一个越界读取漏洞,可以利用该漏洞实现绕过ASLR的信息公开。
  • 该漏洞在天府杯中,结合UAF漏洞可以进行代码执行。

注:CVE-2020-3804与该CVE漏洞原理与漏洞补丁等等大致相同,本文中将不再赘述。

2) 具体细节

  • 此漏洞会被以下JS代码所触发

    // Tested on Adobe Acrobat Reader DC 2019.010.20069
    var f = this.addField('f1', 'text', 0, [1, 2, 3, 4]);
    f.userName = '\xFE\xFF';
  • ANSI字符串被作为Unicode处理时,越界读取将会被触发,原因是处理函数无法找到Unicode字符串的终结符\x00\x00
    以下为miUCSStrlen_safe函数源代码

    unsigned int miUCSStrlen_safe(wchar_t *src, unsigned int max_bytes,
    void *error_handler) {
    unsigned int result;
    wchar_t *str = src;
    if ( src ) {
    unsigned int bytes = 0;
    if ( max_bytes ) {
    do {
    char ch = *(char *)str;
    ++str;
    if ( !ch && !*((char *)str - 1) ) break; // check Unicode terminator
    bytes += 2;
    } while ( bytes < max_bytes );
    }
    if ( bytes == max_bytes ) {
    void *handler = hb_set_invert;
    if ( error_handler ) handler = error_handler;
    handler(L"String greater than maxSize", L"miUCSStrlen_safe", 0, 0, 0);
    result = max_bytes;
    } else {
    result = bytes;
    }
    } else {
    void *handler = hb_set_invert;
    if ( error_handler ) handler = error_handler;
    handler(L"Bad parameter", L"miUCSStrlen_safe", 0, 0, 0);
    result = 0;
    }
    return result;
    }
  • 该漏洞是在为字段对象分配用户名属性时触发的。关键的部分是原始字符串将被复制到新创建的堆缓冲区中,该缓冲区将与属性相关联。
    这意味着我们可以通过JavaScript代码读取泄漏的信息。下面的代码显示了简化的漏洞模型。

    // src <- field.userName <- "\xFE\xFF....."
    // len <- number of bytes
    size_t len = strnlen_safe(src, 0x7FFFFFFF, 0); // Out-Of-Bounds Read
    char* dst = calloc(1, aligned_size(len + 4));
    memcpy(dst, src, len); // Information Disclosure
    dst[len] = dst[len + 1] = '\0';
    // field.userName <- dst

3) 漏洞修补分析

  • 该漏洞已在adobe acrobat reader DC 2019.010.20091中修复。
  • 通过放置额外3个NULL字节(实际上4个)至堆缓冲区的末端。
  • 即便ANSI字符串是由函数miUCSStrlen_safe处理的,它也会正常工作,因为Unicode-NULL终止符总是可以被找到。

b. CVE-2019-8199

  • CVE-2019-8199是一个越界读写漏洞,可以利用它来实现代码执行。

2) 具体细节

  • 此漏洞会被以下JS代码触发

    // Tested on Adobe Acrobat Reader DC 2019.010.20099
    Collab.unregisterReview('\xFE\xFF');
  • 与上面的CVE相同,之所以可以触发越界读写,是因为处理ANSI字符串时无法找到Unicode终止符。
    以下是miUCSStrcpy_safe源代码:

    signed int miUCSStrcpy_safe(wchar_t *dst, unsigned int max_bytes,
    wchar_t *src, void *error_handler) {
    wchar_t *ptr = dst;
    if ( dst ) {
    if ( src ) {
    if ( max_bytes > 1 ) {
    unsigned int max_len = max_bytes >> 1;
    do {
    wchar_t e = *(wchar_t *)((char *)ptr + (char *)src - (char *)dst);
    *ptr = e;
    ++ptr;
    if ( !e ) break; // check Unicode terminator
    --max_len;
    } while ( max_len );
    if ( !max_len ) {
    *(ptr - 1) = 0;
    void *handler = Handler;
    if ( error_handler ) handler = error_handler;
    handler(L"Destination too small", L"miUCSStrcpy_safe", 0, 0, 0);
    }
    return 0;
    }
    } else if ( max_bytes > 1 ) {
    *dst = 0;
    }
    }
    void *handler = Handler;
    if ( error_handler ) handler = error_handler;
    handler(L"Bad parameter", L"miUCSStrcpy_safe", 0, 0, 0);
    return -1;
    }
  • 该漏洞在调用Collab.unregisterReview函数时被触发。关键的部分是原始字符串将被复制到新创建的堆缓冲区中,该缓冲区的大小是通过调用ASstrnlen_safe计算的。但是复制请求是由函数strcpy_safe处理的。下面的代码显示了简化的漏洞模型

    // src <- arg of unregisterReview / unregisterApproval
    // src = "\xFE\xFF......"
    size_t len = ASstrnlen_safe(src, 0x7FFFFFFF, 0); // ANSI Function
    char* dst = (char *)malloc(len + 1);
    strcpy_safe(dst, 0x7FFFFFFF, src, 0); // Generic API -> Unicode Function
  • 所以,我们需要通过控制内存的布局来利用这个漏洞,以达到任意地址读写的目的

    ArrayBuffer对象在任意读写时起到了很大的作用。
    当该对象中的成员byteLength > 0x68时,该对象的后备存储将从系统堆中分配。
    堆内存分配时,所分配的缓冲区会比之前大0x10。这部分0x10大小的内存用于存储ObjectElements中的变量。

    • 新建大量的字符串和ArrayBuffer对象来占据内存。在这里我们创建了五个对象作为一个单元。

      ┌─────────────┬─────────────┬─────────────┬─────────────┬─────────────┐
      │ ArrayBuffer │ String │ ArrayBuffer │ ArrayBuffer │ ArrayBuffer │
      └─────────────┴─────────────┴─────────────┴─────────────┴─────────────┘
    • 将第一个和第三个内存区域释放,以创建大量的内存空洞

      ┌─────────────┬─────────────┬─────────────┬─────────────┬─────────────┐
      │ Hole │ String │ Hole │ ArrayBuffer │ ArrayBuffer │
      └─────────────┴─────────────┴─────────────┴─────────────┴─────────────┘
    • 使原始字符串的堆缓冲区分配到其中一个单元的第一个内存空洞,目标堆缓冲区分配到其中一个单元的第二个内存空洞。

      ┌─────────────┬─────────────┬─────────────┬─────────────┬─────────────┐
      │ Src │ String │ Dst │ ArrayBuffer │ ArrayBuffer │
      └─────────────┴─────────────┴─────────────┴─────────────┴─────────────┘
    • 通过strcpy_safe函数漏洞的越界拷贝,覆盖第四个ArrayBuffer的成员byteLength0xFFFF
      其中,String的内容(每个单元中的第二个对象)将用于覆盖byteLength
      之后通过该单元中的第四个对象(被覆盖byteLength后的ArrayBuffer),修改单元中的第五个对象(也就是图中的最后一个ArrayBuffer)的byteLength0xFFFFFFFF,以取得全局读写权限。

                           (2)byteLength to 0xFFFF             (4)Global Access
      ┌───>───>───>───>───>───>───┐ <──────┬──────>
      ┌─────────────┬┼────────────┬─────────────┬┼────────────┬──────┼──────┐
      │ Src │ String │ Dst │ ArrayBuffer │ ArrayBuffer │
      └──────┼──────┴─────────────┴┼────────────┴┼────────────┴┼────────────┘
      └──>───>───>───>────>─┘ └──>───>───>──┘
      (1)strcpy_safe (3)byteLength to 0xFFFFFFFF
  • 一旦获得了全局读写权限,我们就可以向后搜索来计算ArrayBuffer对象的后备存储缓冲区的基址,从而获得任意地址读写权限。

  • 一旦获得了任意读写权限后,就很容易实现代码执行。之后要做的就是下面这些操作,本文中不再赘述。

    • EIP劫持
    • ASLR绕过
    • DEB绕过
    • CFG绕过

3) 漏洞修补分析

  • 该漏洞已在adobe acrobat reader DC 2019.012.20034 版本中被修复。

  • 在堆缓冲区的末尾增加了两个额外的NULL字节(总共3个)。

    void *__cdecl sub_2383F4F8(int a1, int a2) {
    // --------------------------- cut ---------------------------
    if ( string ) {
    length = ASstrnlen_safe(string, 0x7FFFFFFFu, 0);
    if ( length < 0xFFFFFFFC ) {
    buffer = calloc(1, length + 3); // put 3 '\x00' at the end
    memcpy(buffer, string, length);
    }
    }
    // --------------------------- cut ---------------------------
    }
  • 这个补丁只能阻止漏洞被利用。POC仍然可能造成进程崩溃,因为如果目标堆缓冲区不够大,那么就无法存储Unicode字符串结束符。

    // src <- arg of unregisterReview / unregisterApproval
    // src = "\xFE\xFF......"
    size_t len = ASstrnlen_safe(src, 0x7FFFFFFF, 0); // ANSI Function
    char* dst = (char *)malloc(len + 1); // only sufficient for ANSI string
    strcpy_safe(dst, 0x7FFFFFFF, src, 0); // Generic API -> Unicode Function
  • 该漏洞最终在adobe acrobat reader DC 2019.021.20047版本中得到修复。通过为目标堆缓冲区分配2个额外字节来存储Unicode字符串终止符,解决了这个问题。

    void *__cdecl sub_2212A50B(char *src) {
    // --------------------------- cut ---------------------------
    signed int bytes = strnlen_safe(src, 0x7FFFFFFF, 0);
    void *dst = malloc(bytes + 2);
    memset(dst, 0, bytes + 2);
    strcpy_safe_wrapper(dst, src);
    // --------------------------- cut ---------------------------
    }
    int __cdecl strcpy_safe_wrapper(int dst, int src) {
    return strcpy_safe(dst, 0x7FFFFFFF, src, 0);
    }

白皮书后剩余的CVE-2020-3805由于原理与上述两个漏洞大致相同,故不再赘述。

  • 在这篇针对adobe acrobat Reader DC的漏洞分析中,大部分漏洞都是因为字符串类型混淆,导致ANSI字符串被 本用于处理Unicode字符串的函数 错误操作,其中以strnlen_safe为代表。
  • 在此类型漏洞中,因为在ANSI字符串中找不到Unicode字符串终结符,所以会造成越界 读/写,形成了一种漏洞模型,产生了4个CVE
  • 漏洞修补的方式也大同小异:在缓冲区末尾追加2个及以上的NULL字符,防止越界。

学习所使用的 白皮书PPT 如有需要请自取。议题的连接请点击这里


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK