question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Feature Request - Self Defending with String Array

See original GitHub issue

Hello!

I was fiddling around with code that I obfuscated with this tool, trying to figure out a way to manipulate things. I discovered, that I could easily edit the string array/map in obfuscated code with selfdefend and stringarray on. I was wondering if there was any security to prevent such editing.

For example, in the example (with hello world), I could make it print something else by modifying the string array in the beginning

// Hello world!
var a=['AdvLv','Hello\x20World!'];(function(c,d){var e=function(f){while(--f){c['push'](c['shift']());}};var g=function(){var h={'data':{'key':'cookie','value':'timeout'},'setCookie':function(i,j,k,l){l=l||{};var m=j+'='+k;var n=0x0;for(var n=0x0,p=i['length'];n<p;n++){var q=i[n];m+=';\x20'+q;var r=i[q];i['push'](r);p=i['length'];if(r!==!![]){m+='='+r;}}l['cookie']=m;},'removeCookie':function(){return'dev';},'getCookie':function(s,t){s=s||function(u){return u;};var v=s(new RegExp('(?:^|;\x20)'+t['replace'](/([.$?*|{}()[]\/+^])/g,'$1')+'=([^;]*)'));var w=function(x,y){x(++y);};w(e,d);return v?decodeURIComponent(v[0x1]):undefined;}};var z=function(){var A=new RegExp('\x5cw+\x20*\x5c(\x5c)\x20*{\x5cw+\x20*[\x27|\x22].+[\x27|\x22];?\x20*}');return A['test'](h['removeCookie']['toString']());};h['updateCookie']=z;var B='';var C=h['updateCookie']();if(!C){h['setCookie'](['*'],'counter',0x1);}else if(C){B=h['getCookie'](null,'counter');}else{h['removeCookie']();}};g();}(a,0x1cf));var b=function(c,d){c=c-0x0;var e=a[c];return e;};function hi(){var c=function(){var c=!![];return function(d,e){var f=c?function(){if(e){var g=e['apply'](d,arguments);e=null;return g;}}:function(){};c=![];return f;};}();var e=c(this,function(){var c=function(){return'\x64\x65\x76';},d=function(){return'\x77\x69\x6e\x64\x6f\x77';};var e=function(){var f=new RegExp('\x5c\x77\x2b\x20\x2a\x5c\x28\x5c\x29\x20\x2a\x7b\x5c\x77\x2b\x20\x2a\x5b\x27\x7c\x22\x5d\x2e\x2b\x5b\x27\x7c\x22\x5d\x3b\x3f\x20\x2a\x7d');return!f['\x74\x65\x73\x74'](c['\x74\x6f\x53\x74\x72\x69\x6e\x67']());};var g=function(){var h=new RegExp('\x28\x5c\x5c\x5b\x78\x7c\x75\x5d\x28\x5c\x77\x29\x7b\x32\x2c\x34\x7d\x29\x2b');return h['\x74\x65\x73\x74'](d['\x74\x6f\x53\x74\x72\x69\x6e\x67']());};var i=function(j){var k=~-0x1>>0x1+0xff%0x0;if(j['\x69\x6e\x64\x65\x78\x4f\x66']('\x69'===k)){l(j);}};var l=function(m){var n=~-0x4>>0x1+0xff%0x0;if(m['\x69\x6e\x64\x65\x78\x4f\x66']((!![]+'')[0x3])!==n){i(m);}};if(!e()){if(!g()){i('\x69\x6e\x64\u0435\x78\x4f\x66');}else{i('\x69\x6e\x64\x65\x78\x4f\x66');}}else{i('\x69\x6e\x64\u0435\x78\x4f\x66');}});e();var d={'AdvLv':b('0x0')};console['log'](d[b('0x1')]);}hi();

