首页
论坛
专栏
课程

[原创]PowerShell脚本之Invoke-Expression 函数 Hook

2019-8-6 15:05 2319

[原创]PowerShell脚本之Invoke-Expression 函数 Hook

2019-8-6 15:05
2319

目录

PowerShell脚本之Invoke-Expression 函数 Hook

开发环境

vs2013,WDK8.1,dnspy

Hook的起因

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

 

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

 

图片描述

 

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

 

图片描述

 

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

 

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

Hook的原理

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

 

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

 

图片描述

 

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

 

图片描述

 

那么我们要如何去hook呢?

Hook的流程

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

 

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

(Get-Command Invoke-Expression).ModuleName

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

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

图片描述

 

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

 

图片描述

 

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

 

图片描述

 

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

 

图片描述

 

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

 

图片描述

 

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

 

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

 

图片描述

 

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

move C:\Users\15pb-win7\Desktop\Microsoft.PowerShell.Commands.Utility.dll  C:\Windows\assembly\GAC_MSIL\Microsoft.PowerShell.Commands.Utility\1.0.0.0__31bf3856ad364e35\

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

patch程序的编写

0x01--原始DLL的修改

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

 

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

 

图片描述

 

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

0x02--3环程序的编写

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

 

调试时要hook的模块:

 

图片描述

 

直接运行加载的模块:

 

图片描述

 

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

 

程序正常运行时,如果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:\Windows\assembly\Nativeimages_v2.0.50727_32\Microsoft.PowerShel#\4bdde288f147e3b3f2c090ecdf704e6d\Microsoft.PowerShell.ConsoleHost.ni.dll

C:\Windows\assembly\Nativeimages_v2.0.50727_32\Microsoft.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\n", szDestPath);
            }
        }

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

    FindClose(hFind);


}

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

 

图片描述

0x03--驱动程序的编写

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

 

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

 

图片描述

 

在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\n");
}

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\r\n", &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到目标目录)

    图片描述

效果展示

80层混淆:

 

图片描述

 

去混淆后:

 

图片描述

 

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



[公告]安全测试和项目外包请将项目需求发到看雪企服平台:https://qifu.kanxue.com

最后于 2019-8-6 15:08 被与时尽现。编辑 ,原因: 图片不清晰
上传的附件:
最新回复 (6)
与时尽现。 4 2019-8-6 15:06
2
0
不知为什么,上传MD文档,图片加载不成功,一张一张贴进去的,照片好像有点不清晰
MR.ROBOT 2019-8-6 15:40
3
0
样本呢?
HadesW 1 2019-8-6 15:54
4
0
感谢分享
虫子回归 2019-8-19 22:17
5
0
感谢分享 干货啊,用PsExec提升到system权限能删除嘛?
wx_Chunlin Xiong 2019-10-9 11:53
6
1
您好,Powershell攻击的样本可以分享一下吗?
killpy 2 2019-10-11 12:35
7
0
mark
游客
登录 | 注册 方可回帖
返回