Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fuzz CVE-2019-1118 #59

Open
xinali opened this issue Jun 12, 2020 · 0 comments
Open

fuzz CVE-2019-1118 #59

xinali opened this issue Jun 12, 2020 · 0 comments

Comments

@xinali
Copy link
Owner

xinali commented Jun 12, 2020

fuzz CVE-2019-1118

这篇来分析一下CVE-2019-1118,问题为stack corruption in OpenType font handling due to negative cubeStackDepth

漏洞复现

搭建环境,简单复现一下

git clone https://github.com/adobe-type-tools/afdko
cd afdko
git checkout 2.8.8
cd c
bash buildalllinux.sh debug

根据给出的poc测试

1563767319500

可以发现,出错了,但是被afdko捕获了。

这样的话,在重点位置设一下断点,再来看一下

首先测试一下do_blend_cube.otf

pwndbg> b t2cstr.c:1057
Breakpoint 1 at 0x466af2: file ../../../../../source/t2cstr/t2cstr.c, line 1057.
pwndbg> run -cff do_blend_cube.otf 

看一下效果

1563767767380

结合着反汇编代码看,效果可能更好

.text:0000000000466AF2                 mov     esi, [rbp+nBlends]
.text:0000000000466AF5                 mov     rdi, [rbp+h]
.text:0000000000466AF9                 add     rdi, 32D60h
.text:0000000000466B00                 mov     rax, [rbp+h]    ; h
.text:0000000000466B04                 movsxd  rax, dword ptr [rax+32D44h]; 取得索引
.text:0000000000466B0B                 imul    rax, 1920h; cube大小
.text:0000000000466B12                 add     rdi, rax
.text:0000000000466B15                 imul    esi, [rdi+10h]
.text:0000000000466B19                 mov     [rbp+nElements], esi

可以发现h->cube数组取值是通过乘法实现的,当索引为-1h->cubeStackDepth==-1时,

imul rax, 1920h ==> imul 0xffffffff, 1920h

cube数组每项的大小:sizeof(h->cube[0]) == 0x1920

再变换一下

((struct cube)h->cube)-1

相当于h->cube指针向前移动了一个数组值,即0x1920个字节

再来看看struct _t2cCtx的大小

1563805691018

向前移动了,但是((struct cube)h->cube)-1的位置还是在_t2cCtx结构体中,验证一下

继续单步执行到这,索引值得出

1563805840591

继续si

1563805966760

此时

1563806157829

可以发现0x9d3f8 > 0x31880 ,验证也确实还在结构体中。

这样的话,即使是h->cubeStackDepth==-1也不会导致内存访问出错,最多也就是分析错误,被afkdo捕获也就不奇怪了。我们的结果也确实是这样

但是PJ0给的例子中,存在了一个叫做redzone patch的操作,打完patch之后会出现user-after-poison的错误。

就像这样

==96052==ERROR: AddressSanitizer: use-after-poison on address 0x7ffea1a88890 at pc 0x00000069e6e2 bp 0x7ffea1a46bb0 sp 0x7ffea1a46ba8
READ of size 4 at 0x7ffea1a88890 thread T0
    #0 0x69e6e1 in do_blend_cube afdko/c/public/lib/source/t2cstr/t2cstr.c:1057:58
    #1 0x6855fd in t2Decode afdko/c/public/lib/source/t2cstr/t2cstr.c:1857:38
    #2 0x670a5b in t2cParse afdko/c/public/lib/source/t2cstr/t2cstr.c:2591:18
    #3 0x542960 in readGlyph afdko/c/public/lib/source/cffread/cffread.c:2927:14
    #4 0x541c32 in cfrIterateGlyphs afdko/c/public/lib/source/cffread/cffread.c:2966:9
    #5 0x509662 in cfrReadFont afdko/c/tx/source/tx.c:151:18
    #6 0x508cc3 in doFile afdko/c/tx/source/tx.c:429:17
    #7 0x506b2e in doSingleFileSet afdko/c/tx/source/tx.c:488:5
    #8 0x4fc91e in parseArgs afdko/c/tx/source/tx.c:558:17
    #9 0x4f9470 in main afdko/c/tx/source/tx.c:1631:9
    #10 0x7fa93072e2b0 in __libc_start_main
    #11 0x41e5b9 in _start

如果我们想这样的话,该怎么做呢?

很遗憾,关于手动设置redzone的资料特别少,中文基本上就是纯空白了。而且我发现,基本上这个方法就是PJ0自己成员在用,其他人很少有使用过的。

为了实现这个功能,我把sanitizer的实现资料和相关redzone的源码全部简单过了一遍,最后弄懂了redzone的相关定义和用法,这部分想要了解的话,自己查资料吧,内容比较多,可以重点看一下PJ0介绍sanitizer的几个paper

最后在sanitizer/asan_interface.h中看到了这部分代码