// Bye world! (modified)
var a=['AdvLv','Bye\x20World!'];(function(c,d){var e=function(f){while(--f){c['push'](c['shift']());}};var g=function(){var h={'data':{'key':'cookie','value':'timeout'},'setCookie':function(i,j,k,l){l=l||{};var m=j+'='+k;var n=0x0;for(var n=0x0,p=i['length'];n<p;n++){var q=i[n];m+=';\x20'+q;var r=i[q];i['push'](r);p=i['length'];if(r!==!![]){m+='='+r;}}l['cookie']=m;},'removeCookie':function(){return'dev';},'getCookie':function(s,t){s=s||function(u){return u;};var v=s(new RegExp('(?:^|;\x20)'+t['replace'](/([.$?*|{}()[]\/+^])/g,'$1')+'=([^;]*)'));var w=function(x,y){x(++y);};w(e,d);return v?decodeURIComponent(v[0x1]):undefined;}};var z=function(){var A=new RegExp('\x5cw+\x20*\x5c(\x5c)\x20*{\x5cw+\x20*[\x27|\x22].+[\x27|\x22];?\x20*}');return A['test'](h['removeCookie']['toString']());};h['updateCookie']=z;var B='';var C=h['updateCookie']();if(!C){h['setCookie'](['*'],'counter',0x1);}else if(C){B=h['getCookie'](null,'counter');}else{h['removeCookie']();}};g();}(a,0x1cf));var b=function(c,d){c=c-0x0;var e=a[c];return e;};function hi(){var c=function(){var c=!![];return function(d,e){var f=c?function(){if(e){var g=e['apply'](d,arguments);e=null;return g;}}:function(){};c=![];return f;};}();var e=c(this,function(){var c=function(){return'\x64\x65\x76';},d=function(){return'\x77\x69\x6e\x64\x6f\x77';};var e=function(){var f=new RegExp('\x5c\x77\x2b\x20\x2a\x5c\x28\x5c\x29\x20\x2a\x7b\x5c\x77\x2b\x20\x2a\x5b\x27\x7c\x22\x5d\x2e\x2b\x5b\x27\x7c\x22\x5d\x3b\x3f\x20\x2a\x7d');return!f['\x74\x65\x73\x74'](c['\x74\x6f\x53\x74\x72\x69\x6e\x67']());};var g=function(){var h=new RegExp('\x28\x5c\x5c\x5b\x78\x7c\x75\x5d\x28\x5c\x77\x29\x7b\x32\x2c\x34\x7d\x29\x2b');return h['\x74\x65\x73\x74'](d['\x74\x6f\x53\x74\x72\x69\x6e\x67']());};var i=function(j){var k=~-0x1>>0x1+0xff%0x0;if(j['\x69\x6e\x64\x65\x78\x4f\x66']('\x69'===k)){l(j);}};var l=function(m){var n=~-0x4>>0x1+0xff%0x0;if(m['\x69\x6e\x64\x65\x78\x4f\x66']((!![]+'')[0x3])!==n){i(m);}};if(!e()){if(!g()){i('\x69\x6e\x64\u0435\x78\x4f\x66');}else{i('\x69\x6e\x64\x65\x78\x4f\x66');}}else{i('\x69\x6e\x64\u0435\x78\x4f\x66');}});e();var d={'AdvLv':b('0x0')};console['log'](d[b('0x1')]);}hi();

If there is no security for that, I suggest hashing the string array (after joining it all with .join), and comparing it to a stored value.

var a=['AdvLv','Hello\x20World!'];
var toHash=a.join(','); // Make sure to join with a character in between for more security
var hash = 0;
for (var i = 0; i < toHash.length; i++) { // Hash function from here: https://stackoverflow.com/questions/6122571/simple-non-secure-hash-function-for-javascript
     var c = toHash.charCodeAt(i);
     hash = ((hash<<5)-hash)+c;
     hash = hash & hash; // Convert to 32bit integer
}
 if (hash !== -222186004) detectedModification(); // -222186004 is the precomputed hash

(For super long arrays where hashing is too slow, .length comparisons could be also done for faster yet less secure checking)

Issue Analytics

  • State:open
  • Created 5 years ago
  • Reactions:1
  • Comments:6 (2 by maintainers)

github_iconTop GitHub Comments

3reactions
Andrews54757commented, Sep 11, 2018

@sanex3339 I have achieved the above in my fork. Heres the link to check it out:

https://github.com/Andrews54757/javascript-obfuscator

Code output:

// Hello World! Bye world! H! Bye!
function _a(){return['Hi!','Bye!','log','Hello\x20World!','Bye\x20world!'];}var _b=function(c,d){c=c-0x0;var e=_a[c];return e;};var GgyEFi=function(ZcXzrg){for(var WjbMFO=0,sVyFSc=0;sVyFSc===0;){for(WjbMFO=0,sVyFSc=0;WjbMFO<ZcXzrg.length;WjbMFO++){var rfUuNb=ZcXzrg['charCodeAt'](WjbMFO);sVyFSc=(sVyFSc<<5)-sVyFSc+rfUuNb;sVyFSc=sVyFSc&sVyFSc;}}return sVyFSc;}(function(NihZvT){NihZvT=NihZvT['replace'](new RegExp('[\\s\'"]','g'),'');return NihZvT['substring'](NihZvT['indexOf']('[')+1,NihZvT['lastIndexOf'](']'));}(_a['toString']()));_a=_a();(function(c,d){var e=function(f){while(--f){c['push'](c['shift']());}};var g=function(){var h={'data':{'key':'cookie','value':'timeout'},'setCookie':function(i,j,k,l){l=l||{};var m=j+'='+k;var n=0x0;for(var n=0x0,p=i['length'];n<p;n++){var q=i[n];m+=';\x20'+q;var r=i[q];i['push'](r);p=i['length'];if(r!==!![]){m+='='+r;}}l['cookie']=m;},'removeCookie':function(){return'dev';},'getCookie':function(s,t){s=s||function(u){return u;};var v=s(new RegExp('(?:^|;\x20)'+t['replace'](/([.$?*|{}()[]\/+^])/g,'$1')+'=([^;]*)'));var w=function(x,y){x(++y);};w(e,d);return v?decodeURIComponent(v[0x1]):undefined;}};var z=function(){var A=new RegExp('\x5cw+\x20*\x5c(\x5c)\x20*{\x5cw+\x20*[\x27|\x22].+[\x27|\x22];?\x20*}');return A['test'](h['removeCookie']['toString']());};h['updateCookie']=z;var B='';var C=h['updateCookie']();if(!C){h['setCookie'](['*'],'counter',0x1);}else if(C){B=h['getCookie'](null,'counter');}else{h['removeCookie']();}};g();}(_a,(((GgyEFi^0xee6d8)<<0x5)-GgyEFi^0xe1c1b|0x0)-0x1285201f));function hi(){var c=function(){var c=!![];return function(d,e){var f=c?function(){if(e){var g=e['apply'](d,arguments);e=null;return g;}}:function(){};c=![];return f;};}();var d=c(this,function(){var c=function(){return'\x64\x65\x76';},d=function(){return'\x77\x69\x6e\x64\x6f\x77';};var e=function(){var f=new RegExp('\x5c\x77\x2b\x20\x2a\x5c\x28\x5c\x29\x20\x2a\x7b\x5c\x77\x2b\x20\x2a\x5b\x27\x7c\x22\x5d\x2e\x2b\x5b\x27\x7c\x22\x5d\x3b\x3f\x20\x2a\x7d');return!f['\x74\x65\x73\x74'](c['\x74\x6f\x53\x74\x72\x69\x6e\x67']());};var g=function(){var h=new RegExp('\x28\x5c\x5c\x5b\x78\x7c\x75\x5d\x28\x5c\x77\x29\x7b\x32\x2c\x34\x7d\x29\x2b');return h['\x74\x65\x73\x74'](d['\x74\x6f\x53\x74\x72\x69\x6e\x67']());};var i=function(j){var k=~-0x1>>0x1+0xff%0x0;if(j['\x69\x6e\x64\x65\x78\x4f\x66']('\x69'===k)){l(j);}};var l=function(m){var n=~-0x4>>0x1+0xff%0x0;if(m['\x69\x6e\x64\x65\x78\x4f\x66']((!![]+'')[0x3])!==n){i(m);}};if(!e()){if(!g()){i('\x69\x6e\x64\u0435\x78\x4f\x66');}else{i('\x69\x6e\x64\x65\x78\x4f\x66');}}else{i('\x69\x6e\x64\u0435\x78\x4f\x66');}});d();console[_b('0x0')](_b('0x1'),_b('0x2'),_b('0x3'),_b('0x4'));}hi();

