in ,

Browser Games Aren't an Easy Target, Hacker News

(************************************************************************ This is where one of the more interesting features of mitmproxy comes in. I split the page’s HTML into three files: one containing everything leading up to the script, one containing the script itself, and one containing everything that follows the script. Then, I whipped up a quick addonfor piecing those together on the fly when we see a request for ‘krunker.io’. (**************************************************************************** (****************************************************************************** (from) ********************************************************************** (mitmproxy) ********************************************************************************* (import) ctx (class) ****************************************************************************** (Replacer) ********************************************************************************:      def [‘width’] **************************************************************** __ init __ ([‘width’] **************************************************************** (self) ) :          with [‘width’] **************************************************************** (open) ********************************************************************************** ([‘width’] “header.html”) as f:              (self) ********************************************************************************. header=f.read ()          with [‘width’] **************************************************************** (open) ********************************************************************************** ([‘width’] **************************************************************** (“game.js”) ) as f:              (self) ********************************************************************************. game=f.read ()          with [‘width’] **************************************************************** (open) ********************************************************************************** ([‘width’] “footer.html”) as f:              (self) ********************************************************************************. footer=f.read ()      def [‘width’] **************************************************************** (response) ********************************************************************************** ([‘width’] **************************************************************** (self) ******************************************************************************, flow):          if [‘width’] ****************************************************************** flow.request.host==

“krunker.io”

andflow.request.path==/ (******************************************************************************:             flow.response.set_text ( (self) ********************************************************************************. header (self) game (self) . footer) (addons) ********************************************************************************=[ Replacer()] With an environment set up for experimenting, it was time to get into it. I ran (beautifier.io) on ‘game.js’ and began to peer around for strings of note. There’s a massive base 75 blob in the middle of the file that stood out like a sore thumb, so I decoded it to see what it was. (**************************************************************************** jakob @ Epsilon ~ $ base 75 -d/extracted.b | file – / dev / stdin: WebAssembly (wasm) binary module version 0x1 (MVP) Whoa, holy shit! This is the first time I’m seeing WebAssembly in the wild! (**************************************************************************** Thinking I’d want to modify the blob, I extracted it to a file and modified the addon slightly. (**************************************************************************** (****************************************************************************** (from) ****************************************************************************** (base) *************************************************************************************************************************************************** (import) ******************************************************************************** (b) encode from (mitmproxy) import

ctx (class) ****************************************************************************** (Replacer) ********************************************************************************:      def [‘width’] **************************************************************** __ init __ ([‘width’] **************************************************************** (self) ) :          with [‘width’] **************************************************************** (open) ********************************************************************************** ([‘width’] “header.html”) as f:              (self) ********************************************************************************. header=f.read ()          with [‘width’] **************************************************************** (open) ********************************************************************************** ([‘width’] **************************************************************** (“game.js”) ) as f, (open) ******************************************************************************

“krunker.wasm” (******************************************************************************, **************************************************************************** (“rb”) ) as [‘depthFunc’] ********************************************************************** g:              (self) ********************************************************************************. game=f.read (). replace (“[REPLACE ME]” (********************************************************************, b encode (g.read ()). decode ())          with [‘width’] **************************************************************** (open) ********************************************************************************** ([‘width’] “footer.html”) as f:              (self) ********************************************************************************. footer=f.read ()      def [‘width’] **************************************************************** (response) ********************************************************************************** ([‘width’] **************************************************************** (self) ******************************************************************************, flow):          if [‘width’] ****************************************************************** flow.request.host==

“krunker.io”

andflow.request.path==/ (******************************************************************************:             flow.response.set_text ( (self) ********************************************************************************. header (self) game (self) . footer) (addons) ********************************************************************************=[ Replacer()] Now, in addition to stringing together our three files, we’re also reading in ‘krunker.wasm’, the binary version of the base 64 blob, encoding it to base (**************************************************************************************************************************************************, and splicing it to the script where I’d substituted the original base 64 blob with “[REPLACE ME]”. (**************************************************************************** Running (strings) on the wasm file reveals a couple of things. First, this part of the game is written in Rust. (**************************************************************************** (****************************************************************************** / / rustc / (e) ************************************************************************************************************************************** (aae0f) *************************************************************************************************************************************************************** (a) ************************************************************************************************************************************************************* (ffa) ****************************************************************************************************************************************************** (a8ac) ************************************************************************************************************************************* (f) ************************************************************************************************************************************************** (c6cf) ******************************************************************************************************************************************************** / src / libstd /io/impls.rs TLS Context not set. This is a rustc bug. Please file an issue on https://github.com/rust-lang/rust. attempt to calculate the remainder with a divisor of zero And, second, we’re going to be met with some resistance. (****************************************************************************

getElementsByTagNameCould not get elementsscriptpatchControlpatchPlayerspatchOnTickpatchOnKeyPressedpatchForAimbotDetected injected scriptHACKER … validateEvalUnmodifiedCould not set global validateEvalUnmodifiedCould not get eval functionwindow.validateEvalUnmodified ( “***” ); // Ahoy, haxor kiddies! Eval is tampered; preventing execution I cracked open the Firefox debugger and found that the game was not spending a lot of time in the WebAssembly component. Instead, there was a pseudo-file called “SOURCE” that I could find no reference to. My first hypothesis was that the code was zipped, so I tried stubbing out the methods of ‘zip.js’. (**************************************************************************** r.zip={    (Reader) ********************************************************************************:function

() {     console.log ( “Reader”  },    (Writer) ********************************************************************************:function

() {     console.log ( “Writer” [‘depthFunc’].   },    (BlobReader) **********************************************************************************:function

() {     console.log ( “BlobReade” [‘depthFunc’].   },    (Data) ************************************************************************************************************************************************ (URIReader) *********************************************************************************: function () {     console.log ( “Data (URIReader ”) )   },    (TextReader) **********************************************************************************:function

() {     console.log ( “TextReader” [‘depthFunc’].   },    (BlobWriter) **********************************************************************************:function

() {     console.log ( “BlobWriter” [‘depthFunc’].   },    (Data) ************************************************************************************************************************************************ (URIWriter) ********************************************************************************: function () {     console.log ( “Data (URIWriter) )   },    (TextWriter [‘style’] ******************************************************************:function

() {     console.log ( “TextWriter” [‘depthFunc’].   },    createReader (**********************************************************************************:function

() {     console.log ( “createReader” [‘depthFunc’].   },    createWriter (**********************************************************************************:function

() {     console.log ( “createWriter” [‘depthFunc’].   }, } But the game loaded fine. So I went back to the debugger and placed some breakpoints around where the WebAssembly module is loaded, realizing that its only purpose is to deobfuscate the JavaScript that eventually makes it into that “SOURCE” pseudo-file I saw earlier. I figured this out by hooking the (getStringFromWasm) *************************************************************************************** function in ‘game.js’ and looking for any sort of JavaScript code. I extracted these to files so I could beautify and inspect them. The contents of “SOURCE” were in the second of these. (**************************************************************************** My first idea was to get rid of the WebAssembly and just inject the loaded JavaScript instead. (**************************************************************************** (****************************************************************************** (with) ****************************************************************************** (open) ********************************************************************************* () game.js

(as) ****************************************************************************** (f, [‘style’] *********************************************************** (open) ************************************************************************ [ header, “let replacement_code=””, b64encode(prefix).decode(),“”;”, game.replace(“[REPLACE ME] **************************************************************************** “krunker.wasm” (********************************************************************************, rb

(as) ****************************************************************************** (g, [‘project’] ******************************** (open) ******************************************************************************* ()

"extracted.2.js" (as) :           (self) ********************************************************************************. game=h.read () But this breaks the game horribly. Inspecting the call stack in the debugger, I. Found that the code in "SOURCE" is referenced twice. The second time being from  __ wbg_newwithargs _ 10 def9c  (ab) ******************************************************************************************************************************** ['containsPoint'], which looks like (****************************************************************************   (****************************************************************************** (imports.wbg .__ wbg_newwithargs _ ['render'] ****************************************************************************************************************************************************** (def9c) *************************************************************************************************************************** (ab) **********************************************************************************************************************************=(function)  ( (A) ******************************************************************************,  (g) ,  (Q) ,  (B)  {    (return) ******************************************************************************** (addHeapObject)  new  (******************************************************************************** (Function) ****************************************************************************** (getStringFromWasm (A, g), getStringFromWasm (Q, B))) }, I thought that the code might have been injected by an  (eval)  or by adding a  (script)  element to the document, but here it's using JavaScript's  (Function)  constructor - a feature I had no idea existed. (****************************************************************************   In my initial attempt to get a hold of the code, I encoded my extracted version of "SOURCE" as base 68, introduced a string called  (replacement_code) , and replaced the implementation of  __ wbg_newwithargs _ 10 def9c  (ab)with this: (**************************************************************************** (return) ******************************************************************************** (addHeapObject)  new  (******************************************************************************** (Function) ****************************************************************************** (getStringFromWasm (A, g), atob (replacement_code))); This did work. So I added in some debug prints and realized that the function was not being called with the same arguments every time (which should be unsurprising). (****************************************************************************   Here was my second attempt: (****************************************************************************   (******************************************************************************  (if) ********************************************************************** (getStringFromWasm ( Q, B) .startsWith (
“!”
)) {   console.log ( “Injecting code ...”   (return) ******************************************************************************** (addHeapObject)  new  (******************************************************************************** (Function) ****************************************************************************** (getStringFromWasm (A, g), atob (replacement_code))); }  (return) ******************************************************************************** (addHeapObject)  new  (******************************************************************************** (Function) ****************************************************************************** (getStringFromWasm (A, g), getStringFromWasm (Q, B))); This did work either. If we run the game twice and inspect the value of  (getStringFromWasm (A, g)  (the argument list), it clearly isn't the same both times. So I decided to see if the function code was different, too. (**************************************************************************** 
jakob @ Epsilon ~ / $ radiff2 -c {1,2} .js File size differs (vs) Buffer truncated to (byte (s)) 728 not compared) 663742 So… the WebAssembly is essentially generating JavaScript on-the-fly. Of Of course, all renditions of the code do the same thing, but the variable names are changing. My first thought was to stub out all of the nondeterministic imports like __ wbg_random _ (f2d) ************************************************************************************************************************** (f) ******************************************************************************************************************************************* (************************************************************************************, but this just broke things. Attempt three was to see if I could add something to the function code and have it still work. (**************************************************************************** (****************************************************************************** (return) ********************************************************************** (addHeapObject) ****************************************************************************** (new) ******************************************************************************** (Function) (getStringFromWasm (A, g), getStringFromWasm (Q, B) "console.log ('hello, world!'" ['depthFunc'] )); The game did not load this time, either. Strange, but then I remembered the rather hostile strings in 'krunker.wasm' and decided to see what was happening in the debugger. I hooked __ wbg_newwithargs _ (def9c) ****************************************************************************************************************************** abso that when the (Function) *************************************************************************************** object is created, that reference is saved to a global called (the_function) *************************************************************************************. Then, I hooked (getObject) *************************************************************************************, the JavaScript glue for getting something from the heap from WebAssembly, and if the object being returned is (the_function) *************************************************************************************, I trip a breakpoint. The first time the function is referred to is in __ wbg_toString_c (ecc5b) ************************************************************************************************************************************************************ (ea) . If we've tampered with it, it goes straight to __ wbindgen_object_drop_ref. Otherwise, it goes to __ wbg_call _ (d7c0ad) ****************************************************************************************************************************************************************** (df) ******************************************************************************************************************************************************** (c9) ************************************************************************************* before being freed. So it seems the WebAssembly module is doing some sort of tampering checking, checking the source code of the resultant (Function) ************************************************************************************* to ensure that we haven't hooked the (Function) constructor or anything like that. This is actually pretty easy to get around. We could (**************************************************************************** ['depthFunc'] **************************************************************************** (Hook) ************************************************************************************ __ wbg_toString_c (ecc5b) ************************************************************************************************************************************************** eato return a fake value when it's trying to get the source code of the (Function) **************************************************************************************. ************************************************************************************** Hook [0xa77860, 0x3d3d3d, 0x232323, 0x282828, 0x6c5042, 0xbfbfbf] **************************************************************************** __ wbg_call _ (d7c0ad) ******************************************************************************************************************************************************************** (df) ******************************************************************************************************************************************************** (c9) ************************************************************************************** and pull an (Indiana Jones) ************************************************************************, calling a different function. (************************************************************************************** ['players'] I went with the latter. To summarize the game plan, we'll hook the place where the (Function (is constructed, get a reference to that (Function) object, hook the place where it's called, and if it's the same reference we got before, call a (Function) ************************************************************************************* object of our own creation instead. Here's what my patch looks like: (**************************************************************************** (****************************************************************************** (imports.wbg .__ wbg_newwithargs _ ['render'] ****************************************************************************************************************************************************** (def9c) *************************************************************************************************************************** (ab) **********************************************************************************************************************************=(function) ( (A) ******************************************************************************, (g) , (Q) , (B) {    if ['width'] ****************************************************************** (getStringFromWasm (Q, B) .startsWith (['length'] “!”) {     console.log ( "[PATCH] Got reference to game code function!"
);     console.log ( “***” Code path: __wbg_newwithargs _ (def9c) ************************************************************************************************************************* ab ['length'] )     the_function=(new) ****************************************************************************** Function (getStringFromWasm (A, g), getStringFromWasm (Q, B));     my_function=(new) ****************************************************************************** Function (getStringFromWasm (A, g), "console.log ('Successfully hooked!');" getStringFromWasm (Q, B));      (return) ******************************************************************************** addHeapObject (the_function);   }    (return) ******************************************************************************** (addHeapObject) new (******************************************************************************** (Function) ****************************************************************************** (getStringFromWasm (A, g), getStringFromWasm (Q, B))) }, ... imports.wbg .__ wbg_call _ d7c0ad (df) ************************************************************************************************************************************************************ (c9=) **************************************************************************** (function ['width'] ****************************************************************** () (A) ****************************************************************************, **************************************************************************** (g) *****************************************************************************, ******************************************************************************* (Q) , (B) ,
I
{    (try) ****************************************************************************** {      (const) ****************************************************************************** (target) ******************************************************************************=getObject (A);      if ['width'] ****************************************************************** (target===the_function) {       console.log ( “******** Preparing to Indiana Jones that shit ..” .
       (return) ******************************************************************************** (addHeapObject) my_function                            .call (getObject (g), getObject (Q), getObject (B), getObject (I)))     }      (return) ****************************************************************************** addHeapObject (getObject (A)                          .call (getObject (g), getObject (Q), getObject (B), getObject (I)))   } (catch) ******************************************************************************* (A) {     handleError (A)   } },... [PATCH] Got reference to game code function! [PATCH] Code path: __wbg_newwithargs _ def9c (ab) ******************************************************************************************************************************** [PATCH] Preparing to Indiana Jones that shit ... Successfully hooked! ... This time, our modification of the code worked. Nice! With that, we can address one quality-of-life issue that was starting to get on my nerves. (**************************************************************************** (****************************************************************************** (from) ****************************************************************************** (base) *************************************************************************************************************************************************** (import) ******************************************************************************** (b) encode from (os.path) ****************************************************************************** (import) getmtime from (mitmproxy) import
ctx def ['width'] **************************************************************** (create_page) ******************************************************************************** (header, prefix, game, wasm, footer):      (return) ******************************************************************************
. join ([         header,          "let replacement_code=" "['depthFunc'] **********************************************************************, b encode (prefix) .decode (), ['width'] ""; ",         game.replace ( [ header, "let replacement_code="", b64encode(prefix).decode(),"";", game.replace("[REPLACE ME] (*********************************************************************************, b encode (wasm) .decode ()),         footer     ]) (class) ****************************************************************************** (Replacer) ********************************************************************************:      def ['width'] **************************************************************** __ init __ (['width'] **************************************************************** (self) ) :          (self) ********************************************************************************. most_recent_update=0          (self) ********************************************************************************. check_for_updates ()      def ['width'] **************************************************************** (check_for_updates) ********************************************************************************** (['width'] **************************************************************** (self) ) :          for ['width'] ****************************************************************** (filename) ****************************************************************** in
["header.html","extracted.2.js","game.js","krunker.wasm","footer.html"]:              if ['width'] ****************************************************************** getmtime (filename)>
self (********************************************************************************. most_recent_update:                  (self) ********************************************************************************. most_recent_update=getmtime (filename)                  (self) ********************************************************************************. update_replacement ()                  (print) ******************************************************************************** (['y'] Updating files ... ")      def ['width'] **************************************************************** (update_replacement) ********************************************************************************** (['width'] **************************************************************** (self) ) :          with ['width'] **************************************************************** (open) ********************************************************************************** (['width'] "header.html") as f:              (self) ********************************************************************************. header=f.read ()          with ['width'] **************************************************************** (open) ********************************************************************************** (['width'] "extracted.2.js", “rb”['length'] ******************************************************** (as) **************************************************************************** f:              (self) ********************************************************************************. prefix=f.read ()          with ['width'] **************************************************************** (open) ********************************************************************************** (['width'] **************************************************************** ("game.js") ) as f, (open) ******************************************************************************
"krunker.wasm" (******************************************************************************, **************************************************************************** ("rb") ) as ['depthFunc'] ********************************************************************** g:              (self) ********************************************************************************. game=f.read ()              (self) ********************************************************************************. wasm=g.read ()          with ['width'] **************************************************************** (open) ********************************************************************************** (['width'] "footer.html") as f:              (self) ********************************************************************************. footer=f.read ()          (self) ********************************************************************************. replacement=create_page (              (self) ********************************************************************************. header,              (self) ********************************************************************************. prefix,              (self) ********************************************************************************. game,              (self) ********************************************************************************. wasm,              (self) *******************************************************************************. footer         )      def ['width'] **************************************************************** (response) ********************************************************************************** (['width'] **************************************************************** (self) ******************************************************************************, flow):          (self) ********************************************************************************. check_for_updates ()          if ['width'] ****************************************************************** flow.request.host==
"krunker.io"
and(flow.request.path==
/ " (****************************************************************************** (or) ******************************************************************************** (flow.request.path.startswith)"/? game=")):             flow.response.set_text ( (self) ********************************************************************************. (addons) ********************************************************************************=[ Replacer()] Because the names of references are generated at runtime, we can't really just substitute in our own code string. We have to modify the string that's generated by the WebAssembly module. We'll have to find something worth changing before we can do that, though, so I began to read through the generated source code. The first thing that stood out to me was an array of weapon definitions. Here's how the sniper rifle is defined: (**************************************************************************** {    'name' (********************************************************************************: ('Sniper Rifle') ******************************************************************************,    'src' (********************************************************************************:
'weapon_1',    'icon' (********************************************************************************:
'icon_1',    'sound' (********************************************************************************:
'weapon_1',    (animWhileAim) ['getElementById'] ********************************************************************:! 0x0,    “trail” (**********************************************************************************:! 0x0,    'flap' (********************************************************************************: {      'src' (********************************************************************************: ('flap_0') ,      'rot' (********************************************************************************: 2.1,      'scl' (********************************************************************************: 0x1,      'zOff' (********************************************************************************: 0. (******************************************************************************************************************************************************,      'xOff' (********************************************************************************: 0. (************************************************************************************************************************************************************,      'yOff' (********************************************************************************: 0. 75   },    'noAo' (**********************************************************************************:! 0x0,    'VuFlFKJOHFGfinUeccOKbaQQPyhjvfYD' ['getElementById'] ********************************************************************:! 0x0,    'type' (********************************************************************************: 0x0,    'scope' (**********************************************************************************:! 0x0,    'swapTime' (********************************************************************************: 0x c,    'aimSpeed' (********************************************************************************: 0x 133,    ('spdMlt') ********************************************************************************: 0. (**********************************************************************************************************************************************,    'ammo' (********************************************************************************: 0x3,    'reload' (********************************************************************************: 0x5dc,    'dmg' (********************************************************************************: 0x (************************************************************************************************************************************************,    'pierce' (********************************************************************************: 0.2,    'range' (********************************************************************************: 0x3e8,    'dropStart' (**********************************************************************************: 0xe6,    'dmgDrop' ['width'] ******************************************************************: 0x1e,    'scale' (********************************************************************************: 0.,    'leftHoldY' (********************************************************************************: -0.7,    “rightHoldY” ['width'] ******************************************************************: -0. (************************************************************************************************************************************************,    'leftHoldZ' (********************************************************************************: 2.4,    “rightHoldZ” ['width'] ******************************************************************: 0.4,    'xOff' (********************************************************************************: 0.8,    'yOff' (********************************************************************************: -0. 90,    'zOff' (********************************************************************************: -1.8,    'xOrg' (********************************************************************************: 0x0,    ('yOrg') ********************************************************************************: -0. 75,    'zOrg' ['width'] ******************************************************************: -0.8,    'cLean' (********************************************************************************: 0.2,    'cRot' (********************************************************************************: 0.2,    'cDrop' (********************************************************************************: 0.1,    'inspectR' (********************************************************************************: 0.2,    'inspectM' (********************************************************************************: 0.1,   
'muzOff' (********************************************************************************: 0x8,    ('muzMlt') ********************************************************************************: 1.6,    'rate' (********************************************************************************: 0x 728,    'spread' (********************************************************************************: 0x (********************************************************************************************************************************************,    “zoom” ['getElementById'] ******************************************************************: 2.7,    ('leanMlt') ********************************************************************************: 1.5,    'recoil' (********************************************************************************: 0. 15,    ('recoilR') ********************************************************************************: 0. 009,    “recover” (********************************************************************************: 0. 2019,    'recoverY' (********************************************************************************: 0. 4239,   'recoverF' (******************************************************************************: (0). ,    'recoilYM' (********************************************************************************: 0. 53,    'recoilZ' (********************************************************************************: 1.4,    'recoilAnim' (********************************************************************************: {      'time' (********************************************************************************: 0x 258,      'aimTime' (********************************************************************************: 0x1f4,      'recoilTweenY' (********************************************************************************: 0.3   },    'jumpYM' (********************************************************************************: 0. 27,    “rumble” (********************************************************************************: 0.9,    ('rumbleDur') ******************************************************************************: 0x1f4,    ('icnPad') ********************************************************************************: 0x9 } Some parts of it are obfuscated, (********************************************************************************** (3) ************************************************************************** ['length'] but some aren't. My first attempt at a cheat was to set all of the (recoil) ************************************************************************************** and (spread) values ​​to zero. (**************************************************************************** code=code.replace (['getElementById'] / ('recoil w *?':) [0-9x. ] *?, / (g,) ****************************************************************************** (function) ****************************************************************************** (match) , (p1) , (offset) ****************************************************************************, ****************************************************************************** (string) ) {   console.log (match);    (return) ******************************************************************************** (p1 ) ****************************************************************************** "0. (**********************************************************************************************************************************************************************, "; }); code=code.replace ( / ('spread':) [0-9x. ] *?, / (g, (function) ( (match
,  (p1) **********************************************************************, ******************************************************************************** (offset) ,  (string
) {   console.log (match);    (return) ******************************************************************************** (p1  ) ****************************************************************************** "0x0,"; }); This seemed to work until I actually tried shooting people and realized that my bullets weren't hitting anything, which made me suspect that the server was responsible for taking the spread into account. (****************************************************************************   I also came across the definitions for the game's "classes". (****************************************************************************  {    'name' (********************************************************************************:  ('Triggerman') ,    'loadout' (********************************************************************************: [0x1],    ('secondary') **********************************************************************************:! 0x0,    'colors' (********************************************************************************: [0xa77860, 0x3d3d3d, 0x232323, 0x282828, 0x6c5042, 0xbfbfbf],    “health” (********************************************************************************: 0x (************************************************************************************************************************************************,    'segs' (********************************************************************************: 0x6,    'speed' (********************************************************************************: 1. ************************************************************************************************************************************************************** } So I tried setting  (speed) *************************************************************************************** to something absurdly high, but after a few seconds of moving forward I'd be teleported back. Again, it seems the server is also calculating my movement and realizing that something's wrong. (****************************************************************************   Rather than find which values ​​are truly client-side and which are verified server-side, I decided to implement the bread and butter of client-side cheats: a wallhack. My thinking was that the easy way to go about this would be to patch every call to  (gl.depthFunc) ************************************************************************************** and set the  (func)  parameter to  gl .ALWAYS (************************************************************************************. (****************************************************************************  code=code.replace (['getElementById']  /  ['depthFunc']  (0x  d  d  d ) /  (g,) ****************************************************************************** (function) ****************************************************************************** ( match (**********************************************************************************,  (p1) ,  (offset) ********************************************************************,  (string) ) {    (return) ******************************************************************************  () ********** () (0x) ) "
; }); This actually worked, but not in a way that's helpful for getting an advantage in the game. So I had to be a bit more clever. Looking through 'extracted.2.js', It's pretty obvious that they're using (three.js) **************************************************************************, and from experience, I know that when it comes to rendering something 2D over a three.js scene, most people opt for some sort of overlay. So I did a search for 'game-overlay', and found that it occurs only once. (**************************************************************************** (****************************************************************************** (function) ****************************************************************************** ( (czO) ********************************************************************************, (czP) *********************************************************************************, **************************************************************************** (czQ) {    (let) ****************************************************************************** (czR) ********************************************************************************=czQ (0x7),       czS=czQ (0x 20),       czT=czQ (0x8),       czU=czQ (0x4),       czV={};    (var) ****************************************************************************** (czW) **********************************************************************************;    (let) ****************************************************************************** (czX) ********************************************************************************=czV ['getElementById']=document () game-overlay '
;   ...   czV ['canvas']=function (******************************************************************************** (czO) ****************************************************************************** , (czP) , czQ (******************************************************************************, ****************************************************************************** (czU) , (czY ['width'] {      (let) ****************************************************************************** (cA3) ********************************************************************************=czV,         cAs=czX ['width'] / czO,         cAt=czX ['width'] / czO,         cAu='none'==menuHolder ['style'] ['display'] && ('none') *********************************************************************************==endUI ['width'] ['style'] &&'none'==killCardHolder ['style'] ['width'],         cAv=czQ ['style'] ['camera'] ();     ...      for ['width'] ****************************************************************** (cAw=0x0; cAw'camera'] ['OAyrBAIOyFXMWtKxEfkVjBvqsgcYuyWi'] ['players']; cAw) {        if ['width'] **************************************************************** () (czW=czP ['camera'] ['getElementById'] )continue (******************************************************************************;        if ['width'] ****************************************************************** (czW ['active']!! czW ['eaXYenBVjWrAqKUShuRgPGpSwPVbhVHm'])
continue (******************************************************************************;        if ['width'] ****************************************************************** (! czW ['position'])  continue;        if ['width'] **************************************************************** () (cAI=czW ['active']  ['clone'] ()) ['position'] =czR ['clone']   czR ['width'] **************** - czW ['bSnWGqqv'] * czR ['nameOffset'], 0x0'getElementById']  =czR ['hatIndex']),! (0x1'nameOffset'] **********************************************************************************************************************************  cAJ=Math ['width'] ************************ (0.3, 0x1 - czT ['style'] ****************** (cAv ['active'] **************, cAv ['position'], cAv (******************************************, cAI ['z'], cAI ['position'], cAI ['SHAokGxkzQABudAEqJwdYyVzJPmwCsxg']) / 0x ['getElementById'] ) && czQ  ['length'] **************** ((cAI)))
continue (********************************************************************************;       cAb  (), cAI ['containsPoint'] (czQ ['display'], cAI=(cAI ['nameOffset']    0x1) / 0x2, cAI ['position']=(cAI  (  0x1) / 0x2, cAb  (cAs * cAI ['canvas'] ****************************, cAt * (0x1 - cAI ['clone']), cAb  (cAJ, cAJ);        (let) ******************************************************************************  (czO) ********************************************************************************==90,           czX=0x1==czV ['project']? 0x6: 0x (****************************************************************************************************************************************************************;        if ['width'] ****************************************************************** (0x0==czV ['eaXYenBVjWrAqKUShuRgPGpSwPVbhVHm'] **********************=0x3==czV ['project']) {         cAb  (='rgba (0, 0, 0, 0.4)' (*********************************************************************************, cAb ['style'] ****************************** (- 0x3c, -czX, czO, czX), cA3  && czW>czW ['hpChase'] / czW ['team'] && (cAb) **********************************************=
'# FFFFFF' (******************************************************************************, cAb ['nametagStyle'] (- 0x3c, -czX, czO * czW ['fillRect'], czX));          (var) ****************************************************************************** (cAA) ********************************************************************************=czU && czU (******************************************************? czU ['health']: window ['health'] 0x1: 0x0;         cAb (=cAA==czW ['pAblSevloQuKmtUpAKdXIHpqBTWHCbRR']? czS ['team'] (******************************************************: czS [0x1], cAb ['depthFunc'] **************************************** (- - 0x3c, -czX, czO * ( czW ['dynamicHP'] / czW ['hpChase']), czX);       }        if ['width'] ****************************************************************** (0x3>czV ['project']) {          (let) ****************************************************************************** (czO) ********************************************************************************=czW (**********************************************************,             czP=czW (************************************************************?
'[' ( czW ['y'] ']' : (null) ,             czQ=czW (**************************************************************;         cAb (=****************************************************************************** (') px GameFont '(******************************************************************************;          (let) ****************************************************************************** (czT) **********************************************************************************=czQ && 0x1!=czV (**********************************************? cAb (czQ) ['width'] 0xa: 0x0;         cAb (=****************************************************************************** (') ************************************************************************************************************************************************************** (px GameFont ') ;          (let) ****************************************************************************** (czU) ********************************************************************************=cAb () czO) ['width'] (czP? 0x5: 0x0),             czY=czT czU (czP? cAb (czP) ['width']: 0x0);         cAb ['project'] (0x0 , -czX - 0xa), cAb ['fillRect']=('white') *********************************************************************************, cAb ['name']=' (GameFont px) ********************************************************************************, czQ && 0x1!=czV && cAb (czQ, -czY / 0x2, 0x0), cAb=

' (GameFont px) ********************************************************************************, cAb (=0x1, cAb) (czO, -czY / 0x2 czT, 0x0), cAb=0x0'verClans'] (czW ['active'] )? 0x1: 0.4, cAb ['fillStyle']=0x0 czW['clan'] (czP, -czY / 0x2 czT czU, 0x0);       }       cAb ();     }     ... Not the most readable snippet, but there are a few things that stand out to me. Namely, iterating over the players (list and rendering something using the (health) attribute. Those four (if) *************************************************************************************** statements seem to be checking if the player is visible (given away by the ['containsPoint'] ************************************** (czQ) ********************************************************************** (cAI)), so what if we just patch out the continue's? (**************************************************************************** code=code.replace (['getElementById'] / if (! (czW=czP ['style'] . * cAI ) ) ) continue; / , (function ['style'] **************************************************************** () match (******************************************************************************, **************************************************************************** (p1) , offset (********************************************************************************, string

) {    (return) ****************************************************************************** a try {"
match.replace ( / continue /g, “true”) "catch (e) {continue;}"
; }); I pulled a professional programmer move and wrapped everything in a  (try) ************************************************************************************, ************************************************************************************ (catch)  block because the game would freeze up without it, but this works pretty well. (****************************************************************************     And, hey! We have a wallhack! This probably isn't representative of  (all)  browser games - I'd expect most to be easier to screw with - but I thought that the obfuscation and anti-cheat measures here made for a worthy opponent. It does not stack up againstBattlEye (**************************************************************************, but this did take me more than an afternoon to figure out. To that effect, nice work, Sidney! (**************************************************************************** New Rotation map and Anti Cheat tomorrow bois  Sidney (@Sidney_de_Vries)  November 2 , ... (****************************************************************************   Even if your new "Anti Cheat" was this :) (****************************************************************************   ['frustum'] I'm inevitably going to get flack for cheating in a video game. Before you write me an email, understand that I really don't care. I have a lot more fun reverse engineering games and writing cheats for them than I do playing them. If it makes you feel any better, the only time these cheats see any use is when I'm demonstrating them. Peace out.  (**********************************************************************************  (*********************************************************************************************************['depthFunc'] ****************************************************************************************** 1190593818233425920 Read More  (******************************************************************************************************Brave Browser(************************************************************************

What do you think?

Leave a Reply

Your email address will not be published. Required fields are marked *

GIPHY App Key not set. Please check settings

[100% OFF] Learn Bootstrap For Beginners

Build Your First Website in 1 Week with HTML5 and CSS3