前言
这段事情因为各种事情耽搁,一直没有更新文章。本打算新的一篇出rootkit
来水一篇,后面发现a3佬写的太多了,不想继续看了。然后前阵子一直在思考后续到底是学什么方向,在docker逃逸、chrome内核、iot还有fuzz之间犹豫不决,现在也算是下定决心来学学chrome了,只希望能够快点搞完,后面还是打算更多的去学习docker逃逸。
这里就不提环境安装的事情了,网上有很多相关资料。
基础知识
js作为一个面向对象编程的语言,他的变量都是以类的形式表现的。并且js作为动态语言,他的类成员是可以改变的,这也就导致他在内存中的存在形式相交与C语言要复杂的多。
使用如下程序进行调试:
1 | var int_arr = [1, 2, 3]; |
上面的%DebugPrint(int_arr)
的作用是打印出int_arr
的内存信息,也就是打印出内存地址。后面的%SystemBreak()
函数则是将控制权交给gdb。
1 | 0x011117f8df01 <JSArray[3]> |
从上面可以看到,这里打印出来的是一个int类型的数组,在使用job命令可以清晰的看到这块内存的数据结构。首先是一个指向map的指针,随后在job显示的和telescope
出来的内容有一定出入,这里我选择相信telescope
,所以第二个成员应该是properties
,随后就是elements
指针,最后就是length
。可以看出来elements
指针就是真正指向数据的指针,并且在后面紧跟了elements
内存区域的结构。
starCTF oob
漏洞分析
1 | diff --git a/src/bootstrapper.cc b/src/bootstrapper.cc |
整个题目就只有这样一个diff文件,这里主要需要注意的是中间那一块+号区域,在周围只是为了能够正常进行编译才添加的。
可以看到添加的这个函数在一开始就对参数的数量进行了判断,如果参数数量大于2则返回undefined
,随后接受第一个到receiver
中,进一步得到JSArray结构的变量array,紧接着通过array变量取出对应的elements
,并且在最后拿到了数组的长度。
然后函数又进行判断,如果参数的数量为1则返回数组末尾的后一个地址的值,也就出现了越界读取的漏洞。
如果参数的数量为2则获取第二个参数到value
中,然后写到数组末尾的后一个地址,也就出现了越界写的漏洞。
需要注意的是,这里函数参数的第一个参数默认为this,所以 test_arr.oob() 的含义为一个参数
小总结
其实通过前面的基础知识章节和这里的漏洞分析之后可以很容易的看出来,在操作数组中的内容时都是这样一层一层找下去的。
而在js中对于数组中不是只能存放整型的变量,还可以存放各种类型的对象,而如何区分数组中变量类型就需要用到JSArray
中的map成员进行区分。所以如果我们可以修改map成员即可实现类型混淆。
利用分析
虽然是找到了漏洞点,并且也知道了存在类型混淆的可能性,但根据目前的情况仍无法继续操作。因为可以看到前面我们在看elements
结构中并没有看到map,也就无利用之谈了。
1 | pwndbg> job 0x011117f8df49 |
但是在后续看浮点数中可以看到map的地址紧邻者elements
的数据。
1 | pwndbg> job 0x011117f8dfe1 |
同样的,我们在数组中全是对象的时候也可以看到map的地址紧邻着这些数据。
注意那个地址的最后,它的值看起来不是对齐的。这是因为v8里有个tagged pointer
机制,一个地址指向的如果不是SMI(就是小整数),它的最低位就会打上一个标记,就会有个1,看起来就不是对齐的,用的时候要减1。
接着需要思考的事,一个越界读写能给我造成什么样的效果呢?
1. 泄漏map地址
这里泄漏map地址就很简单,在数组中的成员为对象和浮点型的时候直接调用oob函数即可获取到。
1 | var obj = { "a": 1 }; |
2. 类型混淆获取任意对象地址
1 | function addressOf(leak_obj) { |
这里的实现原理也是非常简单,首先就是将需要获取地址的对象放到obj_array
中,随后使用oob
函数将flt_array_map
的地址写进去,这时再去访问obj_array
中的成员时就会以float的形式返回出对应的地址了,最后在恢复类型即可。
3. 讲任意地址当作对象
1 | function fake_object(address) { |
其实道理和上一步类似。
4. 实现任意地址读取
1 | var arb_rw_arr = [flt_array_map, 1.2, 1.3, 1.4]; |
首先这里是吧arb_rw_arr
这个数组的elements
区域当作的是一个JSArray
结构体,而这个结构对应的指针为fake_obj
。
这里主要解释一下第二行代码调用函数的部分,这里首先是拿到arb_rw_arr
对象的地址,随后减去0x20
则是因为JSArray
和elements
相距固定偏移为0x30
,这里减去0x20的话代表将elements + 0x10
当做了fake JSArray
,根据前面的调试得知在elements + 0x10
的位置开始为数组中数据的部分,所以我们现在是可以实现任意修改fake_obj
的JSArray
部分。
然后就是这里返回的fake_obj
对象,这个对象实质指向的是就是arb_rw_arr
的elements + 0x10
的位置。
又因为可以任意修改这一部分的内容,所以我们可以修改elements
指针,使其指向任意地址配合fake_obj
对象即可达到任意地址读取的效果。
5. 实现任意地址写
1 | var buffer = new ArrayBuffer(16); |
任意地址写的写法在一开始的想法肯定是和任意地址读一致。不过按照上面那样写会出现一个段错误,这是简单的write FloatArray
对浮点数的处理方式造成的,当值以 0x7f 开头等高处的地址都会出现这种问题。为了避免选择使用DataView
来处理。
DataView
对象偏移+0x20
处,存有一个backing_store
指针,该指针指向真正存储数据的地址,改写这个指针即可任意读写。
最终利用: 传统方式
传统的方式是对__free_hook
进行劫持。但是它在libc中,所以首先还是需要考虑泄漏出程序基地址,然后通过got表泄漏出libc地址。
不过目前遇到的是如何泄漏出程序基地址
1 | pwndbg> job 0x0fcdcb2c2d99 |
这里随便查看一个JSArray
内部的map,可以看到其中包含这constructor
1 | pwndbg> job 0x08ad62ed0ec1 |
继续跟进constructor
可以看到有一个成员为code
1 | pwndbg> x/20xg 0x3eadb8bc6981-1 |
在内部可以看到在距离开始位置为0x40的位置就能看到心心念念的程序基地址,所以直接通过这里泄漏即可。
泄漏完基地址就很好办,后续就是通过got表泄漏libc地址,然后劫持__free_hook
即可为system
,然后创建一个函数中申请变量为想要执行的命令。
我这里没看过源码,不过我猜测是因为js是动态变量的缘故,js的变量都是以堆的形式存在的,并且在函数执行结束后会释放掉内存。
最终利用: 非传统方式
这种方法则是往程序中写shellcode,但是程序自身并没有rwx的段。不过存在这样一种技术wasm,使用 这个网站 可以生成一段wasm码,可以用生成一个函数对象:
1 | var wasmCode = new Uint8Array([0,97,115,109,1,0,0,0,1,133,128,128,128,0,1,96,0,1,127,3,130,128,128,128,0,1,0,4,132,128,128,128,0,1,112,0,0,5,131,128,128,128,0,1,0,1,6,129,128,128,128,0,0,7,145,128,128,128,0,2,6,109,101,109,111,114,121,2,0,4,109,97,105,110,0,0,10,138,128,128,128,0,1,132,128,128,128,0,0,65,42,11]); |
1 | var wasmCode = new Uint8Array([0, 97, 115, 109, 1, 0, 0, 0, 1, 133, 128, 128, 128, 0, 1, 96, 0, 1, 127, 3, 130, 128, 128, 128, 0, 1, 0, 4, 132, 128, 128, 128, 0, 1, 112, 0, 0, 5, 131, 128, 128, 128, 0, 1, 0, 1, 6, 129, 128, 128, 128, 0, 0, 7, 145, 128, 128, 128, 0, 2, 6, 109, 101, 109, 111, 114, 121, 2, 0, 4, 109, 97, 105, 110, 0, 0, 10, 138, 128, 128, 128, 0, 1, 132, 128, 128, 128, 0, 0, 65, 42, 11]); |
当我们运行可以看到确实是返回了42。
1 | pwndbg> job 0x365390d5faf9 |
查看一下这个 f ,可以看到他的类型为JSFunction
,继续跟进其中的share_info
。
1 | pwndbg> job 0x365390d5fac1 |
继续跟进data。
1 | pwndbg> job 0x365390d5fa99 |
进一步查看instance
1 | pwndbg> telescope 0x365390d5f901-1 20 |
可以看到在偏移为0x88
处有rwx的段了。最后直接写入shellcode即可
1 | #!/usr/bin/env python |
上面这是我抄的用于生成js的shellcode的python脚本,在开头留有原作者链接。
最终exp
1 | var buf = new ArrayBuffer(8); |
1 | var buf = new ArrayBuffer(8); |