How to avoid static detection when importing functions

Don’t call functions

People who try to create malware for the first time often ask me, “Toko, how are you managing to avoid AV engines?” Well, there are tons of details, but today I’ll talk about one of the ways to avoid static detection.

How does it happen?

During compilation a binary file gets something called IAT (Import Address Table), which is A TABLE that holds a function list and DLL file list that should be used during execution in order to do what the programmer wrote in the code. The problem is that if someone tries to import a function, AVs can statically see that the file was imported because, as I already said before, the DLL list is embedded in the file during compilation.

We can avoid this problem by dynamically loading DLL files. This means we don’t link an executable to a DLL file. We just tell our program to load a DLL file by name during execution. This can be achieved by using WinAPI (Windows API).

Let’s see what happens

I’m going to write some code in order to show you how we can avoid this. Let’s say I want to import WinSock API in my code. I could do this:

1
2
3
4
5
6
7
8
#include <Windows.h>
#include <Ws2_32.h>

#pragma comment(lib, "ws2_32.lib")

int main(){
return 0;
}

The first part of WinSock implementation in the code is this:

1
#include <Ws2_32.h>

This tells the compiler to copy everything from WinSock static library and paste it in my code. The second part is more interesting if you ask me:

1
#pragma comment(lib, "ws2_32.lib")

This tells the linker to link the binary with WinSock library. Everything seems fine, and I can actually start writing code, BUT AVs would flag my code as suspicious because of ws2_32.h. To avoid that, I’ll use Windows API to dynamically load those functions from that library.

Before I write code, there are several functions that need to be explained. For example, LoadLibraryA and GetProcAddressA. The first function loads an actual library. This version of the function takes the standard char* data type (character pointer). Let’s say I want to dynamically import example.dll. I would write this:

1
LoadLibraryA("example.dll");

This, of course, would import the example.dll file dynamically. The best part is that after importing the necessary DLL file, it’s possible to tell the CPU which functions should be used from that file. That can be done by using the GetProcAddress function.

How does this work? Well, to be honest, “importing DLL file” doesn’t mean that it’s being copied to the memory of the process. It just means taking the offset memory address and remembering it. The required DLL file starts from that memory address. Now when I know the offset address of the DLL file in memory, I can choose any function I want. This can be done by getting the starting address of the function from that memory region (where the loaded DLL file is mapped). This can be done like this:

1
2
3
char* example_function = "ExampleFunction";
HMODULE example_dll = LoadLibraryA("example.dll");
GetProcAddress(example_dll, example_function);

The only problem you might face is that you have unencrypted strings left in code. This can be fixed with some encryption/decryption mechanism. You don’t have to use complex encryption algorithms; simple XOR could solve the problem of static detection.

Overengineering encryption like a German person

First I wrote a XOR encryption function. Then I realized it was too simple… So… I just took it and made it complex. I hate when my strings are shown in Ghidra by default. That’s why I write custom algorithms that don’t even exist! I make them UP!

For example, here, take a GOOD look at my Xor->Increase->Shift algorithm that doesn’t do anything better than the default XOR variant, but it’s an overkill, and I feel better than everyone (because I am!):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
void XorFuckedUp(char* data, char* key){
for(unsigned int i = 0; i < strlen(data); i++){
// Doing chars One-By-One
char c_data = data[i];
char c_key = key[i];

_asm {
xor eax, eax
xor ebx, ebx
mov al, BYTE PTR c_data
mov bl, BYTE PTR c_key
xor al, bl
xor ebx, ebx
inc_loop:
inc al
inc bl
cmp bl, 8
jne inc_loop
mov ah, al
and al, 0x0F
shl al, 4
and ah, 0xF0
shr ah, 4
or al, ah
mov BYTE PTR c_data, al
};

data[i] = c_data;
}
}

Yeah… It has inline ASM inside and only works on the x86 version in Visual Studio (not VSCode, you vibe coder). You can use this function to encrypt stuff, but I’m not going to give you the decryption variant of this function. Go ahead and write it on your own! Haha…

I really enjoy overengineering stuff because it makes everything so much more complex. It can be helpful sometimes. Some reverse engineers might waste hours and hours thinking about a function that does something that could be done in two lines of code. As you start learning more and more in the MalDev world, you’ll realize it’s just better to waste the reverse engineer’s time. After all, we are humans with a finite amount of energy, and he’ll shoot himself if you make your virus complex enough. He’ll just retire…

Let’s get back to the topic

Alright. You want me to teach you how to dynamically load functions. First of all, you need to recreate a function type from scratch. This means using typedef to define a new function ‘frame,’ which will be used to hold the original function. This ‘frame’ is like a shell for a turtle (turtle is an original function here).

1
2
3
4
5
6
7
8
9
#include <Windows.h>

int main(void){
HMODULE wslib = LoadLibraryA("ws2_32.dll");
typedef HANDLE(WINAPI* socket_t)(int, int, int); // 'Frame' for 'Socket();' function
socket_t CustomSocket = (socket_t)GetProcAddress(wslib, "Socket");

return 0;
}

CustomSocket(); is now an alias of socket();. Note that I did something weird here:

1
typedef HANDLE(WINAPI* socket_t)(int, int, int);

If you don’t know what this is (you should know what this is if you understood something above), I don’t know what you know in C/C++. I’m writing this because official Microsoft documentation says how to define the socket(); function (How it’s defined, to be more exact). READ THIS TO SEE SOCKET(); DEFINITION

This link will show you this code:

1
2
3
4
5
SOCKET WSAAPI socket(
[in] int af,
[in] int type,
[in] int protocol
);

This is an explanation of the socket function. [in] means that the argument goes inside of the function (it has to be passed as an argument to the function). How this would work?

1
SOCKET sock = socket(AF_INET, SOCK_STREAM, 0);

This is a fully working TCP socket. If you want, I can use the CustomSocket(); function that I’ve created above:

1
SOCKET sock = CustomSocket(AF_INET, SOCK_STREAM, 0);

Where you should hide those strings?

Have you ever been in a situation where you couldn’t take some dust with your broom and you had to hide the dust under a carpet? And then your grandma found out and told your mom? And your mom almost killed you because of that? Imagine that your grandma is a memory scanner and your mom is an AV engine (antivirus engine). What a nice explanation of the process. You need to hide those strings, man. How? Just encrypt them and store bytes as the unsinged char* data type. Then take that variable and decrypt it during execution. After you decrypt those strings, use them to load necessary libraries and functions.

BTW, you’re shooting yourself in a toe when you do that, because file entropy goes high and AVs don’t like that. The best thing to do is to have another file that contains encrypted data.

Don’t save everything on Stack

Adding stuff on the stack is a bad idea. Your friend is Heap, not Stack! BTW, did you know that modern operating systems prevent stack execution? Yes, executing something from the stack is much harder than executing something from the heap. Let’s say you hold a shellcode on the stack. It would be a pain in the ass to run it. But if you take the shellcode from stack to heap… magic happens! Of course, the allocated memory region should have the X bit set (execution permission set) in order to do that.

Ok, we are not talking about shellcodes and in-memory execution in this post, so why not save data on the stack? Because!

You can save encrypted data on the stack. During execution, put them into Heap and clean up Stack. Then do decryption of those strings, use them to load libraries and functions, and clear Heap. No trace of suspicious functions/libraries left in the memory! You just need to act quick, man…

Note that encrypted data (and the key) that you pass has to be NULL terminated because of the strlen(); function.