{"id":16442997,"url":"https://github.com/skyzh/sjtuctf-2019-writeup","last_synced_at":"2026-04-18T03:31:45.116Z","repository":{"id":85472473,"uuid":"216311946","full_name":"skyzh/sjtuctf-2019-writeup","owner":"skyzh","description":"❓ Solutions and exploitation snippets for SJTU CTF 2019","archived":false,"fork":false,"pushed_at":"2020-02-07T04:01:29.000Z","size":23,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-02-26T04:44:40.238Z","etag":null,"topics":["crypto","ctf","exploitation","misc","reverse","sjtu","web","write-ups","writeup"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/skyzh.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2019-10-20T05:24:19.000Z","updated_at":"2022-01-09T00:40:12.000Z","dependencies_parsed_at":"2023-03-13T05:26:07.623Z","dependency_job_id":null,"html_url":"https://github.com/skyzh/sjtuctf-2019-writeup","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/skyzh/sjtuctf-2019-writeup","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/skyzh%2Fsjtuctf-2019-writeup","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/skyzh%2Fsjtuctf-2019-writeup/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/skyzh%2Fsjtuctf-2019-writeup/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/skyzh%2Fsjtuctf-2019-writeup/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/skyzh","download_url":"https://codeload.github.com/skyzh/sjtuctf-2019-writeup/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/skyzh%2Fsjtuctf-2019-writeup/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31955637,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-18T00:39:45.007Z","status":"online","status_checked_at":"2026-04-18T02:00:07.018Z","response_time":103,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["crypto","ctf","exploitation","misc","reverse","sjtu","web","write-ups","writeup"],"created_at":"2024-10-11T09:19:13.575Z","updated_at":"2026-04-18T03:31:45.110Z","avatar_url":"https://github.com/skyzh.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# SJTU CTF 2019 Write-up\n\n## Crypto\n### babyblock\nIt’s obvious that this encryption use the same key and a plaintext-dependent VI every time.\n\nFirst of all, we may brute force sum of the flag plaintext string. Refer to `brute_force_message.py` to see how it works. This script generates a bash script to send request to server with strings of different sum. With the condition that ciphertext begins with `0ops{`, if we observe any reply that has the same first five bytes with ciphertext of the encrypted flag, we can obtain the sum of the original plaintext. In this case it is `2740`.\n\nThen we may obtain characters in the flag one by one. Refer to `decrypt.py` for details. The program generates plaintext to be encrypted on server, then it asks for ciphertext. With the equation that `cipher ^ plain = cipher_payload ^ plain_payload`, we can obtain the flag in less than 30 steps.\n### babylcg\n\nFirst of all we need to solve work of proof. This can be easily done with brute force. Refer to `work.py` for details.\n\nThen we may solve the challenge one by one.\n\nChallenge 0 is easy. Just copy-paste the LCG algorithm.\n\nFor challenge 1, with equation that `m * x + c = next_x (mod p)`, we may obtain `c` with `c = next_x - m * x (mod p)`.\n\nFor challenge 2,\n```\na1 = m * a0 + c (mod p)\na2 = m * a1 + c\na3 = m * a2 + c \n```\nHence, `a2 - a1 = m * (a1 - a0)`, we may obtain that `m = (a2 - a1) * (a1 - a0)^-1`\n\nTherefore, `c` can be obtained with the method described in challenge 1.\n\nFor challenge 3, we should solve an equation. [THERE SHOULD BE A REFERENCE]. Refer to `challenge_3.py` for implementation.\n\n### babyrsa\nFor p and q in this RSA algorithm, we assume that\n```\np = 10^256 * a + b\nq = 10^256 * b + a\n```\nTherefore,\n```\nn = p * q = 10^512 * ab + 10^256 * (a^2 + b^2) + ab\n```\nAs both a, b \u003c 10^256, we can obtain `a*b`, and then obtain `a + b`. As a, b are known, we may obtain p, q. Then we can decrypt the ciphertext with standard RSA methods.\n\n### CRC Forgery\nAs plaintext, CRC and CRC algorithm are known, we may backtrack the missing 64 bits.\n\nLet `cipher_msg = 00...00 + CRC`, `plain_msg = message + const`\n\nAt the i-th iteration of CRC algorithm, the `length - i` bits haven’t been altered. Therefore, we may do the reverse algorithm. For current reverse iteration, if `cipher_msg[(i+64) bit] != plain_msg[(i+64) bit]`, then this bit should be flipped at i-th iteration. XOR cipher message with poly until we obtain what the missing bits should be before calculating CRC of the remaining bits `blank_after_bits`.\n\nThen we calculate CRC from beginning to the missing blanks. We obtain what the missing blanks look like after calculating previous bits. We assume that value to be `blank_before_bits`.\n\nIt’s obvious that `blank_after_bits ^ blank_before_bits = blank_bits`. Then we obtain the result.\n\n### Mixed Cipher\nFrom the Python script we observe that this encryption does two things. First of all, mapping each character to other character, aka. CAESAR encryption. Then, it XOR each byte with a key stream, where key is a constant size.\n\nTherefore, we may first guess key length. As key and message are visible characters, we can brute force different key length. We enumerate: 1. Stride 2. Every position of the key 3. Possible character of key in this position. The correct combination should render the plaintext in visible characters. After several trials, we found that key length is 29.\n\nThen we just guess the key. Fortunately, the key is not a random string. After determining some characters, we may fill the blanks according to the generated `mappings.txt`, just like what we did in high school entrance examination. Then we obtain the key.\n\nNow we’re left with a CAESAR-encrypted ciphertext. With some online tools we can easily obtain the result. [REFERENCE]\n\n## Reverse\n### snake\nIt is easily observed that this is a Python byte-code file. Just decompile it, we get a function with a lot of if statements. Follow logic of the conditions to construct the flag.\n\n### chaos\nIt seems that the ELF binary is corrupted, so we can’t decompile it with Ghidra. Attach gdb to the process, it seems that it’s running something like CAESAR cipher. Find where the input, ciphertext and encryption rule are on stack. Then write a simple program to decrypt it.\n\n### triblock\nThe encryption is hidden in 3 parts: INIT, FIN and main function. Decompile it with Ghidra. We obtain the flag in two steps.\n\nFirst, reversing the encryption method in main. We can easily guess what do some of the functions do, such as `generate_key_from_string`, `encrypt` and `reverse`. Then analyze what the ciphertext is. From INIT function and ELF binary we may reverse what ciphertext is in memory. Then brute force the plaintext. It turned out to be `this_is_not_flag`.\n\nThen we do the second part of the reversing in FIN. Indeed this part of code will never be executed. As encrypt is stateful, which means that what we’ve encrypted last time will affect what we obtain this time, we must run decrypt on phase 1 plaintext to preserve the original program flow. Then it’s easy brute force the plain text with the same method.\n\n## Web\n## basic_web\nJust look at the source code.\n\n## ezxxe\nXXE exploitation with Java. Note that `flag` is a directory.\n\n## Message board\nJust a kind of XSS. Build a server, brute force the md5, and let the admin visit. Then you get the flag.\n\nBut there’s another problem. `view.php` explicitly cleared the cookie. Then XSS Audit came to help. [REFERENCE]. By constructing an URL to disable clearing flag scripts, we obtain the flag on our server.\n\n## Misc\n### anti-hack11\nNever reinvent the wheel. Just download a Chrome extension to solve the captcha, and modify it to run on CTF server. You may refer to `anti-hack11.diff`. [REFERENCE]\n\n### anti-hack10\nUse volidity to analyze the core dump. [REFERENCE] Flag is in memory region of `mspaint.exe`. Use GIMP to view the memory as raw image. Adjust the width and the flag will be shown. [REFERENCE]\n\n### sqlmap yibasuo\nThe `sqlmap` utility guess what’s inside the database one character by one. With Wireshark filter `http.request_in`, we can see all request sent by `sqlmap`. Then find `!=` in HTTP POST form. In this way we obtain the flag.\n\n### weird logo\nWith `pngcheck` we find there’re hidden data at the end of the image. That’s a zip file in reverse byte order. Then we can get a QR code from the image by bit-and 1 lowest bit in pixels of each channel. Refer to `weird_logo.py`. [REFERENCE] Scanning the QR code we obtain the password of the zip file.\n\n### animal\nJust pickle with latest protocol of Python pickle. Here I use a special way to construct the payload automatically.\n\n### dog\nFor the first layer of encryption, there’s no password indeed. Use `ZipCenOp.jar` to remove the password.\nFor the second layer, there’re base32 encoded password at the end of the file.\nThen use `cat * \u003e all.jpg` to get the image. Use binwalk to extract another zip. From EXIF data we obtain the password of the zip.\nFinally brute force the password with john. It turns out to be a 8-digit number.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fskyzh%2Fsjtuctf-2019-writeup","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fskyzh%2Fsjtuctf-2019-writeup","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fskyzh%2Fsjtuctf-2019-writeup/lists"}