(************************************************************************ 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==
"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) importctx 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 (******************************************************************************************************(************************************************************************
GIPHY App Key not set. Please check settings