【干货分享】Sandbox技术之DLL注入

举报
南山一少 发表于 2017/10/31 08:45:44 2017/10/31
【摘要】 我们要在Sandbox中观察恶意样本的行为,就需要在恶意样本的进程中注入我们自己的代码,然后通过这些代码产生监控日志。这是目前Sandbox中监控常用的做法之一,当然也有一些更底层的办法来的监控日志。今天主要介绍用户态下远程DLL注入方式,将监控代码注入到恶意样本进程中。这种实现相对简单通用,只要将监控代码编译为一个DLL,然后注入到恶意样本的进程中,就可以获取到监控行为日志。

我们要在Sandbox中观察恶意样本的行为,就需要在恶意样本的进程中注入我们自己的代码,然后通过这些代码产生监控日志。这是目前Sandbox中监控常用的做法之一,当然也有一些更底层的办法来的监控日志。今天主要介绍用户态下远程DLL注入方式,将监控代码注入到恶意样本进程中。这种实现相对简单通用,只要将监控代码编译为一个DLL,然后注入到恶意样本的进程中,就可以获取到监控行为日志。

Sandbox的minitor程序可以拆分为inject程序和monitor.dll模块,inject程序负责启动app并将monitor.dll注入到app中,而monitor.dll则负责具体的API监控工作。整个monitor大致的运行过程:

  1. inject程序启动后设置自己的权限;

  2. inject程序启动被监控app程序,以挂起方式启动,因为我们希望一开始就进行监控,所以需要先挂起;

  3. inject程序将DLL注入到app进程中;

  4. inject恢复app挂起的线程,让app运行;

文章目录

设置注入程序权限

Inject需要debug权限来读写app进程空间,这里通过EnableDebugPriv函数来设置对应权限。EnableDebugPriv函数根据name来设定权限,调用的时候实参一般是SE_DEBUG_NAME,即给inject进程设置debug权限。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

int EnableDebugPriv(const char* name)

{

    HANDLE hToken;

    if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ALL_ACCESS, &hToken)) {

       printf("OpenProcessToken Error\n");

       return -8;

    }

 

    LUID luid;

    if (!LookupPrivilegeValue(NULL, name, &luid)) {

       printf("LookupPrivilegeValue Error\n");

       return -9;

    }

 

    TOKEN_PRIVILEGES tokenPri;

    tokenPri.PrivilegeCount = 1;

    tokenPri.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;

    tokenPri.Privileges[0].Luid = luid;

    if (!AdjustTokenPrivileges(hToken, 0, &tokenPri, sizeof(TOKEN_PRIVILEGES), NULL, NULL)) {

       printf("AdjustTokenPrivileges Error!\n");

       return -10;

    }

    return 0;

}


启动应用程序

以挂起方式创建app进程,注意参数CREATE_SUSPENDED,这样我们就可以在恶意软件执行前就完成监控初始化工作。然后再恢复app的主线程,让恶意样本继续运行。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

void StartApp(DWORD *pid, DWORD *tid, LPSTR app)

{

    if(pid == NULL || tid == NULL || app == NULL){

        return;

    }

 

    STARTUPINFO sinfo;

    PROCESS_INFORMATION pinfo;

    ZeroMemory(&sinfo, sizeof(sinfo));

    sinfo.cb = sizeof(sinfo);

    ZeroMemory(&pinfo, sizeof(pinfo));

 

    CreateProcess(NULL, app, NULL, NULL, FALSE, CREATE_NEW_CONSOLE | CREATE_SUSPENDED, NULL, NULL, &sinfo, &pinfo);

    *pid = pinfo.dwProcessId;

    *tid = pinfo.dwThreadId;

 

    printf("Remote App Process ID:\t%d\n", *pid);

    printf("Remote App Thread  ID:\t%d\n", *tid);

}


注入DLL

这一步将monitor.dll注入到我们创建的app进程中,采用创建远程线程的方式进行注入(CreateRemoteThread函数)。这里面需要注意的是,CreateRemoteThread 函数传递的实参中,指针地址必须是app进程中的地址,而不能是inject进程中的地址,因为inject中的指针指的地址在app进程中是无意义的。比如这里的pRemoteDllPath指针,是先在app进程中远程申请内存(VirtualAllocEx),然后将字符串写入远程内存中(WriteProcessMemory)。


int InjectDLL(DWORD dwRemoteProcessId, const char* dll)

