zoukankan      html  css  js  c++  java
  • cajviewer逆向分析与漏洞挖掘

    文章首发于

    https://mp.weixin.qq.com/s/7STPL-2nCUKC3LHozN6-zg
    

    前言

    CAJViewer是一个论文查看工具,主要用于查看caj文件格式的论文。本文介绍对该软件进行逆向分析和漏洞挖掘的过程。

    代码地址

    https://github.com/hac425xxx/cajviewer-fuzz-data
    

    正文

    逆向分析

    首先分析的是CAJViewer的Windows版本,由于我们的目的是挖掘软件的漏洞,通过介绍我们知道CAJViewer本质上是一个文件解析程序,因此该软件的高危模块应该是软件中解析文件数据的部分,因此首先应该大概定义软件数据处理部分所在位置,Windows平台下可以使用 process monitor来进行初步的分析。

    首先打开process monitor并开始捕获事件,然后使用CAJViewer打开一个caj文件,等文件解析完成后停止捕获事件。

    image.png

    然后我们可以过滤一下需要查看的事件,比如上图设置了只查看文件操作并且只查看对 input.caj 文件的操作,该文件就是之前让CAJViewer打开的文件。

    然后我们可以找一下读文件的操作(ReadFile),因为大部分文件解析逻辑应该读一部分文件内容解析一部分,因此通过查看读文件时的调用栈就可以大概定位解析数据的模块,然后双击就可以查看调用相应函数的调用栈

    image.png

    通过查看多个数据读取的调用栈,可以发现ReaderEx.dll在调用栈中出现多次,因此大概可以猜测ReaderEx.dll应该主要负责处理文件数据。

    逆向了一会ReaderEx.dll后,发现CAJViewer今年还发布了Linux版本,于是下载下来分析了一下。下载下来后是一个可执行文件CAJViewer-x86_64-libc-2.24.AppImage,执行起来查看进程的maps发现其实软件会在tmp目录把打包好的二进制解压,然后去执行tmp目录下的二进制。

    image.png

    这里可以直接把/tmp/.mount_CAJVierjayBH/拷贝到一个目录,然后就可以直接执行 cajviewer了。

    image.png

    查看解压处理的二进制发现一个libreaderex_x64.so,看名字应该是ReaderEx.dll的Linux版本,然后使用IDA打开,发现比Windows版本的要好分析一点,信息也比ReaderEx.dll的多。于是接下来决定对Linux版本的二进制进行分析。

    首先看看主程序cajviewer,查看main函数可以发现软件是用qt写的

    image.png

    之后翻了一下函数列表,发现了MainWindow::OpenFile,看名称应该是打开一个文件。

    __int64 __fastcall MainWindow::OpenFile(MainWindow *this, const QString *a2)
    {
    
    
      v2 = this;
      QString::toUtf8_helper(&v16, a2);
      memset(v19, 0, sizeof(v19));
      *v19 = 0x2D8;
      *&v19[4] = 256;
      *&v19[8] = CAJFILE_CreateErrorObject(&v20);
      v3 = *&v19[8];
      if ( *v16 > 1 || (v5 = *(v16 + 2), v4 = v16, v5 != 24) )
      {
        QByteArray::reallocData(&v16, v16[1] + 1, *(v16 + 11) >> 31);
        v4 = v16;
        v5 = *(v16 + 2);
      }
      v6 = CAJFILE_OpenEx1(v4 + v5, v19);           // 打开文件
    

    这里对输入的QString进行一些处理后,调用了CAJFILE_OpenEx1函数,该函数位于libreaderex_x64.so

    Fuzz测试

    Fuzz CAJFILE_OpenEx1函数

    函数代码如下

    image.png

    函数的第一个参数是要解析的文件路径,第二个参数是一块内存,这个参数的结构可以查看MainWindow::OpenFile调用点。

    image.png

    可以看到in_buf的结构如下

    +0: 4个字节 in_buf的长度
    +4: 4个字节 一个整形值
    +8: 一个指针, 存放构造好的 ErrorObject
    

    使用调试器在这个函数下个断点,然后打开一个文件就可以看到入参如下

    image.png

    之后有简单的翻了一些该函数的实现,以及使用该函数的位置可以大概确定CAJFILE_OpenEx1用于打开一个文件,并会对文件的内容进行解析,因此下面打算使用AFL Qemu模式Fuzz一下这个函数。Fuzz之前需要写一点代码把so加载到内存,然后构造参数对目标函数进行测试。

    首先需要把SO加载到内存中并获取目标函数的地址

    void my_init(void) __attribute__((constructor)); //告诉gcc把这个函数扔到init section
    void my_init(void)
    {
        void *handle;
        handle = dlopen("/home/hac425/cajviewer/cajviewer-bin/usr/lib/libreaderex_x64.so", RTLD_LAZY);
    
        struct link_map *lm = (struct link_map *)handle;
        printf("%lx
    ", lm->l_addr);
    
        p_CAJFILE_OpenEx1 = dlsym(handle, "CAJFILE_OpenEx1");
        p_CAJFILE_CreateErrorObject = dlsym(handle, "CAJFILE_CreateErrorObject");
    }
    

    my_init会在main函数之前执行,代码流程如下

    • 首先dlopen把so加载到内存,并把so在内存中的基地址打印到屏幕,便于后续测试。
    • 然后使用dlsym获取CAJFILE_OpenEx1和CAJFILE_CreateErrorObject函数的地址。

    然后在main函数中就会构造参数调用目标函数

    int main(int argc, char **argv)
    {
        char buf[0x2D8];
        printf("main:%p
    ", main);
    
        memset(buf, 0, 0x2D8);
        *(unsigned int *)buf = 0x2D8;
        // *(unsigned int *)(buf + 4) = 256;
    
        // *(char* *)(buf + 8) = p_CAJFILE_CreateErrorObject();
    
        char *ret = p_CAJFILE_OpenEx1(argv[1], buf);
        return 0;
    }
    

    代码逻辑很简单,首先构造CAJFILE_OpenEx1函数的第二个参数,然后把argv[1]作为文件路径传入函数。

    然后编译一下

    gcc CAJFILE_OpenEx1.c -o test_CAJFILE_OpenEx1_dbg -ldl -lheapasan -L libheapasan/  -g
    

    编译后执行一下,可以看到正常执行完了,并打印出so的基地址和main函数的地址。

    harness$ ./test_CAJFILE_OpenEx1_dbg ~/input.caj
    string to intstring to intstring to intstring to intstring to intstring to intstring to intstring to intstring to intstring to intstring to intstring to intstringto intstring to intstring to intstring to intstring to intstring to intstring to intstring to intstring to intstring to intstring to intstring to intstring to intstring to intstring to intstring to int
    image base:0x7f6d87bff000
    p_CAJFILE_OpenEx1:0x7f6d881e486c
    main:0x555ed124cb71
    

    接下来再使用afl-qemu-trace执行一下,获取一些地址用于Fuzz,使用afl-qemu-trace执行一个可执行程序时,其进程的so的地址都是固定的。

    harness$ ~/AFLplusplus-2.66c/afl-qemu-trace ./test_CAJFILE_OpenEx1_dbg ~/input.caj
    string to intstring to intstring to intstring to intstring to intstring to intstring to intstring to intstring to intstring to intstring to intstring to intstringto intstring to intstring to intstring to intstring to intstring to intstring to intstring to intstring to intstring to intstring to intstring to intstring to intstring to intstring to intstring to int
    image base:0x400133e000
    p_CAJFILE_OpenEx1:0x400192386c
    main:0x4000000b71
    

    可以看到libreaderex_x64.so的基地址为0x400133e000test_CAJFILE_OpenEx1_dbgmain函数的地址为0x4000000b71

    然后去IDA中查看libreaderex_x64.so中代码段的范围

    image.png

    所以可以得到afl-qemu-trace执行时libreaderex_x64.so中代码段的范围为

    开始地址: 0x400133e000+0x3D4880 = 0x4001712880
    结束地址: 0x400133e000+0x90984F = 0x4001c4784f
    

    然后可以使用AFL进行测试了

    export AFL_CODE_START=0x4001712880
    export AFL_CODE_END=0x4001c4784f
    export AFL_ENTRYPOINT=0x4000000b71
    /home/hac425/AFLplusplus-2.66c/afl-fuzz -m none -Q -t 20000 -i in -o out -- ./test_CAJFILE_OpenEx1_dbg @@
    

    其中设置的环境变量的作用如下

    AFL_CODE_START 和 AFL_CODE_END 表示需要统计覆盖率的范围
    AFL_ENTRYPOINT 表示开启forkserver的位置
    

    image.png

    Fuzz UnCompressImage函数

    在测试CAJFILE_OpenEx1时,去翻了一下libreaderex_x64.so里面的其他函数,在查看字符串时发现了一些源码路径。

    image.png

    拿路径去网上搜了一下,发现是用到了Kakadu_V2.2.3这个开源库,这个库很古老了(2008年的),用于解析jpeg2000格式,版本老往往表示存在漏洞几率较大,而且jpeg2000格式很复杂,在其他软件中也发现了很多漏洞,于是下面仔细的看了下。

    下载到这个库的代码,然后一路回溯发现libreaderex_x64.so应该是在 jpeg2000.cpp里面实现了部分代码,最后一路跟到了DecodeJpeg2000函数,并基于开源代码把DecodeJpeg2000的参数基本弄清楚了。继续往上跟DecodeJpeg2000,找到了UnCompressImage函数,这个函数应该是解析图片数据的统一接口了。

    CAJViewer在解析CAJ等文件时,如果文件中嵌入了图片数据时,就会会使用libreaderex_x64.so中的UnCompressImage函数来对图片数据进行解析。

    image.png

    函数的参数信息如下:

    buffer: 保存从文件中提取出的图片数据
    type: 图片的类型
    buffer_length: 图片数据的长度
    剩下两个参数a4,a5: 个人猜测可能是需要将图片缩放的大小
    

    然后编写代码,my_init的主要逻辑和 CAJFILE_OpenEx1函数的一致,只是需要hook一些函数,避免比Fuzz识别为crash,比如在代码里面有很多assert,如果直接执行到这个函数的话,会被afl识别为crash.

    image.png

    因此这里使用plt hook,把libreaderex_x64.so模块中的一些函数给hook了。

    int my_assert_fail()
    {
        printf("my_assert_fail
    ");
        exit(1);
        return 0;
    }
    
    int my_cxa_throw()
    {
        printf("my_cxa_throw
    ");
        exit(1);
        return 0;
    }
    
    void my_init(void)
    {
    	........................................
    	........................................
        plt_hook_function("libreaderex_x64.so", "__assert_fail", my_assert_fail);
        plt_hook_function("libreaderex_x64.so", "__cxa_throw", my_cxa_throw);
    }
    

    然后再main函数中调用目标函数

    int main(int argc, char **argv)
    {
        printf("main:%p
    ", main);
        int f_sz = 0;
        char* buffer = read_to_buf(argv[1], &f_sz);
        char *ret = p_UnCompressImage(buffer, 4, f_sz, 100, 100);
        return 0;
    }
    

    然后其他的操作和Fuzz CAJFILE_OpenEx1函数时一致,只是环境变量需要重新设置

    /home/hac425/AFLplusplus-2.66c/afl-fuzz -m none -Q -t 20000 -i image_fuzz/ -o UnCompressImageOutput -- ./test_UnCompressImage @@
    

    部分漏洞分析

    CImage::LoadBMP 内存为初始化漏洞

    Cajviewer For Linux 在解析BMP图片时会进入 CImage::LoadBMP 函数,该函数中存在内存未初始化漏洞。

    image.png

    函数的流程如下

    1. 第8行,调用BaseStream::streamLength获取文件的大小。

    2. 第9行,调用FileStream::read从文件中读出14字节的文件头。

    3. 第10行,调用gmalloc分配内存用于存放文件的其他数据,这里实际上是直接调用malloc分配内存。

    4. 第12行,这里将分配的内存没有初始化直接传入FindDIBBits,该函数计算一个地址保存到this->DIBBits域。

    5. 第14行,这里会从this->DIBBits中读取数据,导致crash。

    下面看看FindDIBBits的实现

    image.png

    这里取出a1的开始4个字节作为一个偏移值 v1,然后调用PaletteSize,这个函数的返回值的可以为0,128等数字值。

    由于a1这个内存没有初始化,故v1有可能会很大,进而导致FindDIBBits会返回一个越界的地址

    然后在CImage::CalibrateColor中就会去访问这个内存。

    image.png

    CImage::DecodeJbig 越界读写漏洞

    CajViewer在解析CAJ等文件时,如果需要解析文件中嵌入的图片数据时,会使用libreaderex_x64.so中的函数来对图片进行解析,其中如果带解析的文件类型为Jbig文件时,会进入CImage::DecodeJbig函数进行解析:

    image.png

    其中重要函数的参数和作用如下:

    1. buf: 保存从文件中提取出的图片数据
    2. len: buf的长度

    其中buf一开始是一个JbigInfo的结构,结构体的定义如下:

    image.png

    然后然后会进入CImage::CImage进行简单的文件解析。

    image.png

    首先使用JbigInfo中的字段计算一个sz, 然后使用 gmalloc分配内存,之后会使用memcpy 拷贝数据。

    漏洞位于在计算sz时会导致整数溢出,进而导致会分配一个小于4LL * (1 << jbig_info->width2)的内存,然后在下面memcpy时会导致越界写。

    此外整个过程没有校验jbig_info的长度,所以会导致越界读。

    总结

    本文介绍了如何分析一个软件并使用afl qemu模式来测试闭源二进制。

  • 相关阅读:
    【Ecstore2.0】计划任务/队列/导入导出 的执行问题
    【Ecstore2.0】第三方信任登陆问题解决_备忘
    Ecstore 2.0 报表显示空白
    【Linux】 任务调度/计划 cron
    wdcp/wdlinux一键包的php5.3版本添加Zend.so 和Soap.so
    wdcp/wdlinux 在 UBUNTU/linux 中安装失败原因之创建用户
    假如女人是一种编程语言,你会更喜欢哪一种
    Linux中的ln
    wdcp/wdlinux 常用工具及命令集
    php 数组Array 删除指定键名值
  • 原文地址:https://www.cnblogs.com/hac425/p/14437732.html
Copyright © 2011-2022 走看看