PowerShell脚本之Invoke-Expression Hook

摘要:
目录PowerShell脚本调用表达式钩子开发环境钩子原因钩子原理钩子进程修补程序编写0x01--修改原始DLL 0x02--3环程序编写0x03--驱动程序编程0x04--摘要和排序:

目录

PowerShell脚本之Invoke-Expression Hook

开发环境

vs2013,WDK8.1,dnspy

Hook的起因

最近在分析一个APT组织样本的时候,发现了一件有意思的事情,该样本进行了80多层混淆,可能还要多,没有查。

原始文件第一层powershell脚本代码:

PowerShell脚本之Invoke-Expression Hook第1张

放眼望去,一片混淆,有反转,有正则表达式。开始以为只有几层,就尝试手动解,没想到越解越多。

PowerShell脚本之Invoke-Expression Hook第2张

这样谁能受得了?百度了一下没有找到这种去混淆工具。

既然每次混淆后都会调用执行函数,那就把每次执行的参数记录下来,最后终将执行混淆后的代码

Hook的原理

原理就是Inlie Hook ,不管再怎么混淆,都将执行iex(invoke expression)函数。

我们只需要对该函数进行Inlie Hook ,然后继续执行原始的代码就行了

PowerShell脚本之Invoke-Expression Hook第3张

不过有意思的是,这个Powershell_ise.exe是基于C#开发的,他的dll也是

PowerShell脚本之Invoke-Expression Hook第4张

那么我们要如何去hook呢?

Hook的流程

在Hook之前,我们要必定要找到 代码所在的模块

我们可以使用Get-Command命令来查询一个Cmdlet所在模块

(Get-Command Invoke-Expression).ModuleName

可以使用以下命令查看实现iex所属.net类的完全限定名称:

(Get-Command Invoke-Expression).ImplementingType.AssemblyQualifiedName

PowerShell脚本之Invoke-Expression Hook第5张

我们用dnspy打开GAC程序集,并定位到该模块

PowerShell脚本之Invoke-Expression Hook第6张

定位到Microsoft.Powershell.Commands包下的InvokeExpressionCommand类,发现该类只有一个函数

PowerShell脚本之Invoke-Expression Hook第7张

验证是不是我们要Hook的函数,下个断点,调试程序powershell_ise.exe,并执行iex "get-process",发现确实是我们要Hook的代码

PowerShell脚本之Invoke-Expression Hook第8张

我们在这里尝试修改代码是可以改的,但是却不能让程序执行我们的代码,这个模块所在的位置在用户层是没有权限访问的,或者说exploer.exe(资源管理器)是没有权限访问到这个目录的

PowerShell脚本之Invoke-Expression Hook第9张

在修改完Hook代码后,这里有两种方式替换掉目标dll:

第一种:用ARK工具,例如PcHunter等工具可以访问到该目录,并且可以删除掉。

PowerShell脚本之Invoke-Expression Hook第10张

然后用cmd命令 ,复制到该目录(缺陷:powershell_ise程序在被调试加载和单独运行的时候,加载的模块并不是通一个,然而另外一个模块,用cmd复制过去也是没有用的)

move C:Users15pb-win7DesktopMicrosoft.PowerShell.Commands.Utility.dll  C:WindowsassemblyGAC_MSILMicrosoft.PowerShell.Commands.Utility1.0.0.0__31bf3856ad364e35

第二种,自己编写patch程序(驱动模块,因为需要system权限,我们可以替换cmd的token,也可以自己复制替换)

patch程序的编写

0x01--原始DLL的修改

dll模块的Hook代码,有过C#开发经验的,编写代码还是比较简单的。

我们在这里不改变程序的流程,只把参数记录下来,并写文件到C盘。改后如下:

PowerShell脚本之Invoke-Expression Hook第11张

修改后将代码保存到桌面,原始路径你是没有权限保存的。

0x02--3环程序的编写

之前有说过,我们直接运行powershell_ise和在调试器里运行,其加载的模块是不一样,如下图:

