Thread Local Storage (TLS)

Manish Kumar
7 min readMar 21, 2022

--

In the previous blog, we got to know that every PE file has an address of the entry point from where the program starts executing. But, is it really true! In this blog, we are going to learn how we can run code even before the entry point using a capability typically known as Thread Local Storage (TLS) and what it can be used for.

What is TLS?

Every thread of a process shares its virtual address space. The local variable of a function is unique to each thread. But what about global and static variables. They are shared by all the threads in a process. So, it can happen that in a multithreaded program, these global variables are being accessed and changed by one thread. Which can cause problems for other threads. To avoid this race condition, practically all the multithreaded OSes provide a mechanism to efficiently store state on a per-thread basis. With TLS, we can provide unique data for each thread that they only can access. To understand more about TLS and understand how it is implemented, you can refer to the links provided in the references.

Now, we are going to analyze a sample that has a thread local storage implemented in it. It’s not doing anything malicious here rather just two message boxes pop up, one from the TLS callback function and the other from the main function. The sample I have used can be downloaded from here, (password is: clean). The source code is this. So, let’s get started with our analysis.

Analyzing the binary

Before starting the analysis, let's check what happens when we double click the sample. First, the following pop up box appears:

TLS message box
TLS message box

This message box shows that this is being called from the TLS callback function. After pressing OK, we will get another message box as follows:

Main message box
Main message box

Remember this observation, will come back to this later. Now, let's start with some static analysis. Start the CFF Explorer and open the sample which we are analyzing. (Note: Check if the binary has ASLR enabled. If it is, you may want to disable that because it will make analysis a bit more difficult. To switch this off, go to DLLCharacteristics field under the Optional header in the CFF Explorer and uncheck the option “DLL can move” and save the file). CFFExplorer shows that this sample has 6 sections including one “.tls” section whose relative virtual address is 0x4000.

TLS section
TLS section

Looking at the optional headers of the sample, the address of the entry point is shown as 0x12D8 which is a relative address. The absolute address of the entry point is “image base”(0x400000) + Relative Virtual Address of entry point, which comes to be 0x4012D8.

Entry point
Entry point

Lets now open this in IDA Pro and check what is happening. After its initial analysis, IDA stops at the address 0x00401030, which it thinks is the entry point.

IDA main function
Main function in IDA

Doing cross-reference (keeping cursor on the name and pressing “x”) of this main function, shows that it is being called from an address 0x40126B present in a function which IDA named as “tmainCRTStartup”. This function in turn is called from an address 0x4012DD.

public start

As we can see, this call instruction is present in a method that starts from address 0x4012D8. And if you remember, CFF Explorer also showed that the address of the entry point is 0x4012D8! So, it seems IDA stopped at the correct location from where execution should start. But wait, while analyzing the instructions at this point, we see that one variable is being moved to EAX, then printed on the screen and then a few things are pushed onto the stack probably as an argument for the MessageBox Windows API. In the arguments, “In Main” text is also present which will be displayed by the message box. And indeed in the beginning we saw that “In Main” pop up box appears, but that was not the first box, right!! So, what is happening here? Where is the “In TLS” pop up box code present? It seems that IDA was not able to catch the “original” address of the entry point.

If we check the “Functions” subview in IDA, it shows one function name as TlsCallback_0. Let's check what’s in that.

TLS callback function in IDA

So, our first message box is coming from this region which starts from 0x401000! This is actually a TLS callback function that is executed before the entry point. That’s why the TLS message box appears first.
If we open this file in x32Dbg, it successfully detects the TLS callback function and breaks at that point. The reason for the breakpoint and the point where it breaks are shown below.

In debugger

WinDbg analysis

Open the file in WinDbg. To see all the data directories of this sample, type !dh 400000 -f and enter.

The TLS structure, IMAGE_TLS_DIRECTORY, pointed to by the TLS directory entry has a small number of fields. The structure looks like this:

typedef struct _IMAGE_TLS_DIRECTORY {
UINT32 StartAddressOfRawData;
UINT32 EndAddressOfRawData;
PUINT32 AddressOfIndex;
PIMAGE_TLS_CALLBACK *AddressOfCallBacks;
UINT32 SizeOfZeroFill;
UINT32 Characteristics;
} IMAGE_TLS_DIRECTORY, *PIMAGE_TLS_DIRECTORY;

Let's check these fields for this sample. Execute the command “dt IMAGE_TLS_DIRECTORY <base image + TLS directory address>”.

The one special entry which is of interest is the one pointing to a list of callbacks, AddressOfCallBacks. This particular field is a pointer to a null-terminated array of callbacks, which are called in sequence. Here it is pointing to a location 0x4020c4. Let's check what is at that address.

It is indeed a null-terminated array of addresses (here it’s only 1 because only one TLS callback function is defined in this program). And look what this array contains 0x401000! A pointer to the first and only callback function which is the same as what we saw in IDA’s tlscallback_0 function. Let's put a breakpoint at this location using the command bp 0x401000, and run the application using the command g and enter. The program will stop at this location.

The first instruction that we see is mov eax,dword ptr fs:[0x2c]. From this wiki, we get to know that fs:[2c] stores the linear address of the thread-local storage array. Step over a few commands until the call to MessageBox and then check the TLS array.

We can see that the value 0x21F is now stored at the 1st (eax+4) location of the array. You may wonder why it got stored at [eax+4] location? Part-4 of this blog will explain to you better about this. So, this is the location where all the TLS variables are stored.

Conclusion

This sample is not doing anything except popping up a message box. But think about what can be done using this feature. Samples can check-in TLS callback functions whether they are being monitored or not in a sandbox-like environment. If they are being monitored then take a different course of action than the malicious one to avoid being detected.

They can have an anti-debugging check before execution reaches to the main function and do nothing to deceive the malware researchers in order to prevent or slow down the process of reverse engineering.

Having said that, any code in TLS callback functions is not an indication that the sample is malicious. There is nothing inherently malicious about TLS, but they are being used by some forms of malware in an attempt to hide data from analysis.

Thank you for reading! Hope you learned something new.

References

--

--

Manish Kumar

Cybersecurity Researcher | Travel enthusiast! | Sketch Artist | Reader | Travel and Secure the world!