~$ Dissecting the Central-Infosec Python challenges
Tags:Information SecurityCTFWrite Up
This is a write-up of the "07. Reversing: Code Analysis (CIS-WEBSRV01)" series of challenges in the context of the Central Infosec CTF.
The challenges consist of Python code to analyse and reverse, in order to find a series of flags. This write-up assumes just a basic amount of programming knowledge, and will walk you through most of the rest.
Summary
- Challenge 1: Reading the script
- Challenge 2: Flipping the script
- Challenge 3: Reversing the function
- Challenge 4: Flipping the encoding
- Challenge 5: Dissassembling the operations
Challenge 1: Reading the script
The first challenge, located at the URL $MACHINE_IP/python-01
, is a small python script that refreshes a few basics of string operations in Python, which you can see below.
Let's break this down.
In the code above, we can see the declaration of a few variables (ie. boxes that have a name and that now contain a bit of information).
In this small bit, str1
has the character "Y"
added to it, resulting in it having the value "PY"
. The content of str2
however, gets replaced with the value "57R"
.
This last bit is the bit that does the work of showing the flag, which is Central-InfoSec{PY_57R_C0NC@7}
.
Challenge 2: Flipping the script
The second challenge, located at the URL $MACHINE_IP/python-02
, is a small script that goes on about string replacement. The key is to go from the output ("Central-InfoSec{$7R1N9_R3PL@C3M3N7_$47645558}"
) and reverse the code to find the input ("Central-InfoSec{???}"
).
The way to deal with this is to reverse the steps taken to obfuscate the flag, which are each of the replace
operations.
One thing to note is the optional third parameter that sometimes shows up, which according to the function signature (string.replace(oldvalue, newvalue, count)
) is the amount of times the replacement is allowed to run on one string.
So to reverse this we are going to define the input as the original output, do every step in the opposite order and switch the oldvalue
and newvalue
parameters in each replace
statement copied from the original code.
To do this we're going to be writing the following script:
This means our original string was Central-InfoSec{57R1N9_R3PL@C3M3N7_55762558}
, and is also the flag required for this challenge.
Challenge 3: Reversing the function
The third challenge, located at the URL $MACHINE_IP/python-03
, is a small script that goes on about string replacement. The key is to go from the output ("Central-InfoSec{$7R1N9_R3PL@C3M3N7_$47645558}"
) and reverse the code to find the input ("Central-InfoSec{???}"
).
So, before doing anything, let's find out what this code does.
The first thing it does is define a function (which you can notice by the def
keyword), named encodeNumbers
. A function is a block of code you want to run multiple times without having to rewrite the code each time, and is quite useful in programming.
This function takes three parameters, the first (statement
) being the flag, the second (num1
) and third (num2
) being numbers.
Its first step is defining a parameter named number1
, to which the string representation of the num1
parameter is assigned.
Its second step is defining a parameter named number2
, which is the string representation of the sum of number1
(but as a number, so basically num1
) and the num2
parameter.
Its third step is replacing number1
string with the number2
string in the given flag.
Finally, it returns the flag. (When a function returns a value it allows this value to be assigned to a variable outside of the function itself)
What this function is doing is replacing every occurence of num1
by the sum of num1
and num2
.
So to reverse this function, we make it take the exact same numbers, but invert the order in the replace
statement.
We then do like before and reverse the order of operations in order to obtain:
We can check that this worked correctly by setting the result (Central-InfoSec{3NC0D3D_NUM83R5_15130}
) as the original flag and rerunning the encoding, which will produce the original output.
This means our original string was Central-InfoSec{3NC0D3D_NUM83R5_15130}
, and is also the flag required for this challenge.
Challenge 4: Flipping the encoding
The fourth challenge, located at the URL $MACHINE_IP/python-04
, is a script that deals with function analysis. The key is to invert the function and go from the output ("Central-InfoSec{OPU==_DCUG==_64}"
) and reverse certain operations to find the input ("Central-InfoSec{???}"
).
1. Working on the function
Like the previous challenge, let us start by looking at the function:
This function takes two parameters, s
(the flag) and n
(a number). What it then does is a series of operations, which might be easier to understand if we expand it.
The first thing I wanted to touch on is that the ord
and chr
functions are both functions that are used to transform from an ASCII character to the ASCII index and vice-versa.
What is an ASCII character or an ASCII index? Well, since everything in computers deals primarily with numbers, if you want to write anything you need a reference table (that converts from numbers to characters). For the first 128 characters we call that table the ASCII table (as ASCII stands for American Standard Code for Information Interchange).
As such, the character 'a'
is at ASCII index 97
(and is different to ASCII index 65
, which is the character 'A'
). So our ord
function, if supplied with 'a'
as a parameter, will return 97
. Inversely, our chr
function, if supplied with 97
as a parameter, will return 'a'
.
Another thing you might notice is the % 26
operation. This modulus operation is used to make sure we don't go over 25 characters greater than the ASCII index of 'a'
, which is 'z'
.
Additionally, the for char in s.lower()
is what is known as a "list comprehension". It acts much like a traditional for loop but can be done on a single line. The .lower()
method turns a string into it's lowercase variant.
Finally, the ''.join(...)
is a function used to merge all the elements of a list into a string.
So we can rewrite our b64encode
function like so:
To reverse that function is now easier: We need to do much of the same, but instead of adding the defined offset, just substract it.
However, since the modulo doesn't have a very well defined behavior in Python when it comes to potential negative numbers, we will also add 26 for good measure before we do the modulo operation, just to be safe.
If you want to rewrite this as a shorter version, you can always write:
2. Working on the challenge
We have multiple elements here, so let us devise what values are stored in what variable at the end of the program.
Here we can see that flag2a
and flag2c
are the ones that are affected by the encoding (lines 11 and 12 in the above code). The program first encodes a value, then adds "=="
to the end of it.
So if we run our b64decode
function with parameters "OPU"
and 1
, and once more with parameters "DCUG"
and 2
, we should obtain the original values for the flags.
As such "OPU"
turns into "not"
and "DCUG"
turns into "base"
.
This makes the original flag be Central-InfoSec{not_base_64}
(which would be correct, since the functions are operating more like a Caesar Shift Cipher).
Challenge 5: Dissassembling the operations
The fifth challenge, located at the URL $MACHINE_IP/python-04
, is a script that deals with function analysis. The key is to invert the function and go from the output ("Central-InfoSec{OPU==_DCUG==_64}"
) and reverse certain operations to find the input ("Central-InfoSec{???}"
).
This python script is a bit simpler in structure, but is a bit harder to analyze than our previous codes. Here we will go up from flag2d
and find the original flag2a
, flag2b
and flag2c
.
flag2d
ends up as 84@RD?7415C74@7851
. It is the result of a string flip operation ([::-1]
, which will reverse any list or string), which is done on the result of a '8'.join
operation (which is the same as before, but instead of merging the elements outright, is going to add a '8'
character at each interstice), which is done on the result of a join
operation, which is done on the concatenation of flag2a
, flag2b
and flag2c
.
It gets confusing fast, so let's break it down a little:
If you read the operations in the opposite direction, and by comparing wit the original structure of flag2a
("??_"
) we have some idea as to what flag2a
is: "15_"
.
flag2b
and flag2c
are a bit more complicated however.
We now know that flag2b + flag2c
is "7@47C5147?DR@4_"
. If we look at the original structure of flag2c
and the fact that it is reversed, we can find flag2c: We take the underscore ("_"
) and the 5 character preceding them ("?DR@4"
).
We then reverse this string to obtain: flag2c = "_4@RD?"
So now we only have flag2b
left: "7@47C5147"
.
Line 4 of our original code does the same thing with flag_2b
as what earlier happened with flag_2d
. It splits the string on the "_"
character and merges it again with the "C"
character at the interstices.
To reverse this we replace the "C"
in our flag_2b
by an "_"
, which gets us flag2b = "7@47_5147"
.
To finish this off, we assemble the contents of the flag, which becomes: "15_" + "7@47_5147" + "_4@RD?"
.
The final flag to finish this section off is "Central-InfoSec{15_7@47_5147_4@RD?}"
.
I hope you enjoyed reading this, as I've tried to make it as entry level into Python as possible. I'll be getting into more technical things in the 2 other write-ups, if that's more your speed!