CTF 2023 solutions from Doctor Web. Part 5

Friends, today we are finishing publishing the solutions of our CTF-marathon! It had five levels of difficulty, each with five tasks, for a total of 25 tasks. Here is an analysis of the fifth level of complexity. You can study the previous levels here: part 1, part 2, part 3, part 4.

Marathon results we summed up at the beginning of April, but the assignments are still available – and you can try to solve them for yourself.

CTF-2023 by Doctor Web: I Feel No Pain tasks

1. Prehistoric dinosaur

We launch and see that this is a calculator:

Determine what it is written on:

Further, we either agree that no one writes in Delphi, or we are indignant, because legacy projects still need to be supported, and Delphi is a wonderful language that has not actually become outdated (here I generally agree, but on it no one really writes. Oh yeah, what are we talking about…

This is Delphi, for this there is a tool IDR (Interactive Delphi Reconstructor). We launch it and after analysis we create an ids-script.

PS For those who do not know: never run IDR on the host machine – it can directly run some functions that may be malware, in other words, run malware on the host machine.

We use .idc in IDA, wait half an hour, according to the size of the script…

After applying the script, some of the names are put down by themselves, but this may not be enough if you are not familiar with the Delphi structure. For example, we don’t and won’t have a main as such, there is an entry point, and this should be content with:

We start black magic – we are looking for where we will store all object-oriented things (all sorts of tables, addresses, etc.).

Where in a GUI application can there be any actions? Presumably, in events – therefore, somewhere we need to find these events. The script did not parse correctly, but we have a method table that can be parsed by hand:

In the functions of addition, subtraction, etc., there is a certain function that is called from each such event:

What can we find there? For example, switchcase with these values:

Presumably, these are some values ​​\u200b\u200bthat need to be entered into the calculator. Also in the TForm1_equalsButtonClick function there is a very interesting function that is called if the condition with the required numbers is met:

On this function, by the way, the decompiler lies down to rest:

He lay down here:

There is an “assumption” that it does not accept so many arguments)):

We fix, decompile – and inside we are waiting for … (drum roll) … crypt!

And then the giant line:

Some function call that IDA decided not to parse:

After a little digging into it all, we determine: we have AES, we have an encrypted message, and we need to compose a certain key. There are several values ​​for this key, so let’s brute.

A couple of variants of the decrypt function:

Flag: DrWeb{1_l1k3_d3lph1_r3v3rs3_3ng1n33r1ng_d1d_y0u_kn0w_1_4m_utt3rly_1ns4n3?}


We start the game, we see the following:

Game functionality is very poor: we can shoot, fly and accelerate. If you fly a bit, shoot and get into the virus, you get this:

We see a lot of keys that spawn after hitting the virus. We go over the files, we get the following. Models:

Shaders (we won’t need this):

Textures (we won’t really need this magnificence either):

We open the executable file in the ide and start searching for the available clues. We are especially interested in objects that contain models of viruses and keys. Since we have the names of the models, first of all we are looking for the lines:

Great – the lines are there, and they are not obfuscated. Thus, we quite quickly find the object initialization function:

The key object is not created here, so it must be found. It is also easy to find:

Moreover, this entire construction is in a loop for 0x2121 iterations:

We scroll further, in the cycle we find several functions. One of them sets some constants, a very large amount of bytes, and also has a hardwired string “flagis:”:

Tracking everything from this function:

Finding something wonderful

Turning into an even more beautiful function:

As a result, we have a cycle for 0x2121 iterations, in each iteration a key is generated by XOR with the constants 0x75 0x53AD 0x709D 0x779A 0x8D, and after further XOR with an encrypted flag … horror …

But, knowing this, you can start the game, get to the keys, run the debugger, attach to the process, and dump the memory (well, or search directly in it).

We find this section of memory and dump it all. There will be a very decent number of key objects with “flagis: XXXXXXXX…”, there is already an option to search for the desired key manually (the flag is encoded in base64 so that it is not so easy to find, and next to the flag is the line CORRECTcorrectCORRECTcorrect, by which you can guess what flag is needed).

One of the simple ways is to write a small tool in python that sorts the memory dump we need according to the correct askiya characters.

Two lines came out:

The correctness of the flag can be determined by the additional insert “CORRECTcorrectCORRECTcorrect”.

Let’s decode “RHJXZWJ7NWM0bl9NM19NM20wcnl9” from Base64 and get the flag.

Flag: DrWeb{5c4nM3M3m0ry}

3. Go Go Go DrWeb

In fact, this is just a compiled golang binary. The difficulty lies in the fact that all the characters were stripped. And since go perfectly compiles everything into a binary, we have to somehow magically find Main.