//===-- sanitizer/asan_interface.h ------------------------------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
//
// This file is a part of AddressSanitizer (ASan).
//
// Public interface header.
//===----------------------------------------------------------------------===//
#ifndef SANITIZER_ASAN_INTERFACE_H
#define SANITIZER_ASAN_INTERFACE_H

#include <sanitizer/common_interface_defs.h>

#ifdef __cplusplus
extern "C" {
#endif
/// Marks a memory region (<c>[addr, addr+size)</c>) as unaddressable.
///
/// This memory must be previously allocated by your program. Instrumented
/// code is forbidden from accessing addresses in this region until it is
/// unpoisoned. This function is not guaranteed to poison the entire region -
/// it could poison only a subregion of <c>[addr, addr+size)</c> due to ASan
/// alignment restrictions.
///
/// \note This function is not thread-safe because no two threads can poison or
/// unpoison memory in the same memory region simultaneously.
///
/// \param addr Start of memory region.
/// \param size Size of memory region.
void __asan_poison_memory_region(void const volatile *addr, size_t size);

/// Marks a memory region (<c>[addr, addr+size)</c>) as addressable.
///
/// This memory must be previously allocated by your program. Accessing
/// addresses in this region is allowed until this region is poisoned again.
/// This function could unpoison a super-region of <c>[addr, addr+size)</c> due
/// to ASan alignment restrictions.
///
/// \note This function is not thread-safe because no two threads can
/// poison or unpoison memory in the same memory region simultaneously.
///
/// \param addr Start of memory region.
/// \param size Size of memory region.
void __asan_unpoison_memory_region(void const volatile *addr, size_t size);

// Macros provided for convenience.
#if __has_feature(address_sanitizer) || defined(__SANITIZE_ADDRESS__)
/// Marks a memory region as unaddressable.
///
/// \note Macro provided for convenience; defined as a no-op if ASan is not
/// enabled.
///
/// \param addr Start of memory region.
/// \param size Size of memory region.
#define ASAN_POISON_MEMORY_REGION(addr, size) \
  __asan_poison_memory_region((addr), (size))

/// Marks a memory region as addressable.
///
/// \note Macro provided for convenience; defined as a no-op if ASan is not
/// enabled.
///
/// \param addr Start of memory region.
/// \param size Size of memory region.
#define ASAN_UNPOISON_MEMORY_REGION(addr, size) \
  __asan_unpoison_memory_region((addr), (size))
#else
#define ASAN_POISON_MEMORY_REGION(addr, size) \
  ((void)(addr), (void)(size))
#define ASAN_UNPOISON_MEMORY_REGION(addr, size) \
  ((void)(addr), (void)(size))
#endif

宏定义ASAN_UNPOISON_MEMORY_REGION指向__asan_poison_memory_region,利用这个函数可以设置redzone,之后利用__asan_unpoison_memory_region解除设置

方法找到了,那就来看具体代码,设置redzone,在do_blend_cube函数中

/* Execute "blend" op. Return 0 on success else error code. */
static int do_blend_cube(t2cCtx h, int nBlends) {
    int i;
    __asan_poison_memory_region(h->cube-1, sizeof(struct _t2cCtx)); // 设置redzone
    int nElements = nBlends * h->cube[h->cubeStackDepth].nMasters;
    int iBase = h->stack.cnt - nElements;
    int k = iBase + nBlends;

    if (h->cube[h->cubeStackDepth].nMasters <= 1)
        return t2cErrInvalidWV;
    CHKUFLOW(h, nElements);

    if (h->flags & FLATTEN_CUBE) {
        for (i = 0; i < nBlends; i++) {
            int j;
            double x = INDEX(iBase + i);
            for (j = 1; j < h->cube[h->cubeStackDepth].nMasters; j++)
                x += INDEX(k++) * h->cube[h->cubeStackDepth].WV[j];
            INDEX(iBase + i) = (float)x;
        }
    } else {
        float blendVals[kMaxCubeMasters * kMaxBlendOps];
        for (i = 0; i < nElements; i++) {
            blendVals[i] = INDEX(iBase + i);
        }
        callback_blend_cube(h, nBlends, nElements, blendVals);
    }

    h->stack.cnt = iBase + nBlends;

    __asan_unpoison_memory_region(h->cube-1, sizeof(struct _t2cCtx)); // 解除redzone

    return 0;
}

这里有一点需要注意,poisonsize一定要设置的够大,否则会没有poison的效果,我这里是取_t2cCtx结构体的大小,这样就保证了怎么移动我们都能检测到

来看一下效果

1563807501320

fuzzing 代码

还是这么写main函数

/* Main program. */
int CTL_CDECL main(int argc, char *argv[]) {
    txCtx h;
    char *progname;

    /* Get program name */
    progname = tail(argv[0]);
    --argc;
    ++argv;

    /* Allocate program context */
    h = malloc(sizeof(struct txCtx_));
    if (h == NULL) {
        fprintf(stderr, "%s: out of memory\n", progname);
        return EXIT_FAILURE;
    }
    memset(h, 0, sizeof(struct txCtx_));

    h->app = APP_TX;
    h->appSpecificInfo = NULL; /* unused in tx.c, used in rotateFont.c & mergeFonts.c */
    h->appSpecificFree = txFree;

    txNew(h, progname);

    h->t1r.flags = 0; /* I initialize these here,as I need to set the std Encoding flags before calling setMode. */
    h->cfr.flags = 0;
    h->cfw.flags = 0;
    h->dcf.flags = DCF_AllTables | DCF_BreakFlowed;
    h->dcf.level = 5;
    h->svr.flags = 0;
    h->ufr.flags = 0;
    h->ufow.flags = 0;
    h->t1w.options = 0;

    // 设置cff模式
    setMode(h, mode_cff);

    // argv[1] 文件名,对应上面argv--
    doSingleFileSet(h, argv[0]);

    if (h->failmem.iFail == FAIL_REPORT) {
        fflush(stdout);
        fprintf(stderr, "mem_manage() called %ld times in this run.\n",
                h->failmem.iCall);
    }
    txFree(h);

    return 0;
}

出现异常利用上面的测试一下即可。

fuzz代码我公开了,测出有效洞可得找我哈 : )