{

    HANDLE hRemoteProcess;

    if ((hRemoteProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwRemoteProcessId)) == NULL){

       printf("Error Code:%d\n",GetLastError());

       return -2;

    }

    LPVOID pRemoteDllPath = VirtualAllocEx(hRemoteProcess, NULL, strlen(dll) + 1, MEM_COMMIT, PAGE_READWRITE);

    if (pRemoteDllPath == NULL){

       printf("VirtualAllocEx Error\n");

       return -3;

    }

    SIZE_T Size;

    if (WriteProcessMemory(hRemoteProcess, pRemoteDllPath, dll, strlen(dll) +1, &Size) == 0)

    {

       printf("WriteProcessMemory Error\n");

       return -4;

    }    

    LPTHREAD_START_ROUTINE pLoadLibrary = (LPTHREAD_START_ROUTINE)GetProcAddress(GetModuleHandle(TEXT("kernel32.dll")), "LoadLibraryA");

    if (pLoadLibrary == NULL)

    {

       printf("GetProcAddress Error\n");

       return -5;

    }

    DWORD dwThreadId;

    HANDLE hThread;

    if ((hThread = CreateRemoteThread(hRemoteProcess, NULL, 0, pLoadLibrary, pRemoteDllPath, 0, &dwThreadId)) == NULL) {

       printf("CreateRemoteThread Error\n");

       return -6;

    } else {

        WaitForSingleObject(hThread, INFINITE);

        printf("Remote DLL Thread ID:\t%d\n", dwThreadId);

        printf("Inject is done.\n");

    }

    if (VirtualFreeEx(hRemoteProcess, pRemoteDllPath, 0, MEM_RELEASE) == 0) {

       printf("VitualFreeEx Error\n");

       return -7;

    }

    if (hThread != NULL) CloseHandle(hThread);

    if (hRemoteProcess != NULL) CloseHandle(hRemoteProcess);

    return 0;

}


恢复app线程

远程注入minitor.dll之后,Sandbox的监控环境就绪,下面可以恢复app的主线程,让app运行恶意样本了。

1

2

3

4

5

6

7

void ResumeRemoteThread(DWORD tid)

{

    printf("Resume Remote Thread: %d\n", tid);

    HANDLE thread_handle = OpenThread(THREAD_ALL_ACCESS, FALSE, tid);

    ResumeThread(thread_handle);

    CloseHandle(thread_handle);

}


获取app程序路径

传递给Sandbox的是一个恶意样本,inject程序需要根据恶意样本的文件类型(可以文件后缀识别,也可以文件内容magic识别等),自动查找Sandbox中对应的应用程序路径。一般就是搜索程序目录或者注册表,比如搜索ProgramFiles、ProgramFiles(x86)、System32、SysWOW64等路径,查到对应的app路径就行。本文暂时省略这块,以winword.exe作为固定测试对象跑一下效果。

1

2

3

4

5

6

7

8

9

10

11

char* GetAppCMD(char* buffer, const char* sample)

{

    //TODO:

    //1、Get File Type From Name or Content.

    //2、Get APP path by File Type(ProgramFiles、ProgramFiles(x86)、System32、SysWOW64).

 

    //Only4Test

    char* app = "C:\\Program Files\\Microsoft Office\\Office15\\WINWORD.EXE";

    snprintf((char*)buffer, sizeof(buffer), "%s %s", app, sample);

    return buffer;

}


主函数

写一个简单的测试函数,指定monitor.dll和sample,然后运行样本并执行注入。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

int main(void)

{

    const char* monitor = "C:\\users\\ken\\downloads\\monitor.dll";

    const char* sample = "C:\\users\\ken\\downloads\\sample.docx";

    printf("Current Inject PID: %d\n", GetCurrentProcessId());

 

    char buffer[512];

    LPSTR cmd = GetAppCMD(buffer, sample);

 

EnableDebugPriv(SE_DEBUG_NAME);

 

    DWORD dwRemoteProcessId, dwRemoteThreadId;

    StartApp(&dwRemoteProcessId, &dwRemoteThreadId, cmd);

    InjectDLL(dwRemoteProcessId, monitor);

    ResumeRemoteThread(dwRemoteThreadId);

 

    return 0;

}

通过以上代码我们大致知道了整个monitor程序的运行过程,下面看一下运行效果,图1是运行的打印信息:

  1. inject.exe的PID为14960;

  2. inject.exe创建winword.exe进程并加载恶意样本,winword.exe进程ID为11216,主线程ID为15496;

  3. inject.exe注入monitor.dll模块到winword.exe进程中,远程DLL线程ID为14204;

  4. 恢复winword.exe主线程15496;

dll1

我们可以通过一些进程监控软件看看app进程的运行过程是否符合预期,下面是监控事件截图(注:为了好展示去掉了部分无关事件),我们可以看到monitor.dll已经注入到winword.exe进程中了。

app进程监控图

app进程监控图

到这里我们完成了一个简单的注入器(除了windows编译环境,以上代码可以在ubuntu+ mingw环境下编译测试)。这个注入器可以运行app,然后将DLL注入到app中。还有几个遗留 问题需要考虑:

  1. 对于windows平台,目前有x86和x64,这样我们需要判断对应样本(一般PE需要判断)属于哪个平台,然后启动对应版本的inject才行。对于PE文件我们可以通过IMAGE_DOS_HEADER和IMAGE_NT_HEADERS来判断。如下图,IMAGE_FILE_HEADER中的machine标记,0x014c表示高于i386的CPU,0x8664表示AMD64的CPU。

app1

        2. 那monitor.dll如何监控app的运行呢?最常用的实现方法之一是使用API HOOK技术,在monitor.dll中对app的系统调用进行hook,这样就可以记录app的所有系统交互行为了。我们后面会继续介绍minitor.dll的实现过程,以完成一个完整的Sandbox行为分析系统。



转载请注明:“转自绿盟科技博客”: 原文链接.

【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。