I took a look at jsjiami, a simple console.log("James")
, the encrypted result is actually 3K, which means that the encryption is turned and I don't know how many bends in it. If you want to decrypt a real piece of business code manually, it should be quite tiring, but this article does not study the workload problem, just try manual decryption, and introduce the analysis methods and tool applications to readers.
The same sentence may be encrypted in jsjiami to produce different results. I believe that random factors have been added to this tool. But in order to save space, I will not post the encryption results I used for the experiment here. Some code snippets will be posted during the analysis.
1. The first step is to make it readable
Needless to say, if you want to recognize it manually, you first need to segment sentences. Fortunately, there are still a lot of tools for beautifying (formatting) JS, just find two and try it to see which works better. I am using FeHelper, a browser plug-in.
Then noticed that all variables have changed names, numbers and letters are uncomfortable how to read them. So you need to use the "rename" reconstruction tool to change the name. This makes VSCode work without pressure.
2. Then, a little bit of analysis
2.1. Look at the first two lines
var _0xodm = "jsjiami.com.v6",
_0x47c5 = [_0xodm, "wrvCucKGS1U=", "CGdK", "jsQHMujiLamiSP.Vcom.lrtZMLzvQ6=="];
This sentence declares two variables. One is obviously the version version of jsjiami; the other is an array. Except for the version information, the content is guessed to be Base64. I tried Base64 decoding on the Internet and solved the garbled code, so I put it first, and then Let's see what it is.
In order to facilitate identification, you can rename it to refactor it, and split the declaration according to the specification by the way:
var toolVersion = "jsjiami.com.v6";
var constArray = [toolVersion, "wrvCucKGS1U=", "CGdK", "jsQHMujiLamiSP.Vcom.lrtZMLzvQ6=="];
2.2. Next is an IIFE
The three formal parameters of this IIFE, by the way, change their names: p1
, p2
, p3
. A local function is defined in IIFE and renamed to localFunc1
. This function is called directly after the definition is finished, and after checking it, there is no recursion, so it is equivalent to another IIFE. Similarly, changing its 5 parameters to a meaningless but well-recognizable name, the result:
(function (p1, p2, p3) {
var localFunc1 = function (lp1, lp2, lp3, p14, lp5) {
lp2 = lp2 >> 0x8, lp5 = "po";
var _0x1e174c = "shift",
_0x5428fe = "push";
if (lp2 < lp1) {
while (--lp1) {
p14 = p1[_0x1e174c]();
if (lp2 === lp1) {
lp2 = p14;
lp3 = p1[lp5 + "p"]();
} else if (lp2 && lp3["replace"](/[QHMuLSPVlrtZMLzQ=]/g, "") === lp2) {
p1[_0x5428fe](p14);
}
}
p1[_0x5428fe](p1[_0x1e174c]());
}
return 0xaa95b;
};
return localFunc1(++p2, p3) >> p2 ^ p3;
}(constArray, 0x1c7, 0x1c700));
2.2.1. Parameter kill one is one
He noted that the outer layer IIFE of p1
is above renamed constArray
for that array, anyway scope, simply lamb, to replace it:
p1
toconstArray
, which has the same name as the array outside- At the same time delete the first formal parameter and actual parameter of the outer IIFE
2.2.2. Change the data operation around Yuanyuan back
Now that we know that constArray
is an array, all the attributes acting on it should be related to the array. Just look at these few lines of code and it is not difficult to find:
lp5
only participates in one expression, the result is"pop"
var _0x1e174c = "shift", _0x5428fe = "push"
two variables 0614fc4df4245d are only used as constants.var
toconst
allows the editor to help check whether there is a write operation-of course, the result is not.
Unfortunately, VSCode does not provide an inline refactoring tool, so it can only be done manually by directly replacing these two variables with constants. In _0x1e174c = "shift"
an example, first "shift"
(with quotes) copied to the clipboard, and then _0x1e174c
use several the Ctrl + D all _0x1e174c
are selected, then the Ctrl + V can. Dispose of _0x5428fe = "push"
same way. Then delete the two statements.
2.2.3. Simplify the code, the simpler the better
However, constArray["shift"]()
seems very unaccustomed, it is best to change to constArray.shift()
-this requires the help of ESLint. Initialize the current directory as the npm module project, install and initialize eslint, and then add a rule in the configuration:
"dot-notation": "error"
At this time VSCode will prompt
["shift"] is better written in dot notation.
Move the mouse over and use the quick fix to automatically change all []
calls to .
calls.
2.2.4. Analyze the role of parameters
The next is very interesting. Look at the localFunc1(++p2, p3)
, only two parameters are passed in, so in addition to the lp5
that was removed just now, the formal parameters lp3
and lp4
do not function as parameters, but are used as local variables. . Here you can delete them from the parameter list and use let
define them as local variables-of course, it doesn't matter if this step is done or not.
p2
and p3
are passed in from the external IIFE:
(function (p2, p3) {
...
}(0x1c7, 0x1c700));
At first glance, it looks like a variable, but at a closer look, they are all 0x
, which are obviously integers. And p3
is two p2
0
.
Look at the first sentence inside localFunc1
lp2 = lp2 >> 0x8
(remember that lp2
is the incoming p3
). Isn't this just 0x1c700
two 0
lp2
and p2
it into 0x1c7
- now the value of 0614fc4df425ce and 0614fc4df425cf are the same. And lp1
is the incoming ++p2
, so now lp1 === lp2 + 1
.
In this way, the if
condition (lp2 < lp1)
is satisfied. This if
statement is useless and can be solved directly.
2.2.5. The magical loop
Next is a magical loop, while (--lp1) { }
, without break
middle, that is to say, it needs to loop 0x1c7 + 1
times, that is, 456
times. Basically, it can be guessed that this loop is doing useless things, wasting CPU.
Let’s analyze whether it is:
Now that we have just said that lp3
and lp4
are local variables, you may wish to change the name to local1
and local2
respectively for easy identification. The current while
loop looks like this:
let local1, local2;
while (--lp1) {
local2 = constArray.shift();
if (lp2 === lp1) {
lp2 = local2;
local1 = constArray.pop();
} else if (lp2 && local1.replace(/[QHMuLSPVlrtZMLzQ=]/g, "") === lp2) {
constArray.push(local2);
}
}
Have also analyzed the lp1 === lp2 + 1
, so while (--lp1)
first time execution, lp1
and lp2
to equal, and enter if (lp2 === lp1)
branch; thereafter, will not re-enter the branch, because lp1
been reduced.
Then the content of the first loop execution can be written as:
local2 = constArray.shift(); // toolVersion,即 "jsjiami.com.v6"
lp2 = local2; // "jsjiami.com.v6"
local1 = constArray.pop(); // "jsQHMujiLamiSP.Vcom.lrtZMLzvQ6=="
Since then, no values have been assigned lp2
and local1
At this time, the value of constArray
["wrvCucKGS1U=", "CGdK"] // shift() 和 pop() 操作把头尾的元素干掉了
The following local1.replace(...)
can be directly taken to the console and run. The result is dumbfounding, which is "jsjiami.com.v6"
. Judging from this result, the true
else if (...)
except that it is not executed for the first time. In other words, it is always executed. Then it is the same as else
Well, remove the first loop, this loop becomes:
lp1 = 455; // 0x1c7
// 注意,第一次循环已经把头尾两个元素移出了数组
constArray = ["wrvCucKGS1U=", "CGdK"];
while (--lp1) {
local2 = constArray.shift();
constArray.push(local2);
}
Nothing else, just make a circle, a total of 455 - 1 = 454
times! If you don’t know the number of times, just write a loop and run it:
let a = 455;
let c = 0
while (--a) { c++ };
console.log(c);
local2
not used after while
can be combined into one sentence:
constArray.push(constArray.shift())
This is exactly the same as the sentence after the while
So the total number of executions of this sentence is 454 + 1
, which is 455
times. Since constArray
now has two elements, and 455
is an odd number, after the run, constArray
is like this:
constArray = ["CGdK", "wrvCucKGS1U="];
2.2.6. All useless code
At this point, the first small constArray
code analysis is complete, and nothing meaningful is done except changing 0614fc4df42805.
As for the two sentences return
this code, they are useless, because the return value of the outer IIFE is directly discarded. So the bit operation in the return statement is too lazy to forget it.
The whole piece of code finally becomes one sentence:
constArray = ["CGdK", "wrvCucKGS1U="];
And guess constArray
is actually useless
3. A simple analysis of the remaining code
After analyzing for a long time, there is basically no useful code. And basically it can be concluded that the next dozen lines of code are just a waste of CPU.
Because we know that the original code is console.log("James")
. Therefore, in order to speed up the analysis, I no longer read line by line, and read directly from the back to the front. I saw it at a glance
console[_0x2a10("0", "]o48")](_0x2a10("1", "WCmN"));
Thrust reversers, _0x2a10("0", "]o48")
result is "log"
, and _0x2a10("1", "WCmN")
result is "James"
.
Guess, _0x2a10
is a function for spelling strings, and the first parameter is a tag for branching.
3.1. Look at _0x2a10
Now that you already know that _0x2a10
is a spelling string, getString
's change the name to 0614fc4df428d0. The first parameter is the tag, renamed to flag
, and the second parameter is mostly the initial value for calculation, so it is called initValue
.
The first sentence: flag = ~~"0x".concat(flag);
. This sentence is to convert flag
into a numeric value according to hexadecimal. According to the actual parameters of the call, go to the console running about ~~"0x1"
and ~~"0x2"
know, you can also try it ~~"0xa"
.
The following var _0x1fb2c5 = constArray[flag];
understand, and finally understand here, the original constArray
is used to provide some factors of the splicing string. In that case, it was renamed factor
.
3.2. Next is a long if
statement
If you ignore the if
statement, it can be simplified as follows:
var getString = function (flag, initValue) {
if (getString.iOaiiU === undefined) {
...
getString.LaMLHS = _0xbe9954;
getString.WTsNMX = {};
getString.iOaiiU = !![];
}
...
}
That is, it is initialized when getString
is run for the first time.
Among them, .iOaiiU
has only two references, one for judgment and one for assignment-obviously an initialization mark, which can be renamed to initialized
. It's just that the rename reconstruction tool doesn't seem to be available at this time, so let's rename it manually.
3.3 Ensure globalThis
have on atob()
if
first piece of code in the 0614fc4df429b8 branch is another IIFE. It is copied separately and placed in a separate js
file. VSCode does not prompt that it cannot find variables. So this code can run independently.
(function () {
var _0xea3c63 = typeof window !== "undefined"
? window
: typeof process === "object" && typeof require === "function" && typeof global === "object"
? global
: this;
var _0x5b626 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
_0xea3c63.atob || (_0xea3c63.atob = function (_0x1e0fac) {
var _0x57beec = String(_0x1e0fac).replace(/=+$/, "");
for (var _0x1f3b8d = 0x0, _0x154b1d, _0xad5277, _0x306ad8 = 0x0, _0xcb4400 = ""; _0xad5277 =
_0x57beec.charAt(_0x306ad8++); ~_0xad5277 && (_0x154b1d = _0x1f3b8d % 0x4 ?
_0x154b1d * 0x40 + _0xad5277 : _0xad5277, _0x1f3b8d++ % 0x4) ? _0xcb4400 +=
String.fromCharCode(0xff & _0x154b1d >> (-0x2 * _0x1f3b8d & 0x6)) : 0x0) {
_0xad5277 = _0x5b626.indexOf(_0xad5277);
}
return _0xcb4400;
});
}());
The first sentence is obviously looking for the global
object, which is equivalent to var _0xea3c63 = globalThis
.
Ignore the second sentence first, and the third sentence is obviously to see globalThis
on atob()
, if not, give it one. Since atob()
in most environments, there is no need to worry about its content.
Then, this section of IIFE is to ensure that atob()
available, and you can delete it directly without viewing it.
3.4. A function that seems more useful
Next, a function is defined, the content is removed, and it looks like this:
var _0xbe9954 = function (_0x333549, _0x3c0fbb) {
...
};
getString.LaMLHS = _0xbe9954;
It should be a more useful function to be used through subsequent calls. To facilitate identification, the two parameters were renamed first
and second
.
We also extracted it and copied it to a separate .js
file, and found that there were no missing variables, indicating that it can be taken out and analyzed separately, which is a tool function.
This function defines 5 variables, don't care about them, and look for them when they are used.
3.4.1. atob
The following code is:
let _0x2591ef = ""; // 5 个变量中的一个
first = atob(first);
for (var i = 0x0, len = first.length; i < len; i++) {
_0x2591ef += "%" + ("00" + first.charCodeAt(i).toString(0x10)).slice(-0x2);
}
first = decodeURIComponent(_0x2591ef);
You don't need to look at this code carefully, you probably know that it is to convert a Base64 into %xx
form, and this form of string decodeURICompoment()
(wound a big circle).
Recall that constArray
do look like Base64, so those elements should be processed here.
3.4.2. Then there is the time to burn the brain
The next code is to recover the string we need initValue
and constArray[i]
through a lot of mathematical calculations. The algorithm must be designed by the encryption tool itself, so I don’t want to analyze it. The calculation is not difficult, it is brain-burning, you need to be careful, and you can't make mistakes at all.
4. End
Yes, it's over, it stops abruptly.
The purpose of writing this article is not to completely solve the code, just to prove its possibility, and to introduce analysis methods and tool applications. Part 2 should be finished after writing, because there is no new method used later.
In general, jsjiami added a lot of useless and brain-burning programs to the original code to increase the difficulty of decoding. Such a simple sentence has been solved for so long, not to mention the production code. The price is also there-really burn the CPU.
Well, I did another boring thing!
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。