Issue with dotnetfile's get_user_stream_strings() and finding an alternative solution (ft. Redline Stealer) !

 Introduction

While working on a config extractor for a Redline Stealer variant, I encountered an issue which that when I open the executable in dnSpy I can see the encrypted strings (IP,Key,...) but when I try to automate the extraction using dotnetfile module in python, they don't show up somehow in dotnetfile's get_user_stream_strings()

1- .NET Streams

.NET files contain a "Metadata" section, which contains chunks of bytes called Streams. We're interested in the #US Stream:
  • #US : It's called User Strings. It contains Unicode strings that are referenced by the code instuction (i.e. ldstr, which loads a string).
You can see these stram in CFF Explorer:
(1) .NET Stream in CFF Explorer


There are other streams, you can check ntcore's article to learn about the full format of a .NET executable. I might write about it in the future.

2- dotnetfile's get_user_stream_strings() didn't work

When I deobfuscated the executable and managed to get the strings in dnSpy. I wanted to write a config extractor.
(2) config strings appear in dnSpy

When I used dotnetfile's get_user_stream_strings() to get the string, they don't show up. I went to PE-Studio to check if they show up and they do.

(3) config strings appear in PEStudio


I blame my lack of attention and my trust in tools honestly! 

These strings are ASCII Strings in UTF-16 format. So, every ascii character becomes two bytes and since we don't need the left one it's always 0x00 (null byte). And If we go back to the hex editor we can see it.

(4) config strings in hex editor

One could hardly recognize them.

3- Fetching the raw #US Stream by its address and size

Luckily, dotnetfile provides a way to enumerate streams and get the contents of one using its address and size. Thus, we get the #US stream as it is and we act on it to transform it back to its ascii form.

We can use dotnet_file.dotnet_stream_lookup["#US"].address and dotnet_stream_lookup["#US"].size to get address and size of the #US stream.

Afterwards, we can read the data at that address using dotnet_file.get_data(address, size)

(5) raw #US stream

Now, we're cool! We can proceed to act on this data to transform it back to its obvious form.

(6) data in ascii form

Then we con proceed to write our own config extractor to fetch the C2 Server. We have the encrypted C2 Server and the key we just perform the decryption routine (base64 decode -> xor -> base64 decode in the case of redline stealer)

(7) extracting the C2 Server

Conclusion

It was just a small issue I wanted to share. I already knew that the #US stream data was in UTF-16, so I thought the dotnetfile's #US stream method won't have issues with it. It turns out I was wrong and I had to fetch the stream manually by its address and size.

I hope you learned something new in this blog post.

References

.NET file format : https://www.ntcore.com/files/dotnetformat.htm

Comments

Popular posts from this blog

Writeup of a PHP Web CTF challenge I built for an event (ft. three vulnerabilities)

Hacking a website used to deliver Malware