diff --git a/_posts/2024-02-25-AGENT-TESLA-1.md b/_posts/2024-02-25-AGENT-TESLA-1.md index 4e6ef80..db296b8 100644 --- a/_posts/2024-02-25-AGENT-TESLA-1.md +++ b/_posts/2024-02-25-AGENT-TESLA-1.md @@ -7,7 +7,7 @@ categories: malware ## Introduction -Agent Tesla is a popular info stealer coded in C# that consistently makes lists as one of the most prevalent malware strains. In this post we will be looking at a sample of Agent Tesla that has been packed by a very popular crypter. I am currently not aware of the name of the particular crypter responsible, but the amount of samples I am seeing daily being packed by it is insane. Despite this, I've only found 3 other articles documenting this crypter([here](https://avsw.ru/component/content/article/analiz-semejstva-troyanov-agent-tesla?catid=9:avsoft-blog&Itemid=140), [here](https://infosecwriteups.com/unfolding-remcos-rat-4-9-2-pro-dfb3cb25bbd1) and [here](https://osamaellahi.medium.com/unfolding-agent-tesla-the-art-of-credentials-harvesting-f1a988cfd137)), but they are not very detailed, especially in explaining how to unpack the malware. The goal of this post is to explain how to unpack the final payload, starting from the beginning. +Agent Tesla is a popular info stealer coded in C# that consistently makes lists as one of the most prevalent malware strains. In this post we will be looking at a sample of Agent Tesla that has been packed by a very popular crypter. I am currently not aware of the name of the particular crypter responsible, but the amount of samples I am seeing daily being packed by it is insane. Despite this, I've only found 3 other articles documenting this crypter ([here](https://avsw.ru/component/content/article/analiz-semejstva-troyanov-agent-tesla?catid=9:avsoft-blog&Itemid=140), [here](https://infosecwriteups.com/unfolding-remcos-rat-4-9-2-pro-dfb3cb25bbd1) and [here](https://osamaellahi.medium.com/unfolding-agent-tesla-the-art-of-credentials-harvesting-f1a988cfd137)), but they are not very detailed, especially in explaining how to unpack the malware. The goal of this post is to explain how to unpack the final payload, starting from the beginning. ## First Stage @@ -15,26 +15,31 @@ We begin by opening the initial file `[MD5:B89F6062D174E452D189EC4248AF489C]` in `Application.Run(new DangNhap());`. This creates a new instance of a Windows Form. We click on the `DangNhap` part to navigate to the class. There, we see `this.InitializeComponent();` in the constructor. Again, we click on `InitializeComponent` to go to its code. Looking at the top of the method, nothing seems immediately suspicious. However, as we scroll down more we see something peculiar: ![Alt text](/images/at1/image1.png) +_Figure 1_ This code fetches a resource named `Lux3`. Next, it performs a decryption routine on the resource. Once the routine is done, it dynamically calls `Assembly.Load()` to load the newly decrypted module. After this, it gets the Type at index 9 in the assembly, and the method at index 3 from the aforementioned type. It splits the class variable 'Discart' (defined as `private string Discart = "62657A54_626C66";`) by the underscore delimeter and invokes the method passing both halves of the delimeted string and the string "Sync_Center". We need to debug the code and follow execution into the new assembly. To do this, we will place a breakpoint on the line here by clicking to the left of it by the line number. We will then begin debugging by clicking the `Start` button at the top of the window. ![Alt text](/images/at1/image.png) +_Figure 2_ Once the breakpoint is hit, we can observe in the `Locals` pane all the local variables. If we take a look at the `kb` local variable we can see its value: ![Alt text](/images/at1/image-1.png) +_Figure 3_ Take note of the full path `DeclareTextBoxValue.QuickSort.trist` here, `DeclareTextBoxValue` is the assembly, `QuickSort` is the class, and `trist` is the method being called. If we go to the Modules pane (select Debug->Windows->Modules to display this pane), this new module is visible: ![Alt text](/images/at1/image-2.png) +_Figure 4_ Double clicking on the `DeclareTextBoxValue` assembly will open the assembly in-memory in dnSpy. We can then navigate to the method `trist` like so: ![Alt text](/images/at1/image-3.png) +_Figure 5_ ## Second/Third Stage @@ -43,14 +48,17 @@ Upon arriving at the method in the second stage, it is immediately clear that it We will attempt to use popular .NET deobfuscation tool de4dot and hope that this takes care of the obfuscation. ![alt text](/images/at1/image-4.png) +_Figure 6_ This output looks promising. The next step is drag and drop the newly outputted DeclareTextBoxValue-cleaned.dll into dnSpy and navigate to that same method. Let's see how it looks now: ![alt text](/images/at1/image-5.png) +_Figure 7_ This is MUCH more readable. Let's break down what is going on here. Firstly, we see a call to `Thread.Sleep()` which delays execution for 17129 milliseconds. Then, a byte array is returned from a call to `Vu.smethod_0`. Navigating to this method shows that it is pulling the byte array from a resource named `Key0`: ![alt text](/images/at1/image-6.png) +_Figure 8_ Following this, the array is decompressed to an assembly. `QuickSort.smethod_4` is called which is simply a wrapper for Assembly.Load(). The type `ReactionDiffusionLib.ReactionVessel` is then extracted. @@ -59,166 +67,211 @@ An instance is created of the type, and the method `CasualitySource` is invoked In order to do this, we will go back to the original obfuscated assembly in dnSpy that is currently loaded in memory. Since the code is obfuscated, it will be difficult to find the call to `Assembly.Load()`. What we will do instead is go into the library itself and put a breakpoint on every overload of `Assembly.Load()`. ![alt text](/images/at1/image-8.png) +_Figure 9_ ![alt text](/images/at1/image-9.png) +_Figure 10_ Let's now resume execution. It will take a moment because of that `Thread.Sleep()` from earlier. ![alt text](/images/at1/image-10.png) +_Figure 11_ Nice, it hit one of our breakpoints. Let's step through and see what was loaded. ![alt text](/images/at1/image-11.png) +_Figure 12_ ![alt text](/images/at1/image-12.png) +_Figure 13_ This assembly that was loaded here with the odd symbol as the name is actually part of DeepSea Obfuscator's resource encryption. In the screenshot of the decompiled output of DLL that de4dot cleaned, it decrypted the resource and removed that portion of the code. Thus, we can simply continue on to the next `Assembly.Load()` which should be the same as the first one in the de4dot-cleaned DLL (called on line 23). ![alt text](/images/at1/image-13.png) +_Figure 14_ Now this looks interesting. Let's step out of the function and see what was just loaded. ![alt text](/images/at1/image-14.png) +_Figure 15_ Aha! There is the assembly we saw before: `ReactionDiffusionLib`. Now, let's dump it so we can investigate. This assembly is obfuscated as well, so we'll run it through de4dot first. Afterwards, we can open the deobfuscated assembly and investigate it as well as the two methods it calls: ![alt text](/images/at1/image-15.png) +_Figure 16_ ![alt text](/images/at1/image-16.png) +_Figure 17_ As we can observe above, `CasualitySource` takes a string and decodes it. This is called twice, once for each half of that string (`62657A54` and `626C66`) we saw in the beginning. ![alt text](/images/at1/image-17.png) +_Figure 18_ In the case of `SearchResult`, it decrypts a byte array using XOR operations. Returning back to the previous module, we can see that after decoding those two strings it passes the first one (variable `bezT`) and `EscapedIRemotingFormatter` to function `QuickSort.LowestBreakIteration`. ![alt text](/images/at1/image-18.png) +_Figure 19_ This function reaches back into the stage one assembly and extracts a resource. The resource in this case is a bitmap named `bezT`. ![alt text](/images/at1/image-19.png) +_Figure 20_ Then, a function called `RestoreOriginalBitmap()` is called on the extracted bitmap. The point of this function is to trim off unnecessary information from the bitmap. ![alt text](/images/at1/image-20.png) +_Figure 21_ The result of this function is then passed to `SearchResult', which as we saw earlier is performs a decryption routine. Finally, smethod_4 and smethod_3 are called on the decrypted "bitmap" (which is really the fourth stage payload) ![alt text](/images/at1/image-21.png) +_Figure 22_ Now that we have a complete understanding of the second and third stages, we will once again press the continue button so we can reach that last `Assembly.Load()` call. ![alt text](/images/at1/image-22.png) +_Figure 23_ Now, we step out of the function: ![alt text](/images/at1/image-23.png) +_Figure 24_ A new module named `Tyrone` (lol) has been loaded. At this point we can simply keep stepping until we end up in the module. This is a little bit annoying, but it pays off as well get here to the obfuscated equivalent of `smethod_3`: ![alt text](/images/at1/image-25.png) +_Figure 25_ ## Fourth Stage The method `oII69EjNpf` of class `fjjMqxMfW1UDxAHtaE` in namespace `hShDAuVOfCX6Pa3JR1` in the `Tyrone` module is about to be executed next. Let's beat it to the punch and put a breakpoint there so we can catch it when it executes. ![alt text](/images/at1/image-27.png) +_Figure 26_ Well, this is some gnarly looking decompiler output. Let's rely again on our friend de4dot and run the dumped module through it. After doing that, loading the result in dnSpy and going back to the function we were just in, we are presented with this: ![alt text](/images/at1/image-28.png) +_Figure 27_ de4dot appears to have been able to partially remove the control flow flattening, but not decrypt the strings. I attempted to manually decrypt them using a de4dot feature which allows you to specify the string decryption function token, but that failed as well. The other option we could do is write our own string decryptor or de4dot plugin. However, this article is focused particularly on unpacking and something like that is a bit out of scope for what we are doing. Luckily for any of you curious readers, we will be writing a custom de4dot plugin in the next article! For now, we are unfortunately going to have to resort to manually debugging the rest of the code until we can extract the final stage payload. -But...it sure would be a shame to lose the unflattened control flow we gained from running the module through de4dot. That is when I had an idea. If we look back at figure 10, the method from Tyrone is called with null parameters. That means, we could make a tiny C# loader to execute the deobfuscated assembly and have the benefit of the improved control flow to make our debugging easier! Let's do it! +But...it sure would be a shame to lose the unflattened control flow we gained from running the module through de4dot. That is when I had an idea. If we look back at ***Figure 22***, the method from Tyrone is called with null parameters. That means, we could make a tiny C# loader to execute the deobfuscated assembly and have the benefit of the improved control flow to make our debugging easier! Let's do it! ![alt text](/images/at1/image-30.png) +_Figure 28_ We will drag our compiled binary into dnSpy (making sure it's in the same directory as Tyrone-cleaned.dll) and throw a breakpoint on the `m.Invoke(null, null)` part and run it. ![alt text](/images/at1/image-31.png) +_Figure 29_ ![alt text](/images/at1/image-32.png) +_Figure 30_ I have my breakpoints set on the class constructor and target function. Now let's finish this once and for all! Stepping all the way to the end of the constructor sets all the global variables like so: ![alt text](/images/at1/image-33.png) +_Figure 31_ Function `oII69EjNpf` starts off by getting the path of the current assembly ![alt text](/images/at1/image-34.png) +_Figure 32_ ![alt text](/images/at1/image-35.png) +_Figure 33_ ![alt text](/images/at1/image-36.png) +_Figure 34_ Then it attempts to open a mutex `bncFrQuGyBTmIf`. If it succeeds, the process terminates. Otherwise, it will create the mutex. ![alt text](/images/at1/image-37.png) +_Figure 35_ ![alt text](/images/at1/image-38.png) +_Figure 36_ ![alt text](/images/at1/image-39.png) +_Figure 37_ ![alt text](/images/at1/image-40.png) +_Figure 38_ ![alt text](/images/at1/image-41.png) +_Figure 39_ There is an optional thread sleep that is executed depending on the configuration of the crypter. In this particular sample is is not executed. ![alt text](/images/at1/image-42.png) +_Figure 40_ ![alt text](/images/at1/image-44.png) +_Figure 41_ ![alt text](/images/at1/image-43.png) +_Figure 42_ There is another check to determine if the crypter should display a messagebox, which again this sample does not do. ![alt text](/images/at1/image-45.png) +_Figure 43_ ![alt text](/images/at1/image-46.png) +_Figure 44_ Next, there another unperformed check exists which will attempt to elevate privileges if the file is not being ran as an admin. This code again is not triggered in this sample ![alt text](/images/at1/image-48.png) +_Figure 45_ ![alt text](/images/at1/image-47.png) +_Figure 46_ Another configuration-based decision is the possibility to download and execute another file (which does not happen in this case.) ![alt text](/images/at1/image-49.png) +_Figure 47_ ![alt text](/images/at1/image-50.png) +_Figure 48_ The code then decides if it wants to copy to AppData for persistence, which it does not do. ![alt text](/images/at1/image-51.png) +_Figure 49_ However in the case that it does do that, it will change the copied file's ACL permissions to perserve itself like so: ![alt text](/images/at1/image-52.png) +_Figure 50_ Then, the final payload `8cLv8` is extracted from the resources and decrypted. ![alt text](/images/at1/image-53.png) +_Figure 51_ ![alt text](/images/at1/image-54.png) There is a final check which determines the execution type (Reflection or Process Hollowing) ![alt text](/images/at1/image-55.png) +_Figure 52_ At this point, the injection type used is irrelevant (although if you are curious in this case it does use process hollowing). Since the payload has already been decrypted, we can simply dump that byte array `fjjMqxMfW1UDxAHtaE.obLq1XEEqU` from the `static fields` pane in dnSpy. Opening the dumped file in dnSpy confirms that it is indeed Agent Tesla ![alt text](/images/at1/image-56.png) +_Figure 53_ Stay tuned for part two where we will be removing Agent Tesla's control flow flattening by writing our own de4dot plugin! diff --git a/_posts/2024-02-28-AGENT-TESLA-2.md b/_posts/2024-02-28-AGENT-TESLA-2.md index 6dd09b6..17eb1be 100644 --- a/_posts/2024-02-28-AGENT-TESLA-2.md +++ b/_posts/2024-02-28-AGENT-TESLA-2.md @@ -10,16 +10,19 @@ categories: malware In the [previous post](https://ryan-weil.github.io/posts/AGENT-TESLA-1/), we successfully unpacked Agent Tesla. We left off on a bit of a cliffhanger though, because after opening it in dnSpy it was apparent that it had control flow flattening applied. At first glance it doesn't look too unreadable: ![alt text](/images/at2/first.png) +_Figure 1_ But if we continue looking around other functions, we can see it gets ridiculous. Take a look at this one `zg5QIGkJ` for example: ![alt text](/images/at2/flattened.gif) +_Figure 2_ This took me 20 seconds to scroll from the top of the function to the bottom because it contains a whopping 800 lines of code! How many lines of code do you think the function had originally before the flattening was applied? I'll give you a little sneak peak of what our finished product will look like: ![alt text](/images/at2/unflattened.gif) +_Figure 3_ That's right, only 200 lines of code. The flattening made the code roughly 4x larger than it was before and more or less completely eliminated any readbility. Unless you want to spend 500 years debugging such vomit, we're going need to find a way to deobfuscate this. @@ -28,8 +31,10 @@ That's right, only 200 lines of code. The flattening made the code roughly 4x la First, let's try throwing Agent Tesla into de4dot and see if it removes the control flow like it did for the assemblies mentioned in the first post. ![alt text](/images/at2/de4dot-1.png) +_Figure 4_ ![alt text](/images/at2/failure.png) +_Figure 5_ As shown above, de4dot as it comes by default is completely powerless against this perform of control flow flattening. No changes were made at all to the code. That means one thing: we are going to have to write something ourselves. @@ -38,18 +43,19 @@ As shown above, de4dot as it comes by default is completely powerless against th We first need to analyze the flattening to find a consistent pattern to detect. In order to do that, we will need to look at a control flow graph of the IL code blocks directly. We will use my preferred tool IDA Pro to look at the control flow graph. There is a dnSpy plugin for generating a control flow graph, but I personally prefer how IDA's graph looks. We're going to start by navigating to the main function `8YpydOv4` in IDA. ![alt text](/images/at2/ida_graph.png) +_Figure 6_ Let's break down what is happening here. In the first block, the integer `0` is pushed onto the stack and then stored in the local variable index 0. Then, we have an unconditional jump to block `loc_FF` which then begins a series of checks on the dispatcher variable. When a check passes successfully, it executes the original code and then controls the flow by setting the variable to the next block to be executed. The number `5` here is the last check that is executed. We will refer to this final check as the `loop condition` because if this check fails, then it returns to the beginning of the loop. Otherwise, the function ends. It's also worth noting that case `0` does nothing except set the next case to `1`, i.e there is no actual code here being executed. -Our goal here is to connect each block to the next one in the flow, bypassing the parts that set the dispatcher variable. If we can't find a matching block to one of the setters, we will simply assume it is the last block and connect it to the child block of the loop condition. Granted, this aforementioned case is not applicable to this particular function... - -I feel like I should clarify the terminology I'm going to be using here. When I say 'setter' I mean groups of instructions like this which **set** the dispatcher variable: +Our goal here is to connect each block to the next one in the flow, bypassing the parts that set the dispatcher variable. I feel like I should clarify the terminology I'm going to be using. When I say 'setter' I mean groups of instructions like this which **set** the dispatcher variable: ![alt text](/images/at2/setter.png) +_Figure 7_ When I refer to 'cases' I am talking about blocks like this which **check** the dispatcher variable: ![alt text](/images/at2/case.png) +_Figure 8_ Lastly, before I conclude this section I think it is important to manually unflatten the function in something like notepad just so we have an idea what kind of output to expect. This was a tip that was mentioned by Georgy Kucherin in his [presentation about unflattening DoubleZero](https://www.virusbulletin.com/uploads/pdf/conference/vb2022/papers/VB2022-Combating-control-flow-flattening-in-NET-malware.pdf) (which was way more complex and is totally worth a read!) and I found it to be very helpful. @@ -70,6 +76,7 @@ The first thing we will do is clone the [de4dot repo](https://github.com/de4dot/ Let's open it in Visual Studio. The first step is to create the obfuscator by doing the following steps. First, creating a new folder in this directory: ![alt text](/images/at2/project_layout_1.png) +_Figure 9_ Every deobfuscator in de4dot needs to have a `DeobfuscatorInfo` class. Here is what yours should look like: @@ -172,10 +179,12 @@ It returns a list of `IBlocksDeobfuscator`'s. Each `IBlocksDeobfuscator` then ev At this point, your project structure should look like this: ![alt text](/images/at2/project_layout_2.png) +_Figure 10_ We also need to add the deobfuscator to the `Program.cs` file in `de4dot.cui` so it shows up in the actual application when it's launched ![alt text](/images/at2/de4dot_cui.png) +_Figure 11_ Now, we are going to create a new class that implements the `IBlocksDeobfuscator` interface. I'm going to call it `Unflattener`. @@ -233,6 +242,7 @@ public override IEnumerable BlocksDeobfuscators What we need to do is implement the `Deobfuscate()` method. This method is going to get called on each method in the target binary. That list that's being passed in is all the basic blocks of the method. We want to begin deobfuscation starting with the first block of each method. ![alt text](/images/at2/ida1.png) +_Figure 12_ Each method begins with `ldc.i4` and `stloc`. We can use that as a signature. However, I'm going to make new class called `UnflattenerHelper` to do the actual unflattening part, since I'd like to separate the logic. @@ -272,7 +282,7 @@ public UnflattenerHelper(Block block) if (block.Instructions.Count < 2 || block.Instructions[0].OpCode.Code != Code.Ldc_I4 || block.Instructions[1].OpCode.Code != Code.Stloc) - return; + return; } ``` @@ -293,12 +303,12 @@ public UnflattenerHelper(Block block) if (block.Instructions.Count < 2 || block.Instructions[0].OpCode.Code != Code.Ldc_I4 || block.Instructions[1].OpCode.Code != Code.Stloc) - return; + return; _initialCase = (int)block.Instructions[0].Operand; _startBlock = block; - if (_startBlock.FallThrough == null + if (_startBlock.FallThrough == null || _startBlock.FallThrough.Sources == null || _startBlock.FallThrough.Sources.Count < 2) return; @@ -377,6 +387,7 @@ int IsCaseStartBlock(Block block) Now, we will update our `ExploreControlFlow` to save all the `cases` and `setters` that we logged. To do, I've created two dictionaries that each store the dispatcher number and the matching case or setter block. Keep in mind that when we save the `case` we are **NOT** including the case block itself, but the block it connects/falls through to. ![alt text](/images/at2/ida2.png) +_Figure 13_ ```csharp @@ -420,11 +431,12 @@ void ExploreControlFlow(Block block) After we've extracted all the data we need, it's time to perform the unflattening procedure. We will make a function called `Unflatten` which returns a boolean. The reason it will return a boolean is because the way de4dot works is that it will continuously call the `Deobfuscate` function in the `IBlocksDeobfuscator` class we defined until it returns `false`. The reason for this is that de4dot has built-in optimizers which will remove dead code amongst other things. So, we return `true` because modifications were made. If there were no modifications made for any reason, we return false. If you want to see more, take a look at the class `BlocksCflowDeobfuscator.cs`: ![alt text](/images/at2/deobfuscate.png) +_Figure 14_ The first thing we do is connect the starting block to the first case block with an unconditional jump (SetNewFallThrough). Then, we loop through all the setters and check if there is a corresponding case block for the setter. If so, we connect the block. I've also implemented a function called `CleanBlock()` that will remove the leftover setter instructions from the block. ![alt text](/images/at2/ida3.png) - +_Figure 15_ When we are done unflattening, we also clean the same instructions in the start block. @@ -481,7 +493,7 @@ Now, we need to go back to our `Unflattener.cs` class and add the call to the `U public bool Deobfuscate(List methodBlocks) { UnflattenerHelper unflattenerHelper = new UnflattenerHelper(methodBlocks[0]); - return unflattenerHelper.Unflatten(); + return unflattenerHelper.Unflatten(); } ``` @@ -498,11 +510,15 @@ Now it's time for the fun part. Let's launch de4dot with the following parameter Here are some pictures of the results: ![alt text](/images/at2/result1.png) +_Figure 16_ ![alt text](/images/at2/result2.png) +_Figure 17_ I hope you enjoyed this post. I hope in the future to gain more experience and work on more complex obfuscation schemes. The article below shows a much more difficult type of control flow obfuscation that necessitates a different approach. I would highly recommend reading it. +Lastly, I would like to thank [Ch40zz](https://github.com/Ch40zz) for helping me understand some logic errors that I made. + ## Further reading: [https://www.virusbulletin.com/uploads/pdf/conference/vb2022/papers/VB2022-Combating-control-flow-flattening-in-NET-malware.pdf](https://www.virusbulletin.com/uploads/pdf/conference/vb2022/papers/VB2022-Combating-control-flow-flattening-in-NET-malware.pdf) \ No newline at end of file