调试时要hook的模块:

PowerShell脚本之Invoke-Expression Hook第12张

直接运行加载的模块:

PowerShell脚本之Invoke-Expression Hook第13张

经过分析,加载的模块虽然大小不一样,但是包是一样的,起码我们要改的代码处是一样的。

程序正常运行时,如果Microsoft.PowerShell.Commands.Utility.ni.dll不存在则加载Microsoft.PowerShell.Commands.Utility.dll

我们的patch程序,要做的事有如下几件:

  • 删掉Microsoft.PowerShell.Commands.Utility.dll
  • 删掉Microsoft.PowerShell.Commands.Utility.ni.dll
  • 复制我们修改过dll,到Microsoft.PowerShell.Commands.Utility.dll所在目录下

注:必须要先删除,直接覆盖是不行的

正常运行时的路径如下:

C:WindowsassemblyNativeImages_v2.0.50727_32Microsoft.PowerShel#4bdde288f147e3b3f2c090ecdf704e6dMicrosoft.PowerShell.ConsoleHost.ni.dll

C:WindowsassemblyNativeImages_v2.0.50727_32Microsoft.PowerShel#不变,后边是在变化的,我们要去动态获取。

获取代码(对该目录进行递归,找到文件,保存当前目录路径):

void EnumFile(TCHAR *szPath, TCHAR* szFileName)
{


	TCHAR szTempPath[MAX_PATH] = {};
	wsprintf(szTempPath, L"%s\*", szPath);
	WIN32_FIND_DATA FindData = {};
	TCHAR szNextPath[MAX_PATH] = {};
	TCHAR szOutputPant[MAX_PATH] = {};
	HANDLE hFind = FindFirstFile(szTempPath, &FindData);

	do
	{
		if (FindData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)//输出目录
		{

			if (wcscmp(FindData.cFileName, L".") == 0 || wcscmp(FindData.cFileName, L"..") == 0)
			{
				//如果是这俩目录,就什么都不干

			}
			else
			{

				wsprintf(szNextPath, L"%s\%s", szPath, FindData.cFileName);
				EnumFile(szNextPath, szFileName);

			}
		}
		else
		{
			//如果不是目录,就查看这个玩意是不是我们的目标文件
			if (wcscmp(szFileName, FindData.cFileName) == NULL)
			{

				wsprintf(szDestPath, L"\??\%s\%s", szPath, FindData.cFileName);
			//是就输出
				printf("%ws
", szDestPath);
			}
		}

	} while (FindNextFile(hFind, &FindData));

	FindClose(hFind);


}

将获取的路径写到本地(也可以不用写,把消息发送到0环也行)

PowerShell脚本之Invoke-Expression Hook第14张

0x03--驱动程序的编写

3环的程序,就是为了获取路径

那么0环的程序,自然就是要读取路径,删除,复制(比较简单,并没有什么技术含量)

PowerShell脚本之Invoke-Expression Hook第15张

在0环是没有删除文件和读取文件的,需要自己封装。

驱动的所有代码如下:

#include <Ntddk.h> //编写内核驱动需要包含NTddk头文件.
#include<ntstrsafe.h>

BOOLEAN MyCopyFile(PCWSTR desFile, PCWSTR srcFile)
{


	HANDLE readFileHandle;
	HANDLE writeFileHandle;
	OBJECT_ATTRIBUTES ObjectAttributes;
	OBJECT_ATTRIBUTES ObjectAttributes1;
	UNICODE_STRING readFilePath;
	UNICODE_STRING writeFilePath;
	IO_STATUS_BLOCK IoStatusBlock;
	NTSTATUS status;

	PVOID saveBuffer = NULL;
	LARGE_INTEGER byteOffset;
	ULONG length = 0;




	byteOffset.QuadPart = 0;
	RtlInitUnicodeString(&readFilePath, srcFile);
	RtlInitUnicodeString(&writeFilePath, desFile);

	saveBuffer = ExAllocatePoolWithTag(PagedPool, 1000, "tag1");
	InitializeObjectAttributes(&ObjectAttributes, &readFilePath, OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, NULL, NULL);
	InitializeObjectAttributes(&ObjectAttributes1, &writeFilePath, OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, NULL, NULL);
	status = ZwCreateFile(&readFileHandle, GENERIC_ALL, &ObjectAttributes, &IoStatusBlock, NULL, FILE_ATTRIBUTE_NORMAL, FILE_SHARE_READ, FILE_OPEN_IF, FILE_NON_DIRECTORY_FILE | FILE_RANDOM_ACCESS | FILE_SYNCHRONOUS_IO_NONALERT, NULL, 0);

	if (!NT_SUCCESS(status))
	{
		DbgPrint("Can not create");
		if (readFileHandle != NULL)
			ZwClose(readFileHandle);


		if (saveBuffer != NULL)
			ExFreePool(saveBuffer);


		return FALSE;
	}

	status = ZwCreateFile(&writeFileHandle, GENERIC_ALL, &ObjectAttributes1, &IoStatusBlock, NULL, FILE_ATTRIBUTE_NORMAL, FILE_SHARE_READ, FILE_OPEN_IF, FILE_NON_DIRECTORY_FILE | FILE_RANDOM_ACCESS | FILE_SYNCHRONOUS_IO_NONALERT, NULL, 0);

	if (!NT_SUCCESS(status))
	{
		if (readFileHandle != NULL)
			ZwClose(readFileHandle);

		if (writeFileHandle != NULL)
			ZwClose(writeFileHandle);

		if (saveBuffer != NULL)
			ExFreePool(saveBuffer);
		DbgPrint("Can not create");
		return FALSE;
	}


	do
	{

		length = 1000;
		status = ZwReadFile(readFileHandle, NULL, NULL, NULL, &IoStatusBlock, saveBuffer, length, &byteOffset, NULL);//读取数据



		if (!NT_SUCCESS(status))
		{
			if (status == STATUS_END_OF_FILE)

				DbgPrint("read File End");
			if (readFileHandle != NULL)
				ZwClose(readFileHandle);

			if (writeFileHandle != NULL)
				ZwClose(writeFileHandle);

			if (saveBuffer != NULL)
				ExFreePool(saveBuffer);
			return FALSE;
		}

		length = IoStatusBlock.Information;//返回实际读取数据的大小

		status = ZwWriteFile(writeFileHandle, NULL, NULL, NULL, &IoStatusBlock, saveBuffer, length, &byteOffset, NULL);

		if (!NT_SUCCESS(status))
		{
			DbgPrint("Can not write File ");
			if (readFileHandle != NULL)
				ZwClose(readFileHandle);

			if (writeFileHandle != NULL)
				ZwClose(writeFileHandle);

			if (saveBuffer != NULL)
				ExFreePool(saveBuffer);
			return FALSE;
		}

		byteOffset.QuadPart += length;//文件偏移移动

	} while (1);

	if (readFileHandle != NULL)
		ZwClose(readFileHandle);

	if (writeFileHandle != NULL)
		ZwClose(writeFileHandle);

	if (saveBuffer != NULL)
		ExFreePool(saveBuffer);
	return TRUE;
}


NTSTATUS ZwDeleteFile(IN POBJECT_ATTRIBUTES  ObjectAttributes); //函数声明


NTSTATUS  IBinaryNtZwDeleteFile(UNICODE_STRING uDeletePathName)
{


	OBJECT_ATTRIBUTES obAttri = { 0 };


	//初始化源文件路径并且打开

	InitializeObjectAttributes(&obAttri,
		&uDeletePathName,
		OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE,
		NULL,
		NULL
		);

	return ZwDeleteFile(&obAttri);
}

//卸载回调函数
VOID Unload(__in struct _DRIVER_OBJECT  *DriverObject)
{
	DbgPrint("Unload MyDrive
");
}

NTSTATUS  IBinaryNtReadFile(PVOID pszBuffer, UNICODE_STRING uPathName)
{

	OBJECT_ATTRIBUTES objAttri = { 0 };
	NTSTATUS ntStaus;
	HANDLE hFile;
	IO_STATUS_BLOCK ioStatus = { 0 };


	if (NULL == pszBuffer)
		return STATUS_INTEGER_DIVIDE_BY_ZERO;


	//打开文件读取文件.

	InitializeObjectAttributes(&objAttri,
		&uPathName,
		OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE,
		NULL,
		0);

	ntStaus = ZwCreateFile(&hFile,
		GENERIC_READ | GENERIC_WRITE,
		&objAttri,
		&ioStatus,
		NULL,
		FILE_ATTRIBUTE_NORMAL,
		FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE,
		FILE_OPEN,
		FILE_SYNCHRONOUS_IO_NONALERT,
		NULL,
		NULL);

	if (!NT_SUCCESS(ntStaus))
	{
		ZwClose(hFile);
		return ntStaus;

	}



	ntStaus = ZwReadFile(hFile, NULL, NULL, NULL, &ioStatus, pszBuffer, 500, NULL, NULL);
	if (!NT_SUCCESS(ntStaus))
	{
		ZwClose(hFile);
	}


	return ntStaus;
}

NTSTATUS DriverEntry(__in struct _DRIVER_OBJECT  *DriverObject,
	__in PUNICODE_STRING  RegistryPath)
{
	int i = 0;
	//DbgBreakPoint();
	DbgPrint("HelloWorld, %p
", &i);
	
	//UNICODE_STRING uDestPath = { 0 };

	//RtlUnicodeStringInit(&uDestPath, L"\??\C:\Windows\assembly\NativeImages_v2.0.50727_32\Microsoft.PowerShel#\6337c36083b7d1f5cc12e6fb37d2d430\Microsoft.PowerShell.Commands.Utility.ni.dll1313131313");

	PCWSTR pReadBuffer = NULL;

	UNICODE_STRING uPathName = { 0 };

	RtlUnicodeStringInit(&uPathName, L"\??\C:\patch\dest.txt");


	pReadBuffer = ExAllocatePoolWithTag(PagedPool, 0x1000, "niBI");

	RtlZeroMemory(pReadBuffer, 0x1000);

	IBinaryNtReadFile(pReadBuffer, uPathName);

	
	//复制第一个
	//初始化字符串路径
	RtlUnicodeStringInit(&uPathName, L"\??\C:\Windows\assembly\GAC_MSIL\Microsoft.PowerShell.Commands.Utility\1.0.0.0__31bf3856ad364e35\Microsoft.PowerShell.Commands.Utility.dll");

	IBinaryNtZwDeleteFile(uPathName);

	//复制文件到目标目录
	//C:\Windows\assembly\GAC_MSIL\Microsoft.PowerShell.Commands.Utility\1.0.0.0__31bf3856ad364e35\Microsoft.PowerShell.Commands.Utility.dll
	MyCopyFile(L"\??\C:\Windows\assembly\GAC_MSIL\Microsoft.PowerShell.Commands.Utility\1.0.0.0__31bf3856ad364e35\Microsoft.PowerShell.Commands.Utility.dll",L"\??\C:\patch\patch.dll");


	//复制第二个
	RtlUnicodeStringInit(&uPathName,pReadBuffer);

	NTSTATUS nt =IBinaryNtZwDeleteFile(uPathName);


	//BOOLEAN bl = MyCopyFile(pReadBuffer, L"\??\C:\patch\patch-ni.dll");
	

//RtlUnicodeStringInit(&uPathName, L"\??\C:\Windows\assembly\NativeImages_v2.0.50727_32\Microsoft.PowerShel#\6337c36083b7d1f5cc12e6fb37d2d430\Microsoft.PowerShell.Commands.Utility.ni.dll");

//NTSTATUS nt =IBinaryNtZwDeleteFile(uPathName);

//BOOLEAN bl=	MyCopyFile(L"\??\C:\Windows\assembly\NativeImages_v2.0.50727_32\Microsoft.PowerShel#\6337c36083b7d1f5cc12e6fb37d2d430\Microsoft.PowerShell.Commands.Utility.ni.dll", L"\??\C:\patch\patch.dll");
	//注册一下驱动卸载的函数
	DriverObject->DriverUnload = Unload;

	ExFreePoolWithTag(pReadBuffer, "niBI");
	return STATUS_SUCCESS;
}


0x04--总结和梳理:

  1. iexpatch.exe运行(获取要pacth的dll路径,并写到本地,并运行patch.exe)
  2. patch.exe是驱动加载软件(加载我们的驱动,可以自己写,偷懒了就用了现成的)
  3. 驱动(删除两个dll,并复制我们修改过后的dll到目标目录)

PowerShell脚本之Invoke-Expression Hook第16张

效果展示

80层混淆:

PowerShell脚本之Invoke-Expression Hook第17张

去混淆后:

PowerShell脚本之Invoke-Expression Hook第18张

去混淆后是MuddyWater常用的powershell远控

免责声明:文章转载自《PowerShell脚本之Invoke-Expression Hook》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇织梦DEDECMS小说模块使用和安装全攻略如何在ftp线上改代码?(本地没有项目文件)(老大:张国辉)下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

CLR寄宿(上) MSCOREE.DLL

CLR寄宿(上) MSCOREE.DLL托管代码调用非托管代码,.NET提供了P/Invoke(平台调用)方式,它作为.NET的基础出现在各类书籍和网络资源上,这里不再讨论。那么非托管代码如何去调用托管代码呢?我们知道,一个托管应用程序首先被操作系统启动,然后由操作系统调用CLR来托管该程序。那么.NET框架到底以什么方式让操作系统来认识它并且可以启动它呢...

混合使用Delphi和C ++(附下载)

您想将C ++添加到Delphi应用程序中吗?或者将Delphi代码添加到C ++应用程序中?这是如何做。 您可能不知道的一件事是如何在RAD Studio中集成C ++和Delphi语言。您可以将单个项目中的单个应用程序编译为单个EXE,混合使用两种语言。(当然,你也可以使用DLL或包来实现。)如果使用C ++ Builder,这对添加Delphi实现的...

WPF知识点全攻略13- 绘图

WPF基础图形: Line:直线,可设置笔触(Stroke) Rectangle:矩形,既有笔触(Stroke),又有填充(Fill) Ellipse:椭圆,有笔触和填充(包含正圆) Polygon:多边形(闭合),由多条直线段围成的闭合区域,有笔触和填充 Polyline:折线(不闭合),由多条首尾相接的直线段组成 Path:路径,基本图形中功能最强大的...

WPF自定义控件与样式(10)-进度控件ProcessBar自定义样

一.前言   申明:WPF自定义控件与样式是一个系列文章,前后是有些关联的,但大多是按照由简到繁的顺序逐步发布的等,若有不明白的地方可以参考本系列前面的文章,文末附有部分文章链接。   本文主要内容: ProcessBar自定义标准样式; ProcessBar自定义环形进度样式; 二.ProcessBar标准样式   效果图:     P...

win10如何设置开机自动启动热点WIFI?

1.编写脚本文件(先新建txt文件,编写代码内容netsh wlan start hostednetwork,最后重命名成HotSpot.bat):文件名称:HotSpot.bat,代码内容: netsh wlan start hostednetwork 2.保存到C:ProgramDataMicrosoftWindowsStart MenuProgram...

原装js轮播图,鼠标移入停止轮播,移出继续轮播

要求:1、点击按钮,切换图片;    2、图片能够自动轮播;       3、鼠标移入,轮播停止;移出继续轮播; 知识点:1、定时器:setInterval();     2、鼠标移入事件:onmouseenter/onmouseover;       鼠标移出事件:onmouseleave/onmouseout; 难点:假设轮播图轮播到第二张图片,此时点...