An invitation (re)
We want you to be a member of the Cyber Defense Group! Your invitation has been encoded to avoid being detected by COViD’s sensors. Decipher the invitation and join in the fight!
In this challenge, we are given 3 files: index.html, invite.js, jquery-led.js.
When opening index.html in the browser, nothing shows up. I opened the Chrome Dev Tools and looked at the console. The following error message was given.
Uncaught ReferenceError: gl is not defined
at invite.js:2
Not sure what is going wrong, but I suppose I will need to look more into invite.js to know the problem.
Source files given
index.html is straightforward, it imports the relevant stylesheets and scripts, and places a <canvas>
element in the page.
<!doctype html>
<html lang="en">
<head>
<!-- Required meta tags -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<!-- Bootstrap CSS -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.5.3/dist/css/bootstrap.min.css" integrity="sha384-TX8t27EcRE3e/ihU7zmQxVncDAy5uIKz4rEkgIXeMed4M0jlfIDPvg6uqKI2xXr2" crossorigin="anonymous">
<title>Hello, world!</title>
</head>
<body>
<div class="container">
<br />
<div class="custom1"></div>
<div class="custom2"></div>
<div class="custom3"></div>
<canvas class="GG" id="glglglgl" width="1" height="1" shade="e" type="l"></canvas>
<br />
</div>
<script src="https://code.jquery.com/jquery-3.5.1.slim.min.js" integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@4.5.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-ho+j7jyWK8fNQe+A12Hb8AhRq26LrZ/JpcUGGOn+Y7RsweNrtN/tE3MoK7ZeZDyx" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/raphael/2.2.7/raphael.min.js"></script>
<script src="jquery-led.js"></script>
<script src="invite.js"></script>
</body>
</html>
jquery-led.js is slightly big, with 300 lines. Scrolling through the file, it seems to contain code that is related to showing a LED panel drawn onto the webpage.
invite.js contains one line of obfuscated javascript code. After splitting the code into multiple lines, the code structure looks something like the following.
var _0x3f3a=["\x2E\x47","\x71\x75\x65\x72\x79\x53\x65\x6C\x65\x63\x74\x6F\x72","\x77\x65\x62\x67\x6C",... // truncated
try { /* something */ } catch (err) { /* something */ }
// some more code
As shown above, there is an error in invite.js, so my current goal is to deobfuscate this code to know what is happening.
Better strings
It is clear that _0x3f3a
contains strings in their hexadecimal format, which can be more easily read if we convert them to their ASCII representation. For example, \x2E\x47
is equal to .G
. The easiest way to do so is by pasting the whole array into the console of the browser dev tools.
I replaced the array in the original code with the one with ASCII characters above. Then, I used search and replace to replace _0x3f3a
with strings
to improve readability.
The code now looks like this.
var strings = [/* the strings */];
try {
canvas= document[strings[1]](strings[0]);
gl= canvas[strings[3]](strings[2]);
gl[strings[4]](0.0,0.0,0.0,1.0);
gl[strings[5]](gl.COLOR_BUFFER_BIT);
shade= canvas[strings[7]](strings[6]);
ctype= canvas[strings[7]](strings[8]);
cid= canvas[strings[7]](strings[10])[strings[9]](5,7);
gl[strings[11]]= window[shade+ cid+ ctype]
} catch (err);
// more code
To make the code actually readable, I need to replace all instances of strings[..]
to the actual strings from the array.
I wrote a simple Python script to do this.
strings=[".GG","querySelector","webgl","getContext","clearColor","clear","shade","getAttribute","type","slice","id","KG","||||||function|var||hhh||||for|charCodeAt|if|length|eee||uuu||mmm|||custom|fromCharCode|String|vvv||ggg|location|catLED|type||color||rounded|font_type|background_color|e0e0e0|size|return|zzz|FF0000|value|seed|yyy|rrr||ooo|slice|ttt|false|window|else|you|iii|let|YOU||compare|0xff|||23|re|hostname|console|||57|protocol|file|54|log|max|Math|98|requestAnimationFrame|true|0BB|00|88|09|0FZ|02|0D|06HD|03S|31|get|new|Image|Object|defineProperty|id|unescape|invited|2000|pathname|const|ech||setTimeout|WANT|WE|custom3|custom2|INVITED|RE|custom1|debugger|1000|invite|the|accepting|alert|Thank|indexOf|go|118|3V3jYanBpfDq5QAb7OMCcT|leaHVWaWLfhj4|atob","toString","replace","x=[0,0,0];1C Y=(a,b)=>{V s=\'\';d(V i=0;i<1e.1d(a.g,b.g);i++){s+=q.p((a.e(i)||0)^(b.e(i)||0))}F s};f(u.19==\'1a:\'){x[0]=12}S{x[0]=18}f(Y(R.u.14,\"T\'13 1z!!!\")==1y(\"%1E%1j%1q%17%1p%1o%1n%1m%1l%1i@M\")){x[1]=1k}S{x[1]=1r}6 K(){7 j=Q;7 G=1t 1u();1v.1w(G,\'1x\',{1s:6(){j=1h;x[2]=1b}});1g(6 X(){j=Q;15.1c(\"%c\",G);f(!j){x[2]=1f}})};K();6 N(J){7 m=Z;7 a=11;7 c=17;7 z=J||3;F 6(){z=(a*z+c)%m;F z}}6 U(h){P=h[0]<<16|h[1]<<8|h[2];L=N(P);t=R.u.1B.O(1);9=\"\";d(i=0;i<t.g;i++){9+=q.p(t.e(i)-1)}r=1Z(\"1X//k/1Y=\");l=\"\";f(9.O(0,2)==\"1V\"&&9.e(2)==1W&&9.1U(\'1D-c\')==4){d(i=0;i<r.g;i++){l+=q.p(r.e(i)^L())}1S(\"1T T d 1R 1Q 1P!\n\"+9+l)}}d(a=0;a!=1O;a++){1N}$(\'.1M\').v({w:\'o\',y:\'#H\',C:\'#D\',E:10,A:5,B:4,I:\" W\'1L 1K! \"});$(\'.1J\').v({w:\'o\',y:\'#H\',C:\'#D\',E:10,A:5,B:4,I:\" \"});$(\'.1I\').v({w:\'o\',y:\'#H\',C:\'#D\',E:10,A:5,B:4,I:\" 1H 1G W! \"});1F(6(){U(x)},1A);","\w+","shift","push","0x2","|","split","0x4","","fromCharCode","0x0","0x1","0x3","\b","g"]
code = open("invite.js").read()
i = code.index("strings[")
while True:
try:
i_end = code.index("]", i)
idx = int(code[i+len("strings["): i_end])
strings[idx] = strings[idx].replace("'", "\\'")
strings[idx] = strings[idx].replace('"', '\\"')
strings[idx] = strings[idx].replace('\n', '\\n')
code = code.replace(code[i:i_end+1], '"' + strings[idx] + '"')
open("invite.better.js", "w").write(code)
i = code.index("strings[")
except:
break
Now, we have the following 6 segments of code.
// [1]
try{canvas= document["querySelector"](".G");gl= canvas["getContext"]("webgl");gl["clearColor"](0.0,0.0,0.0,1.0);gl["clear"](gl.COLOR_BUFFER_BIT);shade= canvas["getAttribute"]("shade");ctype= canvas["getAttribute"]("type");cid= canvas["getAttribute"]("id")["slice"](5,7);gl["KG"]= window[shade+ cid+ ctype]}catch(err){};
// [2]
var _0x55f3=["||||||function|var||hhh||||for|charCodeAt|if|length|eee||uuu||mmm|||custom|fromCharCode|String|vvv||ggg|location|catLED|type||color||rounded|font_type|background_color|e0e0e0|size|return|zzz|FF0000|value|seed|yyy|rrr||ooo|slice|ttt|false|window|else|you|iii|let|YOU||compare|0xff|||23|re|hostname|console|||57|protocol|file|54|log|max|Math|98|requestAnimationFrame|true|0BB|00|88|09|0FZ|02|0D|06HD|03S|31|get|new|Image|Object|defineProperty|id|unescape|invited|2000|pathname|const|ech||setTimeout|WANT|WE|custom3|custom2|INVITED|RE|custom1|debugger|1000|invite|the|accepting|alert|Thank|indexOf|go|118|3V3jYanBpfDq5QAb7OMCcT|leaHVWaWLfhj4|atob","toString","replace","x=[0,0,0];1C Y=(a,b)=>{V s=\'\';d(V i=0;i<1e.1d(a.g,b.g);i++){s+=q.p((a.e(i)||0)^(b.e(i)||0))}F s};f(u.19==\'1a:\'){x[0]=12}S{x[0]=18}f(Y(R.u.14,\"T\'13 1z!!!\")==1y(\"%1E%1j%1q%17%1p%1o%1n%1m%1l%1i@M\")){x[1]=1k}S{x[1]=1r}6 K(){7 j=Q;7 G=1t 1u();1v.1w(G,\'1x\',{1s:6(){j=1h;x[2]=1b}});1g(6 X(){j=Q;15.1c(\"%c\",G);f(!j){x[2]=1f}})};K();6 N(J){7 m=Z;7 a=11;7 c=17;7 z=J||3;F 6(){z=(a*z+c)%m;F z}}6 U(h){P=h[0]<<16|h[1]<<8|h[2];L=N(P);t=R.u.1B.O(1);9=\"\";d(i=0;i<t.g;i++){9+=q.p(t.e(i)-1)}r=1Z(\"1X//k/1Y=\");l=\"\";f(9.O(0,2)==\"1V\"&&9.e(2)==1W&&9.1U(\'1D-c\')==4){d(i=0;i<r.g;i++){l+=q.p(r.e(i)^L())}1S(\"1T T d 1R 1Q 1P!\n\"+9+l)}}d(a=0;a!=1O;a++){1N}$(\'.1M\').v({w:\'o\',y:\'#H\',C:\'#D\',E:10,A:5,B:4,I:\" W\'1L 1K! \"});$(\'.1J\').v({w:\'o\',y:\'#H\',C:\'#D\',E:10,A:5,B:4,I:\" \"});$(\'.1I\').v({w:\'o\',y:\'#H\',C:\'#D\',E:10,A:5,B:4,I:\" 1H 1G W! \"});1F(6(){U(x)},1A);","\w+"];
// [3]
(function(_0x92e4x2,_0x92e4x3){var _0x92e4x4=function(_0x92e4x5){while(--_0x92e4x5){_0x92e4x2["push"](_0x92e4x2["shift"]())}};_0x92e4x4(++_0x92e4x3)}(_0x55f3,0x65));
// [4]
var _0x3db8=function(_0x92e4x2,_0x92e4x3){_0x92e4x2= _0x92e4x2- 0x0;var _0x92e4x4=_0x55f3[_0x92e4x2];return _0x92e4x4};
// [5]
var _0x27631a=_0x3db8;
// [6]
gl["KG"](function(_0x92e4x5,_0x92e4x8,_0x92e4x9,_0x92e4xa,_0x92e4xb,_0x92e4xc){var _0x92e4xd=_0x3db8;_0x92e4xb= function(_0x92e4xe){var _0x92e4xf=_0x3db8;return (_0x92e4xe< _0x92e4x8?"":_0x92e4xb(parseInt(_0x92e4xe/ _0x92e4x8)))+ ((_0x92e4xe= _0x92e4xe% _0x92e4x8)> 0x23?String["fromCharCode"](_0x92e4xe+ 0x1d):_0x92e4xe[_0x92e4xf("0x0")](0x24))};if(!""[_0x92e4xd("0x1")](/^/,String)){while(_0x92e4x9--){_0x92e4xc[_0x92e4xb(_0x92e4x9)]= _0x92e4xa[_0x92e4x9]|| _0x92e4xb(_0x92e4x9)};_0x92e4xa= [function(_0x92e4x10){return _0x92e4xc[_0x92e4x10]}],_0x92e4xb= function(){var _0x92e4x11=_0x92e4xd;return _0x92e4x11("0x3")},_0x92e4x9= 0x1};while(_0x92e4x9--){_0x92e4xa[_0x92e4x9]&& (_0x92e4x5= _0x92e4x5[_0x92e4xd("0x1")]( new RegExp(""+ _0x92e4xb(_0x92e4x9)+ "","g"),_0x92e4xa[_0x92e4x9]))};return _0x92e4x5}(_0x27631a("0x2"),0x3e,0x7c,_0x27631a("0x4")["split"]("|"),0x0,{}))
By splitting the code into multiple lines as shown above, I can look at the console again to see which line is causing the error.
test.js:13 Uncaught ReferenceError: gl is not defined
at test.js:13
gl
from [6] is not defined. Looking at the code, gl
was defined in [1].
canvas = document["querySelector"](".G");
gl = canvas["getContext"]("webgl");
Here it seems that gl
is the WebGL context of a <canvas>
element with a class of G
. Looking at index.html, the <canvas>
element has a class of GG
.
<canvas class="GG" id="glglglgl" width="1" height="1" shade="e" type="l"></canvas>
Replacing G
with GG
in the javascript code should fix it. But now I get a different error.
test.js:13 Uncaught TypeError: gl.KG is not a function
at test.js:13
Looking at the code again, gl.KG
was defined in [1] too.
shade= canvas["getAttribute"]("shade");
ctype= canvas["getAttribute"]("type");
cid= canvas["getAttribute"]("id")["slice"](5,7);
gl["KG"]= window[shade+ cid+ ctype]
shade+cid+ctype
evaluates to "e" + "lg" + "l"
, so gl.KG
is assigned window.elgl
. window
contains all global JavaScript objects (refer to this). So, elgl
must be something that was either defined in this program, or is a built-in function.
However, looking around the code, there is no definition of elgl
, so it could possibly be a built-in function? In [6], gl.KG
(or window.elgl
) is called by passing in the return value of another function. It would be interesting to see exactly what the argument is. So, I replaced gl.KG
with console.log
.
Reloading the page, the following interesting piece of code appears in the console. A piece of code, it seems.
x=[0,0,0];1C Y=(a,b)=>{V s='';d(V i=0;i<1e.1d(a.g,b.g);i++){s+=q.p((a.e(i)||0)^(b.e(i)||0))}F s};f(u.19=='1a:'){x[0]=12}S{x[0]=18}f(Y(R.u.14,"T'13 1z!!!")==1y("%1E%1j%1q%17%1p%1o%1n%1m%1l%1i@M")){x[1]=1k}S{x[1]=1r}6 K(){7 j=Q;7 G=1t 1u();1v.1w(G,'1x',{1s:6(){j=1h;x[2]=1b}});1g(6 X(){j=Q;15.1c("%c",G);f(!j){x[2]=1f}})};K();6 N(J){7 m=Z;7 a=11;7 c=17;7 z=J||3;F 6(){z=(a*z+c)%m;F z}}6 U(h){P=h[0]<<16|h[1]<<8|h[2];L=N(P);t=R.u.1B.O(1);9="";d(i=0;i<t.g;i++){9+=q.p(t.e(i)-1)}r=1Z("1X//k/1Y=");l="";f(9.O(0,2)=="1V"&&9.e(2)==1W&&9.1U('1D-c')==4){d(i=0;i<r.g;i++){l+=q.p(r.e(i)^L())}1S("1T T d 1R 1Q 1P!
"+9+l)}}d(a=0;a!=1O;a++){1N}$('.1M').v({w:'o',y:'#H',C:'#D',E:10,A:5,B:4,I:" W'1L 1K! "});$('.1J').v({w:'o',y:'#H',C:'#D',E:10,A:5,B:4,I:" "});$('.1I').v({w:'o',y:'#H',C:'#D',E:10,A:5,B:4,I:" 1H 1G W! "});1F(6(){U(x)},1A);
Since gl.KG
is called with something that looks like code, and it is assigned to window[shade + cid + ctype]
, it is likely that cid
is supposed to be "va"
, which would mean that gl.KG
is assigned window.eval
. This feels like a good assumption, so I decided to continue by assuming so.
Understanding the weird looking code
However, the new piece of code above (let’s call it [7]), does not look like valid JS code. So, I decided to reverse the function that generates it (code segment [6]), to understand more. [6] can be split up to be as follows:
function(_0x92e4x5,_0x92e4x8,_0x92e4x9,_0x92e4xa,_0x92e4xb,_0x92e4xc) {
var _0x92e4xd=_0x3db8;_0x92e4xb= function(_0x92e4xe) {
var _0x92e4xf=_0x3db8;
return (_0x92e4xe< _0x92e4x8?"":_0x92e4xb(parseInt(_0x92e4xe/ _0x92e4x8)))+ ((_0x92e4xe= _0x92e4xe% _0x92e4x8)> 0x23?String["fromCharCode"](_0x92e4xe+ 0x1d):_0x92e4xe[_0x92e4xf("0x0")](0x24))
};
if(!""[_0x92e4xd("0x1")](/^/,String)) {
while(_0x92e4x9--){_0x92e4xc[_0x92e4xb(_0x92e4x9)]= _0x92e4xa[_0x92e4x9]|| _0x92e4xb(_0x92e4x9)};_0x92e4xa= [function(_0x92e4x10){return _0x92e4xc[_0x92e4x10]}],_0x92e4xb= function(){var _0x92e4x11=_0x92e4xd;return _0x92e4x11("0x3")},_0x92e4x9= 0x1
};
while(_0x92e4x9--) {
_0x92e4xa[_0x92e4x9]&& (_0x92e4x5= _0x92e4x5[_0x92e4xd("0x1")]( new RegExp(""+ _0x92e4xb(_0x92e4x9)+ "","g"),_0x92e4xa[_0x92e4x9]))
};
return _0x92e4x5
} (_0x27631a("0x2"), 0x3e, 0x7c, _0x27631a("0x4")["split"]("|"), 0x0, {})
I read through the code and renamed the variables and functions to improve readability. I also cleaned it up and extracted the function arguments into variables.
dict1={}
counter = 0x7c
code = "x=[0,0,0];1C Y=(a,b)=>{V s=\'\';d(V i=0;i<1e.1d(a.g,b.g);i++){s+=q.p((a.e(i)||0)^(b.e(i)||0))}F s};f(u.19==\'1a:\'){x[0]=12}S{x[0]=18}f(Y(R.u.14,\"T\'13 1z!!!\")==1y(\"%1E%1j%1q%17%1p%1o%1n%1m%1l%1i@M\")){x[1]=1k}S{x[1]=1r}6 K(){7 j=Q;7 G=1t 1u();1v.1w(G,\'1x\',{1s:6(){j=1h;x[2]=1b}});1g(6 X(){j=Q;15.1c(\"%c\",G);f(!j){x[2]=1f}})};K();6 N(J){7 m=Z;7 a=11;7 c=17;7 z=J||3;F 6(){z=(a*z+c)%m;F z}}6 U(h){P=h[0]<<16|h[1]<<8|h[2];L=N(P);t=R.u.1B.O(1);9=\"\";d(i=0;i<t.g;i++){9+=q.p(t.e(i)-1)}r=1Z(\"1X//k/1Y=\");l=\"\";f(9.O(0,2)==\"1V\"&&9.e(2)==1W&&9.1U(\'1D-c\')==4){d(i=0;i<r.g;i++){l+=q.p(r.e(i)^L())}1S(\"1T T d 1R 1Q 1P!\n\"+9+l)}}d(a=0;a!=1O;a++){1N}$(\'.1M\').v({w:\'o\',y:\'#H\',C:\'#D\',E:10,A:5,B:4,I:\" W\'1L 1K! \"});$(\'.1J\').v({w:\'o\',y:\'#H\',C:\'#D\',E:10,A:5,B:4,I:\" \"});$(\'.1I\').v({w:\'o\',y:\'#H\',C:\'#D\',E:10,A:5,B:4,I:\" 1H 1G W! \"});1F(6(){U(x)},1A);"
func = "||||||function|var||hhh||||for|charCodeAt|if|length|eee||uuu||mmm|||custom|fromCharCode|String|vvv||ggg|location|catLED|type||color||rounded|font_type|background_color|e0e0e0|size|return|zzz|FF0000|value|seed|yyy|rrr||ooo|slice|ttt|false|window|else|you|iii|let|YOU||compare|0xff|||23|re|hostname|console|||57|protocol|file|54|log|max|Math|98|requestAnimationFrame|true|0BB|00|88|09|0FZ|02|0D|06HD|03S|31|get|new|Image|Object|defineProperty|id|unescape|invited|2000|pathname|const|ech||setTimeout|WANT|WE|custom3|custom2|INVITED|RE|custom1|debugger|1000|invite|the|accepting|alert|Thank|indexOf|go|118|3V3jYanBpfDq5QAb7OMCcT|leaHVWaWLfhj4|atob"
.split("|")
// [1]
some_decode = function(charcode) {
var _0x92e4xf=some_dict;
return (charcode< 0x3e?"":some_decode(parseInt(charcode/ 0x3e)))+ ((charcode= charcode% 0x3e)> 0x23?String["fromCharCode"](charcode+ 0x1d):charcode[_0x92e4xf("0x0")](0x24))
};
// [2]
if(!"".replace(/^/,String)) {
while(counter--){
dict1[some_decode(counter)]= func[counter]|| some_decode(counter)
};
func= function(key){return dict1[key]}
counter= 0x1
};
// [3]
while(counter--) {
func && (code= code.replace( new RegExp("\b"+ "\w+" + "\b","g"),func))
};
Some names don’t make sense, but it does not really matter. I shall highlight the important parts.
- [1] is a function that converts a string into another string.
- [2] creates some dictionaries (or hash-maps, depends on how you like to call it).
- [3] performs a search and replace using regex and a function defined in [2]. Notice that although [3] is a while loop,
counter
was set to1
in [2], so this loop actually only runs once.
Printing out dict1
that was defined in [2] gives the following.
{"0":"0","1":"1","2":"2","3":"3","4":"4","5":"5","6":"function","7":"var","8":"8","9":"hhh","10":"10",
"11":"11","12":"23","13":"re","14":"hostname","15":"console","16":"16","17":"17","18":"57","19":"protocol","1Z":"atob","1Y":"leaHVWaWLfhj4",
"1X":"3V3jYanBpfDq5QAb7OMCcT","1W":"118","1V":"go","1U":"indexOf","1T":"Thank","1S":"alert",
"1R":"accepting","1Q":"the","1P":"invite","1O":"1000","1N":"debugger","1M":"custom1","1L":"RE","1K":"INVITED",
"1J":"custom2","1I":"custom3","1H":"WE","1G":"WANT","1F":"setTimeout","1E":"1E","1D":"ech","1C":"const","1B":"pathname","1A":"2000","1z":"invited","1y":"unescape","1x":"id","1w":"defineProperty","1v":"Object","1u":"Image","1t":"new","1s":"get","1r":"31","1q":"03S","1p":"06HD","1o":"0D","1n":"02","1m":"0FZ","1l":"09","1k":"88","1j":"00","1i":"0BB","1h":"true","1g":"requestAnimationFrame","1f":"98","1e":"Math","1d":"max","1c":"log","1b":"54","1a":"file","Z":"0xff","Y":"compare","X":"X","W":"YOU","V":"let","U":"iii","T":"you","S":"else","R":"window","Q":"false","P":"ttt","O":"slice","N":"ooo","M":"M","L":"rrr","K":"yyy","J":"seed","I":"value","H":"FF0000","G":"zzz","F":"return","E":"size","D":"e0e0e0","C":"background_color","B":"font_type","A":"rounded","z":"z","y":"color","x":"x","w":"type","v":"catLED","u":"location","t":"ggg","s":"s","r":"vvv","q":"String","p":"fromCharCode","o":"custom","n":"n","m":"m","l":"mmm","k":"k","j":"uuu","i":"i","h":"eee","g":"length","f":"if","e":"charCodeAt","d":"for","c":"c","b":"b","a":"a"}
func
is a straightforward function that takes a key
as argument, and returns dict1[key]
.
Now, it seems that [3] is supposed to search for strings in code
(or equivalently [7] from the previous section) based on the regular expression \b\w+\b
, and replace them based on the mappings in dict1
. But it seems to not be working as intended?
After a painful period of trying different things, I realized that the code in [3] calls new RegExp("\b\w+\b","g")
, which should really be new RegExp(/\b\w+\b/,"g")
. Fixing this up gives us the correct code now.
x=[0,0,0];const compare=(a,b)=>{let s='';for(let i=0;i<Math.max(a.length,b.length);i++){s+=String.fromCharCode((a.charCodeAt(i)||0)^(b.charCodeAt(i)||0))}return s};if(location.protocol=='file:'){x[0]=23}else{x[0]=57}if(compare(window.location.hostname,"you're invited!!!")==unescape("%1E%00%03S%17%06HD%0D%02%0FZ%09%0BB@M")){x[1]=88}else{x[1]=31}function yyy(){var uuu=false;var zzz=new Image();Object.defineProperty(zzz,'id',{get:function(){uuu=true;x[2]=54}});requestAnimationFrame(function X(){uuu=false;console.log("%c",zzz);if(!uuu){x[2]=98}})};yyy();function ooo(seed){var m=0xff;var a=11;var c=17;var z=seed||3;return function(){z=(a*z+c)%m;return z}}function iii(eee){ttt=eee[0]<<16|eee[1]<<8|eee[2];rrr=ooo(ttt);ggg=window.location.pathname.slice(1);hhh="";for(i=0;i<ggg.length;i++){hhh+=String.fromCharCode(ggg.charCodeAt(i)-1)}vvv=atob("3V3jYanBpfDq5QAb7OMCcT//k/leaHVWaWLfhj4=");mmm="";if(hhh.slice(0,2)=="go"&&hhh.charCodeAt(2)==118&&hhh.indexOf('ech-c')==4){for(i=0;i<vvv.length;i++){mmm+=String.fromCharCode(vvv.charCodeAt(i)^rrr())}alert("Thank you for accepting the invite!
"+hhh+mmm)}}for(a=0;a!=1000;a++){debugger}$('.custom1').catLED({type:'custom',color:'#FF0000',background_color:'#e0e0e0',size:10,rounded:5,font_type:4,value:" YOU'RE INVITED! "});$('.custom2').catLED({type:'custom',color:'#FF0000',background_color:'#e0e0e0',size:10,rounded:5,font_type:4,value:" "});$('.custom3').catLED({type:'custom',color:'#FF0000',background_color:'#e0e0e0',size:10,rounded:5,font_type:4,value:" WE WANT YOU! "});setTimeout(function(){iii(x)},2000);
I pasted this into another js file and modified index.html to load this.
Almost there
Running a JS prettifier on this code, I get the following.
x = [0, 0, 0];
const compare = (a, b) => { let s = ''; for (let i = 0; i < Math.max(a.length, b.length); i++) { s += String.fromCharCode((a.charCodeAt(i) || 0) ^ (b.charCodeAt(i) || 0)) } return s };
if (location.protocol == 'file:') { x[0] = 23 } else { x[0] = 57 } // [1]
// [2]
if (compare(window.location.hostname, "you're invited!!!") == unescape("%1E%00%03S%17%06HD%0D%02%0FZ%09%0BB@M")) {
x[1] = 88
} else {
x[1] = 31
}
function yyy() {
var uuu = false;
var zzz = new Image();
Object.defineProperty(zzz, 'id', {
get: function() {
uuu = true;
x[2] = 54 // [3]
}
});
requestAnimationFrame(function X() {
uuu = false;
console.log("%c", zzz);
if (!uuu) { x[2] = 98 } // [4]
})
};
yyy();
function ooo(seed) { var m = 0xff; var a = 11; var c = 17; var z = seed || 3; return function() { z = (a * z + c) % m; return z } }
function iii(eee) {
ttt = eee[0] << 16 | eee[1] << 8 | eee[2];
rrr = ooo(ttt);
ggg = window.location.pathname.slice(1);
hhh = "";
for (i = 0; i < ggg.length; i++) {
hhh += String.fromCharCode(ggg.charCodeAt(i) - 1)
}
vvv = atob("3V3jYanBpfDq5QAb7OMCcT//k/leaHVWaWLfhj4=");
hhh = "govtech-ctf"
mmm = "";
if (hhh.slice(0, 2) == "go" && hhh.charCodeAt(2) == 118 && hhh.indexOf('ech-c') == 4) {
for (i = 0; i < vvv.length; i++) { mmm += String.fromCharCode(vvv.charCodeAt(i) ^ rrr()) }
console.log(mmm);
// alert("Thank you for accepting the invite!"+hhh+mmm)
}
}
function jjj(n) {
rrr = ooo(n);
ggg = window.location.pathname.slice(1);
hhh = "";
for (i = 0; i < ggg.length; i++) {
hhh += String.fromCharCode(ggg.charCodeAt(i) - 1)
}
vvv = atob("3V3jYanBpfDq5QAb7OMCcT//k/leaHVWaWLfhj4=");
hhh = "govtech-ctf"
mmm = "";
if (hhh.slice(0, 2) == "go" && hhh.charCodeAt(2) == 118 && hhh.indexOf('ech-c') == 4) {
for (i = 0; i < vvv.length; i++) { mmm += String.fromCharCode(vvv.charCodeAt(i) ^ rrr()) }
console.log(mmm);
// alert("Thank you for accepting the invite!"+hhh+mmm)
}
}
iii(x) // [5]
The code is not very long, but the important parts are marked at [1], [2], [3] and [4], which sets x[0]
, x[1]
and x[2]
to certain values, based on certain checks. [5] then passes x
into iii()
, which generates the flag based on some random values generated by a RNG seeded with ooo
. The correct flag will only be shown if the correct values in x
are provided.
There was a setTimeout
function which adds a 2 second delay before showing the flag through alert
. I replaced this with a console.log
.
Anyways, it is not really important to know how the flag is generated. Since there are only 8 combinations of values for x
, I just tried each combination. The following combination worked after a few tries.
iii([57, 88, 54])
govtech-csg{gr33tz_w3LC0m3_2_dA_t3@m_m8}