~$ Dissecting the Central-Infosec Static Code Analysis challenge
Posted on Apr. 20th, 2021.
This is a write-up for the hardest of the "14. Web Exploitation: Advanced (CIS-WEBSRV01)" series of challenges in the context of the Central Infosec CTF.
The challenge consists of finding the statically encoded credentials to then find the flag, so we need to work through the obfuscated code and see how it works.
- Step 1: Visiting the page
- Step 2: Deobfuscating the script
- Step 3: Determining the password validation scheme
- Step 4: Finding the password
Step 1: Visiting the page
The first step in this challenge is actually finding it. As per the
$MACHINE_IP/robots.txt file, the URL for the challenge is
$MACHINE_IP/hack-the-static-hard, and as we load it up in a browser, we see the following page:
To check out the script, we will first check the source code for a
<script> tag, and check if it is either loading a script from somewhere, or if it is embedded into the page. To do this, we right click the page and press
Inspect element, which renders the following in the sidepane:
The two circled areas are the function that is called when the
"Login" button is pressed, and the second is the actual script, which contains that function.
Step 2: Deobfuscating the script
Let us focus on that script and directly start by separating the functions and indenting the code: (Trust me, you do not want this oneliner in a code box on this page...)
There's a saying that goes: "What in the fresh hell is this?". It is apt in the context.
So let's start at the top:
This sets up an array of strings and function names. Quite nice, let's rename all occurences of
_0x50da in our code to
data, because that will make things easier.
What does this section do? Well, it certainly doesn't make itself readable, that's for sure.
What we can say is that it is an unnamed function that is directly executed, and it takes 2 parameters: our list of values and functions, and the number
0x72 (which is the hexadecimal value of
114. So what we can do is rename
data (again) and rename
In the middle of our unnamed function we have a named function called
dataGetter49 (which just rolls of the tongue). It takes a value related to
n, which is
++n really is doing to
n is saying: "Hey, before you go and do anything, here's a 1 on me". Effectively, at the moment it is then used, the value of
n + 1.
Since it seems to be muddling with the contents of data, we will rename the internal function to
Let's make our code reflect those changes:
prototype structure, you can call a function on them using the string accessor. This is what we see with
data['push'](...), which is the annoying way of writing
Let's follow up on those changes:
We can run it as many times as we want, and that isn't going to change, so we might as well replace lines 2 until 11 of the entire script with:
Onwards then, let us now look at the function
What does this function do? Well, it takes two parameters. The first one is subtracted by
0x0, which is the hexadecimal format for the number
What it then does is access our
data variable (an array) at the index defined by the variable we just converted to a number, and then returns that value.
_0x50da49 has absolutely no effect whatsoever on this function, and thus can be removed.
As such, we've determined that the function acts as a data "getter" sorts, so we can rename it to
Let's put these changes in writing:
Let's already replace the things we know, and then we will get to identifying what is going on:
Weirdly enough, this has made things worse for us. Well then, I guess we will have to run dataGetter for all of these various values and run with that!
Now let us replace the replacement game:
! => false and
!! => true), and maybe transforming the ternary operators (which are one-liner if-else statements in the form of
condition ? do_if_condition_true : do_if_condition_false) into actual if-else statements:
Woow, far out! This has made the code readeable, and let's us progress onto the next part!
Step 3: Determining the password validation scheme
Well, we now have found the diagram for how this works. But what makes the engines turn? How does it work?
If you would look at line 11, the first thing to notice is that it is looking for whether or not the username provided in the form is
If that condition is met, it will set the
isAdmin hidden field to
Then we get into a condition we haven't "decoded" yet. Firstly,
So this password validation scheme is the following:
- It checks whether or not the username entered
"Admin". It sets the
isAdminpage value to 1, which here stands for true.
- If that is the case, it will then check whether the password entered is correct and if that is right, sets a hidden
user_idfield to some obscure value (
"XzB4NDI1MjE2"), which is what the form uses to check for the password's validity.
- Otherwise, it resets the
- If the username was incorrect, it sets the hidden
user_idfield to a value that won't be recognised by the password validity checker.
Step 4: Finding the password
Well, this doesn't help us much. Or does it? If you look at the naming convention of the two functions (
btoa) you might notice that they are two sides of the same coin. The one goes "[from]
b" and the other goes "[from]
So if we apply that principle to our code, and remember our first algebra classes, then we know that if you do one thing to one side, then you do the same to the other:
Which, simplified further is:
We now know of a username (
"Admin") and a password (
"XzB4NWU0NDcz=="). Let's go grab the flag!