Reversing a Web3 Scam via Dynamic Analysis and Deobfuscation
Author: Ching367436
When I was creating a Web3 challenge for AIS3 Pre-exam 2024, I referenced a scam website that had been sent to my wallet before. I wanted to learn how to implement a sleek Web3 wallet connect interface based on it, but I discovered that the website’s code was heavily obfuscated and included anti-debugging mechanisms, making it difficult to use the debugger properly. So, I started investigating the website, and eventually, it became the content of the challenge I created.
Observation
A scam ERC20 token has appeared in my wallet, as shown in the image below. The link leads to a website that looks like a Blast2L site.
Uncover the IP hidden behind Cloudflare
The website is hidden behind Cloudflare, so I first attempted to discover its actual IP address. An SPF record for that domain includes an IP address, as shown below.
The IP address is from Russia. Furthermore, the website’s CSS contains domains from Russia, and even the code includes Russian characters. This suggests a potential association between the website and Russia. I discovered a list of scam websites hosted on that IP from Gist, indicating that the IP is highly suspicious.
If we directly visit the website, we will see a FASTPANEL page.
I modified the request host header to the original domain and sent the request again. The original website was displayed, confirming that the SPF IP is indeed the website’s IP.
Handling Anti-Debugging
The website’s main script is heavily obfuscated and over 2 million characters long. Therefore, we need to use dynamic analysis to learn it quickly.
The breakpoint problem
If we open the developer tools, we will be paused at a breakpoint. Resuming the execution will cause us to be paused again, so let’s try removing the breakpoint first. There are two functions in the call stack when paused; let’s investigate it.
The code around the breakpoint is displayed below. It’s a conditional ternary operator with a constant condition (true), so the false condition code is essentially dead code.
If we deobfuscate the code dynamically, we get the following code within the 0x2a8683
function. The code retrieves the function constructor from a function and uses it to create a new function with a debugger
statement in its body. Then, it calls the function, triggering the breakpoint. Commenting out the code that calls 0x2a8683
solves the breakpoint problem.
Disable disable-devtool.js
After removing the breakpoint, I reopened the dev tools, and the website closed itself, as shown below:
After inspecting the traffic, I found that the website loads the disable-devtool.js, the root cause of the website closing itself.
When reading the disable-devtool.js document, I discovered that one needs to call the DisableDevtool
function to initialize its functionality. So I searched for DisableDevtool
in the script and found where it’s invoked. They forgot to obfuscate it!
I deobfuscated the surrounding code dynamically and obtained the following code. First, it checks if _0x476324["disable_dev_tool"]
is true in order to determine whether to enable disable-devtool.js. If it is true, it then verifies if DisableDevtool
is loaded correctly. If not, it will be reloaded. Therefore, if we block the disable-devtool.js traffic, the web pages will reload infinitely. Commenting out DisableDevtool
should disable disable-devtool.js.
The 0x476324
variable seems interesting. It appears to be a config file, as shown below, revealing the website’s intention of draining the user’s wallet. There is a key called api
. I want to know its function, so let’s dig into it.
API
To trace the usage of the 0x476324['api']
, I hook the getter and setter of it using Object.defineProperty
as shown below:
It indeed hit our breakpoint! Let’s trace its call stack.
In _0x3d3492
, it fetches the API with _0x1b9465
body. Let’s investigate _0x1b9465
.
_0x3d3492
_0x1b9465
is from the _0x1a0343
function. _0x1a0343
encrypts data like customer_id
, the victim’s wallet address, and the website’s URL.
Note that it uses the key inferno
, so it was probably developed by Inferno. (Sunsec told me it was perhaps from Inferno while I presented the research to him.)
Let’s return to the fetch
part of the code.
The _0x1a0343
function after deobfuscation
…
The _0x1a0343
function before deobfuscation
After fetching the API, it is called _0x6899a5
with the returned data. Let us investigate it.
_0x6899a5
decrypts the data and returns it. Let us investigate the decryption key.
Deobfuscated _0x6899a5
The original _0x6899a5
The decryption key is from _0x97a49a
. Let’s look into it.
_0x97a49a()
returns the following information, including the hardcoded key. It also reveals the local storage encryption key, indicating that the local storage is likely encrypted. Let us investigate how the decrypted data is used.
The _0x97a49a()
return data
The function _0x1e89b9 uses the API return data _0x2612b1. If _0x2612b1[‘blacklisted’], the program will exit. It’s important to note that the API has knowledge of the victim’s address and URL, so the website might blacklist the localhost URL or wallet address belonging to security researchers for security reasons. The drainer address is also specified by the API.
The deobfuscated _0x1e89b9
…
The original _0x1e89b9
Automate the code deobfuscation process
Manually deobfuscating the code is effective, but the user experience is bad. I attempted to use existing deobfuscation tools, but they were ineffective. Therefore, I decided to develop my own deobfuscator.
The original code and the deobfuscation using the existing deobfuscator, as well as the deobfuscation using my deobfuscator, are shown below. Let us see how I achieved this.
The original code
The code deobfuscated by an existing deobfuscator
The code deobfuscated by my deobfuscator
Let’s start by identifying the obfuscation in the code. There is a function called __p_0566375197
that appears over twenty thousand times. Let us see what it does.
It checks if the value of __p_5272508748[x]
is present. If it is, it returns the value. If not, it calculates it using __p_6222965791(__p_7737682834[x]))
and fills in __p_5272508748[x]
, then returns the value. What __p_6222965791
does is perform calculations based on its argument. Therefore, we can replace all occurrences of __p_0566375197(x)
with its value.
I found a helpful article called “Deobfuscating JavaScript via AST: Replacing References to Constant Variables with Their Actual Value “ that has been very useful to me. I used the methodology to replace __p_0566375197(x)
with its actual value. And here is the AST visitor I wrote, which does the job.
It indeed makes the code more readable, as shown below.
There is also a function called __p_1452585703_calc
, which occurs over five thousand times. Let us see what it does.
The function __p_1452585703_calc
carries out calculations on its parameters. The specific calculation to be performed is determined by the value of the __p_2651934416
variable, which is passed as a global variable. This may be the reason why an existing deobfuscator is unable to handle it effectively.
I replaced all occurrences of __p_1452585703_calc
with their actual operations using the code below.
After implementing the aforementioned optimizations and others, the code became much more readable. The overall code size was reduced by 72%, a significant reduction.
Vulnerabilities in the scam website
After the code became readable, I performed some static analysis and discovered an XSS vulnerability. The application saves logs in HTML, which could be exploited to carry out XSS attacks, potentially revealing sensitive information of the attackers, such as their IP addresses.
Make it a CTF challenge
After I finished reversing the website, I modified its code and turned it into a CTF challenge. In the end, it became part of the AIS3 Pre-exam 2024 with a difficulty level of hard. Only 3 out of 253 participants were able to solve it. You can find the challenge link and the solution below. Additionally, I taught some of the techniques I used in this challenge in a course at TaiwanHolyHigh.
The CTF challenge:
https://fuwamoco-flag-checker.ching367436.me/