The main snag of the task is that IDA parses golang very badly, and if you untie the symbols, then that’s it. And when someone strips symbols during compilation (without another forbidden knowledge, but simply with a flag that golang offers), they are not stripped, but … unlinked.

So, at the moment of launching and solving CTF, it suddenly turned out that IDA> 8.0 can load these unlinked symbols…

Thus, with this literally forbidden knowledge (😊), you can open a file in IDA > 8.0 and read it literally like an open book.

Main -> PressXToWin

Getting arguments:

Parsing the arguments:

Although it is still better to parse them without decompiling:

+ some help:

Matching the arguments:

PressXToWin already creates drweb.key, it is also worth looking where it creates it – and it creates it in “C:UsersXXXXX\3D ObjectsTempDunnoWhy”..

We run the program with the necessary arguments – and voila, we have drweb.key ii… it is without a flag!

But next to it is this:

Flag: DrWeb{V1rusAn4lyst101}

For IDA < 8.0: Main can be searched for by strings, although they will point to uninitialized data (according to ida).


Rows may not be found, and to fix this, go to start and look for strange offsets in large chunks of data, for example:

We turn everything we can into code, and more.

At one point, certain lines appear – we start dancing from here:

Then we continue, as in the first option, but without naming, so it will be a bit more complicated.

4. CorExeMain

The task itself is very similar in its behavior to malware, so for those who have already analyzed something similar, it may turn out to be very simple.

The executable code is not in the executable file, but in the .dll. The executable itself is a regular dotnet loader.

The .dll contains the executable dotnet code, but already obfuscated.

Let’s go to the entry point.

Next, we can find the following debugger checks:

They manage instantly, even with the basic functionality of dnspy, the main thing is to set the parameters in the settings.

If we run the file, it turns out that the anti-debug is still present:

You can disassemble the functions that we have. Some string decoder:

Dictionary compilation (ID : Base64_string):

Mini Shroud of Jumps:

Antidebug, antivm (which rather works like antiNEvm – such was the idea that ah-ah-ah to run this not on a virtual machine):

Therefore, we find all the functions that interfered with us and nop them by changing the IL instructions:

Do not forget to put down the return value and thus get rid of the entire anti-debugging mechanism.

If something goes wrong, we will see this:

Also, if something breaks during debugging, we can always skip part of the instructions through the “Set Next Statement”.

We start our main, go through the functions of decrypting the payload through AES, we reach the activation of the payload itself (if you are not familiar with functions like Assembly.Load, then I recommend reading the official documentation – they allow you to load the executable .net code from memory):

They forgot to mention that the payload was encrypted in the resources all this time:

Next, we need to go to invouk, which is very non-trivial. We can break the load of the library or walk to here (it doesn’t matter how it works, but it works):

Voila! We are in the payload:

To say that he looks beautiful is an understatement:

Again we kill anti-debugging, now it is much more difficult to do this. We bypassed it with a byte patch in memory 😊

Next, we see several modules for encrypting / decrypting strings, we run through them – and a flag appears in memory.

Flag: DrWeb{S74ySh4rpDoN37}


Lines from the first part of the task:

Lines from the second:


What do we have? A very small file that… doesn’t work!

Okay, joke, it sort of works, of course, but it crashes with the wrong flag.

We open the file in the ide, we meet, well, just a beautiful sample:

We go a little further, and – wow, we have a self-decrypting code!

For decryption, we have… a table for encryption:

Some simple encryption:

Well, the most interesting, of course, is that the code stands for the flag. Even the name of the task itself clearly indicates that it will need to be brute.

First, we propose to make the task easier and think about what the flag should be. Conventionally, it will look like this: r“DrWeb{[a-zA-Z0-9]}”. How do we know this? It’s not for nothing that we solved the tasks before and realized about the DrWeb postscript and about the fact that each flag contains 1337 speech.

Another flag length is 20 bytes (we have a string format of %20s). This means that the length of the flag that needs to be brute is 13 characters.

We also know that our sample is x64 (this is important since we need to brute instructions).

We try the first part of the flag – and now our code has begun to take on some kind of sane form.

We start to brute, uh… We didn’t write any special script – to be honest, it was easier to sort through some of the characters by hand and try to make a word out of it, each time starting the sample again.

Since encryption is done byte by byte, then, having guessed and brute one byte, we can no longer return to it.

Also in some cases we refer to the opcode table, for example http://ref.x86asm.net/coder64.html.

Flag: DrWeb{Pl5P4ckM3B4ck}

PS The task is for those who like to brute, and if we don’t like to brute, then the format of the solution will come out exactly like this.

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *