Contents

🌐 Some Assembly Required 1

A detailed write-up of the Web challenge 'Some Assembly Required 1' from PicoCTF - 2021

/images/PicoGym/PicoCTF-2021/SomeAssemblyRequired1/challenge_presentation.png
Challenge Presentation

📊 Challenge Overview

Category Details Additional Info
🏆 Event PicoGym Event Link
🔰 Category Web 🌐
💎 Points 500 Out of 500 total
⭐ Difficulty 🟡 Medium Personal Rating: 2/10
👤 Author Sears Schulz Profile
🎮 Solves (At the time of flag submission) 32.470 solve rate
📅 Date 18-02-2025 PicoGym
🦾 Solved By mH4ck3r0n3 Team:

📝 Challenge Information

http://mercury.picoctf.net:1896/index.html

🎯 Challenge Files & Infrastructure

Provided Files

1
Files: None

🔍 Initial Analysis

First Steps

Initially, the website appears as follows:

/images/PicoGym/PicoCTF-2021/SomeAssemblyRequired1/site_presentation.png
Site Presentation

As a first step, I immediately thought of Web Assembly due to the challenge title, so I inspected the page source and found a JavaScript file:

/images/PicoGym/PicoCTF-2021/SomeAssemblyRequired1/page_source.png
Page Source
/images/PicoGym/PicoCTF-2021/SomeAssemblyRequired1/js_source.png
Js Source