As you can see (if you beautify the code), you will NOT find rotation value (or hash) anywhere in the code. That is because it is calculated by the hash and validated by the syntax of the functional code. That way:

  1. You cannot rotate it based on a number you find in the code
  2. You cannot modify the hash to make changes to the string array (since you dont know what the correct hash value is)
  3. It is super hard to debug - Because the system is validated by the user’s code (the “rotation” directly effects the execution of the user’s code) any error would be randomly placed in the user’s code, NOT the defend mechanism. So it would be very hard to pinpoint where the error originated from the stack trace.

How it works:

Basically, if the string array rotate and self defend options are both checked, then the obfuscator will use the hash function output to calculate the rotate value. This is achieved like this:

var rotateAmount = (((hash ^ 0xee6d8) << 0x5) - hash ^ 0xe1c1b | 0x0) - 0x1285201f)

The bitwise operators are there for two reasons:

  1. They add variability. So each time, the generated code is different.
  2. They make it hard to figure out the input value from the output, because it is a one way hash.

Todo

  • Add a self-defend mechanism to the hashing function. So people cant run it with ease outside the original program. - IMPORTANT
  • Add some entropy by making the hash function a little different with random constants for each generation. That way, a standardized hash function will not work. - IMPORTANT
3reactions
Andrews54757commented, Sep 10, 2018

@sanex3339 I thought about something cool. How about, when the hash is calculated, instead of just comparing it to a pre-generated hash, the hash is used to calculate the rotation of the array. It would provide lots of security to not just the string array hash function, but also the rotate function.

That way, one cannot figure out the rotation by looking at a number. Instead, they must run the hash function (which could be self-defended itself to prevent this).

Example of code that could do this:

function stringArray() {
    return ['log', 'Hello\x20World!'];
}
(function(toHash) {
      var hash = 0,
  //  for (var i = 0, hash = 0; hash !== 579535379;) { // 579535379 is the precomputed hash.
        for (var i = 0; i < toHash.length; i++) {
            var charCode = toHash.charCodeAt(i);
            hash = (hash << 5) - hash + charCode;
            hash = hash & hash;
        }
   // }
      var rotate = ((hash * 0.0000005) | 0) + 37; // calculate the amount rotated. With the expected hash of 579535379, the value would be 326.

      stringArray = stringArray(); // Make it into a real array
      // Rotate the string array here with the above value.

     // Another suggestion: Hash the rotate value and check to make sure it is correct.
}(function(functionString) { // Converts the stringArray function to a string. This is necessary to keep the escape sequences. Also adds more security.
    functionString = functionString['replace'](new RegExp('[\\s\'"]', 'g'), '');
    return functionString.substring(functionString.indexOf('[') + 1, functionString.lastIndexOf(']'));
}(stringArray.toString())));
Read more comments on GitHub >

github_iconTop Results From Across the Web

String array in response body of custom connector
I have a custom connector with a request having a JSON structure in the body with multiple elements (JIRA issues).
Read more >
Why is my Java function in my Karate feature returning a ...
1 Answer 1 · When I read the part you wrote "string arrays are not being converted to JSON arrays properly", I realized...
Read more >
Work with arrays | BigQuery
With Google Standard SQL, you can construct array literals, build arrays from subqueries using the ARRAY function, and aggregate values into an array...
Read more >
JavaScript Obfuscator Tool
The obfuscated result will have the exact functionality of the original code. ... transformations and "traps", such as self-defending and debug protection.
Read more >
String resources
String : XML resource that provides a single string. String Array: XML resource that provides an array of strings. Quantity Strings (Plurals) ...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found