Friends, the stormy weekend has passed, and we are ready to present you with a new batch of WTAs - this time we will analyze the tasks of the Reverse branch in detail. We hope you have already dealt with two tasks from OSINT and are ready to fully immerse yourself in the reverse engineering process. We promise, it will be interesting;)
This direction was very popular among the participants - 103 people alone decided on 100 tasks. However, the task for 1000 remained unresolved. Therefore, as in the case of OSINT, the kit for the most difficult task CTFzone will be published later in a separate post. Now drop all your business, and full speed ahead!
AURORA: SCI430422 LED lights are blinking in hypnotic patterns. As you know, this system is renowned for its top-notch security measures. Only the most expert or resourceful hackers are available.
Decision:
In this task, we needed to get into the console system. To begin, run the file for execution and see a window with a greeting and an invitation to enter a password:
What to do? Open the file in the OllyDbg debugger and find the line “Please enter password:” - in the main window, scroll up to the address 004010F9:
As you can see, the entered password is checked in the function located at 00401000. Let's try to set a breakpoint at this address (F2 key in OllyDbg), then press F9 (continue execution) and for example, enter some line in the program window:
Press "Enter", and the breakpoint is triggered. Next, go to OllyDbg and press the F7 key to go inside the function at 00401000:
It can be immediately noticed that at the address 004010CF, the function strcmp () is called from the standard C library. This function compares two strings and returns 0 if the strings are the same. We set a breakpoint on the function call (F2 key), press F9 (continue execution) and see which other string, besides the “password123” entered by us, will be passed to it:
In the stack window (and in the register window) we see that the string s1 is equal to "ctfzone {l33t_haxx0r_is_you !! 1}" (without quotes).
Here is the flag!
Answer: ctfzone {l33t_haxx0r_is_you !! 1}
* AURORA: Lieutenant, your co-pilot was abducted by prison. They are out for free! Doors couldn’t be seen.
The inscription on the archivolt read:
"The Doors of Dorun, Lord of Omega. Speak, friend, and enter. I, Norvy, made them. Calabrimbor of Alpha Centauri drew these signs."
But be careful and hurry up. They can be back any moment. *
Decision:
In this task, we had to pick up the password to the gate, behind which our co-pilot was held captive. First of all, we launch CrackMe, and the following window appears on the screen:
Let's try to enter any word and click "Try", but the password does not fit, and we see the following message:
CrackMe itself is a 64-bit executable file of PE format. Open it in IdaPro and try to find the line “the door is still closed!”:
There is only one cross reference to this line:
Here is the function in which IdaPro found references to this line:
Here we also see the encrypted flag (it is easy to make sure that the sub_140001160 function is engaged in decryption) and the function determining the correctness of the password: "sub_1400012C0". Using GetDlgItemTextW, the string entered in the password field is passed to this function. Let's analyze this function:
There is a cycle and two arrays of five elements. It also checks the length of the entered password:
After analyzing this function, we see that the four-character password in UTF-16 encoding (encoding for WideChar in Windows) consists of two numbers with the size of DWORD. Further, we see that the residuals from dividing these numbers by the numbers ((1 << (1 << i)) + 1) are compared with hard-hitting values.
It can be noted that ((1 << (1 << i)) + 1) = 2 ^ (2 ^ i) + 1 , and that these are Fermat numbers: 3, 5, 17, 257, 65537. The password check algorithm is further can be reduced to two systems of comparisons:
X1 % 3 = 0 X1 % 5 = 0 X1 % 17 = 1 X1 % 257 = 241 X1 % 65537 = 995
X2 % 3 = 1 X2 % 5 = 4 X2 % 17 = 6 X2 % 257 = 104 X2 % 65537 = 413
The Chinese Residue Theorem will help us restore the original numbers. Solvers of such comparisons can be found on the Internet:
So we got two numbers that now need to be converted to the string UTF16. To do this, you can use Python (while not forgetting the reverse order of bytes):
Now it remains to verify the result. Enter this line in the password entry box.
That's all! We opened the gate.
Answer: ctfzone {ch1n4_t0wn}
AURORA: Lieutenant, the Doors But we don’t get it.
Decision:
After we opened the gate, it turned out that we face the next door. A new task - a new key :) Let's go!
Run the file for execution, enter the key, but nothing comes out.
Let's try to figure it out. First of all, we should find out what we are dealing with. To determine the type of executable file, you can use the CFF explorer:
Obviously, .Net Assembly, Ida and traditional debuggers are powerless in this case. At this point, you could recall dnSpy - one of the tools that can help in the study of code that uses .Net. We load into it an executable file.
By the name of the main class (PythonMain) and the name of the resources, it can be concluded that this program was written in IronPython. In the main function, only the .NET assembly from the IPDll.WFCrackMe resource occurs. You will have to extract a resource with this name and load it into dnSpy.
Everything is much more interesting here, there are even function names. Immediately suppose that a function with the name “verifyPassword” checks the password we entered, and to verify this, it is enough to test in the debugger which string will be passed to it as an argument.
Next, we need to understand how the password is verified. The biggest problem when analyzing code is to deal with the functioning of “strongBox” and “globalArrayFromContext”. These variables are actually filled in the " __main__
" function.
Consider the reversePassword reversal process using the example of a small piece of code with branching:
if ((arg = (CallSite<Func<CallSite, object, int, bool>>)strongBox.Value[37]).Target(arg, (arg2 = (CallSite<Func<CallSite, CodeContext, object, object, object>>)strongBox.Value[38]).Target(arg2, globalContext, globalArrayFromContext[26].get_CurrentValue(), password), 81)) { num = 73; num = 73; result = globalArrayFromContext[16].get_CurrentValue(); }
First of all, we need to substitute the values ​​in the places “strongBox” and “globalArrayFromContext”. Values ​​are taken from the following lines " __main__
":
strongBox.Value[38] = CallSite<Func<CallSite, CodeContext, object, object, object>>.Create(PythonOps.MakeInvokeAction($globalContext, new CallSignature(1))); strongBox.Value[37] = CallSite<Func<CallSite, object, int, bool>>.Create(PythonOps.MakeComboAction($globalContext, PythonOps.MakeBinaryOperationAction($globalContext, ExpressionType.NotEqual), PythonOps.MakeConversionAction($globalContext, typeof(bool), 1)));
If there is no confidence in the correctness of the value obtained for “strongBox”, it can be checked in the debugger. Substitute the values ​​into the code section under study and simplify it:
if (NotEqual(len(password), 81)) { result = false; } if (len(password) != 81) result = false;
Thus, we found that the password length should be equal to 81 characters. Further analysis of the function is difficult, since the function is quite large, but, due to superficial analysis and debugging, we can draw several conclusions:
Then we get into branching with a terrible, at first glance, condition:
if ((arg49 = (CallSite<Func<CallSite, object, bool>>)strongBox.Value[59]).Target(arg49, (!(arg50 = (CallSite<Func<CallSite, object, bool>>)strongBox.Value[60]).Target(arg50, obj9 = (arg51 = (CallSite<Func<CallSite, CodeContext, object, object, object>>)strongBox.Value[61]).Target(arg51, globalContext, globalArrayFromContext[11].get_CurrentValue(), obj))) ? obj9 : ((!(arg52 = (CallSite<Func<CallSite, object, bool>>)strongBox.Value[62]).Target(arg52, obj10 = (arg53 = (CallSite<Func<CallSite, CodeContext, object, object, object>>)strongBox.Value[63]).Target(arg53, globalContext, globalArrayFromContext[12].get_CurrentValue(), obj))) ? obj10 : (arg54 = (CallSite<Func<CallSite, CodeContext, object, object, object>>)strongBox.Value[64]).Target(arg54, globalContext, globalArrayFromContext[13].get_CurrentValue(), obj))))
After substituting the corresponding values ​​and simplifying this condition turns into a simple test:
if (BoolConvert((!BoolConvert(obj9 = CheckLines(obj))) ? obj9 : ((!BoolConvert(obj10 = checkColumns(obj))) ? obj10 : CheckSquares(obj)))) BoolConvert «a?b:c». : if(CheckLines(obj) && heckColumns(obj) && CheckSquares(obj))
We need to get to the true branch of this condition, i.e. all functions should return “True”.
Now let's analyze the CheckLines function. It is quite simple - the function checks that each row contains numbers from 1 to 9. The CheckColumns and CheckSquares functions are more complicated, but at this stage you can already guess that we are dealing with Sudoku:
On the Internet, you can find a lot of solvers, so you can practice on your own;)
So, using the solver, we get the desired key.
The door is open!
Answer: ctfzone {1_v3ry_l1k3_5ud0ku_9arm3!}
AURORA: Lieutenant, watch your step! There is a pit infested with worms down the road. It was destroyed. I have to go and repair the bridge. Hurry up, we have to save our pilot!
Decision:
So, in this task you need to save the pilot, but you can only reach it by overcoming the destroyed bridge. We need to restore it. The “Bridge.txt” file is proposed as a “bridge”.
By opening the "Bridge.txt" file in a hex editor, we can make sure that it is encrypted. From the job description, it is clear that we were provided with a program that encrypted this file, and we need to decrypt it.
Next, check what is wrong with the file "reverse500.exe". Run the file, and in response to the console a message appears - "you must specify the file for encryption":
With the help of cross references, look for the use of this line in the executable file. To do this, open the file in the IdaPro compiler. Obviously, the file that is passed as a command line argument is encrypted.
From the above code snippet, you can see that function 401F60 performs encryption.
Having examined this function in a superficial way, we can highlight the functions of allocating memory, reading a file, encrypting the contents of a file, and writing encrypted content to the source file:
Next, we will try to understand the structure of the encrypted file by examining the simple “WriteCryptedData” function:
Of course, you can determine the type of hash used, but this is optional. However, for further work, we need to understand how the hash sums are calculated. Looking at the “HashCalculate” function in detail, it becomes obvious that when calculating the hash, 8 bytes of the “IV” vector are used.
So, we found out that the encrypted file has the following structure:
Let's look at the “Bridge.txt” file:
Next, we define the last byte of the vector "IV". To do this, open the program in the debugger, specifying any file as an argument, and put a breakpoint at the address of the WriteCryptedData function. After that, having previously corrected the assembler code a little, let's organize the search of the last byte of the vector “IV”. The result of the search is shown in the screenshot:
Now we have the complete vector “IV”: [47 08 8F E7 C4 C0 E9 AB] .
Next, you need to check the encryption mode and the symmetry of the cipher used.
Let's go back to the “CryptData” function in the debugger by submitting a test file to the program (in my case the file contained the string “HelloWorld!”). Next, we write the “IV” vector (located in EAX before calling CryptData) and the encryption result (located in EAX after calling CryptData).
Verify the identity of the decryption and encryption procedure. To do this, restart the program under the debugger and re-set the breakpoint on “CryptData”. Only now, before its execution, we fix the vector “IV” and the encrypted buffer.
As a result of re-encryption of encrypted data, we get the source text!
Now you can decrypt the file from the job. To do this, remove the first 15 bytes from it and run the program under the debugger, specifying the file with encrypted data as a parameter. Again, we set the breakpoint on “CryptData” and, before calling the function, correct the “IV” vector to [47 08 8F E7 C4 C0 E9 AB]. After correcting the “IV” vector, press the F9 key, as a result of which the program will encrypt the data. Delete the first 15 bytes from the resulting file and open it in a text editor:
Flag found!
Answer: ctfzone {3RR4dIC473_7HIS_WORM!}
PS For those who are interested, we used in this task the Salsa encryption algorithm and the CubeHash hashing algorithm. But it can be noticed only by an experienced eye;)
It seems now everything fell into place. If you have any questions or requests - write to our chat in the telegraph and leave comments. And new knowledge in the field of reverse engineering can be demonstrated at this link - tasks will be available until December 15.
Good luck to everyone and see you soon!
Source: https://habr.com/ru/post/316420/
All Articles