The JavaScript code is quite obfuscated, and after searching online, I found a tool to help deobfuscate it a bit (https://unminify.com/):

/images/PicoGym/PicoCTF-2021/SomeAssemblyRequired1/unminify.png
Unminify

This gave the following output:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
const _0x402c = [
   "value",
   "2wfTpTR",
   "instantiate",
   "275341bEPcme",
   "innerHTML",
   "1195047NznhZg",
   "1qfevql",
   "input",
   "1699808QuoWhA",
   "Correct!",
   "check_flag",
   "Incorrect!",
   "./JIFxzHyW8W",
   "23SMpAuA",
   "802698XOMSrr",
   "charCodeAt",
   "474547vVoGDO",
   "getElementById",
   "instance",
   "copy_char",
   "43591XxcWUl",
   "504454llVtzW",
   "arrayBuffer",
   "2NIQmVj",
   "result",
];
const _0x4e0e = function (_0x553839, _0x53c021) {
   _0x553839 = _0x553839 - 0x1d6;
   let _0x402c6f = _0x402c\[_0x553839];
   return _0x402c6f;
};
(function (_0x76dd13, _0x3dfcae) {
   const _0x371ac6 = _0x4e0e;
   while (!![]) {
       try {
           const _0x478583 =
               -parseInt(_0x371ac6(0x1eb)) +
               parseInt(_0x371ac6(0x1ed)) +
               -parseInt(_0x371ac6(0x1db)) * -parseInt(_0x371ac6(0x1d9)) +
               -parseInt(_0x371ac6(0x1e2)) * -parseInt(_0x371ac6(0x1e3)) +
               -parseInt(_0x371ac6(0x1de)) * parseInt(_0x371ac6(0x1e0)) +
               parseInt(_0x371ac6(0x1d8)) * parseInt(_0x371ac6(0x1ea)) +
               -parseInt(_0x371ac6(0x1e5));
           if (_0x478583 === _0x3dfcae) break;
           else _0x76dd13\["push"](_0x76dd13\["shift"]());
       } catch (_0x41d31a) {
           _0x76dd13\["push"](_0x76dd13\["shift"]());
       }
   }
})(_0x402c, 0x994c3);
let exports;
(async () => {
   const _0x48c3be = _0x4e0e;
   let _0x5f0229 = await fetch(_0x48c3be(0x1e9)),
       _0x1d99e9 = await WebAssembly\[_0x48c3be(0x1df)](await _0x5f0229\[_0x48c3be(0x1da)]()),
       _0x1f8628 = _0x1d99e9\[_0x48c3be(0x1d6)];
   exports = _0x1f8628\["exports"];
})();
function onButtonPress() {
   const _0xa80748 = _0x4e0e;
   let _0x3761f8 = document\["getElementById"](_0xa80748(0x1e4))\[_0xa80748(0x1dd)];
   for (let _0x16c626 = 0x0; _0x16c626 < _0x3761f8\["length"]; _0x16c626++) {
       exports\[_0xa80748(0x1d7)](_0x3761f8\[_0xa80748(0x1ec)](_0x16c626), _0x16c626);
   }
   exports\["copy_char"](0x0, _0x3761f8\["length"]),
       exports\[_0xa80748(0x1e7)]() == 0x1 ? (document\[_0xa80748(0x1ee)](_0xa80748(0x1dc))\[_0xa80748(0x1e1)] = _0xa80748(0x1e6)) : (document\[_0xa80748(0x1ee)](_0xa80748(0x1dc))\[_0xa80748(0x1e1)] = _0xa80748(0x1e8));
}

Renaming the variables and doing some “reverse engineering,” I arrived at the following code:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
const dictionary = [
   "value",
   "2wfTpTR",
   "instantiate",
   "275341bEPcme",
   "innerHTML",
   "1195047NznhZg",
   "1qfevql",
   "input",
   "1699808QuoWhA",
   "Correct!",
   "check_flag",
   "Incorrect!",
   "./JIFxzHyW8W",
   "23SMpAuA",
   "802698XOMSrr",
   "charCodeAt",
   "474547vVoGDO",
   "getElementById",
   "instance",
   "copy_char",
   "43591XxcWUl",
   "504454llVtzW",
   "arrayBuffer",
   "2NIQmVj",
   "result",
];

const getKey = function (index) {
   index = index - 470;
   return dictionary\[index];
};
let exports;
(async () => {
   let wasmFile = await fetch(getKey(489)),
       wasmModule = await WebAssembly\[getKey(471)](await wasmFile\[getKey(482)]()),
       wasmInstance = wasmModule\[getKey(470)];
   exports = wasmInstance\["exports"];
})();
function onButtonPress() {
   let inputText = document\[getKey(480)]("input")\[getKey(472)];
   for (let i = 0; i < inputText.length; i++) {
       exports\[getKey(474)](inputText\[getKey(479)](i), i);
   }
   exports\["copy_char"](0, inputText.length);
   if (exports\[getKey(475)]() == 1) {
       document\[getKey(480)]("result")\[getKey(473)] = getKey(476);
   } else {
       document\[getKey(480)]("result")\[getKey(473)] = getKey(478);
   }
}

From the last cleaning of the code, we can see that a file ./JIFxzHyW8W (the twelfth element of the dictionary array, since the function getKey(482) is called, where 482-470=12) is being loaded, which will be in wasm (Web Assembly) format. The first thing I did was visit the route http://mercury.picoctf.net:1896/JIFxzHyW8W to download the file and analyze it. This can also be done directly with curl by specifying the flag --output filename:

1
curl http://mercury.picoctf.net:1896/JIFxzHyW8W --output JIFxzHyW8W

/images/PicoGym/PicoCTF-2021/SomeAssemblyRequired1/curl.png
Curl

(You can also rename the file in the --output flag, but I preferred to leave it as is). To verify the file, I also used the file command to begin a preliminary analysis of it:

/images/PicoGym/PicoCTF-2021/SomeAssemblyRequired1/file.png
File

Now that we have everything we need, we can move on to the exploitation phase.

🎯 Solution Path

Exploitation Steps

Exploitation

The exploitation could be done in several ways, but a simple static analysis of the file using commands like strings, xxd, wasm2wat, etc., is enough to extract the flag text from the wasm file. Personally, I started the analysis with strings and quickly and clearly found the flag:

1
strings JIFxzHyW8W

Flag capture

/images/PicoGym/PicoCTF-2021/SomeAssemblyRequired1/manual_flag.png
Manual Flag
/images/PicoGym/PicoCTF-2021/SomeAssemblyRequired1/flag_submit.png
Flag Check

🛠️ Exploitation Process

Approach

The automatic exploit downloads the .wasm file from the server and uses subprocess to execute strings on the .wasm file. Then, it extracts the flag from the output using a regex.

🚩 Flag Capture

Flag

Proof of Execution

/images/PicoGym/PicoCTF-2021/SomeAssemblyRequired1/automated_flag.png
Automated Flag
Screenshot of successful exploitation

🔧 Tools Used

Tool Purpose
Python Exploit
Strings WASM Analysis

💡 Key Learnings

Skills Improved

  • Binary Exploitation
  • Reverse Engineering
  • Web Exploitation
  • Cryptography
  • Forensics
  • OSINT
  • Miscellaneous

📊 Final Statistics

Metric Value Notes
Time to Solve 00:10 From start to flag
Global Ranking (At the time of flag submission) Challenge ranking
Points Earned 500 Team contribution

Created: 18-02-2025 • Last Modified: 18-02-2025 *Author: mH4ck3r0n3 • Team: *