还不会的话,只能看我的具体项目了,一起学习fuzz

后记

这个漏洞开始redzone patch的检测,我自己不会写,花了很长时间后来慢慢摸索出来了,就自己写了一个
后来我又给这个漏洞的发现者j00ru发了邮件,向他要了他的redzone patch,他很爽快的答应了,并将代码以附件的形式发给我了。
这里我们来分析一下(比我的redzone检测不知道要高到哪里去了)

代码所有权限属于j00ru,我仅用于学习使用

能有机会看到这么有实际作用的redzone检测代码,且看且珍惜

首先来看一下结构体的几处更改

第一处

1564037195237

接下来的几处

1564037302347

可以发现,在这个最重要的结构体里基本所有的数组都做了redzone标记,这样也就基本保证了这个结构体里的数组指针,在发生越界操作时,
大概率会操作redzone的数据,从而被捕获到。

再来看一下poison

static void PoisonArrays(t2cCtx h) {
  int i;

  ASAN_POISON_MEMORY_REGION(&h->stack.pre_array, sizeof(h->stack.pre_array));
  ASAN_POISON_MEMORY_REGION(&h->stack.pre_blendArray, sizeof(h->stack.pre_blendArray));
  ASAN_POISON_MEMORY_REGION(&h->stack.pre_blendArgs, sizeof(h->stack.pre_blendArgs));
  ASAN_POISON_MEMORY_REGION(&h->pre_BCA, sizeof(h->pre_BCA));
  ASAN_POISON_MEMORY_REGION(&h->pre_cube, sizeof(h->pre_cube));
  ASAN_POISON_MEMORY_REGION(&h->stems.pre_array, sizeof(h->stems.pre_array));
  ASAN_POISON_MEMORY_REGION(&h->mask.pre_bytes, sizeof(h->mask.pre_bytes));
  ASAN_POISON_MEMORY_REGION(&h->pre_regionIndices, sizeof(h->pre_regionIndices));

  ASAN_POISON_MEMORY_REGION(&h->stack.post_array, sizeof(h->stack.post_array));
  ASAN_POISON_MEMORY_REGION(&h->stack.post_blendArray, sizeof(h->stack.post_blendArray));
  ASAN_POISON_MEMORY_REGION(&h->stack.post_blendArgs, sizeof(h->stack.post_blendArgs));
  ASAN_POISON_MEMORY_REGION(&h->post_BCA, sizeof(h->post_BCA));
  ASAN_POISON_MEMORY_REGION(&h->post_cube, sizeof(h->post_cube));
  ASAN_POISON_MEMORY_REGION(&h->stems.post_array, sizeof(h->stems.post_array));
  ASAN_POISON_MEMORY_REGION(&h->mask.post_bytes, sizeof(h->mask.post_bytes));
  ASAN_POISON_MEMORY_REGION(&h->post_regionIndices, sizeof(h->post_regionIndices));

  for (i = 0; i < CUBE_LE_STACKDEPTH; i++) {
    ASAN_POISON_MEMORY_REGION(&h->cube[i].pre_composeOpArray, sizeof(h->cube[i].pre_composeOpArray));
    ASAN_POISON_MEMORY_REGION(&h->cube[i].pre_WV, sizeof(h->cube[i].pre_WV));

    ASAN_POISON_MEMORY_REGION(&h->cube[i].post_composeOpArray, sizeof(h->cube[i].post_composeOpArray));
    ASAN_POISON_MEMORY_REGION(&h->cube[i].post_WV, sizeof(h->cube[i].post_WV));
  }
}

具体详细的patch代码,自己琢磨吧,这种redzone patch的方式,记住,分析crashes肯定用得上!

参考

Microsoft DirectWrite / AFDKO stack corruption in OpenType font handling due to negative cubeStackDepth

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant